Different types of query range value assignment (Expressions and conditions as range value) X++ D365FO

 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[edit]

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(01\01\2000)));

Find records where the Field is blank (null) or an empty string. For more see Sys::Query Docs[1]


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[edit]

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[edit]

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[edit]

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. Priot to Ax 2012, this was not possible to achieve using the standard query ranges. From that version onwards, however, the QueryFilter class can be used to achieve the same result.


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[edit]

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[edit]

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*")

Limitations[edit]

The use of the extended query syntax is not supported by the new having filtering available in Ax 2012.


There are two major limitations to the Query Expressions syntax. The first is the loss of support for wildcards and comma-separated range values, and the second is the inability to reference array fields such as dimensions in some older kernel versions.


Whilst in standard queries you can specify “AA*” or “A,B,C” as criteria, and they will be parsed by Axapta and sent through correctly to the database, these will simply be passed directly through when using the Query Expressions engine. As a result, they will not return the expected results. On a related noted, the use of ‘like’ is not supported, so there is no way to use wildcards in any form.


Query Expressions syntax for array fields such as the Dimension field is known to be suppported since the version 5.0.1500.2116 (RU4) for AX 2009. Previous kernel versions are not tested to support Query Expressions syntax for array fields; it is also known not to work at all in Axapta 3.


There is a discussion regarding the use of array fields on the Discussion page for this article. Please contribute to that discussion if possible!

Comments

Popular posts from this blog

Azure BLOB storage with Dynamics 365 FnO X++

Send Email from Ax / D365 FnO using X++

Import Files from Blob storage using X++ - D365 FnO