Query Ranges in X++
Expressions in query
ranges
One of least understood
but most powerful Axapta features is the so-called Expressions in query
ranges syntax. This is not the same as simply using a
QueryBuildRange object in a query and specifying a criteria for a single field.
Introduction
This is a method of
specifying ranges on queries which allows you to perform complex comparisons,
and create complex join situations which would be impossible using the standard
syntax.
Syntax
To use the special
syntax, you should first add a range to your QueryBuildDataSource object in the
normal way. Note that for this special syntax, it does not matter which field
you use to add the range.
To specify the range
value itself, certain rules must be followed:
§ The entire expression must be enclosed within
single-quotes, not double-quotes
§ The entire expression must be enclosed in
parenthesis (brackets)
§ Each sub-expression must be enclosed in its own
set of parenthesis
§ For fields in the current table, simply the
field name can be used
§ For fields in other tables, a prefix of the
relevant datasource name must be added. This is not always the same as
the table name.
§ String values should be surrounded by
double-quotes, and wrapped in a call to queryValue()
§ Enum values should be specified by their integer
value
§ Date values should be formatted using
Date2StrXpp()
§ Blank string like ' ' will not work as expected,
use sysquery::valueEmptyString().
Examples
In the example below, we
construct a query and add a single datasource.
The range is then added,
using the DataAreaId field on each table. Any field can be used, but using an
unusual one such as DataAreaId helps remind a casual reader of the code that
it's not a normal range.
query = new Query();
dsInventTable =
query.addDataSource(tableNum(InventTable));
// Add our range
queryBuildRange =
dsInventTable.addRange(fieldNum(InventTable, DataAreaId));
Given the above, the
following are valid range specifications:
Simple criteria
Find the record where
ItemId is B-R14. Take note of the single quotes and parenthesis surrounding the
entire expression.
queryBuildRange.value(strFmt('(ItemId == "%1")',
queryValue("B-R14")));
Find records where the ItemType
is Service. Note the use of any2int().
queryBuildRange.value(strFmt('(ItemType == %1)',
any2int(ItemType::Service)));
Find records where the
ItemType is Service or the ItemId is B-R14. Note the nesting of the
parenthesis in this example.
queryBuildRange.value(strFmt('((ItemType == %1) || (ItemId == "%2"))',
any2int(ItemType::Service),
queryValue("B-R14")));
Find records where the
modified date is after 1st January 2000. Note the use of Date2StrXpp() to
format the date correctly.
queryBuildRange.value(strFmt('(ModifiedDate > %1)',
Date2StrXpp(01012000)));
Find records where the
Field is blank (null) or an empty string. For more see Sys::Query Docs
qbrStatement =
this.query().dataSourceName("BankAccountTrans2").addRange(fieldnum(BankAccountTrans,AccountStatement));
//qbrStatement.value("!?*");//this
is the old way that may not work in future versions of AX
qbrStatement.value(sysquery::valueEmptyString());//this is the new way
Complex criteria with combined AND and OR clauses
Find all records where
the ItemType is Service, or both the ItemType is Item and the ProjCategoryId is
Spares. This is not possible to achieve using the standard range syntax.
Note also that in this
example, we are using the fieldStr() method to specify our actual field names
and again, that we have nested our parenthesis for each sub-expression.
queryBuildRange.value(strFmt('((%1 == %2) || ((%1 == %3) && (%4 == "%5")))',
fieldStr(InventTable, ItemType),
any2int(ItemType::Service),
any2int(ItemType::Item),
fieldStr(InventTable,
ProjCategoryId),
queryValue("Spares")));
WHERE clauses referencing fields from multiple tables
For this example below,
we construct a query consisting of two joined datasources (using an Exists
join). Note that we specify the datasource names when adding the datasources to
the query.
The ranges are then
added, using the DataAreaId field on each table as described in the earlier
example.
query = new Query();
dsInventTable = query.addDataSource(tableNum(InventTable), tableStr(InventTable));
dsInventItemBarCode =
dsInventTable.addDataSource(tableNum(InventItemBarCode), tableStr(InventItemBarCode));
dsInventItemBarCode.relations(true);
dsInventItemBarCode.joinMode(JoinMode::ExistsJoin);
// Add our two ranges
queryBuildRange1 =
dsInventTable.addRange(fieldNum(InventTable, DataAreaId));
queryBuildRange2 =
dsInventItemBarCode.addRange(fieldNum(InventItemBarCode, DataAreaId));
Find all records where a
bar code record exists for an item and was modified later than the item was
modified.
In this example, we are
using the range on the BarCode table. Therefore the unqualified ModifiedDate
reference will relate to InventItemBarCode.ModifiedDate. The other field is a
fully-qualified one, using the DatasourceName.FieldName syntax.
queryBuildRange2.value(strFmt('(ModifiedDate > InventTable.ModifiedDate)'));
Note that if we had
added our InventTable datasource using the following code
dsInventTable =
query.addDataSource(tableNum(InventTable), "InventTableCustomName"); // Note that we are
manually specifying a different datasource name
then the query range
would need to appear as follows
queryBuildRange2.value(strFmt('(ModifiedDate > InventTableCustomName.ModifiedDate)'));
Conditional joins
We will modify our
previous example slightly, to remove the automatic addition of relations for
the join.
query = new Query();
dsInventTable =
query.addDataSource(tableNum(InventTable), "InventTable");
dsInventItemBarCode =
dsInventTable.addDataSource(tableNum(InventItemBarCode), "InventItemBarCode");
dsInventItemBarCode.joinMode(JoinMode::ExistsJoin);
// Add our two ranges
queryBuildRange1 =
dsInventTable.addRange(fieldNum(InventTable, DataAreaId));
queryBuildRange2 =
dsInventItemBarCode.addRange(fieldNum(InventItemBarCode, DataAreaId));
We can now use the query
expression to specify whatever we like as the join criteria.
Find all records where
either the ItemType is Service, or the ItemType is Item and a barcode exists.
The join criteria is only applied in the second half of the expression, so all
Service items will appear irrespective of whether they have a bar code. Again,
this is not possible to achieve using the standard query ranges.
queryBuildRange2.value(strFmt('((%1.%2 == %3) || ((%1.%2 == %4) && (%1.%5 == %6)))',
query.dataSourceTable(tableNum(InventTable)).name(), //
InventTable %1
fieldStr(InventTable, ItemType),
// ItemType %2
any2int(ItemType::Service), // %3
any2int(ItemType::Item), // %4
fieldStr(InventTable, ItemId), // ItemId %5
fieldStr(InventItemBarCode,
ItemId))); // %6
Using the techniques
above, it is possible to create queries with almost as much flexibility as
using SQL statements directly.
Filter
on array fields
queryBuildRange.value(strFmt('((%1.%2 == "%4") || (%1.%3 == "%5"))',
queryBuildDataSource.name(),
fieldid2name(tablenum(<table>), fieldid2ext(fieldnum(<table>, Dimension),
Dimensions::code2ArrayIdx(SysDimension::Center))),
fieldid2name(tablenum(<table>),
fieldid2ext(fieldnum(<table>, Dimension),
Dimensions::code2ArrayIdx(SysDimension::Purpose))),
"some dim2 value",
"some dim3 value"));
Note: you must always
specify the datasource name if you use Query Expression syntax to filter on
array fields. See also Limitations section at the bottom of the page.
Using wildcards and comma-separated range values
Again, the previous
example here was using standard syntax, not the special syntax using
expressions. It's not possible to modify the above examples to work with
wildcards.
The above statement
applies to AX versions < 5.0
AX 5.0 introduced
solution to wildcards - while you still cannot directly use wildcards in
ranges, now it supports the 'LIKE' keyword.
(AccountNum LIKE "*AA*" || Name
LIKE "*AA*")
No comments:
Post a Comment