Praxsuite

Relaciones

Vincent Depassier · 29 de agosto de 2026

PraxQL — Relaciones Anidadas

En vez de hacer varias llamadas para traer datos relacionados, PraxQL te deja expresar relaciones dentro de select. El motor las resuelve por lotes, así que no hay problema N+1.


Cómo funcionan las relaciones

Cuando una columna es de tipo Table, ya nombra la tabla a la que apunta — /schema lo reporta como pointsTo. PraxQL lo usa para deducir el join solo. Declarás la relación en select como un objeto en vez de un string:

{
  "refs": {
    "Clientes": "aaa-guid",
    "Pedidos":  "bbb-guid"
  },
  "query": {
    "from": "Clientes",
    "select": [
      "Nombre",
      "Email",
      {
        "table": "Pedidos",
        "select": ["Total", "Fecha", "Estado"]
      }
    ]
  }
}
{
  "data": [
    {
      "Nombre": "Acme Corp",
      "Email": "hola@acme.com",
      "Pedidos": [
        { "Total": 1500.00, "Fecha": "2026-03-01", "Estado": "Completado" },
        { "Total": 800.00,  "Fecha": "2026-04-15", "Estado": "Pendiente"  }
      ]
    }
  ]
}

Los registros relacionados se anidan como arrays dentro de cada fila padre. Nunca se aplanan en filas padre repetidas, así que no tenés que de-duplicar nada.


Todos los campos de un objeto de relación

Campo

Requerido

Descripción

table

sí

Alias de la tabla relacionada. Debe existir en refs.

select

no

Columnas a devolver de esta tabla

where

no

Filtros sobre las filas relacionadas

orderBy

no

Orden de las filas relacionadas

limit

no

Máximo de filas relacionadas por fila padre

relations

no

Más relaciones anidadas

on

no

Columnas de join explícitas, anulando la detección automática

joinType

no

"LEFT" (por defecto) o "INNER"


Filtrar y limitar una relación

{
  "table": "Pedidos",
  "select": ["Total", "Fecha", "Estado"],
  "where": [
    { "field": "Estado", "op": "neq", "value": "Cancelado" }
  ],
  "orderBy": [{ "field": "Fecha", "dir": "desc" }],
  "limit": 5
}

Como mucho cinco pedidos no cancelados por cliente, los más nuevos primero. Ojo que acá limit es por fila padre, no un total sobre toda la respuesta.


Anidamiento profundo

Anidá relaciones dentro de relaciones con la clave relations:

{
  "refs": {
    "Clientes":     "aaa-guid",
    "Pedidos":      "bbb-guid",
    "ItemsPedido":  "ccc-guid"
  },
  "query": {
    "from": "Clientes",
    "select": [
      "Nombre",
      {
        "table": "Pedidos",
        "select": ["Total", "Fecha"],
        "relations": [
          {
            "table": "ItemsPedido",
            "select": ["Producto", "Cantidad", "PrecioUnitario"]
          }
        ]
      }
    ]
  }
}

Joins detectados solos

PraxQL deduce la condición de join cuando cualquiera de los dos lados apunta al otro:

  • la tabla padre tiene una columna Table cuyo pointsTo es la tabla hija, o

  • la tabla hija tiene una columna Table cuyo pointsTo es la tabla padre.

Para una columna de relación normal nunca escribís on — el esquema ya conoce la conexión.


Joins explícitos

Cuando la detección automática elige la columna equivocada, o la relación no está expresada como columna Table, nombrá vos las columnas:

{
  "table": "Facturas",
  "on": { "left": "ID", "right": "ClienteRef" },
  "joinType": "INNER",
  "select": ["NumeroFactura", "Monto"]
}

Campo

Descripción

left

Columna en la tabla padre

right

Columna en la tabla hija

joinType

LEFT conserva las filas padre sin hijos; INNER las descarta


Límite de profundidad

Acá hay dos números y es fácil confundirlos.

5 es el tope de la plataforma. Nada lo sube.

2 es lo que realmente recibe un scope de tabla recién creado — MaxRelationDepthOverride arranca en 2. O sea que una credencial nueva anida dos niveles, no cinco, hasta que alguien lo amplíe. El override se puede mover hasta 5, nunca más allá.

Pasarse del límite efectivo devuelve 403 SCOPE_VIOLATION. Si una consulta que anda en el Playground falla desde tu app, esto es lo primero que hay que mirar.

La tabla relacionada además tiene que permitir relaciones: AllowRelations viene activo en un scope nuevo, pero se puede apagar.


Por qué no es N+1

PraxQL no emite una consulta por fila padre:

  1. Corre la consulta principal y devuelve las filas padre.

  2. Se juntan sus ids.

  3. Una sola sub-consulta por lote trae todas las filas relacionadas para ese conjunto de ids de una vez.

  4. Los resultados se arman antes de construir la respuesta.

Traer 100 clientes con sus pedidos son dos consultas, no 101. Cada nivel extra de anidamiento agrega una consulta, no una por fila.


Toda tabla debe estar en refs

Toda tabla usada en una relación — incluidas las anidadas profundo — debe estar declarada en el refs de primer nivel con su GUID.

Una tabla referenciada en una relación pero ausente de refs devuelve 400 INVALID_QUERY. Es deliberado: refs es la lista completa de lo que un request puede tocar, así que se puede contrastar con tus scopes antes de hacer ningún trabajo.