Class AdvancedCriterionSubquery
DSRequest
that you use to declare
the properties of
a subquery (a fieldQuery
or
valueQuery
) to be used in AdvancedCriteria
.
This class is not meant to be created and used, it is actually documentation of settings
allowed in a DataSource descriptor (.ds.xml file), for use with Smart GWT Pro Edition and
above.
See com.smartgwt.client.docs.serverds
for how to use this documentation.
Subquery definitions are often very compact; only a few properties are permitted for a
client-driven subquery (see "Restrictions on client-driven subqueries", below), and many
use cases can be satisfied specifying just a
dataSource
and a
summary
function
.
Criteria subquery definitions fall into two broad categories:
- Aggregation, where the subquery uses a
SummaryFunctionType
to aggregate or summarize a related dataset, and then filter on that aggregated or summarized value. For example, orders with more than 10 lines, customers with an average order value more than $1000, UK customers with an outstanding payment more than a week old, etc - Related value, where the subquery selects a value (or, for
inSet
-type clauses, a set of values) from a related dataSource and then filters on that value. For example, Products that were not ordered last month, Employees who are based in one of the North American offices, Orders that include a particular category of Product, etc
In ideal circumstances - when both main and subquery dataSource
s are
SQL DataSources
, and a number of other
restrictions are
satisfied - subqueries are implemented by incorporating their functionality into a larger
overall SQL query, because this is the most efficient thing to do, and gives the best
performance. See canEmbedSQL
for a
description
of the rules and nuances around this.
In cases where we cannot implement subqueries by embedding SQL, they are implemented by
converting the subquery definitions into separate real DSRequest
s, executing
them, and then combining their results into the wider resultset.
Subquery Overview
Subqueries are used to derive a value to be compared as part of the main query, from a set of related data where there is no way to directly join to a single record (usually because the subquery is summarizing or extracting from a larger dataset). Each subquery must return a single value per record in the main query, because it going to be compared to a single value from each main record (this value either comes directly from the main record, or is derived via afieldQuery
)
Because the nature of a subquery is that it is deriving a single value from multiple records, subqueries are often involved in some kind of aggregation (returning a record count, or the minimum value, for example). While this is the most common use of subqueries, it is not the only one - see the "Simple valueQuery" example below.
Whether or not a subquery is aggregating multiple records, it typically needs to be constrained by a join to the outer query. For example, if you were looking for all customers that have placed more than $1000-worth of orders, you would use something like the "Simple aggregating subquery" below. As you will see, that example displays no explicit means of joining the subquery to the outer query, and yet one is obviously necessary because we are looking for the sum of "lineValue" customer-by-customer, not the total sum for all customers.
This join from the subquery to the outer query is applied implicitly by Smart GWT's SQL
engine. It auto-discovers a Relation between the main dataSource and subquery dataSource
by following foreignKey
definitions from the subquery
dataSource, until it finds a path to the main dataSource. This auto-discovery usually does
the right thing, but there are two possibilities it does not address:
- There is no direct or indirect Relation from the subquery dataSource to the main
dataSource. This is an unusual case for a subquery - most use use cases call for related
dataSources - but valid cases are conceivable. To cope with this scenario, you set
queryFK
to the special value "*none*
". Note, if Smart GWT fails to find a path from the subquery dataSource to the main dataSource andqueryFK:"*none*"
has not been set, the framework will log a warning and the fetch will fail - There is more than one direct or indirect Relation from the subquery dataSource to the
main dataSource, and you want to use a different one from the one that was auto-discovered.
Again, the solution is to explicitly name the
queryFK
- see the documentation for that property for details of the syntax
Since Smart GWT can only make use of a single value as output from a subquery, if your
subquery returns multiple records, we will simply use the first. If the query returns
multiple fields, you can specify the field to use as the subquery output with
queryOutput
.
If the subquery returns more
than one field and no queryOutput
is specified, we will use the first
aggregated (summaryFunction
) field, or
the first grouped
field
if there are no
aggregated fields, or the primaryKey
field if there are
also no groupBy
fields, or just the first numeric field failing all else.
Note, however, that although we attempt to derive a sensible value, it never makes sense to
return multiple records from a subquery, and it only really makes sense to return multiple
fields if you explicitly identify the correct field with queryOutput
.
Ideally, for the sake of clarity, return a single record, and either have that record
contain a single field, or specify queryOutput
Restrictions on client-driven subqueries
For security reasons, subqueries inrequests
that
came from the
client-side are only permitted to specify a handful of properties. These properties are
sufficient to allow the full power of the subquery feature to be used, without allowing any
of the much broader set of general properties associated with the DSRequest
superclass. The properties that can be set in a client-driven subquery are just those that
are documented as direct properties of the AdvancedCriterionSubquery
class -
specifically canEmbedSQL
,
criteria
,
dataSource
,
groupBy
,
operationId
,
queryFK
,
queryOutput
,
and
summaryFunctions
For DSRequest
s that originally came from the server, it is possible to have a
subquery that specifies any DSRequest
property. Many of these would only have
any relevance or effect if the subquery was run separately rather than embedded (as
described above and in the canEmbedSQL
doc).
If you need to do this, look in the server Javadoc for
DSRequest.setAllowArbitrarySubqueries(boolean)
Finally, note that it is possible to switch off the ability to use subqueries altogether,
either per-DataSource
, or globally by setting
the allowCriteriaSubqueries
flag in your server.properties
file:
allowCriteriaSubqueries: false
Examples
Subqueries are quite hard to describe in narrative text, but the following examples demonstrate their use and should make things clearer
Simple aggregating subquery
This example uses afieldQuery
to select all Order records for customer
1234, where the order total (sum of all the order line values) is greater than $1000.Order.fetchData({ _constructor: "AdvancedCriteria", operator: "and", criteria: [ {fieldName: "customerNumber", operator: "equals", value: 1234}, { fieldQuery: { dataSource: "OrderLine", summaryFunctions: {lineValue: "sum"} }, operator: "greaterThan", value: 1000 } ] });
Simple valueQuery
This example uses avalueQuery
to derive the employeeNumber of a manager when
we only know the email address of that manager, so we can find out who reports to her.
This example shows how to use additional criteria within a subquery. It also demonstrates
the relatively rare situation where no join to the outer dataSource is required (hence the
declaration of queryFK: "*none*"
)Employee.fetchData({ _constructor: "AdvancedCriteria", operator: "and", criteria: [ { fieldName: "reportsTo", operator: "equals", valueQuery: { dataSource: "Employee", queryFK: "*none*", criteria: { operator: "and", criteria: [ {fieldName: "email", operator: "equals", value: 'mpatterson@classicmodelcars.com'} ] } } } ] });
More complex aggregation example
This example uses both afieldQuery
and a valueQuery
to select
all US-based customers who placed more orders in 2022 than they placed in 2021. Note, this
is the number of orders, not the value, and the particular field that we add the "count"
function to is not important (count is the same regardless of which field you count). We
chose the orderNumber in this case, but that choice is arbitraryCustomer.fetchData({ _constructor: "AdvancedCriteria", operator: "and", criteria: [ {fieldName: "country", operator: "equals", value: "USA"}, { fieldQuery: { dataSource: "Order", summaryFunctions : { orderNumber : "count" }, criteria: {fieldName: "orderDate", operator: "iBetweenInclusive", start:new Date("2021-01-01"), end:new Date("2021-12-31")} }, operator: "lessThan", valueQuery: { dataSource: "Order", summaryFunctions : { orderNumber : "count" }, criteria: {fieldName: "orderDate", operator: "iBetweenInclusive", start:new Date("2022-01-01"), end:new Date("2022-12-31")} } } ] });
-
Field Summary
Modifier and TypeFieldDescriptionIndicates whether this subquery can be implemented by embedding SQL directly in the wider SQL statement that will be used to resolve the fetch request.Optional criteria to use for this subquery.The name of theDataSource
to use for this subqueryThe default for a subquery is that the records are joined to the main DataSource on the firstforeignKey
one-to-many relationship found, in field order.The name of the field that will be used as the output of this query. -
Constructor Summary
-
Method Summary
-
Field Details
-
queryOutput
The name of the field that will be used as the output of this query. Only useful if your subquery returns more than one field, and optional even in that case. See theSubqueries overview
for more detailsDefault value is null
- See Also:
-
queryFK
The default for a subquery is that the records are joined to the main DataSource on the firstforeignKey
one-to-many relationship found, in field order. So for example, queryingCustomer
s with a subquery onOrder
s, we discoverOrder.customerId
FK pointing toCustomer.id
. More complicated relations are also auto-discovered: for example, queryingCustomer
s with a subquery onOrderLine
s, we will discover the link toCustomer
viaOrderLine.orderNumber
and thenOrder.customerId
.For many use cases, there is only one relation between any two DataSources, so this default discovery of the relation is often sufficient. However, if you need to, you can optionally specify a
queryFK
as the fieldName of aforeignKey
from the "many" to the "one". If the relation is indirect - as with the join fromOrderLine
toCustomer
described above - you can specifyqueryFK
as a dot-separated path from "many" to "one", like this:queryFK: "orderNumber.customerId"
(but note that this is optional - often, a start point is all that is required to identify the relation-path to use)NOTE:The
queryFK
property leverages theincludeVia
feature, and so is constrained by the same restrictions as that feature. Primarily, this means thatqueryFK
can only name aforeignKey
field on thesubquery dataSouce
; it is not valid to name a field on the main dataSource. In practice, this is not usually a restriction because typically the subquery dataSource will be at the "many" end of a direct or indirect relation to the main dataSource, and so is naturally the one that declares theforeignKey
You can also specify the special value "*none*" for
queryFK
, which means the aggregation query should be done independently from the main query, to simply produce a value separately without any joins to the main dataset. A subquery that specifiesqueryFK: "*none*"
is quite unusual because most use cases of subqueries require a join to the parent dataSource; however, valid use cases do exist - see the "Simple valueQuery" example on thesubquery overview page
Default value is null
- See Also:
-
criteria
Optional criteria to use for this subquery. Note, subqueries usually have implicit criteria, because they are joined to the main dataSource fetch unless thequeryFK
is set to the special value "*none*".Also, because subqueries are themselves part of a larger set of criteria, the subquery result is compared to some other value in that larger criteria - it is easy to get confused by this multi-level criteria issue. You may find it helpful to think of this property as "inside criteria" (criteria used inside this subquery to derive its result) and the criterion of which the subquery is a component (as a
fieldQuery
orvalueQuery
), as "outside criteria"Default value is null
- See Also:
-
canEmbedSQL
Indicates whether this subquery can be implemented by embedding SQL directly in the wider SQL statement that will be used to resolve the fetch request. This property is considered as part of a set of rules we apply to decide whether a subquery can be resolved by embedding SQL (which is the most efficient thing to do, and may be significantly faster), or if we must resolve by separating the subquery out into its ownDSRequest
and manually applying the results.There are two reasons why we might decide to run a subquery as its own
DSRequest
: First, if either the "main" dataSource or the subquery dataSource is not anSQL dataSource
, then obviously we cannot merge into a single SQL statement. More subtly, if there is the possibility that customized logic might be in play, we run the subquery separately because in that case we may interfere with - or completely fail to apply - the custom logic if we do not run the subquery as a full-fledgedDSRequest
.canEmbedSQL
is intended to allow you to override our heuristics, for cases where you know a particular subquery definitely can, or definitely can't, be embedded.Note, whether or not we separate out the subquery into its own
DSRequest
,declarative security rules
are always applied. So, for example, if the current user is not permitted to use thesubquery dataSource
or anoperation
explicitly named on the subquery, the entire fetch operation that the subquery is part of will fail. More nuanced, if the current user is prohibited byfield-level security
from viewing a field, that field will be stripped out of the subquery, which may lead to the subquery failing in a hard-to-predict way (but note, we always log a warning to the server logs when a field is removed in these circumstances).The rules around embedding or separating a subquery are, in order, as follows. As you can see,
canEmbedSQL
overrides all rules except the first (we obviously can't embed SQL if one of the dataSources isn't implemented using SQL)- If either the "main" dataSource or the subquery dataSource is not an
sql dataSource
, the subquery must by separated. Otherwise, - If the
canEmbedSQL
flag is non-null, we honor it. Otherwise, - If the subquery specifies an explicit operationId, the subquery must be separated. Otherwise,
- If the subquery dataSource declares a custom fetch
operation
(that is, a fetch operation that does not declare anoperationId
), the subquery must be separated. Otherwise, - If the subquery dataSource
declares either of the dataSource-level customization properties
DataSource.serverObject
orDataSource.script
, the subquery must be separated. Otherwise, - If the subquery DataSource is a direct instance of the base
class (ie, its canonical class name is
com.isomorphic.sql.SQLDataSource
), the subquery can be embedded. Otherwise, - We now know that subquery dataSource is a
subclass of
SQLDataSource
. We use Reflection to inspect the implementing class: if it overrides any of the methodsexecute()
,executeFetch{}
orSQLExecute
, the subquery must be separated (note, we cache the results of these tests so Reflection is only used the first time a given dataSource instance is used) - If we get to this point without having decided whether the subquery can be embedded, or must be separated, the subquery can be embedded
Default value is null
- See Also:
- If either the "main" dataSource or the subquery dataSource is not an
-
dataSource
The name of theDataSource
to use for this subqueryDefault value is null
- See Also:
-
-
Constructor Details
-
AdvancedCriterionSubquery
public AdvancedCriterionSubquery()
-