Saltar al contenido

Caso práctico: filtros OData sobre un Analytic Model de Datasphere

Paso a paso para filtrar un Analytic Model por OData: dimensión frente a medida agregada, startswith y contains, top N y $metadata, con datos de ejemplo.

Por 13 min de lectura
Diagrama de una petición OData sobre un Analytic Model de SAP Datasphere: el filtro de dimensión se aplica antes de agregar al nivel de $select y el filtro de medida después
Índice del artículo

Cuando un Analytic Model de SAP Datasphere sale hacia algo que no es SAP Analytics Cloud (una app propia, un script de Python, Power BI), la puerta suele ser la OData Consumption API. Este año SAP le ha añadido dos cosas que cambian mucho lo que se puede pedir sin descargar todo: filtrar por el valor agregado de una medida y filtrar texto por coincidencia parcial (startswith, endswith, contains). En este caso práctico las probamos con 24 ventas de ejemplo y sin salir del editor del modelo, y vemos dos trampas: el mismo filtro de medida da resultados distintos según el nivel de agregación que pidas, y al combinarlo con un filtro de dimensión el resultado no es el que esperarías.

Qué vamos a construir

Un modelo de ventas de una distribuidora de alimentación. Al acabar tendrás:

  1. Una tabla local T_FOOD_SALES cargada desde un CSV, una vista fact V_FOOD_SALES y un Analytic Model AM_FOOD_SALES.
  2. Una batería de peticiones OData lanzadas desde el diálogo Generate OData Request del modelo, cada una con su resultado esperado: filtro de dimensión, filtro de medida a dos niveles de agregación, filtros parciales de texto, top N y metadatos.

Todos los nombres técnicos son de ejemplo: usa los de tu convención.

La idea clave, que SAP deja escrita en la documentación de la API: los filtros sobre dimensiones se aplican antes de generar el cubo, y los filtros sobre medidas agregadas, después. Si vienes de SQL, es la diferencia entre WHERE y HAVING. SAP añade que el filtro de medida es más costoso en rendimiento, precisamente por eso. Ojo, porque en mi prueba la combinación de los dos no se comportó como sugiere esa frase (lo vemos en el paso 7).

Paso a paso

  1. Paso:Sube los datos de ejemplo

    Descarga ventas.csv: 24 ventas ficticias de julio a septiembre de 2026, de cinco clientes y seis productos, que suman 17.240. He dejado los nombres de cliente y producto en la propia tabla para que el caso necesite un solo CSV; en un modelo real irían en dimensiones.

    Cliente Nombre Región Importe
    C001 Supermercados Norte NOR 3.740
    C002 Hosteleria Centro CEN 4.660
    C003 Tienda Sur SUR 1.540
    C004 Cash Levante EST 6.000
    C005 Cafeteria Norte NOR 1.300

    Los códigos de producto tienen un patrón a propósito: empiezan por CAF, TE o ACE según la familia, y los de café en formato de 1 kg acaban en 1K.

    En el Data Builder, abre el menú de importación y elige Import CSV File para crear T_FOOD_SALES. En el editor de importación, revisa en Details el tipo de cada columna. En mi prueba Datasphere los detectó bien a la primera: SALE_ID y QUANTITY como Integer, AMOUNT como Number y SALE_DATE como Date. Selecciona SALE_ID, marca Set as Key en Details y despliega.

  2. Paso:Crea la vista fact

    Crea una SQL View V_FOOD_SALES:

    V_FOOD_SALES.sql
    SELECT "SALE_ID", "SALE_DATE", "CUSTOMER_ID", "CUSTOMER_NAME",
    "REGION_ID", "PRODUCT_ID", "PRODUCT_NAME",
    "QUANTITY", "AMOUNT"
    FROM "T_FOOD_SALES"

    Ponle Semantic Usage = Fact, valida el SQL para que aparezcan los atributos y pasa AMOUNT y QUANTITY a Measures (arrastrándolas o con el menú ··· del atributo, Change to Measure). En el mismo menú de SALE_ID, Set as Key. El resto quedan como atributos. Despliega.

  3. Paso:Crea y despliega el Analytic Model

    Crea el Analytic Model AM_FOOD_SALES arrastrando V_FOOD_SALES al lienzo. En el diálogo de alta deja marcadas Add all attributes y Add all measures, y despliega: la API solo sirve modelos desplegados.

    En la pestaña Preview, sin nada en filas, debes ver el total: 17.240 de AMOUNT (y 1.385 de QUANTITY). Desmarca QUANTITY, pon CUSTOMER_ID en filas y tendrás la tabla de clientes del paso 1. Si no cuadra, revisa los tipos de la importación antes de seguir.

    Preview de AM_FOOD_SALES con CUSTOMER_ID en filas y la medida AMOUNT: C001 3.740, C002 4.660, C003 1.540, C004 6.000 y C005 1.300
    Preview de AM_FOOD_SALES por cliente: los mismos importes que la tabla del paso 1.
  4. Paso:Abre Generate OData Request y lanza la primera petición

    En el editor de AM_FOOD_SALES (modo Model), pulsa Generate OData Request en la barra de herramientas. Deja Request = Data. En Query Parameters, el desplegable Name ofrece $filter, $orderby, $select, $skip, $top y sap-language; cada uno que eliges se añade como una fila y su Value es texto libre. Añade $select con CUSTOMER_ID,AMOUNT y pulsa Preview: el diálogo muestra la respuesta JSON, limitada a 100 registros, más que suficiente aquí. Con Configure vuelves a los parámetros.

    La URL que genera tiene esta forma:

    Petición base
    https://<tenant>/api/v1/datasphere/consumption/analytical/<espacio>/AM_FOOD_SALES/AM_FOOD_SALES?$select=CUSTOMER_ID,AMOUNT

    El $select no solo elige columnas: en un Analytic Model decide el nivel de agregación. Con una medida y un atributo obtienes la medida agregada por cada valor del atributo; con solo AMOUNT, el total. Debes ver los cinco clientes con sus importes.

  5. Paso:Filtra por una dimensión

    Añade el parámetro $filter con REGION_ID eq 'NOR', manteniendo $select=CUSTOMER_ID,AMOUNT:

    Filtro de dimensión
    ?$select=CUSTOMER_ID,AMOUNT&$filter=REGION_ID eq 'NOR'

    Resultado: C001 con 3.740 y C005 con 1.300. El filtro elimina filas antes de agregar, como un WHERE.

    Las comillas simples son obligatorias en los valores de texto. Sin ellas (REGION_ID eq NOR) la API interpreta NOR como el nombre de una propiedad y responde SERVICE_BAD_REQUEST: Property ‘NOR’ does not exist in type ‘InaApplicationService.AM_FOOD_SALES’.

  6. Paso:Filtra por una medida a dos niveles de agregación

    Ahora la novedad. Cambia el filtro a AMOUNT gt 1000:

    Filtro de medida por cliente
    ?$select=CUSTOMER_ID,AMOUNT&$filter=AMOUNT gt 1000

    Resultado: los cinco clientes, con sus totales completos, porque todos suman más de 1.000. El filtro se evalúa sobre el importe ya agregado por cliente: si se evaluara venta a venta, C003 y C005 desaparecerían. Si subes el umbral a AMOUNT gt 3000, quedan tres: C001 (3.740), C002 (4.660) y C004 (6.000). La respuesta viene ordenada por cliente, no por importe.

    Vuelve a AMOUNT gt 1000 y añade SALE_ID al $select:

    El mismo filtro, por venta
    ?$select=SALE_ID,CUSTOMER_ID,AMOUNT&$filter=AMOUNT gt 1000

    Resultado: cinco ventas de solo tres clientes: 1001 y 1002 de C001 (1.200 cada una), 1006 de C002 (1.800), y 1015 (2.000) y 1016 (1.800) de C004. C003 y C005 desaparecen: suman más de 1.000, pero ninguna de sus ventas lo supera.

    Es la misma condición y una pregunta de negocio distinta: “clientes que facturan más de 1.000” frente a “ventas de más de 1.000”. En una integración, esto es lo primero que validaría con quien consume los datos.

    Generate OData Request de AM_FOOD_SALES: Query Parameters con $filter AMOUNT gt 1000 y $select SALE_ID,CUSTOMER_ID,AMOUNT, y debajo el Preview JSON con las ventas 1001, 1002, 1006, 1015 y 1016
    Arriba, los parámetros; abajo, el Preview: cinco ventas de tres clientes. He tapado la línea con la URL del tenant.
  7. Paso:Combina filtros parciales de texto y de medida

    Los filtros parciales funcionan sobre atributos de texto. Pide las ventas de café por cliente y quédate con los clientes que superan 1.000 en café:

    Café por cliente, más de 1.000
    ?$select=CUSTOMER_ID,AMOUNT&$filter=startswith(PRODUCT_ID,'CAF') and AMOUNT gt 1000

    Yo esperaba cuatro clientes: C002 (3.500), C004 (2.800), C001 (1.800) y C005 (1.090), con C003 fuera por sus 360 de café. No es lo que devuelve. Salen los cinco, y C003 aparece con 360:

    Cliente Importe devuelto (solo café) Total del cliente (todo)
    C001 1.800 3.740
    C002 3.500 4.660
    C003 360 1.540
    C004 2.800 6.000
    C005 1.090 1.300

    El importe devuelto sí es el de café, pero la condición AMOUNT gt 1000 se ha comparado con el total del cliente de todos los productos. Lo confirmé subiendo el umbral a AMOUNT gt 5000: devuelve solo C004, con 2.800, porque es el único cliente que pasa de 5.000 en total. Con eq pasa lo mismo: PRODUCT_ID eq 'CAF-MOL-1K' and AMOUNT gt 1000 devuelve cuatro clientes, incluidos C003 (360) y C005 (240).

    Funciona como esperas si la dimensión filtrada también está en el $select. Con $select=CUSTOMER_ID,PRODUCT_ID,AMOUNT y el mismo filtro sobre CAF-MOL-1K salen solo C001 (1.200) y C004 (1.800). En el paso 5 no se notaba porque cada cliente tiene una sola región: el total por cliente es el mismo con filtro y sin él.

    Esto choca con lo que dice SAP (la dimensión se filtra antes de generar el cubo), así que puede ser un fallo que corrijan. Mientras tanto, en una integración: si combinas un filtro de dimensión con uno de medida, pon esa dimensión en el $select y agrega tú en el cliente si la necesitas a un nivel superior.

    Prueba también las otras dos funciones, sin filtro de medida, con $select=PRODUCT_ID,AMOUNT, PRODUCT_NAME,AMOUNT o CUSTOMER_NAME,AMOUNT:

    $filter Resultado
    endswith(PRODUCT_ID,'1K') CAF-GRA-1K 3.750 y CAF-MOL-1K 3.600
    contains(PRODUCT_NAME,'grano') Cafe en grano 1 kg 3.750
    contains(CUSTOMER_NAME,'Norte') Cafeteria Norte 1.300 y Supermercados Norte 3.740

    La documentación no dice si estas funciones distinguen mayúsculas y minúsculas. En mi prueba no las distinguen: contains(CUSTOMER_NAME,'norte') devuelve los mismos dos clientes y startswith(PRODUCT_ID,'caf'), los tres productos de café.

  8. Paso:Ordena y limita: el top 3 de clientes

    Top 3 clientes por importe
    ?$select=CUSTOMER_ID,AMOUNT&$orderby=AMOUNT desc&$top=3

    Resultado: C004 (6.000), C002 (4.660) y C001 (3.740). Añadiendo $skip=3 con el mismo orden obtienes los dos siguientes: C003 (1.540) y C005 (1.300). Lo que no funciona en un Analytic Model es $count=true: SAP indica que no se admite en las peticiones analíticas, el desplegable del diálogo ni siquiera lo ofrece y los metadatos lo declaran con Countable = false.

  9. Paso:Consulta los metadatos

    En el mismo diálogo, cambia Request a Metadata y pulsa Preview. Obtienes la definición XML del servicio: un EntityType con una propiedad por medida y atributo, cada una con su tipo (AMOUNT como Edm.Decimal, QUANTITY como Edm.Int64, SALE_DATE como Edm.Date…), y anotaciones de capacidades como la de $count. Desde la 2026.10, SAP añade también información sobre la definición de variables y filtros; este modelo no tiene ninguna, así que aquí no aparece.

    Úsalo para comprobar los nombres exactos de las propiedades antes de escribir un $filter. Aquí coinciden con las columnas de la vista, pero cuando el modelo trae dimensiones asociadas con textos, el nombre de la columna de texto lo genera Datasphere y puede cambiar al redesplegar. Lo vimos en el caso de textos multilingües.

    Preview de Request = Metadata de AM_FOOD_SALES: XML con el EntityType y las propiedades AMOUNT, CUSTOMER_ID, CUSTOMER_NAME, PRODUCT_ID, PRODUCT_NAME, QUANTITY, REGION_ID, SALE_DATE y SALE_ID, y la anotación CountRestrictions con Countable = false
    Metadatos de AM_FOOD_SALES: nombres y tipos de las propiedades, y Countable = false.

Validación

Esto es lo que devolvió cada petición en mi tenant con los datos de ejemplo:

Petición ($select · $filter / otros) Filas Resultado
AMOUNT 1 17.240
CUSTOMER_ID,AMOUNT 5 Tabla del paso 1
CUSTOMER_ID,AMOUNT · REGION_ID eq 'NOR' 2 C001 3.740 · C005 1.300
CUSTOMER_ID,AMOUNT · AMOUNT gt 1000 5 Los cinco clientes
CUSTOMER_ID,AMOUNT · AMOUNT gt 3000 3 C001 · C002 · C004
SALE_ID,CUSTOMER_ID,AMOUNT · AMOUNT gt 1000 5 Ventas 1001, 1002, 1006, 1015, 1016
CUSTOMER_ID,AMOUNT · startswith(PRODUCT_ID,'CAF') and AMOUNT gt 1000 5 Los cinco, con el importe de café
CUSTOMER_ID,PRODUCT_ID,AMOUNT · PRODUCT_ID eq 'CAF-MOL-1K' and AMOUNT gt 1000 2 C001 1.200 · C004 1.800
CUSTOMER_ID,AMOUNT · $orderby=AMOUNT desc&$top=3 3 C004 · C002 · C001

Para cruzar resultados, la pestaña Preview del modelo admite filtros (Add Filter) y, desde la 2026.20, exportar a Excel o CSV, salvo que la exportación esté protegida en las propiedades del modelo. Un detalle si después integras: la misma página de SAP dice en un apartado que las peticiones devuelven por defecto los 10 primeros resultados y, en el de datos analíticos, que se devuelven hasta 50.000 registros. No dependas del valor por defecto: pide siempre $top y sigue el @odata.nextLink para paginar.

Errores habituales

Síntoma Causa Solución
El filtro de medida “no filtra” lo que esperabas Se evalúa sobre el valor agregado al nivel del $select Decide primero el nivel (cliente, venta…) y ajusta el $select
Combinas un filtro de dimensión y uno de medida y salen filas que no cumplen la condición La condición de medida se comparó con el total sin el filtro de dimensión (comportamiento observado en el paso 7) Pon en el $select la dimensión que filtras
Error Property 'NOR' does not exist Valor de texto sin comillas simples: la API lo toma por un nombre de propiedad Escribe REGION_ID eq 'NOR'
Error al usar $count=true No se admite en peticiones analíticas Cuenta en el cliente o usa una vista expuesta con petición relacional
startswith, contains o gt fallan en otro modelo Alguna fuente del linaje no está replicada en Datasphere: entonces $filter solo admite eq, and, or y paréntesis, y no hay $count, $top ni $skip Replica la fuente o filtra en el origen; revisa el linaje con Impact and Lineage Analysis
Error con un valor como O'Brien La API no admite valores con comillas simples Filtra por el código, no por el texto
La URL copiada da error de autenticación en el navegador Tenant en una formation de BDC: hace falta OAuth explícito Usa el Preview del diálogo o un cliente OAuth
Property ... does not exist Nombre de propiedad distinto del que crees (p. ej., columnas de texto) Consulta el nombre real con Request = Metadata

Cierre

Mi criterio después de probarlo: el filtro de medida es la mejora más útil de la API en mucho tiempo, porque evita descargar todo para quedarte con “los clientes que superan X”. Pero úsalo con cabeza. Filtra por dimensiones todo lo que puedas, porque es más barato. Deja el filtro de medida para la pregunta que de verdad lo necesita y deja escrito en la especificación de la integración a qué nivel se agrega. Y si lo combinas con un filtro de dimensión, comprueba el resultado con datos que conozcas: en mi prueba, sin esa dimensión en el $select, devolvió filas que no cumplían la condición. Y si el consumidor va a hacer muchas llamadas, ten en cuenta el límite de SAP: unas 300 peticiones por usuario y minuto, con respuesta HTTP 429 al superarlo.

El siguiente paso natural es repetir estas peticiones desde un cliente externo con un cliente OAuth de usuario técnico. Ojo con la seguridad: con ese tipo de cliente, las Data Access Controls se evalúan con el ID de usuario asociado al cliente OAuth, así que dale a ese ID los permisos que correspondan.

Fuentes