この記事では、sdk for Pythonを使用して Dataverse データに対してクエリを実行する方法について説明します。 構造化クエリ言語 (SQL) と OData ベースの API を使用して、データのクエリを実行できます。
Python開発者は、この記事に進む前に、作業の開始を読んで、Python用 SDK について学習する必要があります。
QueryBuilder
QueryBuilder は、レコードのクエリを実行するための推奨される方法です。 これは、正しい OData クエリを自動的に生成する、直感的で型安全なインターフェイスを提供します。 OData フィルター構文を覚える必要はありません。
# Fluent query builder (recommended)
from PowerPlatform.Dataverse.models.filters import col
for record in (client.query.builder("account")
.select("name", "revenue")
.where(col("statecode") == 0)
.where(col("revenue") > 1000000)
.order_by("revenue", descending=True)
.top(100)
.page_size(50)
.execute()):
print(f"{record['name']}: {record['revenue']}")
QueryBuilder では、値の書式設定、列名の大文字と小文字の区別、OData 構文が自動的に処理されます。
col()演算子と標準のPython演算子を使用してフィルター式を作成します。
# Get results as a pandas DataFrame (consolidates all pages)
df = (client.query.builder("account")
.select("name", "telephone1")
.where(col("statecode") == 0)
.top(100)
.execute()
.to_dataframe())
print(f"Got {len(df)} accounts")
# Comparison filters using col() expressions
query = (client.query.builder("contact")
.where(col("statecode") == 0) # statecode eq 0
.where(col("revenue") > 1000000) # revenue gt 1000000
.where(col("name").contains("Corp")) # contains(name, 'Corp')
.where(col("statecode").in_([0, 1])) # Microsoft.Dynamics.CRM.In(...)
.where(col("revenue").between(100000, 500000)) # revenue ge 100000 and revenue le 500000
.where(col("telephone1").is_null()) # telephone1 eq null
)
文字列フィルター
部分文字列の照合には、 .contains()、 .startswith()、および .endswith() を使用します。 または、%ワイルドカード パターンで.like()を使用します。 SDK は、そのパターンを最も近い同等のフィルターにコンパイルします。
query = (client.query.builder("contact")
.where(col("email").contains("outlook.com")) # contains(email, 'outlook.com')
.where(col("lastname").startswith("Smith")) # startswith(lastname, 'Smith')
.where(col("firstname").endswith("son"))) # endswith(firstname, 'son')
| パターン形式 | Example | 同等のフィルター |
|---|---|---|
val% |
like("Contoso%") |
先頭が |
%val |
like("%Ltd") |
endswith (文字列が特定のサフィックスで終了するかを確認するメソッド) |
%val% |
like("%Corp%") |
含む |
| ワイルドカードなし | like("Contoso") |
正確な等値 |
query = client.query.builder("account").where(col("name").like("Contoso%"))
Note
"Con%oso"などの内部ワイルドカードを持つパターンでは、ValueErrorが発生します。 これらのパターンには、 client.query.fetchxml() または client.query.sql() を使用します。
否定とメンバーシップの設定
各正の演算子には、 .is_not_null()、 .not_in()、および .not_between()という否定された対応する演算子があります。
query = (client.query.builder("contact")
.where(col("telephone1").is_not_null()) # telephone1 ne null
.where(col("statecode").not_in([2, 3])) # not in set
.where(col("creditlimit").not_between(0, 100))) # outside range
Raw OData エスケープ ハッチ
col()がカバーしていない OData 関数または構文が必要な場合は、raw()を使用します。 これは、対応する型付きの式が存在しない OData 式に対して想定されるフォールバックです。
from PowerPlatform.Dataverse.models.filters import col, raw
query = (client.query.builder("account")
.where(col("statecode") == 0)
.where(raw("Microsoft.Dynamics.CRM.Today(PropertyName='createdon')")))
複雑なロジック (OR、NOT、グループ化) の場合は、 &、 |、 ~を使用して式を作成します。
from PowerPlatform.Dataverse.models.filters import col
# OR conditions: (statecode = 0 OR statecode = 1) AND revenue > 100k
for record in (client.query.builder("account")
.select("name", "revenue")
.where(((col("statecode") == 0) | (col("statecode") == 1))
& (col("revenue") > 100000))
.execute()):
print(record["name"])
# NOT, between, and in operators
for record in (client.query.builder("account")
.where(col("statecode") != 2) # NOT inactive
.where(col("revenue").between(100000, 500000)) # revenue in range
.execute()):
print(record["name"])
書式設定された値と注釈
この例では、ローカライズされたラベル、通貨記号、表示名を要求する方法を示します。
# Get formatted values (choice labels, currency, lookup names) — via query builder
for record in (client.query.builder("account")
.select("name", "statecode", "revenue")
.include_formatted_values()
.execute()):
status = record["statecode@OData.Community.Display.V1.FormattedValue"]
print(f"{record['name']}: {status}")
# Get formatted values — via records.list() / records.retrieve() include_annotations param
result = client.records.list(
"account",
select=["name", "statecode"],
include_annotations="OData.Community.Display.V1.FormattedValue",
)
for record in result:
label = record.get("statecode@OData.Community.Display.V1.FormattedValue")
print(f"{record['name']}: {label}")
record = client.records.retrieve(
"account", account_id,
select=["name", "statuscode"],
include_annotations="OData.Community.Display.V1.FormattedValue",
)
if record:
print(record.get("statuscode@OData.Community.Display.V1.FormattedValue"))
使用可能なすべての注釈を要求するには、ビルダーで.include_annotations()を呼び出すか (既定値は"*")、records.list()とrecords.retrieve()にinclude_annotations="*"を渡します。
builder = (client.query.builder("account")
.select("name", "_ownerid_value")
.include_annotations()) # defaults to "*"
ナビゲーション プロパティを展開する
入れ子になった展開とオプションを使用して、 $select、 $filter、 $orderby、および $topを使用してナビゲーション プロパティを展開します。
from PowerPlatform.Dataverse.models.query_builder import ExpandOption
# Expand related tasks with filtering and sorting
for record in (client.query.builder("account")
.select("name")
.expand(ExpandOption("Account_Tasks")
.select("subject", "createdon")
.filter("contains(subject,'Task')")
.order_by("createdon", descending=True)
.top(5))
.execute()):
print(record["name"], record.get("Account_Tasks"))
Paging
execute_pages()を使用して、フィルター処理、並べ替え、書式設定された値などの完全なビルダー オプションを使用して大規模な結果セットをストリーミングします。 より単純な文字列ベースの OData フィルター クエリの場合は、ショートカットとして records.list() と records.list_pages() を使用します。
# Preferred: query.builder().execute_pages() — stream one page at a time, memory stays flat
# Supports composable filters, sorting, formatted values, and expand with nested selects
for page_num, page in enumerate(
client.query.builder("account")
.select("accountid", "name", "revenue")
.where(col("statecode") == 0)
.order_by("name")
.page_size(500) # optional: override Dataverse default (~5000/page)
.execute_pages()
):
print(f"Page {page_num + 1}: {len(page)} records")
for record in page:
print(f" {record['name']}")
# Simple shortcut: records.list() — automatic paging, all records in memory
# Use for basic filter+select queries; string OData filter only (no composable expressions)
result = client.records.list(
"account",
filter="statecode eq 0",
select=["name", "revenue"],
orderby=["name asc"], # optional sort
top=500, # bounds total records returned and number of HTTP round-trips
page_size=200, # optional: hint Dataverse default page size
)
for record in result:
print(record["name"])
# Simple streaming shortcut: records.list_pages() — same params as records.list(), yields one page at a time
for page_num, page in enumerate(
client.records.list_pages("account", filter="statecode eq 0", select=["name"], orderby=["name asc"])
):
print(f"Page {page_num + 1}: {len(page)} records")
for record in page:
print(record["name"])
Note
execute(by_page=True)とexecute(by_page=False)の両方が非推奨となり、UserWarningが出力されます。
execute_pages() (ストリーミング) またはプレーン execute() (熱心) に置き換えます。
QueryBuilder.to_dataframe() も非推奨です。代わりに .execute().to_dataframe() を使用してください。
移行ツールは、これらの呼び出しをすべて自動的に書き換えます。
pip install PowerPlatform-Dataverse-Client[migration]を実行して移行ツールをインストールし、dataverse-migrate path/to/your/scripts/実行します。 代わりに、開発用チェックアウトでは python -m PowerPlatform.Dataverse.migration.migrate_v0_to_v1 を実行します。
レコード数
レコード数を取得するには、要求に $count=true を含めます。
# Via query builder
results = (client.query.builder("account")
.where(col("statecode") == 0)
.count()
.execute())
# Via records.list() — count=True adds $count=true to the OData request
results = client.records.list("account", filter="statecode eq 0", count=True)
FetchXML クエリ
client.query.fetchxml()を呼び出すと、不活性なFetchXmlQueryオブジェクトが返されます。 メソッドは、 .execute() または .execute_pages()を呼び出すまで HTTP 要求を行いません。
xml = """
<fetch>
<entity name="account">
<attribute name="name"/>
<attribute name="revenue"/>
<filter><condition attribute="statecode" operator="eq" value="0"/></filter>
</entity>
</fetch>
"""
# .execute() — blocking, fetches all pages and returns a single QueryResult
result = client.query.fetchxml(xml).execute()
df = result.to_dataframe()
# .execute_pages() — streaming, yields one QueryResult per HTTP page
# Use count="N" in the FetchXML <fetch> element to set page size
for page_num, page in enumerate(client.query.fetchxml(xml).execute_pages()):
print(f"Page {page_num + 1}: {len(page)} records")
for record in page:
print(record["name"])
Important
FETCHXML 文字列は、URL でエンコードされた後、32,768 文字を超えてはなりません。 多くの属性または条件を持つクエリは、通常、この制限に達します。 それに達した場合は、属性と条件の数を減らすことでクエリを簡略化します。
<fetch count="200">など、<fetch>要素にcount属性を設定して、ページ サイズを制御します。
単純なリストショートカット
records.list()呼び出しは、基本的なクエリの生の OData フィルター文字列を受け入れます。 単純なフィルターと選択以外の場合は、構成可能なフィルター、書式設定された値、入れ子になった展開を提供する client.query.builder()を使用します。
# records.list() shortcut — raw OData filter string, all records loaded into memory
# Column names in filter must be lowercase logical names
for record in client.records.list(
"account",
select=["name"],
filter="statecode eq 0",
top=100,
):
print(record["name"])
# Discover navigation property names for $expand (metadata-discovery helper, kept at GA)
nav_props = client.query.odata_expands("account") # → list of navigation property metadata
# Expand navigation properties using the query builder
from PowerPlatform.Dataverse.models.query_builder import ExpandOption
for record in (client.query.builder("contact")
.select("fullname")
.expand(ExpandOption("parentcustomerid_account").select("name"))
.execute()):
acct = record.get("parentcustomerid_account") or {}
print(f"{record['fullname']} -> {acct.get('name')}")
# Build @odata.bind for lookup fields (deprecated helper, still functional with DeprecationWarning)
bind = client.query.odata_bind("contact", "account", account_id)
# Returns: {"parentcustomerid_account@odata.bind": "/accounts(guid)"}
client.records.create("contact", {"firstname": "Jane", **bind})
SQL を使用してデータのクエリを実行する
Dataverse は、SQL SELECT コマンドの限られたセットに対する読み取り専用インターフェイスを提供します。 SQL JOIN、集計、GROUP BY、DISTINCT、および OFFSET FETCH によるページネーションをサポートしています。
Dataverse Web API の ?sql= パラメーターを使用して、SQL クエリ機能にアクセスすることもできます。 この方法では、Python以外の言語で記述されたコードが Dataverse データにアクセスできます。 詳細については、「 SQL を使用して Dataverse Web API を使用してデータにクエリを実行する」を参照してください。
Important
SQL のサポートは、読み取り専用クエリに限定されます。 複雑な結合、サブクエリ、および特定の SQL 関数はサポートされない場合があります。 SQL クエリは、サポートされているサブセットに従う必要があります。
- WHERE にはブール式ツリーのみを指定できます。リーフは 2 項演算子 (
=、>、likeなど) であり、引数の 1 つが直接列参照であり、もう 1 つは定数です。 - TOP では整数リテラルのみを使用できます。
- ORDERBY では列のみを参照でき、複雑な式は許可されません。
サポートされている SQL 構文
SQL インターフェイスでは、次の T-SQL サブセットがサポートされています。
| 特徴 | サポートされている構文 |
|---|---|
| Select |
SELECT、 SELECT DISTINCT、 SELECT TOP N (0 ~ 5000) |
| Joins |
INNER JOIN、 LEFT JOIN (マルチテーブル) |
| フィルター |
WHERE(=、!=、>、<、>=、<=、LIKE、IN、NOT IN、IS NULL、IS NOT NULL、BETWEEN、AND、OR、入れ子の括弧を含む) |
| グループ化と集計 |
GROUP BY、 COUNT(*)、 SUM()、 AVG()、 MIN()、 MAX() |
| 並べ替えとページ分け |
ORDER BY [ASC\|DESC]、OFFSET n ROWS FETCH NEXT m ROWS ONLY |
次のステートメントまたは機能はサポートされていません。 SELECT *、サブクエリ、CTE、 HAVING、 UNION、 RIGHT JOIN、 FULL JOIN、 CROSS JOIN、 CASE、 COALESCE、ウィンドウ関数、文字列/日付/数学関数、および任意の書き込みステートメント (INSERT、 UPDATE、 DELETE)。 サポートされていないクエリまたは書き込みクエリでは、要求が送信される前に ValidationError が発生します。 クライアントは検証できないクエリを実行しますが、サーバーは拒否し、 HttpErrorを発生させます。
次のコード例は、Python の SQL クエリを示しています。
# Basic query
results = client.query.sql(
"SELECT TOP 10 accountid, name FROM account WHERE statecode = 0"
)
# JOINs and aggregates work
results = 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 results directly as a DataFrame
df = client.dataframe.sql(
"SELECT name, revenue FROM account ORDER BY revenue DESC"
)
# Discover columns from metadata (schema-discovery helper, kept at GA)
cols_meta = client.query.sql_columns("account")
col_names = [c["LogicalName"] for c in cols_meta]
# Build queries using the discovered column names
sql = f"SELECT TOP 10 {', '.join(col_names[:5])} FROM account"
df = client.dataframe.sql(sql)