iatoolkit 0.3.6__py3-none-any.whl → 0.3.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of iatoolkit might be problematic. Click here for more details.
- iatoolkit/base_company.py +7 -2
- iatoolkit/iatoolkit.py +42 -11
- iatoolkit/system_prompts/arquitectura.prompt +32 -0
- iatoolkit/system_prompts/format_styles.prompt +97 -0
- iatoolkit/system_prompts/query_main.prompt +67 -0
- iatoolkit/system_prompts/sql_rules.prompt +1337 -0
- {iatoolkit-0.3.6.dist-info → iatoolkit-0.3.8.dist-info}/METADATA +1 -1
- {iatoolkit-0.3.6.dist-info → iatoolkit-0.3.8.dist-info}/RECORD +15 -11
- services/dispatcher_service.py +44 -12
- services/history_service.py +1 -1
- services/profile_service.py +3 -3
- services/prompt_manager_service.py +15 -23
- services/query_service.py +9 -7
- {iatoolkit-0.3.6.dist-info → iatoolkit-0.3.8.dist-info}/WHEEL +0 -0
- {iatoolkit-0.3.6.dist-info → iatoolkit-0.3.8.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,1337 @@
|
|
|
1
|
+
## 🧠 Contexto general
|
|
2
|
+
|
|
3
|
+
- La base de datos es **PostgreSQL**.
|
|
4
|
+
- Muchas columnas contienen información en formato **jsonb**.
|
|
5
|
+
- Todas las consultas deben ser **sintácticamente correctas** y compatibles con PostgreSQL.
|
|
6
|
+
|
|
7
|
+
## 🔍 Uso correcto de campos JSONB
|
|
8
|
+
|
|
9
|
+
### Regla central: ¡NO ASUMAS QUE LAS CLAVES JSONB SON COLUMNAS!
|
|
10
|
+
|
|
11
|
+
- **Error frecuente:** El error `column "X" does not exist` ocurre cuando se intenta
|
|
12
|
+
usar una clave de un JSONB como si fuera una columna directa.
|
|
13
|
+
**¡Esto es incorrecto y la causa principal de errores!**
|
|
14
|
+
- **Solución obligatoria:** Usa SIEMPRE el nombre del campo JSONB contenedor seguido
|
|
15
|
+
del operador `->>` para acceder al valor.
|
|
16
|
+
|
|
17
|
+
#### Ejemplo incorrecto:
|
|
18
|
+
```sql
|
|
19
|
+
-- INCORRECTO: "line_type" no es una columna de bcu_customer.
|
|
20
|
+
SELECT line_type FROM bcu_customer GROUP BY line_type;
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
#### Ejemplo correcto:
|
|
24
|
+
```sql
|
|
25
|
+
-- CORRECTO: Se accede a "line_type" desde jsonb_credit.
|
|
26
|
+
SELECT jsonb_credit->>'line_type' AS line_type
|
|
27
|
+
FROM bcu_customer
|
|
28
|
+
GROUP BY jsonb_credit->>'line_type';
|
|
29
|
+
```
|
|
30
|
+
Nunca asumas que un campo como 'product_type', 'created', 'current_debt', 'amount', etc.
|
|
31
|
+
existe directamente como columna en la tabla base.
|
|
32
|
+
Si el campo está dentro de un objeto JSONB, debes acceder a él explícitamente usando
|
|
33
|
+
la sintaxis: <jsonb_field>->>'<subfield>' o <jsonb_field>->'<subfield>' según el
|
|
34
|
+
tipo requerido (texto o json).
|
|
35
|
+
|
|
36
|
+
No hagas SELECT de columnas inexistentes en tablas físicas.
|
|
37
|
+
Antes de usar columnas como `amount`, `certificate_ids`, `adjudication_date`, etc.,
|
|
38
|
+
verifica si están en la tabla física (como `bcu_tender`) o si provienen
|
|
39
|
+
de un campo JSONB (como `jsonb_collection` en `bcu_customer`).
|
|
40
|
+
Si están en un JSONB, accede a ellas mediante el operador `->>` sobre el JSON,
|
|
41
|
+
no como columna del alias SQL.
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
### Aplicación general:
|
|
45
|
+
Esta regla aplica a todas las cláusulas SQL: `SELECT`, `WHERE`, `GROUP BY`, `ORDER BY`.
|
|
46
|
+
|
|
47
|
+
- si un campo JSONB puede venir como null, primero debe verificarse antes de castear.
|
|
48
|
+
|
|
49
|
+
## 🔢 Casting y tipos de datos
|
|
50
|
+
|
|
51
|
+
### Regla general de casting
|
|
52
|
+
|
|
53
|
+
- 🎯 El tipo de casting debe coincidir con el tipo declarado en el schema:
|
|
54
|
+
- `integer` → usa `::INTEGER`
|
|
55
|
+
- `number` o `numeric` → usa `::NUMERIC`
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
🎯 Regla para castear montos numéricos desde JSONB
|
|
59
|
+
• 🔒 No asumas que los montos en JSONB son siempre enteros.
|
|
60
|
+
Algunos campos pueden venir como strings con decimales implícitos,
|
|
61
|
+
por ejemplo: "3503722.0".
|
|
62
|
+
• Si el valor puede incluir punto decimal, incluso .0, nunca lo cases
|
|
63
|
+
directamente como ::INTEGER, ya que eso genera error de casting.
|
|
64
|
+
• Nunca intentes castear un campo jsonb a jsonb[].
|
|
65
|
+
• En estos casos, usa ::NUMERIC para asegurar compatibilidad y precisión:
|
|
66
|
+
´´´sql
|
|
67
|
+
(jsonb_field->>'field_name')::NUMERIC
|
|
68
|
+
´´´
|
|
69
|
+
|
|
70
|
+
• Si necesitas un entero redondeado, usa ROUND(...) sobre el valor casteado:
|
|
71
|
+
´´´sql
|
|
72
|
+
ROUND((jsonb_field->>'field_name')::NUMERIC) AS field_name
|
|
73
|
+
´´´
|
|
74
|
+
- Siempre usa NULLIF(..., '')::NUMERIC y COALESCE(..., 0) al castear valores numéricos
|
|
75
|
+
provenientes de JSONB para evitar errores de conversión si el campo está vacío.
|
|
76
|
+
ejemplo en ´´´sql
|
|
77
|
+
COALESCE(
|
|
78
|
+
(NULLIF(jsonb_field->>'public_line_available', ''))::NUMERIC, 0
|
|
79
|
+
) AS public_line_available
|
|
80
|
+
|
|
81
|
+
- Siempre que un campo numérico provenga de una clave de tipo JSONB que podría contener string vacío, utiliza la estructura:
|
|
82
|
+
COALESCE(NULLIF(jsonb_objeto->>'campo', '')::NUMERIC, 0)
|
|
83
|
+
para evitar errores de conversión cuando el campo esté vacío.
|
|
84
|
+
|
|
85
|
+
- Esta regla aplica para conversiones a NUMERIC, INTEGER, FLOAT, DOUBLE PRECISION, etc. y debe aplicarse de forma consistente en todos los campos relevantes.
|
|
86
|
+
- Ejemplo correcto:
|
|
87
|
+
COALESCE(NULLIF(jsonb_column->>'field_name', '')::NUMERIC, 0) AS field_name
|
|
88
|
+
- Nunca uses NULLIF(..., '') para valores numéricos ya calculados.
|
|
89
|
+
Aplica NULLIF(..., '') sólo cuando trabajes directamente con strings extraídos
|
|
90
|
+
del JSONB. Para resultados de SUM/ROUND usa solo COALESCE(..., 0).
|
|
91
|
+
|
|
92
|
+
- Nunca castees directamente a NUMERIC si el valor puede ser vacío, ya que provocará un error de sintaxis en Postgres.
|
|
93
|
+
• Como principio general:
|
|
94
|
+
• Usa ::NUMERIC si hay posibilidad de decimales.
|
|
95
|
+
• Usa ::INTEGER solo si estás seguro de que los valores siempre serán enteros sin .0.
|
|
96
|
+
|
|
97
|
+
- Cuando utilices funciones agregadas como SUM, AVG, MIN, MAX
|
|
98
|
+
sobre valores extraídos de un JSONB (usando ->>),
|
|
99
|
+
asegúrate de convertir cada elemento al tipo numérico dentro de la función agregada.
|
|
100
|
+
|
|
101
|
+
Ejemplo correcto: SUM((value->>'monto')::NUMERIC)
|
|
102
|
+
Ejemplo incorrecto: SUM(value->>'monto')::NUMERIC
|
|
103
|
+
|
|
104
|
+
🎯 Regla para castear fechas desde JSONB
|
|
105
|
+
|
|
106
|
+
- Si el campo de fecha proviene de un JSONB y está en formato `DD-MM-YYYY`,
|
|
107
|
+
**no puedes castear directamente a DATE**.
|
|
108
|
+
- Usa `TO_DATE(...)` para convertir el string a fecha:
|
|
109
|
+
```sql
|
|
110
|
+
TO_DATE(jsonb_credit->>'created', 'DD-MM-YYYY')
|
|
111
|
+
´´´
|
|
112
|
+
|
|
113
|
+
- antes de comparar con `CURRENT_DATE` un campo de tipo jsonb,
|
|
114
|
+
**siempre** castea explícitamente
|
|
115
|
+
|
|
116
|
+
- No utilices TO_DATE si la columna ya es de tipo DATE o TIMESTAMP.
|
|
117
|
+
Úsala solo si el campo es tipo TEXT o VARCHAR, y asegúrate de castearlo explícitamente si es necesario.
|
|
118
|
+
TO_DATE espera un string, no una fecha ya en formato DATE, y causará un error de tipo de datos.
|
|
119
|
+
|
|
120
|
+
- Si comparas fechas, asegúrate que ambos lados sean del mismo tipo.
|
|
121
|
+
ejemplo:
|
|
122
|
+
```sql
|
|
123
|
+
-- Si 'init_date' es DATE:
|
|
124
|
+
TO_CHAR(init_date, 'DD-MM-YYYY') = ...
|
|
125
|
+
-- Si 'created' es texto en formato 'DD-MM-YYYY':
|
|
126
|
+
TO_DATE(jsonb_guarantee->>'created', 'DD-MM-YYYY')
|
|
127
|
+
- Evitar comparar campos de tipo DATE o TIMESTAMP con strings generados por TO_CHAR.
|
|
128
|
+
Usar funciones como date_trunc() o INTERVAL directamente.
|
|
129
|
+
|
|
130
|
+
- Cuando necesites construir fechas a partir de valores numéricos
|
|
131
|
+
(por ejemplo, año y mes), utiliza TO_DATE() con un string en formato 'YYYY-MM-DD',
|
|
132
|
+
no uses concatenación sobre literales de tipo DATE.
|
|
133
|
+
|
|
134
|
+
- Si el campo de fecha puede venir vacío (''), usa:
|
|
135
|
+
TO_DATE(NULLIF(campo, ''), 'DD-MM-YYYY')
|
|
136
|
+
Esto evita errores de conversión cuando la fecha está ausente o mal registrada.
|
|
137
|
+
|
|
138
|
+
### operaciones sobre campos de tipo JSONB
|
|
139
|
+
- Cuando uses SUM o alguna otra función agregada sobre un valor extraído
|
|
140
|
+
de un JSONB con ->> (texto), aplica el cast a NUMERIC dentro del SUM y no después.
|
|
141
|
+
- ✅ Siempre castea **antes** de aplicar `SUM`:
|
|
142
|
+
```sql
|
|
143
|
+
SUM((json_field->>'monto')::NUMERIC)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
- ❌ Nunca castees después del `SUM`:
|
|
147
|
+
```sql
|
|
148
|
+
-- INCORRECTO
|
|
149
|
+
SUM(json_field->>'monto')::NUMERIC
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Ejemplo correcto:
|
|
153
|
+
```sql
|
|
154
|
+
SELECT SUM((gv->>'amount')::NUMERIC) AS total
|
|
155
|
+
FROM ...
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Cuando uses SUM(...) con FILTER, aplica COALESCE sobre los valores internos dentro
|
|
159
|
+
del SUM, no sobre el resultado del SUM completo.
|
|
160
|
+
|
|
161
|
+
Siempre que se conviertan campos numéricos extraídos de objetos JSONB
|
|
162
|
+
usando `->>` (por ejemplo, `collection_amount`, `fogape_amount`),
|
|
163
|
+
debes envolver la conversión con `NULLIF(..., '')::NUMERIC` y `COALESCE(..., 0)`
|
|
164
|
+
para evitar errores cuando el valor sea una cadena vacía o `null`.
|
|
165
|
+
|
|
166
|
+
Cuando debas agregar valores numéricos extraídos de campos JSONB que pueden contener texto,
|
|
167
|
+
valores vacíos o null, siempre utiliza la siguiente estructura dentro de la
|
|
168
|
+
función SUM, AVG, COUNT u otra función agregada:
|
|
169
|
+
SUM(COALESCE(NULLIF(<campo_jsonb>, '')::NUMERIC, 0))
|
|
170
|
+
|
|
171
|
+
- Cuando utilices subconsultas agregadas (con SUM, COUNT, etc.) en SQL,
|
|
172
|
+
asegúrate de que todos los campos que aparecen en el SELECT del subquery
|
|
173
|
+
estén también en el GROUP BY de ese subquery, o que se usen únicamente
|
|
174
|
+
dentro de funciones de agregación como SUM(), COUNT(), MAX(), MIN(), etc.
|
|
175
|
+
|
|
176
|
+
- Cuando debas comparar un valor de un campo jsonb contra un string,
|
|
177
|
+
asegúrate de convertir el valor jsonb a texto con ::text antes de la comparación.
|
|
178
|
+
Por ejemplo, para saber si un objeto jsonb está vacío,
|
|
179
|
+
usa (jsonb_field)::text != '{}' en vez de jsonb_field != '{}'.
|
|
180
|
+
|
|
181
|
+
- Siempre que debas acceder a elementos contenidos en un campo JSONB definido como array,
|
|
182
|
+
como `judicial_collection`, utiliza estrictamente la función `jsonb_array_elements(campo_jsonb->'array')`.
|
|
183
|
+
Nunca uses `jsonb_each`, ya que esta función solo aplica a objetos JSONB, no arrays.
|
|
184
|
+
|
|
185
|
+
- Para recorrer arrays dentro de columnas JSONB,
|
|
186
|
+
utiliza la función jsonb_array_elements en la cláusula FROM o en LEFT JOIN LATERAL.
|
|
187
|
+
Ejemplo:
|
|
188
|
+
```sql
|
|
189
|
+
LEFT JOIN LATERAL jsonb_array_elements(tabla.columna_jsonb->'nombre_array') AS alias(elemento) ON TRUE
|
|
190
|
+
|
|
191
|
+
Si necesitas recorrer un array dentro de cada elemento del array anterior,
|
|
192
|
+
anida los jsonb_array_elements usando otro LEFT JOIN LATERAL sobre el alias anterior.
|
|
193
|
+
|
|
194
|
+
- Cuando uses `jsonb_array_elements(...) AS alias(columna)` en una
|
|
195
|
+
cláusula `FROM` o `JOIN`, recuerda que el alias (`alias`) no representa un objeto
|
|
196
|
+
con múltiples columnas, sino una tupla con una sola columna llamada como
|
|
197
|
+
se indica en `AS alias(columna)`. Por lo tanto:
|
|
198
|
+
- Para acceder a los valores internos del JSON debes usar `alias.columna->>'campo'`.
|
|
199
|
+
- Nunca uses `alias.campo` directamente, ya que causará un error de columna no definida.
|
|
200
|
+
- Ejemplo correcto: `alias.value->>'offer_status'`.
|
|
201
|
+
- Ejemplo incorrecto: `alias.offer_status`.
|
|
202
|
+
|
|
203
|
+
- No uses funciones que devuelven múltiples filas (set-returning functions) como
|
|
204
|
+
jsonb_array_elements(), unnest(), generate_series(), etc.
|
|
205
|
+
directamente dentro de funciones de agregación como SUM(), COUNT(), AVG(), etc.
|
|
206
|
+
En su lugar, mueve la función a una subconsulta o usa un LATERAL JOIN para expandir
|
|
207
|
+
primero los elementos y luego aplicar la agregación.
|
|
208
|
+
Ejemplo correcto:
|
|
209
|
+
SELECT SUM((elem->>'amount')::NUMERIC)
|
|
210
|
+
FROM tabla
|
|
211
|
+
LEFT JOIN LATERAL jsonb_array_elements(tabla.json_col) AS elem ON TRUE;
|
|
212
|
+
Ejemplo incorrecto:
|
|
213
|
+
SELECT SUM((jsonb_array_elements(tabla.json_col)->>'amount')::NUMERIC)
|
|
214
|
+
FROM tabla;
|
|
215
|
+
|
|
216
|
+
### Evaluación de booleanos desde JSONB
|
|
217
|
+
|
|
218
|
+
- Las claves booleanas dentro de JSONB devuelven texto.
|
|
219
|
+
- Para evaluarlas correctamente, usa cast explícito:
|
|
220
|
+
```sql
|
|
221
|
+
(jsonb_field->>'pre_judicial')::BOOLEAN IS TRUE
|
|
222
|
+
|
|
223
|
+
- aplica esta regla a todos los campos que definan true/false
|
|
224
|
+
|
|
225
|
+
📌 Evita casteos múltiples de un mismo campo
|
|
226
|
+
• Si vas a usar (jsonb->>'campo')::TIPO varias veces en la query,
|
|
227
|
+
guarda el resultado con WITH o como subquery para evitar repetir el casteo.
|
|
228
|
+
ejemplo:
|
|
229
|
+
´´´sql
|
|
230
|
+
WITH datos AS (
|
|
231
|
+
SELECT
|
|
232
|
+
rut,
|
|
233
|
+
(jsonb_column->>'field_name')::NUMERIC AS field_name
|
|
234
|
+
FROM bcu_customer
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
📌 No uses jsonb -> 'obj'::text != '{}'
|
|
238
|
+
• Esa comparación es lenta y poco confiable. prefiere:
|
|
239
|
+
´´´sql
|
|
240
|
+
jsonb_typeof(jsonb->'obj') = 'object'
|
|
241
|
+
AND jsonb_object_keys(jsonb->'obj') IS NOT NULL
|
|
242
|
+
|
|
243
|
+
🟢 Regla para uso de operadores JSONB sobre arrays y objetos
|
|
244
|
+
|
|
245
|
+
- Cuando uses la función jsonb_each sobre un objeto jsonb,
|
|
246
|
+
**usa el alias AS e(key, value)** solo dentro de FROM o LATERAL,
|
|
247
|
+
nunca en un subquery escalar sin alias de tabla. Ejemplo correcto:
|
|
248
|
+
FROM jsonb_each(jsonb_object) AS e(key, value)
|
|
249
|
+
- Si necesitas solo value, puedes usar: FROM jsonb_each(jsonb_object) AS e
|
|
250
|
+
- Cuando hagas subconsultas escalares que usan jsonb_each, **recuerda que el alias debe ser simple (AS e), y accede a los valores usando e.value->>'campo'**.
|
|
251
|
+
Ejemplo correcto para sumar montos dentro de un objeto:
|
|
252
|
+
(SELECT SUM((e.value->>'amount')::NUMERIC) FROM jsonb_each(objeto_jsonb) AS e WHERE e.value->>'amount' IS NOT NULL)
|
|
253
|
+
|
|
254
|
+
- Antes de utilizar operadores JSONB como jsonb_each o jsonb_array_elements en un campo,
|
|
255
|
+
asegúrate de que el tipo de dato corresponde:
|
|
256
|
+
• Utiliza jsonb_each solo sobre campos JSONB de tipo objeto (dict/JSON {}).
|
|
257
|
+
. Nunca uses jsonb_each sobre arrays, nulls o strings.
|
|
258
|
+
• Utiliza jsonb_array_elements solo sobre campos JSONB de tipo array ([]).
|
|
259
|
+
|
|
260
|
+
- Nunca apliques jsonb_each sobre un campo que pueda contener un array o valor nulo.
|
|
261
|
+
- Nunca utilices jsonb_each sobre un campo JSONB si puede ser un array.
|
|
262
|
+
Siempre verifica el tipo con jsonb_typeof.
|
|
263
|
+
Si puede ser un array, utiliza jsonb_array_elements y filtra por tipo si es necesario.
|
|
264
|
+
|
|
265
|
+
- Cuando uses funciones como jsonb_each(jsonb_column) asegúrate de que el
|
|
266
|
+
campo sea un objeto JSONB. usa la condición:
|
|
267
|
+
```sql
|
|
268
|
+
LEFT JOIN LATERAL jsonb_each(c.jsonb_object->'operation') AS e ON
|
|
269
|
+
c.jsonb_object->'operation' IS NOT NULL
|
|
270
|
+
AND jsonb_typeof(c.jsonb_object->'operation') = 'object'
|
|
271
|
+
|
|
272
|
+
- Cuando debas recorrer elementos dentro de un campo JSONB:
|
|
273
|
+
• Usa jsonb_each si el campo es un objeto (dict/{}).
|
|
274
|
+
• Usa jsonb_array_elements si el campo es un array ([]).
|
|
275
|
+
|
|
276
|
+
Si necesitas recorrer un campo JSONB que es un array,
|
|
277
|
+
utiliza siempre jsonb_array_elements.
|
|
278
|
+
Usa jsonb_each solo si el campo es un objeto (tipo diccionario).
|
|
279
|
+
Ejemplo para arrays:
|
|
280
|
+
```sql
|
|
281
|
+
SELECT ... FROM jsonb_array_elements(campo_jsonb->'mi_array')
|
|
282
|
+
|
|
283
|
+
Ejemplo para objetos:
|
|
284
|
+
```sql
|
|
285
|
+
SELECT ... FROM jsonb_each(campo_jsonb->'mi_objeto')
|
|
286
|
+
|
|
287
|
+
## 📐 Cálculos y expresiones
|
|
288
|
+
|
|
289
|
+
### Porcentajes y divisiones
|
|
290
|
+
|
|
291
|
+
- Usa `* 100.0` para forzar el uso de decimales en porcentajes.
|
|
292
|
+
- Usa `NULLIF` para proteger divisiones por cero.
|
|
293
|
+
|
|
294
|
+
### Coherencia en expresiones
|
|
295
|
+
|
|
296
|
+
- Asegura que todos los componentes de una expresión numérica compartan el mismo tipo.
|
|
297
|
+
- Protege `COALESCE` y literales con el mismo tipo que el campo:
|
|
298
|
+
```sql
|
|
299
|
+
COALESCE(SUM(...), 0::INTEGER)
|
|
300
|
+
COALESCE(SUM(...), 0::NUMERIC)
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### ➕ SUM sobre valores booleanos
|
|
304
|
+
- `SUM()` y otras funciones de agregación no aceptan directamente expresiones booleanas.
|
|
305
|
+
- Para contar condiciones booleanas, transforma el resultado a número:
|
|
306
|
+
|
|
307
|
+
```sql
|
|
308
|
+
SUM(CASE WHEN condición_booleana THEN 1 ELSE 0 END)
|
|
309
|
+
|
|
310
|
+
Ejemplo correcto:
|
|
311
|
+
```sql
|
|
312
|
+
SUM(CASE WHEN (jsonb_field->'field_name')::jsonb @> '[{"status": "vigente"}]'::jsonb THEN 1 ELSE 0 END)
|
|
313
|
+
|
|
314
|
+
❌ Incorrecto:
|
|
315
|
+
´´´sql
|
|
316
|
+
SUM((jsonb_guarantee->'field_name')::jsonb @> '[{"status": "vigente"}]'::jsonb)
|
|
317
|
+
|
|
318
|
+
### Evita mezcla implícita de tipos
|
|
319
|
+
|
|
320
|
+
- 🚫 No mezcles `INTEGER` y `NUMERIC` sin casting explícito.
|
|
321
|
+
- Castea siempre al tipo de mayor precisión si los mezclas.
|
|
322
|
+
|
|
323
|
+
### ⚠️ Validación segura al usar jsonb_array_length()
|
|
324
|
+
|
|
325
|
+
Siempre que uses jsonb_array_length(jsonb_column->'key'),
|
|
326
|
+
asegúrate de anteponer una condición en WHERE como:
|
|
327
|
+
jsonb_typeof(jsonb_column->'key') = 'array'
|
|
328
|
+
|
|
329
|
+
Añade explícitamente la verificación jsonb_typeof(jsonb_column->'key') = 'array'
|
|
330
|
+
**antes** de llamar a jsonb_array_length.
|
|
331
|
+
Por ejemplo:
|
|
332
|
+
```
|
|
333
|
+
WHERE jsonb_column IS NOT NULL
|
|
334
|
+
AND jsonb_typeof(jsonb_column->'key') = 'array'
|
|
335
|
+
AND jsonb_array_length(jsonb_column->'key') > 0
|
|
336
|
+
```
|
|
337
|
+
Esto evita que jsonb_array_length falle si el campo no es un array.
|
|
338
|
+
|
|
339
|
+
Esta regla se aplica especialmente a cualquier campo que pueda
|
|
340
|
+
contener una lista variable
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
### ⚠️ Uso de CTEs
|
|
344
|
+
|
|
345
|
+
- PostgreSQL **no permite usar alias definidos en el SELECT en la cláusula ORDER BY dentro del mismo nivel** si se hace `GROUP BY`.
|
|
346
|
+
- Solución: Repite la expresión original en `ORDER BY`.
|
|
347
|
+
|
|
348
|
+
❌ Esto puede fallar:
|
|
349
|
+
```sql
|
|
350
|
+
SELECT to_char(init_date, 'MM-YYYY') AS mes
|
|
351
|
+
GROUP BY mes
|
|
352
|
+
ORDER BY to_date('01-' || mes, 'DD-MM-YYYY')
|
|
353
|
+
|
|
354
|
+
Haz esto en su lugar:
|
|
355
|
+
´´´sql
|
|
356
|
+
ORDER BY to_date('01-' || to_char(init_date, 'MM-YYYY'), 'DD-MM-YYYY')
|
|
357
|
+
´´
|
|
358
|
+
|
|
359
|
+
Esta regla aplica en CTEs y subqueries donde defines alias y haces GROUP BY y ORDER BY juntos.
|
|
360
|
+
- Si una consulta usa GROUP BY, todas las columnas del SELECT que no estén dentro
|
|
361
|
+
de funciones agregadas deben estar explícitamente en la cláusula GROUP BY.
|
|
362
|
+
- Además, si en el ORDER BY se hace referencia a una columna (ej: c.public_line),
|
|
363
|
+
esa columna también debe agregarse al SELECT y al GROUP BY,
|
|
364
|
+
a menos que esté en una función de agregación.
|
|
365
|
+
- Esto incluye columnas provenientes de CTEs (WITH) como clientes_top u otras subconsultas.
|
|
366
|
+
- esto mismo aplica si vas a referenciar campos de una tabla en subconsultas,
|
|
367
|
+
LEFT JOIN LATERAL o expresiones posteriores,
|
|
368
|
+
asegúrate de seleccionar esos campos explícitamente en el CTE o subconsulta de origen.
|
|
369
|
+
|
|
370
|
+
-- Incorrecto:
|
|
371
|
+
-- GROUP BY c.rut, c.client_name
|
|
372
|
+
-- ORDER BY c.public_line ← Error porque no está ni en SELECT ni en GROUP BY
|
|
373
|
+
|
|
374
|
+
-- Correcto:
|
|
375
|
+
-- SELECT c.rut, c.client_name, c.public_line
|
|
376
|
+
-- GROUP BY c.rut, c.client_name, c.public_line
|
|
377
|
+
-- ORDER BY c.public_line
|
|
378
|
+
|
|
379
|
+
- detecta automáticamente si hay columnas en el SELECT u ORDER BY que no
|
|
380
|
+
están en GROUP BY y agregarlas.
|
|
381
|
+
- las columnas de CTEs también deben respetar esta regla, ya que PostgreSQL aplica
|
|
382
|
+
las restricciones de agrupamiento a nivel global.
|
|
383
|
+
|
|
384
|
+
Siempre que haya columnas con el mismo nombre en más de una CTE,
|
|
385
|
+
usa el nombre de la tabla o CTE como prefijo en el SELECT final para
|
|
386
|
+
evitar ambigüedad. Ejemplo: cartera.garantias_vigentes o cobranza_judicial.garantias_vigentes.
|
|
387
|
+
|
|
388
|
+
- Cuando definas múltiples CTEs con WITH, cada CTE debe estar separado por una coma (,)
|
|
389
|
+
Nunca olvides la coma entre cada definición de CTE, excepto después del último CTE antes del SELECT final.
|
|
390
|
+
Ejemplo:
|
|
391
|
+
```sql
|
|
392
|
+
WITH a AS (SELECT 1),
|
|
393
|
+
b AS (SELECT 2)
|
|
394
|
+
SELECT * FROM a, b;
|
|
395
|
+
|
|
396
|
+
Incorrecto (genera error):
|
|
397
|
+
```sql
|
|
398
|
+
WITH a AS (SELECT 1)
|
|
399
|
+
b AS (SELECT 2) -- ❌ falta la coma
|
|
400
|
+
SELECT * FROM a, b;
|
|
401
|
+
|
|
402
|
+
- Nunca uses directamente los alias definidos dentro de un CTE en el SELECT principal.
|
|
403
|
+
Si necesitas usar una columna de un CTE, accede a ella mediante una subconsulta
|
|
404
|
+
completa que incluya el CTE o usa una tabla derivada con alias.
|
|
405
|
+
|
|
406
|
+
Reutiliza exactamente los nombres de columnas definidos en las CTE previas o SELECT
|
|
407
|
+
anteriores. Evita variaciones similares como 'executive' vs 'executable'."
|
|
408
|
+
Si necesitas retornar una fila de una CTE o subquery con varias columnas como
|
|
409
|
+
un solo campo, usa row_to_json(...).
|
|
410
|
+
Si solo necesitas columnas específicas, trae cada columna por separado en el SELECT.
|
|
411
|
+
ejemplo:
|
|
412
|
+
´´´sql
|
|
413
|
+
SELECT
|
|
414
|
+
(SELECT row_to_json(prejudicial) FROM prejudicial) AS prejudicial
|
|
415
|
+
|
|
416
|
+
o bien:
|
|
417
|
+
´´´sql
|
|
418
|
+
SELECT
|
|
419
|
+
(SELECT clientes_prejudicial FROM prejudicial) AS clientes_prejudicial,
|
|
420
|
+
(SELECT monto_prejudicial FROM prejudicial) AS monto_prejudicial
|
|
421
|
+
|
|
422
|
+
Si usas el mismo nombre de columna proveniente de varias tablas en un JOIN
|
|
423
|
+
o en una CTE, asegúrate de que en el SELECT final no quede ambigüedad,
|
|
424
|
+
usando siempre el prefijo de la tabla o seleccionando explícitamente solo
|
|
425
|
+
una de las columnas.
|
|
426
|
+
|
|
427
|
+
Cuando uses un alias de columna en el SELECT de una consulta, recuerda que en PostgreSQL no puedes referenciar ese alias directamente en el ORDER BY a menos que la consulta esté envuelta en una subquery.
|
|
428
|
+
Si necesitas ordenar por ese alias, repite la expresión o usa una subconsulta.
|
|
429
|
+
|
|
430
|
+
- Cuando uses JOIN entre tablas que tienen columnas con el mismo nombre (por ejemplo 'rut'),
|
|
431
|
+
nunca hagas SELECT t1.columna, t2.*.
|
|
432
|
+
En su lugar, selecciona los campos de forma explícita o usa alias para desambiguar.
|
|
433
|
+
ejemplo:
|
|
434
|
+
```sql
|
|
435
|
+
SELECT cert.rut AS rut_cert, cert.status, cert.guarantee_amount_pesos FROM clientes_kaponte c JOIN bcu_certificate cert ON cert.rut = c.rut
|
|
436
|
+
|
|
437
|
+
- Siempre que escribas una consulta SQL con más de una tabla o subconsulta,
|
|
438
|
+
debes anteponer el alias de la tabla o subconsulta a cada columna que aparece en
|
|
439
|
+
la cláusula SELECT, JOIN, ON, WHERE, GROUP BY, HAVING u ORDER BY.
|
|
440
|
+
Ejemplo correcto:
|
|
441
|
+
```sql
|
|
442
|
+
SELECT b.rut, c.client_name FROM base b JOIN clientes c ON c.rut = b.rut
|
|
443
|
+
|
|
444
|
+
- Nunca uses nombres de columnas sin el alias de su tabla correspondiente cuando
|
|
445
|
+
puedan existir en varias tablas o subconsultas (por ejemplo: rut, folio, status, etc.).
|
|
446
|
+
|
|
447
|
+
- Si usas LATERAL o subconsultas, también antepone el alias de tabla/subconsulta.
|
|
448
|
+
Ejemplo:
|
|
449
|
+
```sql
|
|
450
|
+
LEFT JOIN LATERAL (SELECT ... FROM jsonb_array_elements(b.jsonb_collection->'judicial_collection')) ...
|
|
451
|
+
|
|
452
|
+
Nunca uses alias de tabla (c.) fuera del alcance donde fueron definidos.
|
|
453
|
+
Sólo usa alias cuando la tabla está explícitamente definida con alias en
|
|
454
|
+
el FROM de esa subconsulta o CTE.
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
- Cuando crees un CTE (subconsulta con WITH), los campos disponibles en los
|
|
458
|
+
siguientes CTE o en el SELECT principal son solo los que seleccionaste en
|
|
459
|
+
el SELECT del CTE anterior.
|
|
460
|
+
- No intentes acceder a columnas originales de la tabla base que no están
|
|
461
|
+
proyectadas explícitamente en el CTE.
|
|
462
|
+
|
|
463
|
+
- Si necesitas un campo más adelante, asegúrate de incluirlo en el SELECT del CTE.
|
|
464
|
+
- Por ejemplo, si extraes jsonb_column->>'guarantee_executive' AS ejecutiva,
|
|
465
|
+
luego usa ejecutiva, no intentes usar de nuevo jsonb_column->>'guarantee_executive' en ese CTE.
|
|
466
|
+
|
|
467
|
+
- Nunca pongas un WHERE entre la definición de un alias lateral y el GROUP BY.
|
|
468
|
+
Si necesitas filtrar resultados de un LATERAL, pon el filtro dentro de la subconsulta LATERAL.
|
|
469
|
+
|
|
470
|
+
- Si tu campo es un array, usa siempre jsonb_array_elements (no jsonb_each).
|
|
471
|
+
- Si necesitas filtrar resultados de una función LATERAL (como jsonb_each o jsonb_array_elements),
|
|
472
|
+
pon el filtro en una subconsulta lateral y nunca en el ON.
|
|
473
|
+
- No repitas nombres de columnas o alias en diferentes scopes;
|
|
474
|
+
usa nombres claros, especialmente en LATERAL.
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
## Evitar referencias a alias fuera de su contexto
|
|
478
|
+
|
|
479
|
+
Cuando generes subconsultas, CTEs (WITH) o expresiones LATERAL que utilicen una
|
|
480
|
+
tabla virtual creada con VALUES, asegúrate de **no referenciar alias que no
|
|
481
|
+
estén definidos en ese scope**.
|
|
482
|
+
|
|
483
|
+
Por ejemplo, en una subconsulta tipo:
|
|
484
|
+
SELECT ... FROM (VALUES ...) AS m(col1, col2)
|
|
485
|
+
Solo puedes referenciar m.col1, m.col2, etc. en ese contexto.
|
|
486
|
+
Si la subconsulta recibe un alias (por ejemplo, j), solo puedes referenciar j.columna en el SELECT externo. **Nunca uses alias como g.columna si g no está definido en ese nivel.**
|
|
487
|
+
|
|
488
|
+
Ejemplo incorrecto:
|
|
489
|
+
SELECT g.col1 FROM (SELECT m.col1 FROM (VALUES ...) AS m) j
|
|
490
|
+
|
|
491
|
+
Ejemplo correcto:
|
|
492
|
+
SELECT j.col1 FROM (SELECT m.col1 FROM (VALUES ...) AS m) j
|
|
493
|
+
|
|
494
|
+
**Siempre asegúrate de que los campos referenciados en el SELECT provengan de los alias definidos en ese mismo FROM/JOIN.**
|
|
495
|
+
|
|
496
|
+
📘 Reglas para Optimización de Consultas jsonb Numéricas
|
|
497
|
+
|
|
498
|
+
🔁 1. Evitar casteos repetidos
|
|
499
|
+
• Descripción: No repitas el casteo de un mismo campo jsonb->>'campo' a NUMERIC, INTEGER, etc.
|
|
500
|
+
• Instrucción:
|
|
501
|
+
Usa un alias intermedio o un CTE para calcular el valor una sola vez.
|
|
502
|
+
Ejemplo (malo):
|
|
503
|
+
´´´sql
|
|
504
|
+
ORDER BY (jsonb_column->>'public_line')::NUMERIC DESC
|
|
505
|
+
LIMIT 50
|
|
506
|
+
|
|
507
|
+
Ejemplo (bueno):
|
|
508
|
+
´´´sql
|
|
509
|
+
WITH base AS (
|
|
510
|
+
SELECT
|
|
511
|
+
rut,
|
|
512
|
+
client_name,
|
|
513
|
+
(jsonb_column->>'public_line')::NUMERIC AS public_line
|
|
514
|
+
FROM bcu_customer
|
|
515
|
+
)
|
|
516
|
+
SELECT * FROM base ORDER BY public_line DESC LIMIT 50
|
|
517
|
+
|
|
518
|
+
📦 2. Usar CTEs para mejorar legibilidad
|
|
519
|
+
• Descripción: Si estás usando varios campos jsonb->>'...'::NUMERIC, sepáralos con un WITH al inicio.
|
|
520
|
+
• Instrucción:
|
|
521
|
+
Calcula todos los campos requeridos en un CTE y luego trabaja solo con sus alias.
|
|
522
|
+
|
|
523
|
+
🏗️ USO DE CTE Y REFERENCIAS DE COLUMNAS
|
|
524
|
+
|
|
525
|
+
- Cuando uses un CTE, debes referenciar únicamente los nombres de columnas que defines en el SELECT del CTE.
|
|
526
|
+
- Ejemplo: Si el CTE `base` define `rut`, usa `base.rut` (no `base.client_rut`).
|
|
527
|
+
- Si necesitas un alias distinto para la columna, utiliza `AS` en el SELECT del CTE:
|
|
528
|
+
```sql
|
|
529
|
+
b.rut AS client_rut
|
|
530
|
+
|
|
531
|
+
📊 4. Ordenar por campos casteados
|
|
532
|
+
• Descripción: Si necesitas ORDER BY sobre un campo jsonb casteado:
|
|
533
|
+
transpórtalo primero.
|
|
534
|
+
• Instrucción:
|
|
535
|
+
Usa un alias para ordenar. Evita reescribir el cast dentro del ORDER BY.
|
|
536
|
+
|
|
537
|
+
📌 Cuidado con el ORDER BY de expresiones sin índice
|
|
538
|
+
• Si ordenas por un campo jsonb, considera:
|
|
539
|
+
• Promoverlo a una columna normal (materializada).
|
|
540
|
+
• Crear un índice funcional (si es factible).
|
|
541
|
+
|
|
542
|
+
Cuando crees un alias con una función (por ejemplo, TO_CHAR(init_date, 'MM-YYYY') AS mes),
|
|
543
|
+
usa siempre la expresión completa en el GROUP BY y en el ORDER BY,
|
|
544
|
+
a menos que estés en una subconsulta o CTE donde el alias ya está definido.
|
|
545
|
+
No uses el alias directamente en el mismo nivel del SELECT.
|
|
546
|
+
ejemplo : ´´´sql
|
|
547
|
+
SELECT TO_CHAR(init_date, 'MM-YYYY') AS mes, ...
|
|
548
|
+
GROUP BY TO_CHAR(init_date, 'MM-YYYY')
|
|
549
|
+
ORDER BY TO_DATE('01-' || TO_CHAR(init_date, 'MM-YYYY'), 'DD-MM-YYYY') DESC
|
|
550
|
+
|
|
551
|
+
### 🧠 No reutilices campos JSONB que ya descompusiste
|
|
552
|
+
|
|
553
|
+
- Si en un CTE extraes subcampos desde un JSONB, por ejemplo:
|
|
554
|
+
```sql
|
|
555
|
+
jsonb_column->>'guarantee_executive' AS ejecutiva,
|
|
556
|
+
(jsonb_column->>'active')::INTEGER AS garantias_vigentes,
|
|
557
|
+
no podrás referenciar jsonb_column directamente después en el mismo CTE.
|
|
558
|
+
|
|
559
|
+
❌ Incorrecto:
|
|
560
|
+
´´´sql
|
|
561
|
+
SELECT ... FROM cartera WHERE TO_DATE(jsonb_column->>'created', ...) >= ...
|
|
562
|
+
´´
|
|
563
|
+
|
|
564
|
+
✅ Correcto:
|
|
565
|
+
• Si necesitas seguir accediendo al objeto completo (jsonb_column),
|
|
566
|
+
inclúyelo también explícitamente en el SELECT del CTE.
|
|
567
|
+
• O extrae directamente el campo requerido:
|
|
568
|
+
´´´sql TO_DATE(jsonb_column->>'created','DD-MM-YYYY') AS fecha_creacion ´´sql
|
|
569
|
+
y usa ese alias (fecha_creacion) más adelante.
|
|
570
|
+
|
|
571
|
+
### ✅ Evita `jsonb_object_keys` en `WHERE`
|
|
572
|
+
|
|
573
|
+
- **No uses `jsonb_object_keys(...)` dentro de `WHERE` o funciones escalares como `jsonb_array_length(...)`**.
|
|
574
|
+
PostgreSQL no permite aplicar funciones escalares sobre funciones set-returning dentro de filtros.
|
|
575
|
+
|
|
576
|
+
- Si quieres validar si un objeto JSONB tiene claves (no está vacío), haz:
|
|
577
|
+
```sql
|
|
578
|
+
jsonb_column IS NOT NULL
|
|
579
|
+
AND jsonb_typeof(jsonb_column) = 'object'
|
|
580
|
+
AND jsonb_column::text != '{}'
|
|
581
|
+
|
|
582
|
+
Correcto:
|
|
583
|
+
´´´sql
|
|
584
|
+
WHERE jsonb_column IS NOT NULL
|
|
585
|
+
AND jsonb_typeof(jsonb_column) = 'object'
|
|
586
|
+
AND jsonb_column::text != '{}'
|
|
587
|
+
|
|
588
|
+
❌ Incorrecto:
|
|
589
|
+
´´´sql
|
|
590
|
+
WHERE jsonb_array_length(jsonb_object_keys(jsonb_column)) > 0
|
|
591
|
+
|
|
592
|
+
## validación de array de jsonb no vacio
|
|
593
|
+
Para verificar si un array JSONB no está vacío, usa siempre:
|
|
594
|
+
```sql
|
|
595
|
+
jsonb_typeof(jsonb_column) = 'array'
|
|
596
|
+
AND jsonb_array_length(jsonb_column) > 0
|
|
597
|
+
|
|
598
|
+
### ✅ Agregaciones condicionales con JSONB
|
|
599
|
+
|
|
600
|
+
- Nunca uses `SUM(<expresión booleana>)` directamente. PostgreSQL no admite sumar valores booleanos sin convertirlos de forma explícita.
|
|
601
|
+
- En lugar de eso, usa `CASE WHEN ... THEN 1 ELSE 0 END` para transformar condiciones booleanas en enteros antes de agregarlas.
|
|
602
|
+
|
|
603
|
+
✅ Correcto:
|
|
604
|
+
|
|
605
|
+
```sql
|
|
606
|
+
SUM(CASE WHEN jsonb_column->'subkey'::text != '{}' THEN 1 ELSE 0 END)
|
|
607
|
+
|
|
608
|
+
❌ Incorrecto:
|
|
609
|
+
´´´sql
|
|
610
|
+
SUM((jsonb_column->'subkey')::text != '{}'::text)
|
|
611
|
+
|
|
612
|
+
### ✅ consultas anidadas
|
|
613
|
+
Cuando utilices funciones anidadas como `SUM`, `COALESCE`, `ROUND` y `NULLIF`,
|
|
614
|
+
asegúrate de que los paréntesis estén correctamente balanceados.
|
|
615
|
+
La estructura recomendada para calcular porcentajes sobre agregados es:
|
|
616
|
+
|
|
617
|
+
COALESCE(
|
|
618
|
+
ROUND(
|
|
619
|
+
100.0 * SUM(...) / NULLIF(SUM(...), 0),
|
|
620
|
+
0
|
|
621
|
+
),
|
|
622
|
+
0
|
|
623
|
+
)
|
|
624
|
+
|
|
625
|
+
Cuando utilices COALESCE en una expresión compleja,
|
|
626
|
+
no anides múltiples COALESCE en la misma expresión.
|
|
627
|
+
Solo usa un COALESCE externo para manejar valores nulos.
|
|
628
|
+
Por ejemplo: COALESCE(ROUND(...), 0) es correcto.
|
|
629
|
+
Evita COALESCE(..., 0), 0 o anidaciones similares.
|
|
630
|
+
|
|
631
|
+
- Evita envolver `ROUND` dentro de otro `COALESCE` si ya estás manejando los nulos con `NULLIF`.
|
|
632
|
+
- No uses `SUM((...)::BOOLEAN)` porque `SUM` espera un valor numérico. Para contar condiciones,
|
|
633
|
+
usa `SUM(CASE WHEN ... THEN 1 ELSE 0 END)`.
|
|
634
|
+
- Si estás usando subconsultas `SELECT ... FROM jsonb_array_elements(...)`,
|
|
635
|
+
asegúrate de que estén dentro de la agregación correctamente y no mezcles `SUM(SELECT SUM(...))` — eso debe evitarse.
|
|
636
|
+
Usa `SUM(...)` directamente sobre los elementos o con una subconsulta que retorne un valor escalar.
|
|
637
|
+
|
|
638
|
+
- Cuando uses jsonb_array_elements sobre un campo JSONB (por ejemplo, jsonb_column->'contract'),
|
|
639
|
+
el resultado es una sola columna llamada value (o el alias que definas, por ejemplo, contract).
|
|
640
|
+
Para acceder a subcampos dentro del JSON, debes usar la notación alias->>'campo' y no alias.campo.
|
|
641
|
+
Nunca intentes seleccionar directamente alias.campo si no existe como columna nativa.
|
|
642
|
+
Ejemplo correcto:
|
|
643
|
+
```sql
|
|
644
|
+
LEFT JOIN LATERAL jsonb_array_elements(c.jsonb_column->'contract') AS o(contract) ON TRUE
|
|
645
|
+
SELECT o.contract->>'contract_date' AS contract_date
|
|
646
|
+
|
|
647
|
+
Ejemplo incorrecto (genera error):
|
|
648
|
+
```sql
|
|
649
|
+
SELECT o.contract_date -- Esto fallará porque 'contract_date' no es una columna, sino un subcampo del JSON.
|
|
650
|
+
|
|
651
|
+
|
|
652
|
+
## funciones de agregación en subconsultas
|
|
653
|
+
- Nunca incluyas funciones de agregación como COUNT(*), SUM(...), etc.,
|
|
654
|
+
directamente dentro de subconsultas del tipo LEFT JOIN LATERAL (...) ...
|
|
655
|
+
si el agregado depende del contexto de la fila padre.
|
|
656
|
+
|
|
657
|
+
- Si necesitas sumar o contar datos relacionados, primero usa el LATERAL para expandir los elementos,
|
|
658
|
+
y solo después, en la consulta exterior, agrupa y aplica la agregación.
|
|
659
|
+
|
|
660
|
+
Ejemplo correcto:
|
|
661
|
+
```sql
|
|
662
|
+
LEFT JOIN LATERAL (
|
|
663
|
+
SELECT
|
|
664
|
+
(contr.value->>'contract_amount')::NUMERIC AS monto_contrato
|
|
665
|
+
FROM jsonb_array_elements(c.jsonb_column->'contract') AS contr(value)
|
|
666
|
+
WHERE contr.value->>'status' = 'vigente'
|
|
667
|
+
) v ON TRUE
|
|
668
|
+
|
|
669
|
+
Luego, en el SELECT principal puedes usar SUM(v.monto) o COUNT(*).
|
|
670
|
+
|
|
671
|
+
- No uses CASE WHEN ... THEN ... ELSE ... END como
|
|
672
|
+
subconsulta dentro de un LEFT JOIN LATERAL (...).
|
|
673
|
+
- Las subconsultas de JOIN LATERAL deben ser expresiones que empiecen por SELECT ...,
|
|
674
|
+
o una función set-returning válida como jsonb_array_elements(...).
|
|
675
|
+
- Si necesitas filtrar o condicionar un lateral,
|
|
676
|
+
usa WHERE dentro de la subconsulta o pon la condición en el ON.
|
|
677
|
+
- Para expandir arrays JSONB, usa directamente:
|
|
678
|
+
```sql
|
|
679
|
+
LEFT JOIN LATERAL jsonb_array_elements(b.jsonb_field) AS alias ON ...
|
|
680
|
+
|
|
681
|
+
- Si quieres filtrar que solo se ejecute cuando sea un array y no esté vacío:
|
|
682
|
+
```sql
|
|
683
|
+
LEFT JOIN LATERAL jsonb_array_elements(b.jsonb_field) AS alias
|
|
684
|
+
ON b.jsonb_field IS NOT NULL AND jsonb_typeof(b.jsonb_field) = 'array' AND jsonb_array_length(b.jsonb_field) > 0
|
|
685
|
+
|
|
686
|
+
- Cuando necesites devolver un array de objetos JSON
|
|
687
|
+
(por ejemplo, al mostrar un perfil, una lista de clientes o resultados agrupados)
|
|
688
|
+
utiliza únicamente json_agg() sobre una subconsulta, SIN envolverlo en row_to_json().
|
|
689
|
+
Ejemplo correcto:
|
|
690
|
+
(SELECT json_agg(t) FROM (SELECT * FROM cartera_grupo ORDER BY ejecutiva) t) AS perfil_cartera
|
|
691
|
+
Ejemplo incorrecto (NO USAR):
|
|
692
|
+
row_to_json((SELECT json_agg(t) FROM (SELECT * FROM cartera_grupo ORDER BY ejecutiva) t)) AS perfil_cartera
|
|
693
|
+
|
|
694
|
+
- Si la consulta debe retornar un arreglo JSON, **usa solamente `json_agg`** en la subconsulta.
|
|
695
|
+
- **No utilices `row_to_json` sobre un resultado que ya viene en formato JSON**.
|
|
696
|
+
- Si quieres un objeto JSON de UNA SOLA fila, usa `row_to_json`. Si es un ARREGLO de filas, usa solo `json_agg`.
|
|
697
|
+
|
|
698
|
+
- No incluyas ORDER BY directo dentro de json_agg a menos que lo hagas en un subselect,
|
|
699
|
+
porque causará errores de grouping.
|
|
700
|
+
Ejemplo correcto:
|
|
701
|
+
SELECT json_agg(row_to_json(x)) FROM (SELECT * FROM tabla ORDER BY campo) x;
|
|
702
|
+
|
|
703
|
+
## ✅ Buenas prácticas generales
|
|
704
|
+
|
|
705
|
+
- Usa nombres estándar de campos según el schema.
|
|
706
|
+
- Respeta los alias comunes:
|
|
707
|
+
- `folio` ≡ `certificate_id`
|
|
708
|
+
- `client_rut` en certificados corresponde a `rut` en clientes
|
|
709
|
+
|
|
710
|
+
- Usa `snake_case` y aliases consistentes en las consultas.
|
|
711
|
+
- cuando filtres por ejecutiva o grupos de garantias o credito,
|
|
712
|
+
siempre usa su ´**username**´ nunca su nombre completo.
|
|
713
|
+
- siempre que compares strings utiliza ilike para que sea case insensitive
|
|
714
|
+
|
|
715
|
+
- - Cuando combines condiciones con AND y OR, **usa paréntesis** para agrupar correctamente y evitar resultados inesperados.
|
|
716
|
+
- Ejemplo correcto:
|
|
717
|
+
```sql
|
|
718
|
+
WHERE ...
|
|
719
|
+
AND (
|
|
720
|
+
status ILIKE 'vigente'
|
|
721
|
+
OR status ILIKE 'terminada'
|
|
722
|
+
)
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
- 📝 Regla para búsqueda de RUT en SQL (formato limpio, sin puntos)
|
|
726
|
+
- siempre elimina los puntos del valor buscado
|
|
727
|
+
- Haz la comparación directamente con el campo, ya que ya está en formato limpio.
|
|
728
|
+
- El campo rut en la base está guardado con guion y sin puntos.
|
|
729
|
+
ejemplo:
|
|
730
|
+
´´´sql
|
|
731
|
+
-- Incorrecto:
|
|
732
|
+
WHERE rut = '76.768.219-0'
|
|
733
|
+
|
|
734
|
+
-- Correcto:
|
|
735
|
+
WHERE rut = REPLACE('76.768.219-0', '.', '')
|
|
736
|
+
|
|
737
|
+
• No elimines el guion al comparar, ya que en la base el
|
|
738
|
+
RUT debe tener formato: 76768219-0.
|
|
739
|
+
|
|
740
|
+
- Nunca utilices columnas o campos JSONB de una tabla si esa columna no existe en la tabla.
|
|
741
|
+
- Nunca hagas dos SELECTs seguidos usando los mismos CTEs.
|
|
742
|
+
Si necesitas múltiples datasets, combínalos en un solo SELECT
|
|
743
|
+
(usando subqueries, agregados o json_agg), o ejecuta varias queries separadas.
|
|
744
|
+
- Si una subconsulta puede retornar varias filas,
|
|
745
|
+
encapsúlala con json_agg(row_to_json(...)) en vez de solo row_to_json(...).
|
|
746
|
+
- Nunca uses CASE unnest(array[...]) directamente, ni dentro de la función.
|
|
747
|
+
Siempre primero aplica unnest(...) en el FROM y luego el CASE sobre el campo resultante.
|
|
748
|
+
|
|
749
|
+
- cuando el modelo necesite realizar una función agregada condicional
|
|
750
|
+
(por ejemplo, contar o sumar solo bajo ciertas condiciones),
|
|
751
|
+
debe usar la sintaxis con CASE WHEN ... THEN ... ELSE ... END dentro de la
|
|
752
|
+
función agregada.
|
|
753
|
+
|
|
754
|
+
- No debe utilizar la sintaxis AGGREGATE(...) FILTER (WHERE ...).
|
|
755
|
+
ejemplo correcto:
|
|
756
|
+
```sql
|
|
757
|
+
SUM(CASE WHEN <condición> THEN <valor> ELSE 0 END)
|
|
758
|
+
COUNT(CASE WHEN <condición> THEN 1 ELSE NULL END)
|
|
759
|
+
|
|
760
|
+
Ejemplo incorrecto:
|
|
761
|
+
```sql
|
|
762
|
+
SUM(<valor>) FILTER (WHERE <condición>)
|
|
763
|
+
COUNT(*) FILTER (WHERE <condición>)
|
|
764
|
+
|
|
765
|
+
- Evita utilizar '.value' para acceder a columnas cuando hagas JOIN lateral a tablas reales (por ejemplo, bcu_certificate).
|
|
766
|
+
Usa '.value' únicamente cuando la referencia provenga directamente de funciones JSON como jsonb_each() o jsonb_array_elements().
|
|
767
|
+
Si el JOIN lateral apunta directamente a tablas SQL estándar, accede a las columnas directamente por su nombre original sin '.value'.
|
|
768
|
+
|
|
769
|
+
- Cuando hagas JOIN o subconsultas a la misma tabla original (`bcu_customer`),
|
|
770
|
+
**evita usar los mismos nombres de columnas sin alias**,
|
|
771
|
+
y califica siempre con el alias correspondiente (`c.`, `cc.`, etc.)."
|
|
772
|
+
|
|
773
|
+
- Al combinar resultados con UNION, INTERSECT o EXCEPT,
|
|
774
|
+
nunca uses funciones o expresiones en la cláusula ORDER BY como TO_DATE(...) o concatenaciones.
|
|
775
|
+
Solo puedes ordenar por columnas explícitamente seleccionadas en el SELECT externo.
|
|
776
|
+
|
|
777
|
+
- Si deseas agregar un campo calculado al resultado de una subconsulta o CTE,
|
|
778
|
+
no utilices `SELECT *` y luego calcules sobre los campos resultantes.
|
|
779
|
+
En su lugar, expón todos los campos explícitamente y realiza el cálculo directamente
|
|
780
|
+
dentro del `SELECT`, evitando aplicar funciones como `ROUND()` sobre registros
|
|
781
|
+
completos.
|
|
782
|
+
|
|
783
|
+
- Cuando expandas un array JSONB usando `jsonb_array_elements(...)`,
|
|
784
|
+
todos los campos internos (como `tender_id`) deben extraerse desde `value->>'campo'`,
|
|
785
|
+
no como columnas normales.
|
|
786
|
+
Ejemplo:
|
|
787
|
+
```sql
|
|
788
|
+
SELECT value->>'tender_id' AS tender_id FROM jsonb_array_elements(...) AS value
|
|
789
|
+
|
|
790
|
+
- Al crear un alias para una columna o expresión en SQL,
|
|
791
|
+
utiliza siempre la sintaxis `AS alias` y nunca `alias = expresión`.
|
|
792
|
+
Esto aplica especialmente a expresiones `CASE`, agregaciones y conversiones de tipo.
|
|
793
|
+
Ejemplo:
|
|
794
|
+
✅ CASE WHEN condicion THEN valor ELSE otro_valor END AS nombre_columna
|
|
795
|
+
❌ nombre_columna = CASE WHEN condicion THEN valor ELSE otro_valor END
|
|
796
|
+
|
|
797
|
+
- No utilices comentarios con // en SQL, ya que no son válidos en PostgreSQL.
|
|
798
|
+
|
|
799
|
+
— Reglas para expandir JSONB sin errores en agregaciones
|
|
800
|
+
|
|
801
|
+
# SRF-LATERAL-001 (PROHIBIDO):
|
|
802
|
+
# No llames funciones que retornan sets (SRF) como jsonb_array_elements/jsonb_each/jsonb_each_text
|
|
803
|
+
# dentro de agregados (json_agg/jsonb_agg/array_agg). Causa: "aggregate function calls cannot contain set-returning function calls".
|
|
804
|
+
# ❌ Malo:
|
|
805
|
+
# json_agg(jsonb_array_elements(t.obj->'items'))
|
|
806
|
+
|
|
807
|
+
# SRF-LATERAL-002 (OBLIGATORIO):
|
|
808
|
+
# Para expandir arrays/objetos JSONB usa siempre un JOIN LATERAL y luego agrega.
|
|
809
|
+
# ✅ Bueno (patrón base):
|
|
810
|
+
# SELECT
|
|
811
|
+
# k,
|
|
812
|
+
# COALESCE(jsonb_agg(e.elem), '[]'::jsonb) AS items
|
|
813
|
+
# FROM base b
|
|
814
|
+
# LEFT JOIN LATERAL jsonb_array_elements(b.obj->'items') AS e(elem)
|
|
815
|
+
# ON b.obj IS NOT NULL
|
|
816
|
+
# AND b.obj ? 'items'
|
|
817
|
+
# AND jsonb_typeof(b.obj->'items') = 'array'
|
|
818
|
+
# GROUP BY k;
|
|
819
|
+
|
|
820
|
+
# SRF-LATERAL-003 (GUARDAS DE TIPO):
|
|
821
|
+
# Antes de expandir, valida existencia y tipo para evitar errores en tiempo de ejecución.
|
|
822
|
+
# b.obj IS NOT NULL
|
|
823
|
+
# AND b.obj ? 'ruta'
|
|
824
|
+
# AND jsonb_typeof(b.obj->'ruta') = 'array' -- o 'object' según corresponda
|
|
825
|
+
|
|
826
|
+
# SRF-LATERAL-004 (AGREGADO RECOMENDADO):
|
|
827
|
+
# Prefiere jsonb_agg para producir JSONB y normaliza a '[]' cuando no hay elementos.
|
|
828
|
+
# COALESCE(jsonb_agg(e.elem), '[]'::jsonb)
|
|
829
|
+
|
|
830
|
+
# SRF-LATERAL-005 (ORDEN DENTRO DEL AGREGADO):
|
|
831
|
+
# Si necesitas determinismo, ordena dentro del agregado.
|
|
832
|
+
# jsonb_agg(e.elem ORDER BY e.elem->>'campo')
|
|
833
|
+
|
|
834
|
+
# SRF-LATERAL-006 (TEMPLATE REUTILIZABLE):
|
|
835
|
+
# Usa este molde cuando necesites coleccionar elementos desde un JSONB.
|
|
836
|
+
# -- INPUT: tabla base b(pk, obj jsonb)
|
|
837
|
+
# SELECT
|
|
838
|
+
# b.pk,
|
|
839
|
+
# COALESCE(jsonb_agg(x.elem), '[]'::jsonb) AS elementos
|
|
840
|
+
# FROM base b
|
|
841
|
+
# LEFT JOIN LATERAL jsonb_array_elements(b.obj->'ruta') AS x(elem)
|
|
842
|
+
# ON b.obj IS NOT NULL
|
|
843
|
+
# AND b.obj ? 'ruta'
|
|
844
|
+
# AND jsonb_typeof(b.obj->'ruta') = 'array'
|
|
845
|
+
# GROUP BY b.pk;
|
|
846
|
+
|
|
847
|
+
# SRF-LATERAL-007 (CASO CONCRETO: CONTRATOS):
|
|
848
|
+
# Cuando necesites agrupar contratos contenidos en jsonb_operation->'contract':
|
|
849
|
+
# WITH datos_base AS (
|
|
850
|
+
# SELECT ... , c.jsonb_operation
|
|
851
|
+
# FROM bcu_customer c
|
|
852
|
+
# WHERE c.rut = :rut
|
|
853
|
+
# ),
|
|
854
|
+
# contratos AS (
|
|
855
|
+
# SELECT
|
|
856
|
+
# b.rut,
|
|
857
|
+
# COALESCE(jsonb_agg(o.value), '[]'::jsonb) AS contratos
|
|
858
|
+
# FROM datos_base b
|
|
859
|
+
# LEFT JOIN LATERAL jsonb_array_elements(b.jsonb_operation->'contract') AS o(value)
|
|
860
|
+
# ON b.jsonb_operation IS NOT NULL
|
|
861
|
+
# AND b.jsonb_operation ? 'contract'
|
|
862
|
+
# AND jsonb_typeof(b.jsonb_operation->'contract') = 'array'
|
|
863
|
+
# GROUP BY b.rut
|
|
864
|
+
# )
|
|
865
|
+
# SELECT ...
|
|
866
|
+
# FROM datos_base b
|
|
867
|
+
# LEFT JOIN contratos c ON c.rut = b.rut;
|
|
868
|
+
|
|
869
|
+
# SRF-LATERAL-008 (TEXTOS DERIVADOS):
|
|
870
|
+
# Si necesitas concatenar campos (p. ej., lista de emails en texto):
|
|
871
|
+
# SELECT
|
|
872
|
+
# b.rut,
|
|
873
|
+
# array_to_string(ARRAY_AGG(o.value->>'email' ORDER BY o.value->>'email'), ', ') AS emails
|
|
874
|
+
# FROM datos_base b
|
|
875
|
+
# LEFT JOIN LATERAL jsonb_array_elements(b.jsonb_operation->'contract') AS o(value)
|
|
876
|
+
# ON b.jsonb_operation IS NOT NULL
|
|
877
|
+
# AND b.jsonb_operation ? 'contract'
|
|
878
|
+
# AND jsonb_typeof(b.jsonb_operation->'contract') = 'array'
|
|
879
|
+
# GROUP BY b.rut;
|
|
880
|
+
|
|
881
|
+
# SRF-LATERAL-009 (EVITA WHERE QUE EXCLUYA TODO ANTES DEL LATERAL):
|
|
882
|
+
# Coloca las guardas de tipo en la condición del JOIN LATERAL (ON), no en WHERE, para no eliminar filas base.
|
|
883
|
+
|
|
884
|
+
# SRF-LATERAL-010 (NOMBRADO CONSISTENTE):
|
|
885
|
+
# Nombra la columna expandida como (alias).value o (alias).elem para claridad y reutilización en agregados.
|
|
886
|
+
|
|
887
|
+
### SRF-ALIAS & CLAVES — Reglas para no inventar columnas al expandir JSONB
|
|
888
|
+
|
|
889
|
+
# SRF-ALIAS-001 (COLUMNAS DISPONIBLES POR SRF):
|
|
890
|
+
# - jsonb_array_elements(x) → alias(elem|value) # 1 sola columna
|
|
891
|
+
# - jsonb_each(x) / jsonb_each_text(x) → alias(key, value) # 2 columnas
|
|
892
|
+
# - jsonb_object_keys(x) → alias(key) # 1 columna
|
|
893
|
+
# Al usar estas SRF, el alias NO trae columnas de la tabla base (ej.: ct.rut NO existe).
|
|
894
|
+
# Las claves/PK (ej.: rut) deben seleccionarse desde la tabla padre (ej.: b.rut).
|
|
895
|
+
|
|
896
|
+
# SRF-ALIAS-002 (CLAVE PADRE SIEMPRE DESDE LA TABLA BASE):
|
|
897
|
+
# En joins LATERAL encadenados, proyecta siempre la clave de la fila base:
|
|
898
|
+
# SELECT b.rut, ct.value->>'email' ...
|
|
899
|
+
# FROM base b
|
|
900
|
+
# LEFT JOIN LATERAL jsonb_array_elements(b.obj->'arr') AS c(value) ON ...
|
|
901
|
+
# -- NUNCA: SELECT c.rut ...
|
|
902
|
+
|
|
903
|
+
# SRF-ALIAS-003 (GUARDAS DE TIPO EN EL ON DEL LATERAL):
|
|
904
|
+
# Coloca las validaciones de existencia/tipo en el ON del JOIN LATERAL:
|
|
905
|
+
# LEFT JOIN LATERAL jsonb_array_elements(b.obj->'arr') AS c(value)
|
|
906
|
+
# ON b.obj ? 'arr' AND jsonb_typeof(b.obj->'arr') = 'array'
|
|
907
|
+
|
|
908
|
+
# SRF-ALIAS-004 (DEDUPLICACIÓN):
|
|
909
|
+
# Si solo necesitas eliminar duplicados sin agregar, usa DISTINCT en vez de GROUP BY sin agregados.
|
|
910
|
+
|
|
911
|
+
# SRF-ALIAS-005 (PLANTILLA SEGURA):
|
|
912
|
+
# SELECT DISTINCT
|
|
913
|
+
# b.rut,
|
|
914
|
+
# ct.value->>'name' AS name,
|
|
915
|
+
# ct.value->>'email' AS email
|
|
916
|
+
# FROM base b
|
|
917
|
+
# LEFT JOIN LATERAL jsonb_array_elements(b.jsonb_operation->'contract') AS contrato(value)
|
|
918
|
+
# ON b.jsonb_operation ? 'contract'
|
|
919
|
+
# AND jsonb_typeof(b.jsonb_operation->'contract') = 'array'
|
|
920
|
+
# LEFT JOIN LATERAL jsonb_array_elements(contrato.value->'signatories') AS ct(value)
|
|
921
|
+
# ON contrato.value ? 'signatories'
|
|
922
|
+
# AND jsonb_typeof(contrato.value->'signatories') = 'array'
|
|
923
|
+
# WHERE ct.value->>'email' IS NOT NULL;
|
|
924
|
+
|
|
925
|
+
# SRF-ALIAS-006 (AGREGAR COMO JSON):
|
|
926
|
+
# Para devolver arreglos JSON por rut:
|
|
927
|
+
# SELECT
|
|
928
|
+
# b.rut,
|
|
929
|
+
# COALESCE(jsonb_agg(ct.value), '[]'::jsonb) AS signatories
|
|
930
|
+
# FROM base b
|
|
931
|
+
# LEFT JOIN LATERAL jsonb_array_elements(b.jsonb_operation->'contract') AS contrato(value)
|
|
932
|
+
# ON b.jsonb_operation ? 'contract'
|
|
933
|
+
# AND jsonb_typeof(b.jsonb_operation->'contract') = 'array'
|
|
934
|
+
# LEFT JOIN LATERAL jsonb_array_elements(contrato.value->'signatories') AS ct(value)
|
|
935
|
+
# ON contrato.value ? 'signatories'
|
|
936
|
+
# AND jsonb_typeof(contrato.value->'signatories') = 'array'
|
|
937
|
+
# GROUP BY b.rut;
|
|
938
|
+
|
|
939
|
+
### JSONB-AGG vs row_to_json — Reglas para agregar JSON/JSONB
|
|
940
|
+
|
|
941
|
+
# JSONB-001 (NO usar row_to_json sobre json/jsonb):
|
|
942
|
+
# Si el dato ya viene de jsonb_array_elements/jsonb_each como JSONB, NO lo envuelvas en row_to_json().
|
|
943
|
+
# Usa jsonb_agg(...) para JSONB o json_agg((...)::json) si necesitas JSON.
|
|
944
|
+
# ❌ Malo:
|
|
945
|
+
# json_agg(row_to_json(con.value)) -- con.value es JSONB
|
|
946
|
+
# ✅ Bueno:
|
|
947
|
+
# jsonb_agg(con.value) -- mantiene JSONB
|
|
948
|
+
# json_agg((con.value)::json) -- si necesitas JSON (no JSONB)
|
|
949
|
+
|
|
950
|
+
# JSONB-002 (COALESCE tipado):
|
|
951
|
+
# Asegura que COALESCE tenga el mismo tipo:
|
|
952
|
+
# COALESCE(jsonb_agg(...), '[]'::jsonb)
|
|
953
|
+
# COALESCE(json_agg(...), '[]'::json)
|
|
954
|
+
|
|
955
|
+
# JSONB-003 (DISTINCT en agregados JSONB):
|
|
956
|
+
# Puedes usar DISTINCT dentro de jsonb_agg/json_agg:
|
|
957
|
+
# jsonb_agg(DISTINCT ct.value)
|
|
958
|
+
|
|
959
|
+
# JSONB-004 (Guardas de tipo antes de expandir):
|
|
960
|
+
# Antes de expandir, valida existencia y tipo en el ON del JOIN LATERAL:
|
|
961
|
+
# b.obj ? 'ruta' AND jsonb_typeof(b.obj->'ruta') = 'array'
|
|
962
|
+
# Evita WHERE que elimine la fila base accidentalmente.
|
|
963
|
+
|
|
964
|
+
# JSONB-005 (Convertir al final si hace falta):
|
|
965
|
+
# Si la API de salida exige JSON (no JSONB), castea sólo al final:
|
|
966
|
+
# (jsonb_agg(...))::json
|
|
967
|
+
|
|
968
|
+
# Al convertir arrays JSONB a texto separado por comas, NO uses aliases no definidos.
|
|
969
|
+
Usa `array_to_string(ARRAY(SELECT jsonb_array_elements_text(col_jsonb)), ', ')`
|
|
970
|
+
o bien alias explícito: `FROM jsonb_array_elements_text(col_jsonb) AS elem` y `SELECT elem`.
|
|
971
|
+
examples:
|
|
972
|
+
- bad: "array_to_string(ARRAY(SELECT c FROM jsonb_array_elements_text(x)), ', ')"
|
|
973
|
+
- good: "array_to_string(ARRAY(SELECT jsonb_array_elements_text(x)), ', ')"
|
|
974
|
+
- good: "array_to_string(ARRAY(SELECT elem FROM jsonb_array_elements_text(x) AS elem), ', ')"
|
|
975
|
+
also:
|
|
976
|
+
- "Si el array puede ser nulo o vacío, usa COALESCE(..., '') para retornar cadena vacía."
|
|
977
|
+
|
|
978
|
+
# En una subconsulta ESCALAR (usada como expresión en el SELECT),
|
|
979
|
+
NO usar `SELECT *` si la fuente tiene múltiples columnas.
|
|
980
|
+
Debe devolver **exactamente una columna**. Para filas compuestas,
|
|
981
|
+
envolver en `row_to_json(alias)` o seleccionar columnas individuales.
|
|
982
|
+
patterns:
|
|
983
|
+
- bad: "(SELECT * FROM some_cte WHERE ... ) AS col"
|
|
984
|
+
- good: "(SELECT row_to_json(s) FROM some_cte s WHERE ... ) AS col"
|
|
985
|
+
- good: "(SELECT s.una_col FROM some_cte s WHERE ... ) AS col"
|
|
986
|
+
|
|
987
|
+
|
|
988
|
+
# Nunca agrupes por columnas tipo JSON. Si necesitas agregar o devolver arrays/objetos,
|
|
989
|
+
usa tipos JSONB y funciones jsonb_*.
|
|
990
|
+
rules:
|
|
991
|
+
- "Usar jsonb_agg/to_jsonb/jsonb_build_object en lugar de json_agg/to_json."
|
|
992
|
+
- "Defaults con '[]'::jsonb o '{}'::jsonb, NO ::json."
|
|
993
|
+
- "Si una columna será parte del SELECT con GROUP BY, asegúrate de que sea jsonb (o cámbiala en el CTE)."
|
|
994
|
+
- "Para filas compuestas en una escalar, usar row_to_json(...) o to_jsonb(...)."
|
|
995
|
+
examples:
|
|
996
|
+
- bad: "COALESCE(json_agg(x), '[]'::json) AS datos; ... GROUP BY datos"
|
|
997
|
+
- good: "COALESCE(jsonb_agg(x), '[]'::jsonb) AS datos; ... GROUP BY datos"
|
|
998
|
+
- good: "(SELECT row_to_json(r) FROM resumen r WHERE r.id = t.id) AS resumen"
|
|
999
|
+
|
|
1000
|
+
# Al seleccionar desde una CTE que ya AGRUPÓ datos, no mezclar funciones
|
|
1001
|
+
agregadas nuevas (COUNT/MIN/...) con columnas no agregadas de esa misma CTE.
|
|
1002
|
+
Usa directamente las columnas preagregadas o agrega TODO con GROUP BY.
|
|
1003
|
+
bad_example: |
|
|
1004
|
+
SELECT COUNT(*), min(col), otros_campos FROM cte_agrupada;
|
|
1005
|
+
good_example: |
|
|
1006
|
+
SELECT otros_campos FROM cte_agrupada;
|
|
1007
|
+
-- o, si necesitas re-agrupar:
|
|
1008
|
+
SELECT MAX(otros_campos) FROM cte_agrupada GROUP BY clave;
|
|
1009
|
+
|
|
1010
|
+
# Toda columna referenciada en SELECT/WHERE debe estar presente en el scope
|
|
1011
|
+
(CTE o tabla) con el alias usado. Si usas alias `b`, asegúrate que la CTE
|
|
1012
|
+
`base` seleccione esos campos.
|
|
1013
|
+
patterns:
|
|
1014
|
+
- bad: "WITH base AS (SELECT rut FROM t) ... SELECT b.rut, b.flag FROM base b"
|
|
1015
|
+
- good: "WITH base AS (SELECT rut, flag FROM t) ... SELECT b.rut, b.flag FROM base b"
|
|
1016
|
+
also:
|
|
1017
|
+
- "Si un flag no existe, derivarlo desde JSONB (no asumir columnas)."
|
|
1018
|
+
|
|
1019
|
+
En PostgreSQL no usar comentarios con '#'. Usa '--' para línea
|
|
1020
|
+
o '/* ... */' para bloque.
|
|
1021
|
+
2) Al usar jsonb_array_elements sobre posibles NULL, siempre proteger
|
|
1022
|
+
con LEFT JOIN LATERAL + guardas en ON.
|
|
1023
|
+
examples:
|
|
1024
|
+
- bad: "# comentario"
|
|
1025
|
+
- good: "-- comentario"
|
|
1026
|
+
- bad: "LEFT JOIN LATERAL jsonb_array_elements(c.x->'arr') AS e(value) ON TRUE"
|
|
1027
|
+
- good: "LEFT JOIN LATERAL jsonb_array_elements(c.x->'arr') AS e(value)
|
|
1028
|
+
ON c.x IS NOT NULL AND c.x ? 'arr' AND jsonb_typeof(c.x->'arr')='array'"
|
|
1029
|
+
|
|
1030
|
+
- id: scalar_subquery_json_row
|
|
1031
|
+
description: >
|
|
1032
|
+
Las subconsultas escalares deben devolver UNA sola columna. Si necesitas
|
|
1033
|
+
varias columnas, envuelve la fila en JSON con row_to_json(alias).
|
|
1034
|
+
patterns:
|
|
1035
|
+
- bad: "(SELECT * FROM cte) AS x"
|
|
1036
|
+
- good: "(SELECT row_to_json(c) FROM cte c) AS x"
|
|
1037
|
+
- good: "(SELECT c.una_col FROM cte c) AS x"
|
|
1038
|
+
also:
|
|
1039
|
+
- "Al usar jsonb_array_elements sobre posibles NULL, envuelve con COALESCE(...,'[]'::jsonb)."
|
|
1040
|
+
|
|
1041
|
+
- id: no_truncated_sql
|
|
1042
|
+
description: "Nunca dejes '...' u otros placeholders en SQL: el parser fallará."
|
|
1043
|
+
examples:
|
|
1044
|
+
- bad: "ROUND(100.0*total_con_maxxa/lici..."
|
|
1045
|
+
- good: "ROUND(100.0*total_con_maxxa/NULLIF(licitaciones_ganadas,0),1)"
|
|
1046
|
+
|
|
1047
|
+
- id: avoid_srf_in_select_list
|
|
1048
|
+
description: >
|
|
1049
|
+
No uses funciones que devuelven sets (json[b]_array_elements*, unnest, etc.)
|
|
1050
|
+
en la lista SELECT para contarlas. Usa jsonb_array_length(col) o un
|
|
1051
|
+
LEFT JOIN LATERAL + COUNT(*).
|
|
1052
|
+
examples:
|
|
1053
|
+
- bad: "COUNT(jsonb_array_elements_text(ct.certificate_ids))"
|
|
1054
|
+
- good: "SUM(COALESCE(jsonb_array_length(ct.certificate_ids),0))"
|
|
1055
|
+
- good: |
|
|
1056
|
+
LEFT JOIN LATERAL jsonb_array_elements(ct.certificate_ids) AS e(x) ON TRUE
|
|
1057
|
+
COUNT(e.x)
|
|
1058
|
+
|
|
1059
|
+
- id: percent_with_nullif
|
|
1060
|
+
description: "Al calcular porcentajes, usa NULLIF(den,0) para evitar división por cero."
|
|
1061
|
+
examples:
|
|
1062
|
+
- good: "ROUND(100.0 * ganadas / NULLIF(total,0), 1)"
|
|
1063
|
+
|
|
1064
|
+
- id: date_casts_vs_to_date
|
|
1065
|
+
description: >
|
|
1066
|
+
No uses TO_DATE sobre columnas DATE/TIMESTAMP. TO_DATE solo acepta texto.
|
|
1067
|
+
Para DATE usa MIN(col); para TIMESTAMP usa ::date o date_trunc.
|
|
1068
|
+
Si la columna es texto, entonces sí usar TO_DATE(text, 'YYYY-MM-DD').
|
|
1069
|
+
examples:
|
|
1070
|
+
- bad: "TO_DATE(ts_col, 'YYYY-MM-DD')" # ts_col es TIMESTAMP
|
|
1071
|
+
- good: "ts_col::date"
|
|
1072
|
+
- bad: "TO_DATE(date_col, 'YYYY-MM-DD')" # date_col es DATE
|
|
1073
|
+
- good: "date_col"
|
|
1074
|
+
- good: "TO_DATE(text_col, 'YYYY-MM-DD')" # text_col es TEXT
|
|
1075
|
+
|
|
1076
|
+
- id: aggregate_from_correct_table
|
|
1077
|
+
description: >
|
|
1078
|
+
No agregues columnas (status, montos) desde una CTE/tabla que no las tiene.
|
|
1079
|
+
Si los datos están en otra tabla (p.ej. bcu_certificate), usa subselect
|
|
1080
|
+
correlacionado o un JOIN/CTE agregado por rut.
|
|
1081
|
+
bad: "FROM bcu_customer ... SUM(CASE WHEN status ...)"
|
|
1082
|
+
good: "FROM bcu_customer c ... (SELECT SUM(...) FROM bcu_certificate bc WHERE bc.rut=c.rut ...)"
|
|
1083
|
+
|
|
1084
|
+
- id: cte_scope_single_statement
|
|
1085
|
+
description: >
|
|
1086
|
+
Las CTE (WITH ...) solo viven durante UNA sentencia SQL. Si haces
|
|
1087
|
+
múltiples SELECTs separados por ';', debes repetir el WITH o consolidar
|
|
1088
|
+
todo en una sola sentencia (p.ej. usando subselects o agregando a JSON).
|
|
1089
|
+
bad: |
|
|
1090
|
+
WITH a AS (...) SELECT ...;
|
|
1091
|
+
SELECT * FROM a; # falla: 'a' ya no existe
|
|
1092
|
+
good: |
|
|
1093
|
+
WITH a AS (...), b AS (...) SELECT ... FROM a ...;
|
|
1094
|
+
-- o repetir el WITH antes del segundo SELECT
|
|
1095
|
+
|
|
1096
|
+
- Si una columna (jsonb_*) se usará después, debe proyectarse en el CTE inicial con el mismo alias.
|
|
1097
|
+
No referenciar columnas que no estén proyectadas.
|
|
1098
|
+
Si necesitas el objeto completo y un campo derivado, incluye ambos.
|
|
1099
|
+
|
|
1100
|
+
- Usa EXACTAMENTE el mismo nombre para cada CTE/tabla en su definición y en los JOIN/SELECT posteriores (respeta mayúsculas/minúsculas y ortografía).
|
|
1101
|
+
Antes del SELECT final, verifica que todos los identificadores referenciados (p. ej., guarantee_summary) existen y coinciden con los CTE definidos (p. ej., guarante_summary).
|
|
1102
|
+
Prohíbe crear o unir CTE con nombres “parecidos”; si cambias un nombre, actualízalo en todas las referencias.
|
|
1103
|
+
|
|
1104
|
+
En el SELECT final, cualquier subconsulta debe ser escalar.
|
|
1105
|
+
• Para objetos: SELECT row_to_json(sub) FROM (SELECT col1, col2, ...) sub.
|
|
1106
|
+
• Para listas: SELECT json_agg(row_to_json(sub)) FROM (SELECT ...) sub.
|
|
1107
|
+
• Nunca SELECT * dentro del SELECT final.
|
|
1108
|
+
|
|
1109
|
+
- No usar row_to_json() sobre valores json/jsonb.
|
|
1110
|
+
Si el LLM necesita serializar un json/jsonb, usar to_json()/to_jsonb() o devolver el campo tal cual.
|
|
1111
|
+
Reservar row_to_json() solo para records (filas) provenientes de subconsultas/CTEs o SELECT ... con columnas.
|
|
1112
|
+
|
|
1113
|
+
### RULE-XXX — No usar `ctid` fuera de su tabla de origen
|
|
1114
|
+
⚠️ **Prohibido referenciar la columna `ctid` fuera de la consulta directa a su tabla de origen.**
|
|
1115
|
+
|
|
1116
|
+
- `ctid` es una columna interna de PostgreSQL que solo puede ser usada directamente en la tabla donde existe.
|
|
1117
|
+
- No puede referenciarse desde subconsultas o alias si ya no está presente en el conjunto de columnas seleccionadas.
|
|
1118
|
+
- Si necesitas contar o verificar filas, usa una columna real (por ejemplo, `tender_id`) o `COUNT(*)` sin `ctid`.
|
|
1119
|
+
|
|
1120
|
+
#### Ejemplo incorrecto:
|
|
1121
|
+
```sql
|
|
1122
|
+
COUNT(*) FILTER (WHERE ctid IS NOT NULL) AS garantias_maxxa
|
|
1123
|
+
|
|
1124
|
+
### RULE-AGG-001 — No mezclar agregados con columnas sin agrupar
|
|
1125
|
+
Cuando uses agregaciones (`COUNT`, `SUM`, `AVG`, `STRING_AGG`, etc.) **sin `GROUP BY`**, todo lo que aparezca en el `SELECT` debe estar dentro de una función agregada.
|
|
1126
|
+
|
|
1127
|
+
✅ Correcto:
|
|
1128
|
+
```sql
|
|
1129
|
+
SELECT STRING_AGG(
|
|
1130
|
+
ej.ejecutiva_garantia || ' (GC: ' || ej.grupo_garantia || ')'
|
|
1131
|
+
|| ' | '
|
|
1132
|
+
|| ej.ejecutiva_credito || ' (CC: ' || ej.grupo_credito || ')',
|
|
1133
|
+
', '
|
|
1134
|
+
) AS ejecutivos
|
|
1135
|
+
FROM ejecutivos ej;
|
|
1136
|
+
|
|
1137
|
+
RULE-AGG-003:
|
|
1138
|
+
description: No usar MAX(boolean); convertir booleanos a enteros con CASE WHEN ... THEN 1 ELSE 0 END antes de usar funciones agregadas como MAX o SUM.
|
|
1139
|
+
match: MAX\(CASE\s+WHEN\s+.*\s+THEN\s+(TRUE|FALSE)\s+ELSE\s+(TRUE|FALSE)\s+END\)
|
|
1140
|
+
fix: Reemplazar el CASE para devolver 1/0 en lugar de TRUE/FALSE. Ejemplo: CASE WHEN condición THEN 1 ELSE 0 END
|
|
1141
|
+
|
|
1142
|
+
RULE-GROUPBY-001:
|
|
1143
|
+
description: Toda columna que aparece en SELECT dentro de una subconsulta con GROUP BY debe también estar en el GROUP BY o estar dentro de una función agregada.
|
|
1144
|
+
match: SELECT\s+(.*?)\s+FROM\s+(.*?)\s+GROUP\s+BY\s+(?!.*\1)
|
|
1145
|
+
fix: Asegúrate de incluir en el GROUP BY todas las columnas del SELECT que no estén envueltas en funciones agregadas.
|
|
1146
|
+
|
|
1147
|
+
|
|
1148
|
+
RULE-JSON-005:
|
|
1149
|
+
description: Al concatenar valores extraídos de JSON (`->>`), asegúrate de envolver cada fragmento con `COALESCE(..., '')` y tratarlo explícitamente como texto (`::text`) para evitar errores de representación inválida.
|
|
1150
|
+
match: \|\|\s*contract\.value->>'\w+'
|
|
1151
|
+
fix: Usar `COALESCE(contract.value->>'campo', '')` y aplicar cast `::text` al resultado de la concatenación si es necesario.
|
|
1152
|
+
|
|
1153
|
+
RULE-CTE-001:
|
|
1154
|
+
description: Si usas una tabla o CTE como `base` dentro de un subselect, asegúrate de incluir `FROM base` dentro de ese subselect.
|
|
1155
|
+
match: SELECT\s+.*base\.
|
|
1156
|
+
fix: Agrega `FROM base` dentro del subselect que usa columnas prefijadas con `base.`.
|
|
1157
|
+
|
|
1158
|
+
|
|
1159
|
+
RULE-SUBSELECT-MULTICOLUMN-ERROR-001:
|
|
1160
|
+
description:
|
|
1161
|
+
No puedes usar `SELECT *` desde una subconsulta (CTE o tabla derivada) dentro del SELECT final
|
|
1162
|
+
si esa subconsulta devuelve más de una columna. PostgreSQL lanza error: "subquery must return only one column".
|
|
1163
|
+
|
|
1164
|
+
context_where_this_happens:
|
|
1165
|
+
Cuando escribes en el SELECT final algo como `(SELECT * FROM algo)` y `algo` tiene más de una columna.
|
|
1166
|
+
|
|
1167
|
+
why_this_fails:
|
|
1168
|
+
PostgreSQL espera que la subconsulta devuelva una sola columna. Pero `*` devuelve todas, causando el error.
|
|
1169
|
+
|
|
1170
|
+
how_to_fix_it:
|
|
1171
|
+
- Si quieres agrupar varias columnas en una sola columna tipo objeto, usa:
|
|
1172
|
+
`(SELECT row_to_json(alias) FROM algo alias)`
|
|
1173
|
+
- Si solo necesitas una columna, escríbela explícitamente:
|
|
1174
|
+
`(SELECT columna FROM algo)`
|
|
1175
|
+
|
|
1176
|
+
bad_example: |
|
|
1177
|
+
SELECT
|
|
1178
|
+
nombre,
|
|
1179
|
+
(SELECT * FROM resumen_licitaciones) AS licitaciones
|
|
1180
|
+
FROM base
|
|
1181
|
+
|
|
1182
|
+
good_example_option_1: |
|
|
1183
|
+
SELECT
|
|
1184
|
+
nombre,
|
|
1185
|
+
(SELECT row_to_json(r) FROM resumen_licitaciones r) AS licitaciones
|
|
1186
|
+
FROM base
|
|
1187
|
+
|
|
1188
|
+
good_example_option_2: |
|
|
1189
|
+
SELECT
|
|
1190
|
+
nombre,
|
|
1191
|
+
(SELECT total_participa FROM resumen_licitaciones) AS licitaciones
|
|
1192
|
+
FROM base
|
|
1193
|
+
|
|
1194
|
+
RULE-JSONB-OPERATOR-001:
|
|
1195
|
+
description: >
|
|
1196
|
+
El operador `->>` solo puede usarse sobre columnas de tipo `jsonb`, no sobre strings literales ni columnas de tipo `text`.
|
|
1197
|
+
bad_examples:
|
|
1198
|
+
- "'guarantee_executive'->>'group'"
|
|
1199
|
+
- "'algo'->>'otra_cosa'"
|
|
1200
|
+
- "'text_literal'->>'key'"
|
|
1201
|
+
good_examples:
|
|
1202
|
+
- "c.jsonb_guarantee->>'guarantee_group'"
|
|
1203
|
+
- "cb.jsonb_credit->>'credit_executive'"
|
|
1204
|
+
|
|
1205
|
+
RULE-CONCATENATION-001:
|
|
1206
|
+
description: >
|
|
1207
|
+
No uses `%%` en concatenaciones de texto. PostgreSQL no lo interpreta como un símbolo de `%`, y lanzará un error de sintaxis.
|
|
1208
|
+
Usa solo `%` si estás concatenando strings.
|
|
1209
|
+
bad_examples:
|
|
1210
|
+
- "'('||ROUND(ganadas*100.0/NULLIF(total,0),1)||'%%') de ellas.'"
|
|
1211
|
+
- "CONCAT('Avance: ', ROUND(x*100,2), '%% completado')"
|
|
1212
|
+
good_examples:
|
|
1213
|
+
- "'('||ROUND(ganadas*100.0/NULLIF(total,0),1)||'%'||') de ellas.'"
|
|
1214
|
+
- "CONCAT('Avance: ', ROUND(x*100,2), '% completado')"
|
|
1215
|
+
|
|
1216
|
+
RULE-JSONB-ACCESS-VALUE-001:
|
|
1217
|
+
description: >
|
|
1218
|
+
No se puede acceder a un campo de un objeto JSONB con notación de punto (por ejemplo: `cc.comment`) cuando se itera con `jsonb_array_elements`.
|
|
1219
|
+
En su lugar, debes usar `cc.value->>'campo'` o `cc.value->'campo'`.
|
|
1220
|
+
|
|
1221
|
+
when_to_apply: >
|
|
1222
|
+
Si estás usando `jsonb_array_elements(...) AS alias` en una subconsulta, debes acceder a los campos con `alias.value->>'campo'`.
|
|
1223
|
+
|
|
1224
|
+
how_to_fix:
|
|
1225
|
+
- Reemplaza `cc.campo` por `cc.value->>'campo'` si necesitas el valor como texto.
|
|
1226
|
+
- Usa `cc.value->'campo'` si necesitas el valor como JSONB.
|
|
1227
|
+
|
|
1228
|
+
bad_example: |
|
|
1229
|
+
SELECT cc.comment FROM jsonb_array_elements(jsonb_col) cc
|
|
1230
|
+
|
|
1231
|
+
good_example: |
|
|
1232
|
+
SELECT cc.value->>'comment' FROM jsonb_array_elements(jsonb_col) cc
|
|
1233
|
+
|
|
1234
|
+
RULE-SUBQUERY-SINGLE-ROW-001:
|
|
1235
|
+
description: >
|
|
1236
|
+
Si usas una subconsulta escalar en el `SELECT`, como `(SELECT row_to_json(...) FROM ...)`, debes garantizar que la subconsulta devuelve **solo una fila**.
|
|
1237
|
+
Si no hay `WHERE`, podrías obtener múltiples filas, lo que causará un error en tiempo de ejecución.
|
|
1238
|
+
|
|
1239
|
+
when_to_apply: >
|
|
1240
|
+
Siempre que estés usando una subconsulta en el `SELECT` que espera una sola fila (por ejemplo: `(SELECT row_to_json(...) FROM nombre_cte)`), debes filtrar por una clave única como `rut`.
|
|
1241
|
+
|
|
1242
|
+
how_to_fix:
|
|
1243
|
+
- Agrega un `WHERE` en la subconsulta que filtre por una columna que asegure una sola fila (como `rut = b.rut`).
|
|
1244
|
+
- Alternativamente, limita a una fila con `LIMIT 1`, pero no es recomendable si puede haber inconsistencias.
|
|
1245
|
+
|
|
1246
|
+
bad_example: |
|
|
1247
|
+
(SELECT row_to_json(s) FROM resumen_garantias s)
|
|
1248
|
+
|
|
1249
|
+
good_example: |
|
|
1250
|
+
(SELECT row_to_json(s) FROM resumen_garantias s WHERE s.rut = b.rut)
|
|
1251
|
+
|
|
1252
|
+
RULE-SCALAR-SUBQUERY-LIMIT-001:
|
|
1253
|
+
description: >
|
|
1254
|
+
Cuando uses una subconsulta escalar dentro de un `SELECT`, debes asegurarte de que la subconsulta devuelve solo una fila.
|
|
1255
|
+
Si existe la posibilidad de múltiples filas, se producirá un error.
|
|
1256
|
+
|
|
1257
|
+
when_to_apply: >
|
|
1258
|
+
Si estás usando `(SELECT ... FROM ...)` como un campo en el SELECT principal.
|
|
1259
|
+
|
|
1260
|
+
how_to_fix:
|
|
1261
|
+
- Asegúrate de que la subconsulta solo puede devolver una fila.
|
|
1262
|
+
- Usa `LIMIT 1` si estás seguro de que cualquier fila es válida, o filtra con `WHERE` para obtener una única.
|
|
1263
|
+
|
|
1264
|
+
bad_example: |
|
|
1265
|
+
(SELECT texto FROM tabla)
|
|
1266
|
+
|
|
1267
|
+
good_example: |
|
|
1268
|
+
(SELECT texto FROM tabla LIMIT 1)
|
|
1269
|
+
-- o
|
|
1270
|
+
(SELECT texto FROM tabla WHERE id = b.id)
|
|
1271
|
+
|
|
1272
|
+
RULE-JSONB-FUNCTION-NULL-PROTECTION-001:
|
|
1273
|
+
description: >
|
|
1274
|
+
Antes de aplicar funciones como `jsonb_each`, `jsonb_array_elements`, `jsonb_object_keys`, asegúrate que el campo JSONB no sea NULL.
|
|
1275
|
+
Estas funciones no aceptan NULL como entrada, y generarán errores incluso si luego filtras con condiciones como `WHERE 1=0`.
|
|
1276
|
+
|
|
1277
|
+
when_to_apply: >
|
|
1278
|
+
En subconsultas, joins laterales o expresiones donde accedes a funciones jsonb_*.
|
|
1279
|
+
|
|
1280
|
+
how_to_fix:
|
|
1281
|
+
- Agrega `WHERE campo IS NOT NULL` o `ON campo IS NOT NULL` cuando uses funciones jsonb.
|
|
1282
|
+
- Evita aplicar funciones jsonb directamente sobre campos potencialmente nulos.
|
|
1283
|
+
|
|
1284
|
+
bad_example: |
|
|
1285
|
+
SELECT jsonb_each(b.jsonb_data)
|
|
1286
|
+
|
|
1287
|
+
good_example: |
|
|
1288
|
+
SELECT jsonb_each(b.jsonb_data)
|
|
1289
|
+
WHERE b.jsonb_data IS NOT NULL
|
|
1290
|
+
|
|
1291
|
+
RULE-JSONB-ARRAY-PROTECT-001:
|
|
1292
|
+
description: "Antes de aplicar jsonb_array_elements() sobre una clave de tipo JSONB, verificar que dicha clave exista y sea de tipo 'array'."
|
|
1293
|
+
example_good: >
|
|
1294
|
+
b.jsonb_data ? 'items'
|
|
1295
|
+
AND jsonb_typeof(b.jsonb_data->'items') = 'array'
|
|
1296
|
+
AND jsonb_array_elements(b.jsonb_data->'items')
|
|
1297
|
+
example_bad: >
|
|
1298
|
+
jsonb_array_elements(b.jsonb_data->'items')
|
|
1299
|
+
|
|
1300
|
+
- rule_id: RULE-AGG-JSON-ORDER
|
|
1301
|
+
description: >
|
|
1302
|
+
Al usar jsonb_agg sobre un campo JSON construido en un LATERAL (ej. j.comi),
|
|
1303
|
+
no se puede ordenar por columnas internas del subquery (ej. j.anio), ya que no existen en el scope externo.
|
|
1304
|
+
Siempre ordenar usando expresiones sobre el JSON (ej. (j.comi->>'anio')::INT).
|
|
1305
|
+
bad_example: |
|
|
1306
|
+
jsonb_agg(j.comi ORDER BY j.anio DESC)
|
|
1307
|
+
good_example: |
|
|
1308
|
+
jsonb_agg(j.comi ORDER BY (j.comi->>'anio')::INT DESC)
|
|
1309
|
+
|
|
1310
|
+
RULE-GRAIN-001 (no mezclar granos en JOIN):
|
|
1311
|
+
• Define el grano de cada CTE (ej.: rut, tender_id, product_type).
|
|
1312
|
+
• Nunca hagas JOIN entre CTEs de granos distintos usando subconsultas con LIMIT 1 para obtener una “llave”.
|
|
1313
|
+
• Si necesitas métricas de rut en un agregado por product_type, primero une por rut (o agrega ambas fuentes al mismo grano) y recién después agrupa por product_type.
|
|
1314
|
+
|
|
1315
|
+
Anti-patrón a prohibir: JOIN ... ON key = (SELECT ... LIMIT 1)
|
|
1316
|
+
Patrón correcto: incluye la clave real (p.ej., rut) en el CTE de detalle (creditos) y JOIN ... ON j.rut = c.rut antes de GROUP BY product_type.
|
|
1317
|
+
|
|
1318
|
+
{
|
|
1319
|
+
"rule_id": "RULE-UNION-001",
|
|
1320
|
+
"description": "Evita errores de UNION/UNION ALL por desalineación de columnas entre subconsultas.",
|
|
1321
|
+
"applies_to": "sql_bcu",
|
|
1322
|
+
"rule": "Todas las subconsultas que participan en un UNION o UNION ALL deben tener exactamente el mismo número de columnas, con los mismos nombres (o alias) y en el mismo orden. Si una de las subconsultas agrega columnas adicionales como ROW_NUMBER, esas columnas deben ser excluidas explícitamente en el SELECT final para que ambas subconsultas coincidan.",
|
|
1323
|
+
"examples": {
|
|
1324
|
+
"incorrect": [
|
|
1325
|
+
"SELECT a, b FROM tabla1 UNION ALL SELECT a, b, c FROM tabla2",
|
|
1326
|
+
"SELECT * FROM (SELECT a, b, ROW_NUMBER() OVER (...) AS rn FROM x) x WHERE rn <= 10 UNION SELECT a, b FROM y"
|
|
1327
|
+
],
|
|
1328
|
+
"correct": [
|
|
1329
|
+
"SELECT a, b FROM tabla1 UNION ALL SELECT a, b FROM tabla2",
|
|
1330
|
+
"SELECT a, b FROM (SELECT a, b, ROW_NUMBER() OVER (...) AS rn FROM x) x WHERE rn <= 10"
|
|
1331
|
+
]
|
|
1332
|
+
},
|
|
1333
|
+
"fix_suggestion": "Asegúrate de que todas las subconsultas dentro del UNION seleccionen explícitamente las mismas columnas. Si se usa ROW_NUMBER u otras funciones analíticas, excluye esas columnas en el SELECT exterior antes del UNION."
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
- No usar guiones (-) en los alias de columnas SQL.
|
|
1337
|
+
Usar guion bajo (_) en su lugar para evitar errores de sintaxis.
|