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 |
| 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:
|
| odata_bind |
Build an 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:
|
| odata_expand |
Return the navigation property name to Discovers via relationship metadata. Returns the exact PascalCase
string for the Example:
|
| odata_expands |
Discover all Returns entries for each outgoing lookup (single-valued navigation
property). Each entry contains the exact PascalCase navigation
property name needed for Example:
|
| odata_select |
Return a list of column logical names suitable for Can be passed directly to Example:
|
| sql |
Execute a read-only SQL query using the Dataverse Web API. The Dataverse SQL endpoint supports a broad subset of T-SQL:
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
|
| sql_columns |
Return a simplified list of SQL-usable columns for a table. Each dict contains Example:
|
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
|
Table schema name (e.g. |
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
|
Well-formed FetchXML query string. The root |
Returns
| Type | Description |
|---|---|
|
Inert query object with |
Exceptions
| Type | Description |
|---|---|
|
If the FetchXML is missing a root |
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
|
Schema name of the entity being created/updated. |
|
to_table
Required
|
Schema name of the target entity the lookup points to. |
|
target_id
Required
|
GUID of the target record. |
Returns
| Type | Description |
|---|---|
|
A dict like |
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
|
Schema name of the source table (e.g. |
|
to_table
Required
|
Schema name of the target table (e.g. |
Returns
| Type | Description |
|---|---|
|
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
|
Schema name of the table (e.g. |
Returns
| Type | Description |
|---|---|
|
List of dicts, each with:
|
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
|
Schema name of the table (e.g. |
|
include_system
Required
|
Include system columns (default |
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
|
Supported SQL SELECT statement. |
Returns
| Type | Description |
|---|---|
|
List of Record objects. Returns an empty list when no rows match. |
Exceptions
| Type | Description |
|---|---|
|
If |
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
|
Schema name of the table (e.g. |
|
include_system
Required
|
When |
Keyword-Only Parameters
| Name | Description |
|---|---|
|
include_system
|
Default value: False
|
Returns
| Type | Description |
|---|---|
|
List of column metadata dicts. |