Restricting query results by using fields on joined entities
You seldom want to retrieve all instances of the primary type of a query. In many cases, you join
a dependent entity to a query so you can restrict the results by applying predicates to
one or more of its properties. To apply predicates to the dependent entity in a join,
you can save the Table object that the join method
returns in a local variable. Then, you can use predicate methods on the
Table object, such as compare, to restrict which
dependent instances to include in the result. That restriction also restricts the
primary entity instances that the result returns.
Applying predicates to the dependent entity of an inner join
For example, you want a query that returns all companies in the city of Chicago. The SQL statement for this query looks like:
SELECT * FROM pc_contact
JOIN pc_address
ON pc_address.ID = pc_contact.PrimaryAddressID
WHERE pc_contact.Subtype IN (1,8)
AND pc_address.City = 'Chicago';
Using the query API, the primary entity type of your query is Company, because you want
instances of that type in your result.
var queryCompany = Query.make(Company)
The Company entity does not have a City property. That property is on the
Address entity. You need to join the Address entity to your query. You
must capture the object reference that the join method returns so that you can specify
predicates on values in Address.
var tableAddress = queryCompany.join(Company#PrimaryAddress)
tableAddress.compare(Address#City, Equals, "Chicago")
queryCompany.join(Company#PrimaryAddress).compare(Address#City, Equals, "Chicago")When you run the query, the result contains companies that have Chicago as the city on their primary addresses. The following code demonstrates the use of this predicate on a joined entity.
uses gw.api.database.Query
uses gw.api.database.Relop
// Start a new query with Company as the primary entity.
var queryCompany = Query.make(Company)
// Join Address as the dependent entity to the pimary entity Company.
var tableAddress = queryCompany.join(Company#PrimaryAddress)
// Add a predicate on the dependent entity.
tableAddress.compare(Address#City, Equals, "Chicago")
// Fetch the data with a for loop.
var result = queryCompany.select()
for (company in result) {
print (company + ", " + company.PrimaryAddress.City)
}
Running this code produces a list like the following one:
Jones and West Insurance, Chicago
Shumway Contracting, Chicago
New Energy Corp., Chicago
...
Auto Claims of the West, Chicago
Auto Claims Defenders, Chicago
The query result of the previous example contains only Company instances. Even though the code
includes the Address entity in the query, the results do not include information from joined
entities. The code example uses dot notation to access information from related Address
instances after retrieving Company instances from the result. By joining
Address to the query and applying the predicate, the code ensures that
Company instances in the result have only “Chicago” in the property
company.PrimaryAddress.City.
Predicates on the left outer join dependent entity differ from SQL
With the query builder APIs, generally you join a dependent entity to a query only so you can restrict the results with predicates on the dependent entity. Using the compare method to apply a predicate to a dependent entity that you join with a left outer join has no effect on the rows that Gosu retrieves.
The ways that SQL queries and Gosu code filter the dependent table rows differ. Gosu fetches the dependent
entity only if necessary, if code that accesses the dependent entity executes. Gosu applies the filter when
running the query on the primary table, not when fetching the dependent table row. The value of a dependent
entity property that you access by using dot notation is null only if there is no matching
entity instance regardless of the filter. To retrieve filtered values from related entities, use a row
query.
For example, the following SQL filters the names of group supervisors. If the name does not match, the value
for the supervisor user and contact columns are null.
SELECT * FROM groups
LEFT OUTER JOIN users
ON groups.supervisor = users.ID
LEFT OUTER JOIN contacts
ON users.contactID = contacts.ID
AND contacts.lastName = 'Visor';
The following Gosu code shows the effect of dot notation to access information related to the primary entity of
an outer-join query. The code accesses the DisplayName information for the group supervisor
from the User entity with dot notation on Group instances retrieved from the
result. The output shows the supervisor name for every group because Gosu applies the filter on the primary
table query, not when print retrieves the property values for related entities.
uses gw.api.database.Query
// -- Query the Group entity --
var queryGroup = Query.make(Group)
// -- Supervisor is a foreign key from Group to User.
var userTable = queryGroup.outerJoin(Group#Supervisor)
// -- Contact is a foreign key from User to UserContact.
var contactTable = userTable.outerJoin(User#Contact)
// -- Apply a filter to the outer joined entity
contactTable.compare(UserContact#LastName, Relop.Equals, "Visor")
// -- Fetch the Group instances with a for loop and print them --
var result = queryGroup.select()
for (group in result) {
if (group.Supervisor != null) {
// Every supervisor’s name appears because the outer join does not filter the groups
print(group.Name + ": " + group.Supervisor.Contact.DisplayName)
} else { // Provide user-friendly text if the group has no supervisor
// This text appears only for groups that have no supervisor
print (group.Name + ": " + "This group has no supervisor")
}
}
See also
