Consultar datos

En este artículo se describen los métodos disponibles para consultar datos de Dataverse mediante el SDK para Python. Puede consultar datos mediante el lenguaje de consulta estructurado (SQL) y las API basadas en OData.

Los desarrolladores de Python deberían primero familiarizarse con el SDK para Python leyendo Primeros pasos antes de continuar con este artículo.

QueryBuilder

QueryBuilder es la manera recomendada de consultar registros. Proporciona una interfaz fluida con comprobación de tipos que genera automáticamente consultas OData válidas. No es necesario recordar la sintaxis del filtro 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 gestiona automáticamente el formato de los valores, el uso de mayúsculas y minúsculas en los nombres de columna y la sintaxis de OData. Compile expresiones de filtro con col() y operadores de Python estándar.

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

Para la lógica compleja (OR, NOT, agrupación), componga expresiones con &, , |~:

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

Valores con formato y anotaciones

En este ejemplo se muestra cómo solicitar etiquetas localizadas, símbolos de moneda y nombres para mostrar.

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

Expandir las propiedades de navegación

Utilice la expansión anidada con opciones para expandir las propiedades de navegación mediante $select, $filter, $orderby y $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

Use execute_pages() para transmitir conjuntos de resultados grandes con opciones completas del generador, como filtrar, ordenar y aplicar formato a valores. Para consultas de filtro OData basadas en cadenas más sencillas, use records.list() y records.list_pages() como accesos directos.

# 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

Tanto execute(by_page=True) como execute(by_page=False) están obsoletos y generan un UserWarning. Reemplácelas por execute_pages() (streaming) o por execute() sin formato (eager). QueryBuilder.to_dataframe() también está en desuso: use .execute().to_dataframe() en su lugar.

La herramienta de migración vuelve a escribir todas estas llamadas automáticamente. Instale la herramienta de migración ejecutando pip install PowerPlatform-Dataverse-Client[migration] y luego ejecute dataverse-migrate path/to/your/scripts/. Como alternativa, ejecute python -m PowerPlatform.Dataverse.migration.migrate_v0_to_v1 para los checkouts de desarrollo.

Recuento de registros

Para obtener un recuento de registros, incluya $count=true en la solicitud.

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

Consultas FetchXML

La llamada a client.query.fetchxml() devuelve un objeto inert FetchXmlQuery . No se realiza ninguna solicitud HTTP hasta que se llama a .execute() o .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"])

Acceso directo de lista simple

La records.list() llamada acepta una cadena de filtro OData sin procesar para las consultas básicas. Para cualquier cosa que vaya más allá de un simple filtro y selección, es preferible usar client.query.builder(), que ofrece filtros combinables, valores con formato y expansiones anidadas.

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

Consulta de datos mediante SQL

Dataverse proporciona una interfaz de solo lectura a un conjunto limitado de comandos SQL SELECT . Se ofrece compatibilidad con JOIN de SQL, funciones de agregado, GROUP BY, DISTINCT y paginación OFFSET FETCH.

También puede acceder a la funcionalidad de consulta SQL mediante el parámetro ?sql= api web de Dataverse, por lo que el código escrito en lenguajes distintos de Python puede acceder a los datos de Dataverse. Para obtener más información, consulte Uso de SQL para consultar datos con la API web de Dataverse.

Importante

La compatibilidad con SQL se limita a las consultas de solo lectura. Es posible que no se admita combinaciones complejas, subconsultas y determinadas funciones SQL. La consulta SQL debe seguir el subconjunto admitido:

  • WHERE solo puede ser un árbol de expresión booleano donde las hojas son operadores binarios ( =, >, como, etc.) con uno de los argumentos que son una referencia de columna directa y otro es una constante.
  • TOP solo permite un literal entero
  • ORDERBY solo puede hacer referencia a columnas y no permite expresiones complejas

En el código de ejemplo siguiente se muestra una consulta SQL en 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)

Consulte también