SQL Seleziona elementi in cui la somma del campo è inferiore a n
-
12-12-2019 - |
Domanda
Dato che ho un tavolo con il seguente, contenuto molto semplice:
# select * from messages;
id | verbosity
----+-----------
1 | 20
2 | 20
3 | 20
4 | 30
5 | 100
(5 rows)
.
Vorrei selezionare n messaggi, quale somma della verbosità è inferiore a y (a scopo di test diciamo che dovrebbe essere 70, quindi i risultati corretti saranno messaggi con ID 1,2,3). È davvero importante per me, quella soluzione dovrebbe essere indipendente dal database (dovrebbe funzionare almeno su postgres e sqlite).
Stavo provando con qualcosa come:
SELECT * FROM messages GROUP BY id HAVING SUM(verbosity) < 70;
.
Tuttavia non sembra funzionare come previsto, perché in realtà non somma tutti i valori dalla colonna della verbosità.
Sarei molto grato per qualsiasi suggerimento / aiuto.
Soluzione
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;
.
Questo presuppone un id
unico ascendente come hai nel tuo esempio.
.
in Modern Postgres - o generalmente con Modern Standard SQL (ma non in SQLite):
semplice cte
WITH cte AS (
SELECT *, sum(verbosity) OVER (ORDER BY id) AS total
FROM messages
)
SELECT *
FROM cte
WHERE total <= 70
ORDER BY id;
.
ricorsivo cte
dovrebbe essere più veloce per le grandi tabelle in cui recupera solo un piccolo set.
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;
.
Tutte le funzionalità standard, ad eccezione del LIMIT
.
Sorveramente parlando, non esiste una cosa come "database-indipendente". Ci sono vari standard SQL, ma nessun RDBMS è conforme completamente. LIMIT
funziona per PostgreSQL e SQLite (e alcuni altri). Utilizzare TOP 1
per SQL Server, rownum
per Oracle. Ecco un lista completa su wikipedia.
SQL: 2008 Standard sarebbe:
...
FETCH FIRST 1 ROWS ONLY
.
... quale postgresql supporta - ma a malapena qualsiasi altro RDBMS.
L'alternativa pura che funziona con più sistemi sarebbe di avvolgerla in una sottoquery e
SELECT max(total) FROM <subquery>
.
Ma questo è lento e ingombrante.
Altri suggerimenti
Questo funzionerà ...
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
)
.
Tuttavia, è necessario comprendere che SQL è progettato come un linguaggio basato su set, anziché uno iterativo, quindi progettato per il trattamento dei dati come un set, piuttosto che su una riga per riga.