QueryOperations Class

Namespace for query operations.

Accessed via client.query. Provides query and search operations against Dataverse tables.

Example:


   from PowerPlatform.Dataverse.models.filters import col

   client = DataverseClient(base_url, credential)

   # Fluent query builder (recommended)
   for record in (client.query.builder("account")
                  .select("name", "revenue")
                  .where(col("statecode") == 0)
                  .order_by("revenue", descending=True)
                  .top(100)
                  .execute()):
       print(record["name"])

   # SQL query
   rows = client.query.sql("SELECT TOP 10 name FROM account ORDER BY name")
   for row in rows:
       print(row["name"])

Constructor

QueryOperations(client: DataverseClient)

Parameters

Name Description
client
Required

The parent DataverseClient instance.

Methods

builder

Create a fluent query builder for the specified table.

Returns a QueryBuilder that can be chained with filter, select, and order methods, then executed directly via .execute().

fetchxml

Return an inert FetchXmlQuery object.

No HTTP request is made until execute or execute_pages is called on the returned object.

Use for SQL-JOIN scenarios, aggregate queries, or other operations that the OData builder endpoint cannot express.

Example:


   query = client.query.fetchxml("""
     <fetch top="50">
       <entity name="account">
         <attribute name="name" />
         <link-entity name="contact" from="parentcustomerid"
                      to="accountid" alias="c" link-type="inner">
           <attribute name="fullname" />
         </link-entity>
       </entity>
     </fetch>
   """)

   # Eager — collect all pages:
   result = query.execute()
   df = result.to_dataframe()

   # Lazy — process one page at a time:
   for page in query.execute_pages():
       process(page.to_dataframe())
odata_bind

Build an @odata.bind entry for setting a lookup field.

Auto-discovers the navigation property name and entity set name from metadata. Returns a single-entry dict that can be merged into a create or update payload.

Example:


   # Instead of manually constructing:
   #   {"parentcustomerid_account@odata.bind": "/accounts(guid)"}
   # Just do:
   bind = client.query.odata_bind("contact", "account", acct_id)
   client.records.create("contact", {
       "firstname": "Jane",
       "lastname": "Doe",
       **bind,
   })
odata_expand

Return the navigation property name to $expand from one table to another.

Discovers via relationship metadata. Returns the exact PascalCase string for the expand= parameter.

Example:


   nav = client.query.odata_expand("contact", "account")
   # Returns e.g. "parentcustomerid_account"
   for page in client.records.get("contact",
                                  select=["fullname"],
                                  expand=[nav],
                                  top=5):
       for r in page:
           acct = r.get(nav) or {}
           print(f"{r['fullname']} -> {acct.get('name', 'N/A')}")
odata_expands

Discover all $expand navigation properties from a table.

Returns entries for each outgoing lookup (single-valued navigation property). Each entry contains the exact PascalCase navigation property name needed for $expand and @odata.bind, plus the target entity set name.

Example:


   expands = client.query.odata_expands("contact")
   for e in expands:
       print(f"expand={e['nav_property']}  -> {e['target_table']}")

   # Use in a query
   e = next(e for e in expands if e['target_table'] == 'account')
   for page in client.records.get("contact",
                                  select=["fullname"],
                                  expand=[e['nav_property']]):
       ...
odata_select

Return a list of column logical names suitable for $select.

Can be passed directly to client.records.get(table, select=...).

Example:


   cols = client.query.odata_select("account")
   for page in client.records.get("account", select=cols, top=10):
       for r in page:
           print(r)
sql

Execute a read-only SQL query using the Dataverse Web API.

The Dataverse SQL endpoint supports a broad subset of T-SQL:


   SELECT / SELECT DISTINCT / SELECT TOP N (0-5000)
   FROM table [alias]
   INNER JOIN / LEFT JOIN (multi-table, no depth limit)
   WHERE (=, !=, >, <, >=, <=, LIKE, IN, NOT IN, IS NULL,
          IS NOT NULL, BETWEEN, AND, OR, nested parentheses)
   GROUP BY column
   ORDER BY column [ASC|DESC]
   OFFSET n ROWS FETCH NEXT m ROWS ONLY
   COUNT(*), SUM(), AVG(), MIN(), MAX()

SELECT * is not supported – specify column names explicitly. Use sql_columns to discover available column names for a table.

Not supported: SELECT >>*<<, subqueries, CTE, HAVING, UNION, RIGHT/FULL/CROSS JOIN, CASE, COALESCE, window functions, string/date/math functions, INSERT/UPDATE/DELETE. For writes, use client.records methods.

sql_columns

Return a simplified list of SQL-usable columns for a table.

Each dict contains name (logical name for SQL), type (Dataverse attribute type), is_pk (primary key flag), and label (display name). Virtual columns are always excluded because the SQL endpoint cannot query them.

Example:


   cols = client.query.sql_columns("account")
   for c in cols:
       print(f"{c['name']:30s} {c['type']:20s} PK={c['is_pk']}")

builder

Create a fluent query builder for the specified table.

Returns a QueryBuilder that can be chained with filter, select, and order methods, then executed directly via .execute().

builder(table: str) -> QueryBuilder

Parameters

Name Description
table
Required
str

Table schema name (e.g. "account").

Returns

Type Description

A QueryBuilder instance bound to this client.

Examples

Build and execute a query fluently:


   from PowerPlatform.Dataverse.models.filters import col

   for record in (client.query.builder("account")
                  .select("name", "revenue")
                  .where(col("statecode") == 0)
                  .where(col("revenue") > 1_000_000)
                  .order_by("revenue", descending=True)
                  .top(100)
                  .page_size(50)
                  .execute()):
       print(record["name"])

With composable expression tree:


   from PowerPlatform.Dataverse.models.filters import col

   for record in (client.query.builder("account")
                  .where((col("statecode") == 0) | (col("statecode") == 1))
                  .where(col("revenue") > 100_000)
                  .execute()):
       print(record["name"])

fetchxml

Return an inert FetchXmlQuery object.

No HTTP request is made until execute or execute_pages is called on the returned object.

Use for SQL-JOIN scenarios, aggregate queries, or other operations that the OData builder endpoint cannot express.

Example:


   query = client.query.fetchxml("""
     <fetch top="50">
       <entity name="account">
         <attribute name="name" />
         <link-entity name="contact" from="parentcustomerid"
                      to="accountid" alias="c" link-type="inner">
           <attribute name="fullname" />
         </link-entity>
       </entity>
     </fetch>
   """)

   # Eager — collect all pages:
   result = query.execute()
   df = result.to_dataframe()

   # Lazy — process one page at a time:
   for page in query.execute_pages():
       process(page.to_dataframe())
fetchxml(xml: str) -> FetchXmlQuery

Parameters

Name Description
xml
Required
str

Well-formed FetchXML query string. The root <entity name="..."> element determines the entity set endpoint.

Returns

Type Description

Inert query object with .execute() and .execute_pages() methods.

Exceptions

Type Description

If the FetchXML is missing a root <entity> element or the entity name attribute.

odata_bind

Build an @odata.bind entry for setting a lookup field.

Auto-discovers the navigation property name and entity set name from metadata. Returns a single-entry dict that can be merged into a create or update payload.

Example:


   # Instead of manually constructing:
   #   {"parentcustomerid_account@odata.bind": "/accounts(guid)"}
   # Just do:
   bind = client.query.odata_bind("contact", "account", acct_id)
   client.records.create("contact", {
       "firstname": "Jane",
       "lastname": "Doe",
       **bind,
   })
odata_bind(from_table: str, to_table: str, target_id: str) -> Dict[str, str]

Parameters

Name Description
from_table
Required
str

Schema name of the entity being created/updated.

to_table
Required
str

Schema name of the target entity the lookup points to.

target_id
Required
str

GUID of the target record.

Returns

Type Description

A dict like {"NavProp@odata.bind": "/entityset(guid)"}.

Exceptions

Type Description

If no relationship found between the tables.

odata_expand

Return the navigation property name to $expand from one table to another.

Discovers via relationship metadata. Returns the exact PascalCase string for the expand= parameter.

Example:


   nav = client.query.odata_expand("contact", "account")
   # Returns e.g. "parentcustomerid_account"
   for page in client.records.get("contact",
                                  select=["fullname"],
                                  expand=[nav],
                                  top=5):
       for r in page:
           acct = r.get(nav) or {}
           print(f"{r['fullname']} -> {acct.get('name', 'N/A')}")
odata_expand(from_table: str, to_table: str) -> str

Parameters

Name Description
from_table
Required
str

Schema name of the source table (e.g. "contact").

to_table
Required
str

Schema name of the target table (e.g. "account").

Returns

Type Description
str

The navigation property name (PascalCase).

Exceptions

Type Description

If no navigation property found for the target.

odata_expands

Discover all $expand navigation properties from a table.

Returns entries for each outgoing lookup (single-valued navigation property). Each entry contains the exact PascalCase navigation property name needed for $expand and @odata.bind, plus the target entity set name.

Example:


   expands = client.query.odata_expands("contact")
   for e in expands:
       print(f"expand={e['nav_property']}  -> {e['target_table']}")

   # Use in a query
   e = next(e for e in expands if e['target_table'] == 'account')
   for page in client.records.get("contact",
                                  select=["fullname"],
                                  expand=[e['nav_property']]):
       ...
odata_expands(table: str) -> List[Dict[str, Any]]

Parameters

Name Description
table
Required
str

Schema name of the table (e.g. "contact").

Returns

Type Description

List of dicts, each with:

  • nav_property – PascalCase navigation property for $expand

  • target_table – target entity logical name

  • target_entity_set – target entity set (for @odata.bind)

  • lookup_attribute – the lookup column logical name

  • relationship – relationship schema name

odata_select

Return a list of column logical names suitable for $select.

Can be passed directly to client.records.get(table, select=...).

Example:


   cols = client.query.odata_select("account")
   for page in client.records.get("account", select=cols, top=10):
       for r in page:
           print(r)
odata_select(table: str, *, include_system: bool = False) -> List[str]

Parameters

Name Description
table
Required
str

Schema name of the table (e.g. "account").

include_system
Required

Include system columns (default False).

Keyword-Only Parameters

Name Description
include_system
Default value: False

Returns

Type Description

List of lowercase column logical names.

sql

Execute a read-only SQL query using the Dataverse Web API.

The Dataverse SQL endpoint supports a broad subset of T-SQL:


   SELECT / SELECT DISTINCT / SELECT TOP N (0-5000)
   FROM table [alias]
   INNER JOIN / LEFT JOIN (multi-table, no depth limit)
   WHERE (=, !=, >, <, >=, <=, LIKE, IN, NOT IN, IS NULL,
          IS NOT NULL, BETWEEN, AND, OR, nested parentheses)
   GROUP BY column
   ORDER BY column [ASC|DESC]
   OFFSET n ROWS FETCH NEXT m ROWS ONLY
   COUNT(*), SUM(), AVG(), MIN(), MAX()

SELECT * is not supported – specify column names explicitly. Use sql_columns to discover available column names for a table.

Not supported: SELECT >>*<<, subqueries, CTE, HAVING, UNION, RIGHT/FULL/CROSS JOIN, CASE, COALESCE, window functions, string/date/math functions, INSERT/UPDATE/DELETE. For writes, use client.records methods.

sql(sql: str) -> List[Record]

Parameters

Name Description
sql
Required
str

Supported SQL SELECT statement.

Returns

Type Description

List of Record objects. Returns an empty list when no rows match.

Exceptions

Type Description

If sql is not a string or is empty.

Examples

Basic query:


   rows = client.query.sql(
       "SELECT TOP 10 name FROM account ORDER BY name"
   )

JOIN with aggregation:


   rows = client.query.sql(
       "SELECT a.name, COUNT(c.contactid) as cnt "
       "FROM account a "
       "JOIN contact c ON a.accountid = c.parentcustomerid "
       "GROUP BY a.name"
   )

sql_columns

Return a simplified list of SQL-usable columns for a table.

Each dict contains name (logical name for SQL), type (Dataverse attribute type), is_pk (primary key flag), and label (display name). Virtual columns are always excluded because the SQL endpoint cannot query them.

Example:


   cols = client.query.sql_columns("account")
   for c in cols:
       print(f"{c['name']:30s} {c['type']:20s} PK={c['is_pk']}")
sql_columns(table: str, *, include_system: bool = False) -> List[Dict[str, Any]]

Parameters

Name Description
table
Required
str

Schema name of the table (e.g. "account").

include_system
Required

When False (default), columns that end with common system suffixes (_base, versionnumber, timezoneruleversionnumber, utcconversiontimezonecode, importsequencenumber, overriddencreatedon) are excluded.

Keyword-Only Parameters

Name Description
include_system
Default value: False

Returns

Type Description

List of column metadata dicts.