# Lenguaje de consultas (LQL)

## Para qué sirve

LQL es el lenguaje con el que le hacés preguntas a tus datos en [Explorar](https://lymen.app/docs/datos/explorar/), en los tiles de [Dashboards](https://lymen.app/docs/datos/dashboards/) y donde se pida una consulta. Es uno solo para todos los tipos de dato: no hace falta aprender uno distinto para métricas, logs, eventos o trazas.

Escribir consultas a mano es para Sistemas y Administración. El resto de los roles puede armarlas con el asistente "Armar con ayuda", que genera el texto por vos.

Una **consulta** siempre solo lee: no modifica ni borra datos.

## Cuándo usarlo

- El asistente no llega a lo que necesitás (por ejemplo, calcular un porcentaje entre dos conteos).
- Querés guardar y repetir una pregunta exacta en un tile.
- Querés ajustar la consulta que armó el asistente.

## Cómo se escribe, paso a paso

1. Empezá por el **origen**: `from logs`, `from eventos`, `from trazas`, `from rum` o `series` (métricas).
2. Agregá **cláusulas**, una detrás de otra, cada una con su palabra clave: `where`, `show`, `order by`, `take`, etc. No hay barras `|`.
3. Si querés agrupar o calcular, usá una función de resumen, como `count`, `sum(monto)` o `p95(duracion)`, con `by` y `every`.
4. Ejecutá con Ctrl+Enter. Si hay un error, el editor lo marca con línea y columna y sugiere el nombre correcto.

Los saltos de línea y los espacios dan igual. Los comentarios empiezan con `#`.

## Ejemplos para copiar

```
# Métricas: series de tiempo
series avg(cpu) by host
series p95(duracion) where servicio = "web-turnos"
series sum(pedidos) by servicio, operacion every 5m
series rate(errores) / rate(pedidos) * 100 by servicio        # % de errores

# Logs
from logs
where servicio = "web-turnos" and nivel = "ERROR"
where mensaje has "timeout"
show ts, servicio, nivel, mensaje
order by ts desc
take 100

from logs where nivel in ("ERROR", "WARN") count by servicio every 10m

# Eventos de negocio
from eventos where evento = "pago.aprobado" sum(monto) by canal
from eventos where evento startswith "turno." count by evento every 1h
from eventos where evento = "pago.rechazado" show ts, monto, canal take 50

# Experiencia de usuarios
from rum where evento = "rum.vital" and vital = "LCP" p75(valor) by vista

# Trazas
from trazas where servicio = "web-turnos" and duracion > 1s order by duracion desc take 20
from trazas where error = true count by servicio, operacion

# Más
from logs where servicio = "web-turnos" extract "turno {turno:int} reservado" from mensaje where turno is not null take 20
from logs exclude nivel = "DEBUG" series count by nivel every 5m
from eventos compute hora = hour(ts) count by hora
from eventos where evento = "pago.aprobado" compute tramo = if(monto > 50000, "alto", "normal") count, sum(monto) by tramo
from trazas where error = true unique operacion show ts, servicio, operacion, duracion
from eventos count_if(evento = "pago.rechazado") * 100 / count_if(evento startswith "pago.") every 1h
from eventos total = sum(monto), cantidad = count by canal order by total desc
from logs hide bucket, span_id rename mensaje as texto
```

## Orígenes y campos

Cada origen tiene sus campos. En el editor, el autocompletado te muestra los reales de tu instalación, con valores sugeridos.

| Origen | Para qué | Campos principales |
|---|---|---|
| `series` | Métricas: números medidos en el tiempo | Métricas `pedidos`, `errores`, `duracion`, `cpu`, `memoria`, `disco`, `carga`, las de negocio de tu perfil y otras métricas por su nombre técnico. Se agrupan por `servicio`, `operacion`, `host`, `estado`, etc. |
| `from logs` | Líneas de texto de tus sistemas | `ts`, `servicio`, `nivel`, `mensaje`, `bucket`, `trace_id`, `span_id` y los atributos que existan |
| `from eventos` | Hechos de negocio | `ts`, `evento`, `categoria`, `servicio`, `monto`, `moneda`, `canal`, `sesion`, `trace_id` y `attrs["clave"]` |
| `from trazas` | Recorridos de pedidos | `ts`, `servicio`, `operacion`, `duracion`, `error`, `codigo_http`, `trace_id` y `attrs["clave"]` |
| `from rum` | Experiencia de usuarios | `sitio`, `plataforma`, `evento`, `vital`, `vista`, `valor` y más |

Notas:

- En `rum`, `sitio` se escribe con el **nombre de la aplicación** como figura en [Experiencia de usuarios](https://lymen.app/docs/datos/experiencia-usuarios/) (`sitio = "Demo web"`). Si dos aplicaciones tienen el mismo nombre en distintas plataformas, usá `"Web · Demo web"`. `plataforma` es `Web`, `Android` o `iOS`.
- Los atributos con punto se escriben así: `attrs["pago.medio"]`.
- Una **métrica** es un número medido cada cierto tiempo (por ejemplo, el uso de CPU). Un **evento** es un hecho puntual.

## Cláusulas

| Cláusula | Qué hace | Ejemplo |
|---|---|---|
| `where cond` | Se queda con lo que cumple. | `where nivel = "ERROR"` |
| `exclude cond` | Saca lo que cumple. | `exclude nivel = "DEBUG"` |
| `show a, b` | Muestra solo esas columnas. | `show ts, servicio` |
| `hide a, b` | Oculta columnas. | `hide bucket` |
| `compute x = expr` | Crea una columna calculada. | `compute hora = hour(ts)` |
| `rename a as b` | Cambia el nombre de una columna. | `rename mensaje as texto` |
| `order by x desc` | Ordena (`asc` o `desc`). | `order by duracion desc` |
| `take n` | Se queda con las primeras n filas. | `take 100` |
| Resumen | Calcula una o varias agregaciones, con `by` y `every`. | `count by servicio every 5m` |
| `series resumen` | Convierte el resultado en una serie de tiempo. | `series count by nivel` |
| `extract "patrón" from campo` | Saca datos de un texto. | `extract "turno {turno:int}" from mensaje` |
| `unique campo` | Deja una fila por valor distinto. | `unique operacion` |
| Período | Fija la ventana de tiempo (ver más abajo). | `last 2h` |

## Operadores

| Tipo | Operadores |
|---|---|
| Comparación | `=` `!=` `>` `>=` `<` `<=` (un solo `=`, nunca `==`) |
| Lógicos | `and` `or` `not` y paréntesis |
| Listas | `x in ("a", "b")` · `x not in ("a", "b")` |
| Texto | `has` (contiene) · `startswith` · `endswith` · `matches` (expresión regular, hasta 200 caracteres) |
| Nulos | `x is null` · `x is not null` |
| Aritmética | `+` `-` `*` `/` |
| Valores | texto entre comillas dobles, números, duraciones (`500ms`, `2s`, `5m`, `1h`, `1d`), `true`, `false` |

Las palabras clave van en minúscula. Palabras reservadas que no sirven como nombre de campo: `from series where exclude show hide compute rename order by take extract unique every as and or not in has startswith endswith matches is null asc desc true false last since until today yesterday`. Si un campo se llama así, escribilo entre acentos graves: `` `order` ``.

## Agregaciones

| Función | Qué calcula |
|---|---|
| `count` | Cantidad de filas |
| `sum(x)`, `avg(x)`, `min(x)`, `max(x)` | Suma, promedio, mínimo y máximo |
| `p50(x)`, `p75(x)`, `p90(x)`, `p95(x)`, `p99(x)` | Percentiles: `p95` es el valor que el 95 % no supera |
| `percentile(x, n)` | Cualquier percentil, con `n` entero de 1 a 99 escrito tal cual |
| `count_distinct(x)` | Cuántos valores distintos |
| `count_if(cond)` | Cuántas filas cumplen la condición |
| `first(x)`, `last(x)` | Primer y último valor |
| `distinct_values(x)` | Lista de valores distintos (hasta 100) |

Podés ponerles nombre (`total = sum(monto)`) y combinarlas con aritmética (`count_if(...) * 100 / count`). En métricas, los percentiles solo valen para histogramas y medidores, y también existe `rate(x)` (ritmo por segundo).

`every 5m` agrupa en tramos de tiempo. Si lo omitís, se calcula para tener unos 60 puntos del período.

## Funciones para calcular

| Grupo | Funciones |
|---|---|
| Texto | `lower`, `upper`, `trim`, `substring(x, desde, largo)`, `concat(a, b…)`, `replace(x, "a", "b")`, `length`, `split(x, ",")[n]` |
| Condicionales | `if(cond, a, b)`, `coalesce(a, b…)` |
| Números | `round(x, n)`, `abs`, `floor`, `ceil`, `to_number`, `to_text` |
| Tiempo | `bucket(ts, 5m)`, `now()`, `format_time(ts, "HH:mm")`, `hour(ts)`, `day_of_week(ts)` |

## Extraer datos de un texto

`extract "patrón" from campo` saca partes de un texto como columnas nuevas. En el patrón, `{nombre}` toma el texto hasta el próximo literal; también `{nombre:int}` (entero), `{nombre:num}` (número), `{nombre:ip}` y `{nombre:palabra}`. El `*` ignora un tramo. Hasta 10 marcadores por patrón.

## Período dentro de la consulta

Una cláusula opcional, a lo sumo una por consulta:

| Forma | Significa |
|---|---|
| `last 2h` | Las últimas 2 horas (también `30m`, `7d`) |
| `since 3d` | Desde hace 3 días hasta ahora |
| `since 3d until 1d` | Desde hace 3 días hasta hace 1 día |
| `since "2026-10-27 10:00" until "2026-10-27 12:00"` | Entre dos fechas (entre comillas) |
| `today` | Desde las 00:00 de hoy hasta ahora |
| `yesterday` | El día de ayer completo |

- Las fechas sin zona horaria se interpretan en la zona horaria de tu organización. Con zona (`Z` o `-03:00`) se respeta la escrita.
- **Si la consulta trae período, ese manda** sobre el del dashboard, el tile o Explorar. El selector queda deshabilitado con el cartel "Período de la consulta".
- `last(x)` con paréntesis es la agregación "último valor"; `last 2h` sin paréntesis es el período.

## Topes y límites

| Límite | Valor | Impacto |
|---|---|---|
| Largo de la consulta | 4.000 caracteres | Más largo se rechaza. |
| Período | Hasta 31 días | Un período mayor da error. |
| Puntos por serie | Hasta 1.100 | Si un `every` explícito da más, es un error en el `every`. |
| Filas | Hasta 10.000 cuando una parte se resuelve en Lymen | La pantalla avisa "Se calculó sobre las primeras 10.000 filas". |
| Tiempo | 15 segundos por consulta | Se corta. |
| Consultas a la vez | 3 por persona | Las demás esperan. |
| Marcadores en `extract` | 10 | |
| Expresión regular (`matches`) | 200 caracteres, sin referencias hacia atrás | |

Para evitar cortes, filtrá temprano con `where`, achicá el período y limitá con `take`.

## Seguridad

Los valores que escribís nunca se pegan sin control en la consulta real: Lymen valida todo contra el catálogo de campos y los escapa. Cada consulta ejecutada queda en una auditoría.

## Consultas en formato anterior

Si pegás una consulta del formato anterior, el editor la detecta y ofrece **Convertir**. Los tiles viejos se pasan solos al formato nuevo cuando es posible; los que no, se ven en solo lectura con el cartel "Consulta avanzada anterior".

## Problemas comunes

- **"escribí «where» en minúscula"**: las palabras clave van en minúscula.
- **Usé `==` y falla**: es un solo `=`.
- **"el campo «servcio» no existe"**: revisá el nombre; el editor sugiere el correcto.
- **Con `matches` da error**: regex muy larga (más de 200 caracteres) o con referencias hacia atrás.
- **"El número de percentile tiene que ser un entero de 1 a 99"**: escribilo tal cual, por ejemplo `percentile(duracion, 80)`.
- **"el período no puede pasar de 31 días"**: acortalo.
- **Un campo con nombre reservado**: ponelo entre acentos graves.
- **No me deja escribir**: tu rol es de consulta; usá el asistente.
