Firebird Documentation IndexGuida sull'uso di NULL nel linguaggio SQL di Firebird → Le funzioni di aggregazione
Firebird Home Firebird Home Indietro: Gli ordinamentiFirebird Documentation IndexRisali: Guida sull'uso di NULL nel linguaggio SQL di FirebirdAvanti: Frasi condizionali e cicliche

Le funzioni di aggregazione

La clausola GROUP BY
La clausola HAVING

Le funzioni di aggregazione – COUNT, SUM, AVG, MAX, MIN e LIST – non gestiscono NULL allo stesso modo delle funzioni ordinarie e degli operatori. Invece di riportare NULL non appena viene trovato un operando a NULL, queste considerano solo i campi non NULL per calcolare il risultato. Cioè se avete questa tabella:

Tabella
ID Nome Importo
1 John 37
2 Jack NULL
3 Jim 5
4 Joe 12
5 Josh NULL

...eseguendo select sum(Importo) from Tabella riporta 54, che è appunto 37 + 5 + 12. Se fossero stati sommati tutti i cinque campi, il risultato sarebbe stato NULL. Per la funzione AVG, i campi non-NULL sono sommati tra loro, e la somma divisa per il numero dei campi non-NULL.

C'è una eccezione a questa regola: COUNT(*) riporta il numero di tutte le righe, perfino di quelle in cui tutti i campi sono NULL. Ma COUNT(NomeCampo) si comporta come le altre funzioni aggregate, nel senso che conta solo le righe in cui quello specifico campo non è NULL.

Un'altra cosa che vale la pena di sapere è che sia COUNT(*) sia COUNT(FieldName) non riportano mai NULL: se non ci sono righe nell'insieme risultato della SELECT..., entrambe valgono 0. Inoltre, anche COUNT(NomeCampo) vale 0 se tutti i campi NomeCampo nel risultato sono NULL. In questi casi tutte le altre funzioni di aggregazione riportano NULL. Bisogna avvertire che anche SUM riporta NULL se utilizzata su un insieme risultato vuoto, che è contrario alla logica comune: se non ci sono righe la media, il minimo ed il massimo non sono definiti, ma la somma è zero.

Ora mettiamo il tutto insieme in una tabella riassuntiva:

Tabella 7. I risultati delle funzioni aggregate in diverse situazioni

Funzione Risultati
Insieme vuoto Insieme o colonna tutta a null Altri casi
COUNT(*) 0 Numero totale delle righe Numero totale delle righe
COUNT(Campo) 0 0 Numero di righe dove Campo non è NULL
MAX, MIN NULL NULL Il valore minimo o massimo trovato nella colonna
SUM NULL NULL Somma dei valori non NULL della colonna
AVG NULL NULL Media dei valori non NULL della colonna. Questo vale SUM(Campo) / COUNT(Campo).[a]
LIST[b] NULL NULL Stringa ottenuta concatenando i valori non NULL della colonna separati da virgola

[a] Se Campo è un campo di tipo intero, AVG è sempre arrotondato verso 0. Esempio: 6 INT con somma -11 danno media -1, non -2.

[b] LIST è stata aggiunta in Firebird 2.1


La clausola GROUP BY

Una clausola GROUP BY non cambia la logica delle funzioni di aggregazione descritta sopra, eccetto che adesso è applicata a ciascun gruppo individualmente invece che all'insieme di righe risultante nel suo insieme. Supponiamo d'avere una tabella Dipendenti con dei campi Dipartimento e Salario che permettono entrambi NULL, e di lanciare questa query:

SELECT Dipartimento AS DIP, SUM(Salario) AS SOMMA FROM Dipendenti GROUP BY Dipartimento

Il risultato potrebbe essere una cosa del genere (Dipartimento a <null> può essere in cima o in fondo, dipende dalla versione di Firebird):

DIP                     SOMMA
====== =====================
<null>             219465.19
000                266643.00
100                155262.50
110                130442.81
115              13480000.00
120                   <null>
121                110000.00
123                390500.00

Notare che chi ha il dipartimento sconosciuto (NULL) è raggruppata inscieme, sebbene non si possa dire che abbiamo davvero lo stesso valore del campo Dipartimento. L'alternativa sarebbe stata dare a ciascuno di essi un «gruppo» a sé. Non solo questo avrebbe generato un grandissimo numero di linee al risultato, ma avrebbe degenerato lo scopo del raggruppare: quelle linee non sarebbero un raggruppamento, ma semplicemente «SELECT Dipartimento, Salario». Pertanto ha senso raggruppare i dipartimenti a NULL secondo il loro stato e gli altri secondo il loro valore.

Comunque, non è questo che interessa maggiormente. Che cosa ci dice la colonna SUM? Forse che tutti i salari sono diversi da NULL, eccetto nel dipartimento 120? No. Quel che si può dire è che tranne nel dipartimento 120, c'è nel database almeno un dipendente con salario noto. Ogni dipartimento può avere salari a NULL; nel 120 tutti i salari sono NULL.

Per saperne di più, aggiungendo una o più colonne COUNT(). Per sapere ad esempio il numero dei salari a NULL in ciascun gruppo si aggiunge una colonna «COUNT(*)» oppure «COUNT(Salary)».

Countare le frequenze

Una clausola GROUP BY può essere usata per trovare le frequenze con cui i valori ricorrono in una tabella. In questo caso si usa lo stesso nome campo più volte nella query. Con una tabella TT che ha una colonna A i cui valori sono { 3, 8, NULL, 6, 8, -1, NULL, 3, 1 }. Per avere la lista delle frequenze, si può fare:

SELECT A, COUNT(A) FROM TT GROUP BY A

che dà il seguente risultato:

           A        COUNT
============ ============
          -1            1
           1            1
           3            2
           6            1
           8            2
      <null>            0

Oh! Qualcosa non va col conteggio dei NULL, ma perchè? Rivediamo: COUNT(Nomecampo) salta tutti i campi a NULL, pertanto con COUNT(A) il conteggio del gruppo <null> può essere solo 0! Riformulando la query in questo modo:

SELECT A, COUNT(*) FROM TT GROUP BY A

e si otterrà il valore corretto (nel caso 2).

La clausola HAVING

La clausola HAVING può aggiungere restrizioni ad una query con aggregati, così come la WHERE fa nelle query record per record. La clausola HAVING impone condizioni su ogni colonna del risultato o combinazioni di colonne, che siano aggregate o meno.

Per quanto riguarda i NULL, vanno tenuti ben presenti i seguenti due fatti (e credo di non meravigliare nessuno ormai):

  • Le righe per le quali la condizione di HAVING risolve a NULL non sono incluse nel risultato finale. («Va bene solo il true.»)

  • «HAVING <col> IS [NOT] NULL» è una condizione lecita e spesso utile, indipendentemente dal fatto che <col> sia raggruppata o meno. (Se <col> non è aggregata, si può risparmiare un po' di lavoro al server cambiando HAVING in WHERE e mettendolo nelle condizioni prima del «GROUP BY». Questo vale per tutte le condizioni su colonne non di aggregazione.)

Aggiungendo ad esempio la seguente clausola alla query d'esempio del paragrafo «GROUP BY»:

...HAVING Dipartimento IS NOT NULL

si impedisce alla prima riga di essere stampata, mentre con quest'altra:

...HAVING SUM(Salario) IS NOT NULL

sopprima la sesta riga (quella del Dipartimento = 120).

Indietro: Gli ordinamentiFirebird Documentation IndexRisali: Guida sull'uso di NULL nel linguaggio SQL di FirebirdAvanti: Frasi condizionali e cicliche
Firebird Documentation IndexGuida sull'uso di NULL nel linguaggio SQL di Firebird → Le funzioni di aggregazione