• Projects
  • Service
  • About
  • branding.bz
  • Podcast
  • Tips
  • FAQ
  • Recruit
  • Download
  • Contact
  • branding.bz(ブランド構築SaaS)
  • DESIGN NOW(デザインメディア)
  • X
  • LinkedIn
  • Spotify
  • Facebook

213-0011 神奈川県川崎市高津区久本3-6-7-303

© 2026 ID INC. All rights reserved

claude-skills/スキル
SKILLOfficialdatabase

dv-query

プラグイン
dataverse
ソース
GitHub で見る ↗
説明

Dataverse(マイクロソフトのデータ管理プラットフォーム)上のデータを大量に読み込んだり、複数ページにわたって処理したり、分析したりするスキルです。 次のような場合に使用: ユーザーがレコード(データベースの個別記録)を読み込む、一覧表示する、絞り込む、集計する、グループ化する、複数のデータを組み合わせる、または分析したいとき。pandas DataFrame(データ分析用の表形式データ構造)を使ったワークフローやノートブック環境での探索的な作業に向いています。

原文を表示

Bulk reads, multi-page iteration, and analytics over Dataverse data. Use when the user wants to read, list, filter, aggregate, group, join, or analyze records — including pandas DataFrame workflows and notebook exploration.

ユースケース
  • Datavaseのレコードを読み込みたいとき
  • 複数ページのデータを処理するとき
  • データを絞り込んで分析したいとき
  • 複数データを組み合わせて集計するとき
  • 探索的なデータ分析を行うとき
本文(日本語訳)

スキル: クエリ — Dataverse レコードの読み取りと分析

このスキルは Python のみを使用します。 Dataverse スクリプトに Node.js、JavaScript、その他の言語は使用しないでください。概要スキルの「Hard Rules(厳守ルール)」を参照してください。


読み取りにおける SDK 優先ルール

すべての読み取り操作は SDK を使用します — urllib、requests、または生の HTTP は使用しません。 これは dv-data の「SDK 優先ルール」と同じルールを、読み取り操作にも適用したものです。 クエリのために urllib.request や get_token() を書こうとしている場合は、STOP — SDK がそれを処理します。 例外は $apply による集計と N:N の $expand のみで、以下で説明します。


データに関する質問への回答方法

ユーザーがデータについて質問した場合、ユーザーが何を聞いているかに基づいてアプローチを選択してください。知っている API を基準に選ばないでください。

ユーザーの質問 アプローチ 理由
「オープンなチケットを表示して」/ 単純なフィルター MCP read_query(利用可能な場合)または client.records.get() + $filter 少量の結果、集計なし
「X はいくつある?」/ 単純なカウント MCP read_query または client.records.get() + count=True 単一の数値
単一テーブルの集計(最大値/合計/平均/上位N件) $apply サーバーサイド集計(生の Web API) 1回の HTTP 呼び出し、グループ化結果のみを返す
クロステーブル集計 client.dataframe.get() + 最小限の $select + pd.merge() サーバーはJOINできないため、最小限の列指定でpandasマージを高速に実行
「関連するYを含むXを表示して」/ ルックアップ解決 client.records.get() + $expand、または QueryBuilder(b8+) ルックアップ解決
「このデータをエクスポートして」/ 一括抽出 client.dataframe.get() + select= DataFrame → CSV に直接出力
「ノートブックに読み込んで」/ インタラクティブ分析 client.dataframe.get() または QueryBuilder .to_dataframe()(b8+) pandas ネイティブ
「重複を見つけて」/ 複雑なフィルター client.records.get() + $filter、または QueryBuilder(b8+) SDK がページネーションを処理
単純なフィルター読み取り(5,000行未満) client.query.sql() WHERE / ORDER BY / TOP 付きの軽量 SQL SELECT

基本原則: サーバーに処理させる。 単一テーブルの集計には $apply を使用してください — サーバーサイドで実行され、グループ化された結果のみを返します。 クロステーブルの質問には、各テーブルで client.dataframe.get() + 最小限の $select を使用し、その後 pd.merge() を行ってください — マージ自体はサブ秒で完了し、ボトルネックはネットワーク転送であり、それを $select で最小化します。

常にライブの Dataverse 環境に対してクエリを実行してください。 ユーザーが Dataverse からの結果を期待している場合、ローカルコピー、キャッシュファイル、またはソースデータベースにクエリを実行しないでください。Dataverse のデータが信頼できる唯一の情報源です。


SQL クエリ — client.query.sql()

client.query.sql() は Dataverse Web API の ?sql= パラメーターを使用します — これは 限定的な SQL サブセットです(MCP read_query と同じ制限)。 GROUP BY、JOIN、HAVING、DISTINCT、サブクエリはサポートされていません。結果は最大 約5,000行 に制限されます。

次のような場合に使用: 5,000行未満のテーブルに対する高速なフィルター読み取り。 これらの場合、1回の HTTP 呼び出しで完結するため、ページイテレーションや DataFrame よりも大幅に高速です(約2〜6秒)。

# 小さなテーブル(5,000行未満)への高速フィルター読み取り
results = client.query.sql(
    "SELECT TOP 100 name, estimatedvalue "
    "FROM opportunity "
    "WHERE statecode = 0 "
    "ORDER BY estimatedvalue DESC"
)
for r in results:
    print(f"{r['name']}: ${r.get('estimatedvalue', 0):,.0f}")

次のような場合は使用しないでください: 5,000行を超えるテーブル(結果がサイレントに切り捨てられます)、集計(GROUP BY なし)、またはクロステーブルクエリ(JOIN なし)。 単一テーブルの集計には $apply を、クロステーブルには client.dataframe.get() + pd.merge() を使用してください。


スキルの境界

必要な操作 代わりに使用するスキル
レコードの作成・更新・削除 dv-data
テーブル・列・リレーションシップの作成 dv-metadata
ソリューションのエクスポートまたはデプロイ dv-solution

セットアップ

import os, sys
sys.path.insert(0, os.path.join(os.getcwd(), "scripts"))
from auth import get_client

# get_client は User-Agent ヘッダーにプラグインのアトリビューションコンテキストを設定します。
# コンテキスト値は変更しないでください — これはサーバーサイドのテレメトリ
#(app/skill/agent)向けのクローズドスキーマです。シークレットや PII を含めないでください。
client = get_client("dv-query")

get_client(skill) は認証、環境 URL、およびプラグインのアトリビューション(User-Agent タグ付け)を処理します。 scripts/auth.py を参照してください。 完了まで実行するスクリプトでは、接続の自動クリーンアップのために返されたクライアントを with 文でラップしてください。


フィールド名の大文字小文字ルール

これを誤ると 400 エラーが発生します。

プロパティの種類 規則 例 使用場面
構造的(列) LogicalName — 常に小文字 new_name, new_priority $select、$filter、$orderby
ナビゲーション(ルックアップ) Navigation Property Name — 大文字小文字を区別、$metadata に一致 new_AccountId $expand
  • システムテーブルのナビゲーションプロパティ(例: parentaccountid、ownerid): 小文字
  • カスタムルックアップのナビゲーションプロパティ: 大文字小文字を区別し、$metadata の SchemaName に一致(例: new_AccountId)

レコードのクエリ(複数ページ)

client.records.get() はプライマリの読み取りメソッドです — すべての SDK バージョン(b6+)で動作します。 複数レコードのクエリに対してはページイテレーターを返し、GUID による取得では単一の Record を返します。 常に select= を使用して列を制限してください。

for page in client.records.get(
    "new_ticket",
    select=["new_name", "new_priority", "new_status"],
    filter="new_status eq 100000000",
    orderby=["new_name asc"],
    top=50,
):
    for r in page:
        print(r["new_name"], r["new_priority"])

client.records.get() はページイテレーターを返します — 常にページを反復し、その後各ページ内のレコードを反復してください。 各レコードは dict ライクなアクセスをサポートする Record オブジェクトです: r["column"]、r.get("column")、r.keys()。 r.data.get() は使用せず、r.get() を直接使用してください。


ID による単一レコードの取得

record = client.records.get("new_ticket", "<record-guid>",
    select=["new_name", "new_priority", "new_status"])
print(record["new_name"])

ルックアップ列での $select(GUID なしの表示名)

GUID の代わりに表示名を表示するには、include_annotations を使用してフォーマット済み値アノテーションをリクエストします:

for page in client.records.get("opportunity",
    select=["name", "estimatedvalue", "_parentaccountid_value"],
    include_annotations="OData.Community.Display.V1.FormattedValue",
):
    for r in page:
        account_name = r.get("_parentaccountid_value@OData.Community.Display.V1.FormattedValue")
        print(f"{r['name']} — {account_name}")

include_annotations の指定は必須です — 指定しない場合、Prefer: odata.include-annotations ヘッダーが送信されず、フォーマット済み値がレスポンスに含まれません。 すべてのアノテーションには "*" を使用するか、上記の特定のアノテーション名を指定してください。

フォーマット済み値は、ルックアップ・選択肢・ステータス・オーナーのフィールドで利用できます。


$expand — ルックアップを関連レコード全体に解決する

for page in client.records.get("opportunity",
    select=["name", "estimatedvalue"],
    expand=["parentaccountid($select=name)"],   # ネストされた $select でアカウントの全列取得を回避
):
    for r in page:
        account = r.get("parentaccountid") or {}
        print(f"{r['name']} — {account.get('name', 'Unknown')}")

$expand 内では常にネストされた $select を使用してください — 指定しない場合、Dataverse は関連エンティティのすべての列を返し、帯域幅とメモリを無駄にします。

複数のカスタムルックアップを持つ $expand

for page in client.records.get(
    "new_ticket",
    select=["new_name", "new_priority", "new_status"],
    expand=["new_CustomerId($select=new_name)", "new_AgentId($select=new_name)"],  # ネストされた $select + 大文字小文字を区別するナビゲーションプロパティ
):
    for r in page:
        customer = r.get("new_CustomerId") or {}
        agent    = r.get("new_AgentId") or {}
        print(f"{r['new_name']} | {customer.get('new_name','')} | {agent.get('new_name','')}")

expand には Navigation Property Name(new_CustomerId)を使用し、小文字の LogicalName(new_customerid)は使用しないでください。小文字を使用すると 400 エラーが発生します。


高度なクエリパターン(Web API のみ)

集計および多対多の展開については、SDK が直接サポートしていないため、生の Web API を使用します。 完全なコードサンプルは references/web-api-advanced.md を参照してください。

クイックリファレンス:

  • N:N リレーションシップでの $expand: GET /<entitySet>?$expand=<n:n_nav>($select=...) — 単一ページのみ。5,000件を超える結果は @odata.nextLink をたどってください。

  • $apply による集計: サーバーサイドで実行され、1回の呼び出しでグループ化された結果を返します。 パターン: groupby((col),aggregate(metric with sum as total))、aggregate($count as count)、aggregate(amount with average as avg)。 ソースレコードの上限は 50,000件。

  • クロステーブル集計: $apply は単一エンティティセット内でのみ動作します。 テーブルごとに client.dataframe.get(entity, select=[...]) を使用し、pd.merge() → groupby() を行ってください。 常に select= を指定してください — 指定しない場合、データ転送量が10〜20倍になります。


QueryBuilder — フルエント クエリ API(SDK b8+)

PowerPlatform-Dataverse-Client b8+ で利用可能。 単一の OData URL や FetchXML 文字列では扱いにくい複雑なクエリを構築するための、チェーン可能なビルダーです。 完全なリファレンスとサンプルは references/querybuilder.md を参照してください。


Jupyter Notebook のセットアップ

ノートブックで

原文(English)を表示

Skill: Query — Read and Analyze Dataverse Records

This skill uses Python and the Dataverse CLI. Do not use Node.js, JavaScript, or any other language for Dataverse scripting. See the overview skill's Hard Rules.

Reads: prefer a managed surface, choose by shape

Fast path for simple reads: If dataverse auth who shows an active profile, skip workspace setup and query directly with the CLI examples below. No .env, auth.py, pip install, or PAC needed for data reads.

Pick MCP, the Dataverse CLI, or the SDK by the shape of the read — all three handle auth and retry (see the routing table below and the overview's Tool Capabilities / Hard Rule 2). MCP fits small, interactive reads; the CLI fits headless one-liners (OData, SQL, count); the SDK fits bulk iteration and analytics. For $apply aggregation and N:N $expand, prefer client.query.fetchxml() (aggregates + link-entity) or the managed dataverse api escape hatch; reach for hand-rolled urllib/get_token() only to stay in-process inside a tight Python loop (e.g. paging thousands of rows with client-side processing — see web-api-advanced.md).

Dataverse CLI gotchas (custom tables + Windows)

When you drive the dataverse CLI directly (headless reads/CRUD; note the CLI needs .NET + a keyring, so it is blocked on ChatGPT web / Codex cloud — use the SDK there), two empirical traps:

  • Custom-table SQL pluralization. dataverse data query in SQL mode auto-pluralizes the table name, and irregular plurals resolve wrong: FROM im_category looks up entity set im_categorys and returns a 404 that reads like "table missing." It is not — switch to OData mode with the explicit entity set: dataverse data query --table im_categories --select im_name. Discover the real EntitySetName from EntityDefinitions when unsure; never conclude the table doesn't exist from this 404.
  • Windows shell quoting. Wrap the whole --path value in double quotes so cmd.exe/PowerShell don't treat & as a command separator. Keep & literal — it separates OData query options; encoding it to %26 merges them and breaks the query. Encode only $->%24 (in PowerShell a bare $select is read as a variable). If an unquoted & splits the command, the wrapper can exit nonzero even when the API returned valid JSON — quoting prevents it. (This is why the dataverse api request examples in other skills quote the path, use %24, and leave & literal.)

Dataverse CLI query examples (copy-paste ready)

All dataverse commands take --context for skill attribution (global flag).

# OData filtered read (--table takes the EntitySet name, e.g. accounts not account)
dataverse data query --table accounts --select "name,accountid" --filter "name eq 'john'" --top 10 --json --context "app=dataverse-skills/<ver>;skill=dv-query;agent=<agent>"

# Count records
dataverse data count --table accounts --context "app=dataverse-skills/<ver>;skill=dv-query;agent=<agent>"

# SQL mode (uses the logical name, e.g. account not accounts)
dataverse data query --sql "SELECT name, accountid FROM account WHERE name LIKE '%john%'" --json --context "app=dataverse-skills/<ver>;skill=dv-query;agent=<agent>"

# Get single record by ID
dataverse data get --table accounts --id <guid> --json --context "app=dataverse-skills/<ver>;skill=dv-query;agent=<agent>"

# Raw API escape hatch
dataverse api request --target dataverse --path "/api/data/v9.2/accounts?%24select=name&%24top=5" --context "app=dataverse-skills/<ver>;skill=dv-query;agent=<agent>"

ERP target is a separate path. ERP (Finance and Operations), when linked to a Dataverse env, does not go through the Python SDK. See references/erp-reads.md.

How to Answer Data Questions

When the user asks a question about their data, pick the approach by what they're asking, not by which API you know:

User asks... Approach Why
"show me open tickets" / simple filter MCP read_query, CLI dataverse data query --table ... --filter ..., or client.records.list(table, filter=...) Small result, no aggregation
"how many X" / simple count CLI dataverse data count --table ..., MCP read_query, or client.query.sql("SELECT COUNT(*) ...") Server-side count (no row download)
Single-table aggregation (most/sum/avg/top-N) $apply (raw) or client.query.sql() GROUP BY Both run server-side, return only grouped results
Cross-table aggregation client.query.sql("...INNER JOIN...GROUP BY...") or client.query.fetchxml(...) (server-side); else builder->DataFrame + pd.merge() sql() supports INNER/LEFT JOIN + GROUP BY; pandas merge for shapes SQL can't express
"show me X with related Y" / resolve lookups client.records.list(table, expand=...) or QueryBuilder Lookup resolution
"export this data" / bulk extract client.query.builder(t).select(...).execute().to_dataframe() Direct to DataFrame → CSV
"load into notebook" / interactive analysis client.query.builder(t).select(...).execute().to_dataframe() pandas native
"find duplicates" / complex filter client.records.list(table, filter=...) or QueryBuilder SDK handles pagination
Simple filtered read (<5K rows) CLI dataverse data query --sql "SELECT ...", or client.query.sql() Lightweight single call

Key principle: Let the server do the work. For single-table aggregation, use $apply (raw) or client.query.sql() GROUP BY — both run server-side and return only grouped results. For cross-table questions, prefer a server-side sql() JOIN (INNER/LEFT) or fetchxml() link-entity; when SQL can't express it, pull each table via client.query.builder(t).select(...).execute().to_dataframe() and pd.merge() — the merge is sub-second; the bottleneck is network transfer, which select minimizes.

Always query the live Dataverse environment. Do not query local copies, cached files, or source databases when the user expects results from Dataverse. The data in Dataverse is the source of truth.


SQL Queries — client.query.sql()

client.query.sql() uses the Dataverse Web API ?sql= parameter — a T-SQL subset. It supports SELECT / SELECT DISTINCT / SELECT TOP N (0-5000), INNER JOIN / LEFT JOIN, WHERE, GROUP BY, ORDER BY, OFFSET/FETCH, and COUNT/SUM/AVG/MIN/MAX. It does NOT support SELECT *, subqueries, CTEs, HAVING, UNION, RIGHT/FULL/CROSS JOIN, CASE, or string/date/math functions. Results are capped at ~5,000 rows.

When to use: Fast filtered reads on tables with <5K rows. For these, it's significantly faster (~2-6s) than page iteration or DataFrames because it's a single HTTP call.

# Fast filtered read on small tables (<5K rows)
results = client.query.sql(
    "SELECT TOP 100 name, estimatedvalue "
    "FROM opportunity "
    "WHERE statecode = 0 "
    "ORDER BY estimatedvalue DESC"
)
for r in results:
    print(f"{r['name']}: ${r.get('estimatedvalue', 0):,.0f}")

Do NOT use for: Tables >5K rows (results silently truncated), SELECT *, subqueries/CTEs, HAVING, UNION, RIGHT/FULL/CROSS JOIN, or functions. INNER/LEFT JOIN and GROUP BY are supported — use them for server-side joins/aggregation on <5K-row results; for larger or unsupported shapes use fetchxml() or $apply.

FetchXML — server-side joins and aggregates

For SQL-JOIN scenarios or aggregates the OData builder cannot express, use FetchXML. client.query.fetchxml(xml) returns an inert query object — no HTTP is made until you call .execute() (eager, all pages) or .execute_pages() (lazy, one page at a time). Both return QueryResult pages with .to_dataframe().

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>
""")

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

# Or stream one page at a time for large results:
for page in query.execute_pages():
    print(page.to_dataframe().shape)

Discover queryable columns — client.query.sql_columns()

Before writing a SQL or $select read, list the columns the SQL endpoint can actually query — virtual and computed lookup-display columns are excluded. Each entry has name, type, is_pk, is_name, and label.

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

For deeper schema inspection — full column metadata and table relationships — use dv-metadata (client.tables.list_columns(), client.tables.list_relationships(), client.tables.list_table_relationships()).

Skill boundaries

Need Use instead
Create, update, delete records (Dataverse) dv-data
Query, create, update, delete records (ERP) See references/erp-reads.md and erp-writes
Create tables, columns, relationships dv-metadata
Export or deploy solutions dv-solution

Setup

import os, sys
sys.path.insert(0, os.path.join(os.getcwd(), "scripts"))
from auth import get_client

# get_client sets a plugin attribution context on the User-Agent header.
# Do not modify the context value — it is a closed schema for server-side
# telemetry (app/skill/agent). Never include secrets or PII.
client = get_client("dv-query")

get_client(skill) handles auth, environment URL, and plugin attribution (User-Agent tagging). See scripts/auth.py. For scripts that run to completion, wrap the returned client in a with statement for automatic connection cleanup. For ERP, use ERP MCP or the Dataverse CLI --target erp path — see references/erp-reads.md.


Field Name Casing Rule

Getting this wrong causes 400 errors.

Property type Convention Example When used
Structural (columns) LogicalName — always lowercase new_name, new_priority $select, $filter, $orderby
Navigation (lookups) Navigation Property Name — case-sensitive, matches $metadata new_AccountId $expand
  • System table navigation properties (e.g., parentaccountid, ownerid): lowercase
  • Custom lookup navigation properties: case-sensitive, match $metadata SchemaName (e.g., new_AccountId)

Query Records

client.records.list() is the primary read method on the GA SDK. It collects all pages and returns a flat QueryResult you iterate directly (records, not pages). For very large result sets, client.records.list_pages() streams one QueryResult per HTTP page. Always use select= to limit columns.

# list() -- flat QueryResult, iterate records directly
result = client.records.list(
    "new_ticket",
    select=["new_name", "new_priority", "new_status"],
    filter="new_status eq 100000000",
    orderby=["new_name asc"],
    top=50,
)
for r in result:
    print(r["new_name"], r["new_priority"])

print(f"{len(result)} tickets")   # QueryResult supports len(), indexing, .first(), .to_dataframe()

For large tables where you do not want every row in memory at once, stream pages:

for page in client.records.list_pages("new_ticket", select=["new_name"], page_size=200):
    for r in page:          # each page is a QueryResult
        print(r["new_name"])

Each record is a Record object that supports dict-like access: r["column"], r.get("column"), r.keys(). Do not use r.data.get() -- use r.get() directly.

Migrating from records.get(): records.get() is deprecated on the GA SDK. Replace for page in client.records.get(...): for r in page: with for r in client.records.list(...): (flat), or keep the page loop using list_pages(...). Replace a by-GUID records.get(table, guid) with records.retrieve(table, guid) (returns None if not found).


Fetch a Single Record by ID

client.records.retrieve() returns the record, or None if no row has that GUID (no exception on 404).

record = client.records.retrieve("new_ticket", "<record-guid>",
    select=["new_name", "new_priority", "new_status"])
if record is not None:
    print(record["new_name"])
else:
    print("Ticket not found")

$select with Lookup Columns (GUID-free display)

To show display names instead of GUIDs, request the formatted value annotation via include_annotations:

for r in client.records.list("opportunity",
    select=["name", "estimatedvalue", "_parentaccountid_value"],
    include_annotations="OData.Community.Display.V1.FormattedValue",
):
    account_name = r.get("_parentaccountid_value@OData.Community.Display.V1.FormattedValue")
    print(f"{r['name']} — {account_name}")

You MUST pass include_annotations — without it, the Prefer: odata.include-annotations header is not sent and formatted values are not in the response. Use "*" for all annotations or the specific annotation name above.

Formatted values are available for lookup, choice, status, and owner fields.


$expand — Resolve Lookup to Full Related Record

for r in client.records.list("opportunity",
    select=["name", "estimatedvalue"],
    expand=["parentaccountid($select=name)"],   # nested $select avoids fetching all account columns
):
    account = r.get("parentaccountid") or {}
    print(f"{r['name']} — {account.get('name', 'Unknown')}")

Always use nested $select inside $expand — without it, Dataverse returns every column on the related entity, which wastes bandwidth and memory.

$expand with multiple custom lookups

for r in client.records.list(
    "new_ticket",
    select=["new_name", "new_priority", "new_status"],
    expand=["new_CustomerId($select=new_name)", "new_AgentId($select=new_name)"],  # nested $select + case-sensitive nav props
):
    customer = r.get("new_CustomerId") or {}
    agent    = r.get("new_AgentId") or {}
    print(f"{r['new_name']} | {customer.get('new_name','')} | {agent.get('new_name','')}")

expand uses the Navigation Property Name (new_CustomerId), not the lowercase logical name (new_customerid). Using lowercase causes a 400 error.


Advanced query patterns (raw Web API)

$apply aggregation and N:N $expand on the OData path are raw-only. Note the SDK does cover most aggregation/joins — client.query.sql() (INNER/LEFT JOIN, GROUP BY, COUNT/SUM/AVG) and client.query.fetchxml() (aggregate + link-entity). Reach for raw Web API only for the $apply transform and N:N $expand. See references/web-api-advanced.md for full code samples.

Quick reference:

  • $expand on N:N relationships: GET /<entitySet>?$expand=<n:n_nav>($select=...) — single page only; follow @odata.nextLink for >5,000 results.
  • $apply for aggregations: runs server-side, returns grouped results in one call. Patterns: groupby((col),aggregate(metric with sum as total)), aggregate($count as count), aggregate(amount with average as avg). 50K source-record limit.
  • Cross-table aggregation: $apply only works within one entity set. Prefer client.query.sql() (INNER/LEFT JOIN + GROUP BY) or fetchxml() link-entity; else pull each table via client.query.builder(t).select(...).execute().to_dataframe() → pd.merge() → groupby(). Always pass select; without it transfers 10-20x more data.

QueryBuilder — Fluent Query API

Chainable builder for complex queries that would be awkward as a single OData URL or FetchXML string. Full reference and examples in references/querybuilder.md.

Jupyter Notebook Setup

For interactive querying in notebooks (auth + DataverseClient + DataFrame display), see references/jupyter-setup.md.

Querying ERP data

On ERP-linked envs, ERP reads do not go through DataverseClient. Use ERP MCP or dataverse data query/get/count --target erp. See references/erp-reads.md.

Common Query Errors

Status Cause Fix
400 Wrong field casing in $select/$filter (must be lowercase LogicalName) or $expand (must be case-sensitive Navigation Property Name) Verify names via EntityDefinitions(LogicalName='...')/Attributes
400 Unsupported SQL — MCP read_query rejects DISTINCT/HAVING/subqueries/OFFSET/UNION/CAST/CONVERT/CASE/date-functions (but allows JOIN + GROUP BY); client.query.sql() rejects SELECT */subqueries/CTE/HAVING/UNION/RIGHT/FULL/CROSS JOIN/functions (but allows INNER/LEFT JOIN, GROUP BY, DISTINCT) Use fetchxml()/$apply for shapes sql() can't express, or pandas for cross-table
404 Table logical name not found Check spelling — use client.tables.get("<name>") to verify
429 Rate limited SDK retries automatically; reduce page size or add delays between pages

For HttpError handling in SDK scripts, see the error handling pattern in dv-data.


Windows Scripting Notes

  • ASCII only in .py files — curly quotes and em dashes cause SyntaxError on Windows.
  • No python -c for multiline code — write a .py file instead.
  • Generate GUIDs in scripts: str(uuid.uuid4()), not shell backtick substitution.

原文・著作権は Anthropic および各プラグイン作者に帰属します。日本語訳は Claude API による自動翻訳です。