FetchXML seems to be a quirky subset of the SQL implemented in XML and optimized for UI tasks, including:
- the ability select specific fields (
foo
,b.bar
) or all fields (*
) from an entity - perform
LEFT OUTER
andINNER
joins - assign aliases to attributes (
select a.b as foo
) and (joined) entities (left join foo as BAR
) - perform simple joins conditions (i.e.
on a.foo = b.bar
) - use filter conditions (i.e. the WHERE clause)
- order the results
- use aggregate functions (
sum
,count
, etc.)
However, at first glance, compared to SQL, it lacks:
- join on more than one field
- filter by comparing fields in separate tables/entities
- the
HAVING
clause RIGHT
andFULL OUTER
joins- the
NOT
operator - nested queries (though some use-cases can be replicated)
In addition, it has the ability to:
- implicitly join on entity relationships stored in the CDS/Dataverse (i.e. you can join without specifying the join fields if there is already a relationship defined in the Dataverse)
The Awesome XRM ToolBox has a couple of extensions that make working with fetch xml a breeze. Specifically:
- SQL4CDS: Write SQL and then click a button to render the equivalent FetchXML and execute the query
- FetchXML Builder: Use a GUI to build a FetchXML statement, and then click a button to render the equivalent SQL and Odata Query, and execute the query
Did you find the official docs as useless as I did? Don't worry, Mark Carrington put together some fantastic blog posts on FetchXML:
- Inside FetchXML Part 1:
<attribute>
- Inside FetchXML Part 2:
<link-entity>
- Inside FetchXML Part 3:
<filter>
- Inside FetchXML Part 4:
<order>
- Inside FetchXML Part 5:
<attribute aggregate="...">
- Inside FetchXML Part 6: Datetime Grouping
- Inside FetchXML Part 7:
<attribute rowaggregate="...">
- Inside FetchXML Part 8: Page Count and Top
- Inside FetchXML Part 9: Distinct
- Inside FetchXML Part 10: Link Entity (Again)
- Paging Gotchas
- FetchXML Multiple Links
- Column Comparisons in Fetchxml - a.k.a
<filter valueof="..."/>
- FetchXML Date Filtering
- Multi-Select Picklist Filtering
- Querying Metadata with FetchXML
- Dataverse Paging with Distinct
If you want to understand the nuts and bolts of FetchXML, you'll want to understand the schema. However, if you're not a computer, xml schemas are difficult to read, and I find re-phrasing the schema in my own words to be a useful exercise, and here it is in my own words:
Note that the Green Book icon (π) indicates an element type
A FetchXML query (on the account
table, in this case) looks basically like this:
<fetch >
<entity name='account'>
... lots of stuff
</entity>
</fetch>
and each element can have a pre-defined set of attributes (e.g.
name='account'
) and child elements (e.g. the <entity>
element nested
within the fetch
element), and the type definitions in the schema define
all the possible attribute and child element types.
A single fetch (i.e. SELECT) statement
Notes:
- Setting
distinct=true
prevents the default behavior of including the primary id of the main entity in the query results - For text fields,
distinct
is based on the first 2000 characters
<entity>
: The main table/entity of the query<order/>
: An field/attribute on which to order the results
- version (string): Always "1.0" as far as I can tell
- mapping (
"internal"
or"logical"
): Always"logical"
as far as I can tell - distinct (boolean): SQL's
SELECT DISTINCT ...
- top (integer): SQL's
TOP
- count (integer, default: 5000, max: 5000)): [Paging] Number of records to return per page
- page (integer): [Paging] Page number to return
- paging-cookie (string): [Paging] The paging cooke from the previous page
- no-lock (boolean, optional, default="false): SQL's
NOLOCK
- output-format (
"xml-ado"
,"xml-auto"
,"xml-elements"
,"xml-raw"
or"xml-platform"
): format for the return value? - returntotalrecordcount (boolean, optional, default="false): return the record count of the query
- min-active-row-version (boolean, optional, default="false"): T-SQL's
MIN_ACTIVE_ROWVERSION
- aggregate (boolean): Unused? Deprecated?
- utc-offset (IntOrEmpty): Unused? Deprecated?
Represents the main table being fetched (i.e. the FOO
from select * from FOO inner join b on a.id = foo.id
).
This is also the container for all the rest of the SQL clauses (JOIN
s,
WHERE
, ORDER BY
,) so acts kind of like the SELECT
statement.
<attribute/>
: Attributes selected from the main table<order/>
: An field/attribute on which to order the results<link-entity>
: Nested table joins<filter>
: SQL'sWHERE
clause
- name (string, required): The name of the table
- enableprefiltering (boolean)
- prefilterparametername (string)
An individual boolean condition (e.g where foo.id = 1
)
Note that the attribute "value" is used for all operators that compare to a single value (for example, eq). The element "value" is used for operators that compare to multiple values (for example, in). Some operators require neither the attribute "value" or the element "value" (for example, null).
<value>
: A value when the operator takes more than one comparable (e.g. thenIN
operator)
- attribute (string): Attribute name (
BAR
ofSELECT foo.BAR
) - entityname (string): Entity name (
FOO
ofSELECT FOO.bar
) - alias (string): Alias for the field (attribute)
- operator (Operator): the operator used by the comparison
- value (string): Value when ther is exactly one comparable (e.g. then
EQ
operator) - valueof (string): The name of a field/attribute (within the same entity!!) to which the
column
field/attribute is compared - aggregate (AggregateType): one of
"count"
,"countcolumn"
,"sum"
,"avg"
,"min"
,"max"
- rowaggregate (RowAggregateType:
"countchildren"
- column (string): ???
- uiname: (internal / unused)
- uitype: (internal / unused)
- uihidden (TrueFalse01Type): (internal / unused)
Basic and/or logical operators for aggregating conditions into a single
condition (e.g. where foo.id = 1 AND foo.amount > 1
)
Note that conditions always need to be wrapped in a filter, even if there
is only one condition. Hence, this is kind of a mix between the WHERE
and
AND
/OR
keywords.
<condition>
: Conditions on the columns of the current entity (i.e.entity.month = "February"
)<filter>
(Self referential): for mixing and and or statments (e.g.a = 1 AND (b = 1 OR c = 3)
)
- type ("and" or "or"; default: "and"): operator used to combine child
<filter>
's and<condition/>
's - isquickfindfields (boolean): ???
Attribute selectors (e.g. the foo.bar as bat
of select FOO.BAR AS BAT from baz
).
Note that this is a mix of SQL's GROUPBY
(e.g. GROUPBY foo.bar
) and the
SQL's field selectors (select FOO.BAR AS BAT
), as the grouping is defined
in this node (which I find strangly appealing).
To apply an aggregate function, supply the aggregate="[AggregateType]"
attribute. In which case:
- All the other
<attribute />
s should have eithergroupby="true"
or anaggregate="[AggregateType]"
specifiedname="foo" aggregate="count"
is equivalent to SQL'sCOUNT()
name="foo" aggregate="countcolumn"
is equivalent to SQL'sCOUNT(foo NOT NULL)
name="foo" aggregate="countcolumn" distinct="true"
is equivalent to SQL'sCOUNT(DISTINCT foo)
- when grouping on a date/time field, you must specify the
dategrouping
attribute - when aggregating on a parent record, setting
rowaggregate="countchildren"
returns the count of child records
- name (string, required): the attribute name (e.g. the
BAR
offoo.BAR as bat
) - addedby (string): (internal / unused)
- alias (string): attribute aliase (e.g. the
BAT
offoo.bar as BAT
). Note that this can cause problems with filters, so Mark Carrington does NOT recommend using this feature - groupby (FetchBoolType): Group by this
field/attribute (when at least one
<attribute/>
includes anaggregate="..."
function) - dategrouping (DateGroupingType ]): required when setting
groupby="true"
on a datetime field. - aggregate (AggregateType): The name of an aggregate function to apply to the field
- distinct (FetchBoolType): for use with
aggregate="countcolumn"
to formCOUNT( DIST foo )
- usertimezone (FetchBoolType): (unused?)
- build (build type): (internal / unused)
The type specification for the <order>
element. Important notes:
- Each
<order />
element represents a single field ordering within SQL'sORDER
clause. If you want to order more than one field, just include multiple<order />
elements. Note that: - If you sort on the foreign-key of a link-entity, the sording will be based on
the
link-entity
's primary name field (Hey, this is optimized for UI, right?) - You can sort on fields within
link-entity
's by incliuding an<order />
within thelink-entity
. However, there is no way to specify the precedence of the fields across multiple entities. - Ties are broken using the primary key where possible (for consistent paging purposes)
- Aliases muck up sorting
- attribute (string): the field (attribute) on which to order the results
- alias (string): ???
- descending (boolean, default: false): Obvious
The type specification for the <link-entity>
element. A mix between SQL's
JOIN
operator, and attribute selector for field of joined tables (i.e.
select B.C from a INNER JOIN B ON A.ID = B.ID
)
This is limited to:
LEFT OUTER
andINNER
joins- exact matching on a single field
- a maximum of 10 links per query
<attribute/>
: Include a particular element from the linked entity<all-attributes/>
: Include all attributes of the linked entity (i.e.SELECT *
)<order/>
: An ordering for the results based on the current entity<link-entity>
(Self referential): Another entity linked to the current entity<filter>
: A filter condition based on the current entity
- name (string, required): the linked entity name (e.g. the
B
infrom a inner join B
) - to (string): the
FOO
infrom a inner join b on a.FOO = b.bar
(note, this feels backward) - from (string): the
BAR
infrom a inner join b on a.foo = b.BAR
(note, this feels backward) - alias (string): the linked entity alias (e.g. the
BAZ
infrom a inner join b as BAZ
). This affects the field names in the query output - link-type (string):
"inner"
(SQL'sINNER JOIN
) or"outer"
(SQL'sLEFT OUTER JOIN
), and maybe others?? - visible (boolean): used? deprecated?
- intersect (boolean): internal? N:N relationships depend on a special Relationship Entity, sometimes called an "Intersect"...
- enableprefiltering (boolean): Improve report performance by using filters
- prefilterparametername (string): Improve report performance by using filters
These represent many of the tokens in the SQL language (this designation is mine, and not part of the Fetch XML Spec)
The Star operator (i.e. SELECT *
), represented by the simple element <all-attributes>
Represents a value when an operator includes multiple comparisons (like the
IN
operator). Example: <value>hello world</value>
Empty string or string of ints. Used by utc-offset
attribute of FetchType
One of "1"
or "0"
. Used by condition (unhidden
attribute) and SerializedTrueFalse01Type
One of "count"
, "countcolumn"
, "sum"
, "avg"
, "min"
or "max"
Just "countchildren"
One of "day"
, "week"
, "month"
, "quarter"
, "year"
, "fiscal-period"
, "fiscal-year"
One of "true"
, "false"
, "1"
, or "0"
. Used by some fetch attributes
Build number. One of "1.504021"
or "1.003017"
Enumeration of operators used by the condition
and savedquery.columnset.filter.condition
types
"eq"
"neq"
"ne"
"gt"
"ge"
"le"
"lt"
"like"
"not-like"
"in"
"not-in"
"between"
"not-between"
"null"
"not-null"
"under"
"eq-or-under"
"not-under"
"above"
"eq-or-above"
"eq-useroruserhierarchy"
"eq-useroruserhierarchyandteams"
"begins-with"
"not-begin-with"
"ends-with"
"not-end-with"
"contain-values"
"not-contain-values"
"eq-userid"
"ne-userid"
"eq-userteams"
"eq-useroruserteams"
"eq-businessid"
"ne-businessid"
"eq-userlanguage"
"on"
"on-or-before"
"on-or-after"
"in-fiscal-year"
"in-fiscal-period"
"in-fiscal-period-and-year"
"in-or-before-fiscal-period-and-year"
"in-or-after-fiscal-period-and-year"
"yesterday"
"today"
"tomorrow"
"last-seven-days"
"next-seven-days"
"last-week"
"this-week"
"next-week"
"last-month"
"this-month"
"next-month"
"last-year"
"this-year"
"next-year"
"this-fiscal-year"
"this-fiscal-period"
"next-fiscal-year"
"next-fiscal-period"
"last-fiscal-year"
"last-fiscal-period"
Operators that compare to the current date with a specified range (e.g.
created within the last 3 days: '<condition attribute="createdon" operator="last-x-days" value="3" />
):
"last-x-hours"
"next-x-hours"
"last-x-days"
"next-x-days"
"last-x-weeks"
"next-x-weeks"
"last-x-months"
"next-x-months"
"olderthan-x-months"
"olderthan-x-years"
"olderthan-x-weeks"
"olderthan-x-days"
"olderthan-x-hours"
"olderthan-x-minutes"
"last-x-years"
"next-x-years"
"last-x-fiscal-years"
"last-x-fiscal-periods"
"next-x-fiscal-years"
"next-x-fiscal-periods"
The savedquery
element
is used to create views within the Dynamics. These are not of interest to me
just yet, so to simplify understanding of the core Fetch type, definitions
exclusive to savedquery
are in this separate section
A big and ugly object.
Non-negative integer string. Used within savedquery
Version string format. Used by IntroducedVersion
attribute of savedquery
Empty string or string of boolean. Used by savedquery.layoutxml
Extension of TrueFalse01Type. Used for savedquery
boolean attributes
An instnace of the FieldXmlFieldUIType
An instnace of the FieldXmlFieldUIType
An instnace of the FieldXmlFieldUIType
a simple mapping between an id (guid), language code (int) and descripiton (string)
A string in guid format