Contacting hotels
Archived
3 Tasks
5 mins
Scenario
FSG wants to ensure that each hotel with which it has agreements to book rooms for events is treated as equally as possible. FSG would like to identify hotels within 5 miles of an event within the last month that have not been asked to reserve a block of rooms for an event. FSG plans to use this information to send an email to those hotels making them aware the FSG will make it a priority to issue a room request at the earliest opportunity.
The following table provides the credentials you need to complete the exercise.
Role | Operator ID | Password |
---|---|---|
Admin | admin@booking | rules |
Design a query that outputs hotel contact information according to the above requirements. Expose any properties not currently exposed that are output by the report and/or must be exposed to perform joins.
Detailed Tasks
1 Design approach
A brute force approach would identify every Event held within the past month then iterate the list of Events. For each Event, capture the pyGUIDs of the hotels that were contacted. Next have the D_AddressesWithinDistance Data Page search IsFor=HOTEL records with 5 miles of the Venue where the Event was held. The Reference values in the D_AddressesWithinDistance result set would then be captured. Any Reference not in the list of FSG-Data-Hotel pyGUIDs would be kept; any Reference that does match an FSG-Data-Hotel pyGUID would be ignored. Every remaining Reference would be used to lookup an FSG-Data-Hotel record. Then capture contact information (pyFirstName, pyLastName, and pyEmail1) from each Hotel record’s embedded Contact Field Group.
A problem with this iterative approach is that it is possible for multiple events to be held at the same Venue within the past month. Also, two Venues may be within 10 miles of each other, meaning overlap could exist within a 5 mile radius for each Venue. A Hotel may not have been contacted for one Event but could have been contacted for a different Event at some point within the past month.
The D_AddressesWithinDistance data page sources an activity that calls a Connect-SQL rule that contains a query based on the Haversine formula. The second table subselect, the one aliased as “p”, in the Haversine formula, currently asks for a single location’s coordinates.
Alternatively a Table subselect could list the latitudes and longitudes of every Venue for every Event held in the past month. Instead of joining to one row as it does now, the join would be made across multiple venue address coordinates.
Current Definition of “p” Table Subselect |
---|
JOIN ( /* these are the query parameters */<br /> SELECT {AddressPage.Latitude Decimal } AS latpoint,<br /> {AddressPage.Longitude Decimal } AS longpoint,<br /> {AddressPage.Distance Decimal } AS radius,<br /> {AddressPage.pyNote Decimal } AS distanceunit,<br /> {AddressPage.IsFor } AS isfor<br /> ) AS p ON p.isfor = z.isfor |
Possible Definition of “p” Table Subselect |
---|
JOIN (<br /> SELECT VenueAddress.latitude AS latpoint,<br /> VenueAddress.longitude AS longpoint<br /> FROM pegadata.pr_fsg_data_address AS VenueAddress<br /> INNER JOIN pegadata.pc_fsg_booking_work AS BookEvent ON BookEvent.{exposedVenueGUID}= VenueAddress.reference<br /> AND BookEvent.pxobclass = 'FSG-Booking-Work-BookEvent<br /> WHERE BookEvent.{exposedEventStartDt} {is within the past month}<br /> ) AS p ON 1 = 1' |
Note: Radius, distanceunit, and isfor were removed since they can be defined elsewhere
The above modification to the Haversine Formula, however does not filter out addresses for Hotels that were contacted during the past month. That needs to be done elsewhere in the query.
The solution requires that the expression below also be implemented
HotelAddressesNotContacted ::= AllAddressesWithinDistance EXCEPT HotelAddressesContacted |
In a future release of the Pega Platform, Connect-SQL rule queries may not allow tables that contain Pega-specific columns, (columns that that start with px, py, or pz. The FSG-Data-Address table (pegadata.pc_fsg_data), is defined in the CustomerData schema and has no pega specific columns and thus can be referenced in Connect-SQL rule queries. However, the work pool table for the Booking application case types (pc_fsg_booking_work) should not be queried using Connect-SQL rules since that table contains columns such as pzInsKey and pzPvStream.
In the long run the best approach is either (A) store the history of pyGUIDs for contacted hotels in the CustomerData schema or (B) find a way to “insert” lists of pyGUIDs into a Connect-SQL query where each list of pyGUIDs is obtained from the PegaData schema.
The challenge with approach (A) is that storing historical pyGUIDs and date-times in the CustomerData schema requires extra work since it is the near equivalent of using a temporary table. A Savable Data Page could also be used to reinsert data. Approach (A) adds complexity and potentially result in loss of data integrity since the same information would be stored in multiple locations.
On the other hand, with approach (B), it is possible to insert SQL using syntax such as: {Asis: property-name}. Below is a revised expression for HotelAddressesNotContacted.
HotelAddressesNotContacted ::= AllAddressesWithinDistance WHERE Reference NOT IN ({Asis:D_HotelsContacted.AsisExpression }) |
An example AsisExpression property value is shown below.
'5de1376c-aa4d-41dc-acd2-c5cd44eb3f61','d45d7130-471b-4138-87f2-efb689983ee2','6584d826-06e6-4c47-b4ee-2a9c6cd62e7d','f7620988-c687-474f-8b3d-27bb0371680e','6681b769-f2de-45cb-aa7a-e89526765f34','44eb05f1-9c16-48e7-b363-7eaeb63e08e2' |
The number of Hotel pyGUIDs should be small enough not to cause the entire SQL statement to exceed the database’s maximum allowed length. As lengthy as the above example may appear due to the 32 hex character length of a GUID, it would be difficult to exceed the maximum allowable SQL statement length. For example, the maximum length of an SQL statement in PostgresSQL is now 2,147,483,648 characters or roughly 2GB.
A D_HotelsContacted data page that applies to FSG-Data-Address could be modeled after D_AddressesWithinDistance. To do this the Connect_SQL_pr_fsg_data_address activity would first be copied to Connect_SQL_HotelsNotContacted. Next a post-processing activity such as Post_Connect_SQL_HotelsNotContacted would be created.
The Connect_SQL_HotelsNotContacted activity would first use a D_HotelsContacted data page that invokes a report definition that obtains the list of GUIDs for hotels contacted within the past month. Afterward it would post-process the pxResults() PageList to construct the value for a property named AsisExpression.
The solution for deriving AllAddressesWithinDistance (of an Event Venue) requires similar treatment. The goal is to solve the right side of the expression below. Here we want to use “IN” as opposed to “NOT IN”.
<span class="rulename">AllAddressesWithinDistance</span> :=<br /> SELECT latitude AS latpoint,<br /> longitude AS longpoint<br /> FROM pegadata.pr_fsg_data_address<br /> WHERE Reference<br /> IN ({D_VenuesUsed.AsIsExpression}) |
This AsIsExpression value would be the sequence of Venue Address pyGUID values associated to FSG Events held in the past month. An example D_VenuesUsed.AsisExpression property value is shown below. D_VenuesUsed.AsisExpression would be shorter than D_HotelsContacted.AsisExpression since multiple hotels can be contacted per Event. See the example below.
'7e99dbc3-4e54-4419-a15f-f2ca1f0be265','b7206d9a-36d9-45c2-9849-c5fcd995dadb','d8825749-e2fb-46b3-97aa-3ead12b89584' |
2 Detailed Steps
Expose .VenueGUID within FSG-Booking-Work-BookEvent
- In the Case Designer click on the Book Event case and open the Data Model tab
- Add a .VenueGUID (Text) property
- Within the App Explorer right click on .VenueGUID and choose “Define expression”
- Set .VenueGUID = .Venue.pyGUID
- Within the App Explorer right click on .VenueGUID and choose Optimize for reporting
Expose .Event.StartDT within FSG-Booking-Work-BookEvent
- Within the App Explorer right click on .Event.StartDT and choose “Optimize for reporting
- To verify, create a BookEvent case advancing it past the start date entry screen
- Using pgAmin4, execute:
SELECT startdt_1,
pxcreatedatetime
FROM pegadata.pc_fsg_booking_work
WHERE startdt_1 is not null
AND pxobjclass = 'FSG-Booking-Work-BookEvent'
ORDER BY pxcreatedatetime desc
Create an Address report definition that JOINS BookEvent by .Reference = .VenueGUID
- See below:
Expose the HotelGUID property within FSG-Booking-Work-RoomsRequest
- In the Case Designer click on the RoomRequest case and open the Data Model tab
- Add a .HotelGUID (Text) property
- Within the App Explorer right click on .HotelGUID and choose Define expression
- Set .HotelGUID = .RoomsRequest.HotelGUID
- Within the App Explorer right click on .HotelGUID and choose Optimize for reporting
- To verify, create a BookEvent case, select Hotel Service, spin off at least one RoomsRequest subcase
- Using pgAmin4, execute:
SELECT hotelguid,
pxcreatedatetime
FROM pegadata.pc_fsg_booking_work
WHERE hotelguid is not null
AND pxobjclass = 'FSG-Booking-Work-RoomsRequest’
ORDER BY pxcreatedatetime desc
Create an Address Report Definition that JOINS RoomsRequest by .Reference = ROOMS.HotelGUID
- Query tab exactly the same as in the VenueAddressLatLong report definition
- However, the Data Access tab is different. .Reference = ROOMS.HotelGUID and ROOMS.pxCoverInsKey = EVENT.pzInsKey
Define Data Pages that source the Hotel and Venue Address-querying Report Definitions that also Transform the Response
The logic to define the two Report Definitions was discussed during solution design. In the table below the Response Data Transform sets pyNote.
Data Page | Report Definition | Response Data Transform |
---|---|---|
D_HotelsContacted | HotelAddressLatLong | GenerateAsisExpressionFromAddressReference |
D_VenuesUsed | VenueAddressLatLong | GenerateAsisExpressionFromAddressReference |
Modify the HotelsNotContacted Connect-SQL rule to utilize the two Report Definition-sourced Data Pages
- The final state of the HotelsNotContacted Connect-SQL rule is shown below.
-
AddressPage.pyNote = (param.dist_unit = "km" ? "111.045" : "69.407")
SELECT
pyGUID AS pyGUID,
Reference AS Reference, IsFor AS IsFor, Street AS Street, City AS City, State AS State, PostalCode AS PostalCode, Country AS Country, Latitude AS Latitude, Longitude AS Longitude, Distance AS Distance
FROM (
SELECT z.pyguid AS pyGUID,
z.reference AS Reference,
z.isfor AS IsFor,
z.street AS Street,
z.city AS City,
z.state AS State,
z.postalcode AS PostalCode,
z.country AS Country,
z.latitude AS Latitude,
z.longitude AS Longitude, {AddressPage.pyNote Decimal }
* DEGREES(ACOS(COS(RADIANS(p.latpoint))
* COS(RADIANS(z.latitude))
* COS(RADIANS(p.longpoint - z.longitude))
+ SIN(RADIANS(p.latpoint))
* SIN(RADIANS(z.latitude)))) AS Distance
FROM {Class:FSG-Data-Address} AS z
JOIN (
SELECT latitude AS latpoint, longitude AS longpoint
FROM pegadata.pr_fsg_data_address
WHERE reference IN ({Asis:D_VenuesUsed.pyNote}) ) AS p ON 1 = 1 ) AS d
WHERE distance <= {AddressPage.Distance Decimal }
AND reference NOT IN ({Asis:D_HotelsContacted.pyNote})
AND isfor = 'HOTEL'
ORDER BY distance
LIMIT 15
Verify your work
Proving the solution works requires at least two hotels within the specified radius from a venue, here 5 miles. One of the hotels would be asked to provide rooms, the second hotel would not be asked to provide rooms. When the query is run, the second hotel should be returned in the result set.
Locate the Code-Pega-List Connect_SQL_HotelsNotContacted Activity. Select Actions > Trace, then Actions > Run. Enter 5 as the distance and mi as dist_unit.
SQL | SELECT pyGUID AS pyGUID, Reference AS Reference, IsFor AS IsFor, Street AS Street, City AS City, State AS State, PostalCode AS PostalCode, Country AS Country, Latitude AS Latitude, Longitude AS Longitude, Distance AS Distance FROM ( SELECT z.pyguid AS pyGUID, z.reference AS Reference, z.isfor AS IsFor, z.street AS Street, z.city AS City, z.state AS State, z.postalcode AS PostalCode, z.country AS Country, z.latitude AS Latitude, z.longitude AS Longitude, ? * DEGREES(ACOS(COS(RADIANS(p.latpoint)) * COS(RADIANS(z.latitude)) * COS(RADIANS(p.longpoint - z.longitude)) + SIN(RADIANS(p.latpoint)) * SIN(RADIANS(z.latitude)))) AS Distance FROM pegadata.pr_FSG_Data_Address AS z JOIN ( SELECT latitude AS latpoint, longitude AS longpoint FROM pegadata.pr_fsg_data_address WHERE reference IN ('8d586ede-06ab-4e4c-93a8-6e5b353af790') ) AS p ON 1 = 1 ) AS d WHERE distance <= ? AND reference NOT IN ('c3580ccf-2e8f-4b0e-a7bc-d04819b47968') AND isfor = 'HOTEL' ORDER BY distance LIMIT 15 |
---|---|
SQL Inserts | <69.407> <5> |
The query returns the address for hotels that were not contacted. To identity the hotel and the hotel’s contact would require the pyTempListPage.pxResults() PageList to be iterated.
For each page in D_Hotels, invoke the D_Hotel Lookup data page using “reference” as the pyGUID parameter
3 Solution RAP
Report_Query_Design_Exercise.zip
(179.79 KB)