SQL selecciona elementos donde la suma del campo es menor que N
-
12-12-2019 - |
Pregunta
Dado que tengo una tabla con el siguiente contenido muy simple:
# select * from messages;
id | verbosity
----+-----------
1 | 20
2 | 20
3 | 20
4 | 30
5 | 100
(5 rows)
Me gustaría seleccionar N mensajes, cuya suma de detalle sea inferior a Y (para fines de prueba, digamos que debería ser 70, luego los resultados correctos serán mensajes con ID 1,2,3).Es realmente importante para mí que esa solución sea independiente de la base de datos (debería funcionar al menos en Postgres y SQLite).
Estaba intentando con algo como:
SELECT * FROM messages GROUP BY id HAVING SUM(verbosity) < 70;
Sin embargo, no parece funcionar como se esperaba, porque en realidad no suma todos los valores de la columna de detalle.
Estaría muy agradecido por cualquier sugerencia/ayuda.
Solución
SELECT m.id, sum(m1.verbosity) AS total
FROM messages m
JOIN messages m1 ON m1.id <= m.id
WHERE m.verbosity < 70 -- optional, to avoid pointless evaluation
GROUP BY m.id
HAVING SUM(m1.verbosity) < 70
ORDER BY total DESC
LIMIT 1;
Esto supone un único y ascendente id
como lo tienes en tu ejemplo.
En el Postgres moderno -o en general con SQL estándar moderno (pero no en SQLite):
CTE simple
WITH cte AS (
SELECT *, sum(verbosity) OVER (ORDER BY id) AS total
FROM messages
)
SELECT *
FROM cte
WHERE total <= 70
ORDER BY id;
CTE recursivo
Debería ser más rápido para mesas grandes donde solo se recupera un conjunto pequeño.
WITH RECURSIVE cte AS (
( -- parentheses required
SELECT id, verbosity, verbosity AS total
FROM messages
ORDER BY id
LIMIT 1
)
UNION ALL
SELECT c1.id, c1.verbosity, c.total + c1.verbosity
FROM cte c
JOIN LATERAL (
SELECT *
FROM messages
WHERE id > c.id
ORDER BY id
LIMIT 1
) c1 ON c1.verbosity <= 70 - c.total
WHERE c.total <= 70
)
SELECT *
FROM cte
ORDER BY id;
Todas las características estándar, excepto LIMIT
.
Estrictamente hablando, no existe el concepto "independiente de la base de datos".Existen varios estándares SQL, pero ningún RDBMS los cumple por completo. LIMIT
funciona para PostgreSQL y SQLite (y algunos otros).Usar TOP 1
para servidor SQL, rownum
para Oráculo.Aquí está un lista completa en Wikipedia.
El SQL: estándar 2008 sería:
...
FETCH FIRST 1 ROWS ONLY
...que admite PostgreSQL, pero casi ningún otro RDBMS.
La alternativa pura que funciona con más sistemas sería envolverla en una subconsulta y
SELECT max(total) FROM <subquery>
Pero eso es lento y difícil de manejar.
Otros consejos
Esto funcionará ...
select *
from messages
where id<=
(
select MAX(id) from
(
select m2.id, SUM(m1.verbosity) sv
from messages m1
inner join messages m2 on m1.id <=m2.id
group by m2.id
) v
where sv<70
)
Sin embargo, debe comprender que SQL está diseñado como un lenguaje a base de conjunto, en lugar de una iterativa, por lo que está diseñado para tratar los datos como un conjunto, en lugar de una fila por fila.