Abfragedaten

In diesem Artikel werden Methoden zum Abfragen von Dataverse-Daten mithilfe des SDK für Python beschrieben. Sie können Daten mithilfe von STRUCTURED Query Language (SQL) und OData-basierten APIs abfragen.

Python Entwickler sollten sich zunächst über das SDK für Python informieren, indem Sie Getting started lesen, bevor Sie mit diesem Artikel fortfahren.

Querybuilder

QueryBuilder ist die empfohlene Methode zum Abfragen von Datensätzen. Es stellt eine typsichere, leicht verwendbare Schnittstelle bereit, die automatisch korrekte OData-Abfragen generiert. Sie müssen sich nicht an die OData-Filtersyntax erinnern.

# 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 übernimmt die Wertformatierung, die Groß-/Kleinschreibung von Spaltennamen und die OData-Syntax automatisch. Erstellen Sie Filterausdrücke mit col() und Standardoperatoren 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
         )

String-Filter

Verwenden Sie .startswith(), .endswith() und .contains() für den Teilzeichenfolgenabgleich. Oder verwenden Sie % mit einem .like() Platzhaltermuster. Das SDK kompiliert dieses Muster zum nächstgelegenen entsprechungsfilter.

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')
Musterform Example Äquivalenter Filter
val% like("Contoso%") startswith
%val like("%Ltd") endswith
%val% like("%Corp%") contains
Keine Wildcard like("Contoso") Genaue Gleichheit
query = client.query.builder("account").where(col("name").like("Contoso%"))

Note

Muster mit internen Platzhaltern wie "Con%oso" lösen einen ValueError aus. Verwenden Sie client.query.fetchxml() oder client.query.sql() für diese Muster.

Negation und Festlegen der Mitgliedschaft

Jeder positive Operator hat ein negiertes Gegenstück: .is_not_null(), .not_in(), und .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

Roh-OData-Notausgang

Wenn Sie eine OData-Funktion oder -Syntax benötigen, die col() nicht abgedeckt wird, verwenden Sie raw(). Es ist der beabsichtigte Fallback für OData-Ausdrücke, für die keine typierte Entsprechung vorhanden ist.

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

Für komplexe Logik (OR, NOT, Gruppierung) verfassen Sie Ausdrücke mit &, , |: ~

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

Formatierte Werte und Anmerkungen

In diesem Beispiel wird gezeigt, wie lokalisierte Bezeichnungen, Währungssymbole und Anzeigenamen angefordert werden.

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

Um alle verfügbaren Annotationen anzufordern, rufen Sie auf dem Builder .include_annotations() auf (standardmäßig ist "*" festgelegt), oder übergeben Sie include_annotations="*" an records.list() und records.retrieve().

builder = (client.query.builder("account")
           .select("name", "_ownerid_value")
           .include_annotations())   # defaults to "*"

Erweitern von Navigationseigenschaften

Verwenden Sie verschachteltes Expand mit Optionen, um Navigationseigenschaften mit $select, $filter, $orderby und $top zu erweitern.

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

Verwenden Sie execute_pages(), um große Ergebnismengen mit allen Builder-Optionen zu streamen, wie Filterung, Sortierung und formatierte Werte. Verwenden Sie für einfachere zeichenfolgenbasierte OData-Filterabfragen records.list() und records.list_pages() als Kurzformen.

# 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

Sowohl execute(by_page=True) als auch execute(by_page=False) sind veraltet und geben ein UserWarning aus. Ersetzen Sie sie durch execute_pages() (Streaming) oder einfach execute() (eifrig). QueryBuilder.to_dataframe() ist auch veraltet – stattdessen verwenden .execute().to_dataframe() .

Das Migrationstool schreibt alle diese Aufrufe automatisch um. Installieren Sie das Migrationstool, indem Sie pip install PowerPlatform-Dataverse-Client[migration] ausführen, und führen Sie dataverse-migrate path/to/your/scripts/ aus. Führen Sie alternativ python -m PowerPlatform.Dataverse.migration.migrate_v0_to_v1 für Entwicklungs-Checkouts aus.

Datensatzanzahl

Um die Anzahl der Datensätze abzurufen, fügen Sie $count=true in die Anforderung ein.

# 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-Abfragen

Durch Aufrufen client.query.fetchxml() wird ein inert-Objekt FetchXmlQuery zurückgegeben. Die Methode stellt erst dann eine HTTP-Anforderung vor, wenn Sie aufrufen .execute() oder .execute_pages().

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

Wichtig

Die FetchXML-Zeichenfolge darf nach der URL-Codierung 32.768 Zeichen nicht überschreiten. Abfragen mit vielen Attributen oder Bedingungen erreichen diesen Grenzwert häufig. Wenn Sie dieses Limit erreichen, vereinfachen Sie die Abfrage, indem Sie die Anzahl der Attribute und Bedingungen reduzieren. Steuern Sie die Seitengröße, indem Sie das count Attribut für das <fetch> Element festlegen, z. B <fetch count="200">. .

Einfache Listen-Tastenkombination

Der records.list() Aufruf akzeptiert eine unformatierte OData-Filterzeichenfolge für grundlegende Abfragen. Verwenden Sie für alles, was über einfache Filter- und Auswahlfunktionen hinausgeht, client.query.builder(), das kombinierbare Filter, formatierte Werte und geschachtelte Expandierungen bietet.

# 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})

Abfragen von Daten mit SQL

Verwenden Sie die Python SDK-client.query.sql-Methodenmethode, um eine SQL-Abfrage an den Parameter der Dataverse-Web-API ?sql= zu senden. Weitere Informationen finden Sie unter Verwenden von SQL zum Abfragen von Daten mit der Dataverse-Web-API.

Unterstützte SQL

Dataverse stellt eine schreibgeschützte SQL-Schnittstelle bereit, die die folgende T-SQL-Teilmenge unterstützt.

Merkmal Unterstützte Syntax
Auswählen SELECT, SELECT DISTINCT, SELECT TOP N (0–5000)
Verknüpfungen INNER JOIN, LEFT JOIN (mehrere Tabellen)
Filtern WHERE mit =, !=, >, <, <=, >=, LIKE, IS NULL, IS NOT NULL, NOT IN, BETWEEN, IN, AND, OR, geschachtelte Klammern
Gruppierung und Aggregation GROUP BY, COUNT(*), SUM(), AVG(), , MIN(), MAX()
Sortierung und Seitennavigation ORDER BY [ASC\|DESC], OFFSET n ROWS FETCH NEXT m ROWS ONLY

Note

Jeder Befehl muss eine einzelne SELECT Anweisung enthalten. Dataverse unterstützt keine Befehle mit mehreren Ergebnismengen, wie z. B. SELECT name FROM account; SELECT fullname FROM contact.

Wichtig

SQL-Abfragen müssen diese Anforderungen erfüllen:

  • WHERE Kann nur eine boolesche Ausdrucksstruktur sein, bei der Blätter binäre Operatoren (=, >, likeusw.) sind, wobei eines der Argumente ein direkter Spaltenverweis ist und eine andere eine Konstante ist.
  • TOP lässt nur ein ganzzahliges Literal zu.
  • ORDER BY kann nur auf Spalten verweisen und keine komplexen Ausdrücke zulassen.

Die folgenden Anweisungen oder Funktionen werden nicht unterstützt: SELECT *, Unterabfragen, CTEs, HAVING, UNION, FULL JOIN, RIGHT JOIN, CROSS JOIN, CASE, COALESCE, Fensterfunktionen, String-/Datums-/Mathematikfunktionen sowie jede andere Anweisung außer DECLARE, z. B. ALTER TABLE, SELECT, INSERT, UPDATE oder DELETE.

Nicht unterstützte Abfragen lösen ValidationError aus, bevor die Anfrage gesendet wird. Abfragen, die der Client nicht validieren kann, die der Server jedoch ablehnt, lösen HttpError aus.

SQL-Beispiele

Der folgende Beispielcode veranschaulicht eine SQL-Abfrage in Python.

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

Siehe auch