データのクエリを実行する

この記事では、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
         )

複雑なロジック (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"))

ナビゲーション プロパティを展開する

入れ子になった展開とオプションを使用して、 $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"])

単純なリストショートカット

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 項演算子 (=、 >など) で、引数の 1 つが直接列参照であり、もう 1 つは定数です。
  • TOP では整数リテラルのみを使用できます
  • ORDERBY では列のみを参照でき、複雑な式は許可されません

次のコード例は、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)

参照