Torna all'elenco degli articoli Articoli
Tempo di lettura: 11 minuti

Qual è la differenza tra le clausole WHERE e HAVING in SQL?

Poiché la quantità di dati nel mondo cresce di giorno in giorno, aumenta la necessità di gestire e interrogare i dati in modo efficiente. L'SQL offre la possibilità di manipolare e interrogare i dati su qualsiasi scala. Le clausole WHERE e HAVING consentono di filtrare i dati secondo le proprie esigenze, ma non sono la stessa cosa. Spiegheremo in dettaglio le differenze tra di esse.

Uno dei compiti più comuni nella manipolazione e nell'interrogazione dei dati è quello di filtrare i dati utilizzando le clausole WHERE e HAVING. Sebbene entrambe filtrino i dati in base a condizioni e/o funzioni definite dall'utente, esistono importanti differenze nel modo in cui ciascuna clausola viene eseguita.

Questo articolo spiega in dettaglio quando utilizzare WHERE o HAVING. Spiegheremo brevemente il flusso logico di una query SQL che include le clausole WHERE e/o HAVING, seguito da alcuni scenari pratici ed esempi da risolvere insieme.

Flusso logico delle query con WHERE e HAVING

L'ordine di scrittura di una query SQL è diverso dall'ordine di esecuzione. Vediamo il seguente esempio:

SELECT country_id, city, MIN(salary), MAX(salary) 
FROM hr.emp_details_view
WHERE country_id IN ('US', 'UK')
GROUP BY country_id, city
HAVING MIN(salary) < 15000
ORDER BY country_id; 

Il flusso logico di una query SQL è rappresentato nella figura seguente.

Ordine di esecuzione nelle query SQL

Figura 1: Ordine di esecuzione delle query SQL.


  1. L'elaborazione logica di una query SQL inizia con l'istruzione FROM, che raccoglie i dati dalle tabelle elencate nella query. Può contenere anche una clausola JOIN che combina due o più tabelle utilizzando l'operatore ON.

    Si noti che le condizioni utilizzate nella clausola WHERE possono essere utilizzate anche nell'operatore ON della clausola JOIN. Inoltre, le tabelle combinate utilizzando le condizioni dell'operatore ON della clausola JOIN possono essere usate nella clausola WHERE. Tuttavia, si dovrebbe usare WHERE solo per condizioni che filtrano singole righe. Questo migliora la leggibilità della query e consente all'operatore ON di gestire solo la combinazione delle tabelle, che è il suo scopo.

  2. Il passo successivo nel flusso del processo logico è la clausola WHERE, che filtra i record a quelli che soddisfano le condizioni e/o le funzioni definite dall'utente e li inoltra.
  3. La clausola WHERE è seguita dalla clausola GROUP BY, che raggruppa i record ricevuti dalla condizione WHERE. Ad esempio, le colonne elencate nella clausola GROUP BY possono essere le squadre, mentre le altre colonne vengono aggregate dalla funzione aggregata e assegnate ai gruppi/squadre corrispondenti.
  4. Segue la clausola HAVING, che filtra i gruppi creati in GROUP BY piuttosto che i singoli record.
  5. A questo punto, l'elaborazione logica passa al comando SELECT. Valuta quali colonne devono essere inviate all'output. Valuta anche eventuali parole chiave come UNIQUE, DISTINCT, e TOP, se incluse.
  6. La clausola ORDER BY viene eseguita alla fine del flusso logico del processo. Ordina i dati in base alle colonne specificate e in ordine crescente per impostazione predefinita.

Innanzitutto, gli elementi essenziali: Specificare le condizioni in WHERE e HAVING

Esiste un'ampia gamma di operatori che ci aiutano a creare e a combinare le condizioni nelle clausole WHERE e HAVING:

  • segni di confronto: <, >, <=, >=, =, <>
  • verifica del valore nullo: IS NULL, IS NOT NULL
  • caratteri jolly: LIKE, '%=_'
  • test dell'intervallo: BETWEEN
  • verifica della presenza in un insieme: IN
  • test di esistenza: EXISTS
  • segni di confronto quantificati: ALL, ANY, SOME
  • combinazione logica delle condizioni: AND, OR, NOT

Gli operandi di una clausola WHERE possono includere le colonne elencate in SELECT, ad eccezione delle colonne utilizzate nelle funzioni aggregate. Al contrario, HAVING filtra in base ai valori risultanti della funzione aggregata, come SUM(), COUNT(), e AVG(), tra gli altri.

Esempi

Le tabelle employee e emp_details_view, mostrate parzialmente di seguito, sono utilizzate nei prossimi esempi.

SELECT * 
FROM hr.employees;
EMPLOYEE_IDLAST_NAMEMANAGER_IDHIRE_DATESALARYJOB_ID
101Kochhar10021-SEP-0517000AD_VP
102De Haan10013-JAN-0117000AD_VP
108Greenberg10117-AUG-0212008FI_MGR
103Hunold10203-JAN-069000IT_PROG
105Austin10325-JUN-054800IT_PROG
116Baida11424-DEC-052900PU_CLERK
117Tobias11424-JUL-052800PU_CLERK
145Russell10001-OCT-0414000SA_MAN
148Cambrault10015-OCT-0711000SA_MAN
SELECT * 
FROM hr.emp_details_view;
EMPLOYEE_IDLAST_NAMEFIRST_NAMESALARYDEPARTMENT_IDCOUNTRY_IDCITYLOCATION_ID
103HunoldAlexander900060USSouthlake1400
105AustinDavid480060USSouthlake1400
101KochharNeena1700090USSeattle1700
102De HaanLex1700090USSeattle1700
108GreenbergNancy12008100USSeattle1700
116BaidaShelli290030USSeattle1700
117TobiasSigal280030USSeattle1700
145RussellJohn1400080UKOxford2500
148CambraultGerald1100080UKOxford2500

Scenario I: La clausola WHERE

La clausola WHERE viene utilizzata per filtrare le singole righe di dati prelevate dal database da SELECT. I record che non soddisfano le condizioni di WHERE non vengono inclusi nell'output.

Vediamo un esempio:

where age > 25 AND city='New York'

La query passa solo i record la cui età è superiore a 25 anni e il cui city è New York.

Nella sintassi della query, la clausola WHERE viene utilizzata direttamente dopo SELECT e FROM. Essa filtra le singole righe - i record grezzi, non raggruppati - recuperate dalla query, per determinare quali record devono essere passati alla clausola GROUP BY.

Esaminiamo l'output della query seguente che utilizza la clausola WHERE.

SELECT last_name, hire_date, manager_id 
FROM hr.employees
WHERE last_name LIKE '%a%' 
AND (hire_date BETWEEN '01-JAN-05' AND '01-JAN-07') 
AND manager_id IS NOT NULL;
LAST_NAMEHIRE_DATEMANAGER_ID
Baida24-DEC-05114
Kochhar21-SEP-05100
Tobias24-JUL-05114

L'esempio precedente produce le colonne last_name, hire_date e manager_id dalla tabella hr.employee. La clausola WHERE filtra l'output come segue:

  • last_name deve contenere una lettera "a";
  • hire_date deve essere maggiore o uguale a 01-GEN-05 e minore o uguale a 01-GEN-07; e
  • manager_id deve avere un valore e non essere nullo.

Solo i record che soddisfano le condizioni di WHERE vengono presentati nell'output.

Vediamo un altro esempio dello stesso scenario:

SELECT employee_id, salary, job_id 
FROM hr.employees
WHERE (salary < 3000 OR salary = 9000) 
AND job_id IN ('IT_PROG', 'FI_MGR', 'PU_CLERK'); 
EMPLOYEE_IDSALARYJOB_ID
1039000IT_PROG
1162900PU_CLERK
1172800PU_CLERK

Questo esempio produce le colonne employee_id, salary, e job_id dalla tabella hr.employee. La clausola WHERE filtra l'output come segue:

  • salary deve essere inferiore a 3000, oppure deve essere uguale a 9000; e
  • job_id deve essere uguale a qualsiasi valore elencato nell'operatore IN.

Come nella query precedente, l'output contiene solo i record che soddisfano la condizione WHERE.

Si tenga presente che WHERE non può contenere condizioni che includono funzioni aggregate. Questo è il compito di HAVING.

Scenario II: La clausola HAVING

HAVING è usata per filtrare gruppi di record creati dalla clausola GROUP BY. Per questo motivo, la clausola HAVING deve seguire la clausola GROUP BY. È simile alla clausola WHERE che filtra l'output di SELECT, solo che WHERE filtra i singoli record mentre HAVING filtra i gruppi.

Le funzioni aggregate, come SUM(), MAX(), MIN(), AVG() e COUNT(), sono oggetto della clausola HAVING.

Vediamola in azione negli esempi.

SELECT SUM(salary), department_id 
FROM hr.emp_details_view
GROUP BY department_id
HAVING SUM(salary) > 10000; 
SUM(SALARY)DEPARTMENT_ID
3400090
1380060
12008100
2500080

L'esempio precedente elenca i reparti e la somma di tutti gli stipendi di ciascun reparto. Nell'output vengono elencati solo i department_idil cui stipendio totale è superiore a 10000. Quelli che non soddisfano la condizione impostata dalla clausola HAVING vengono filtrati.

Vediamo un altro esempio:

SELECT COUNT(employee_id), job_id, salary
FROM hr.employees
WHERE salary > 12000
GROUP BY job_id, salary
HAVING COUNT(employee_id) < 10; 
COUNT(EMPLOYEE_ID)JOB_IDSALARY
1SA_MAN14000
2AD_VP17000
1FI_MGR12008

Questa query elenca il numero di dipendenti per ogni combinazione di job_id e stipendio. La clausola WHERE filtra i record con stipendi superiori a 12000. La clausola GROUP BY, che segue una WHERE, specifica il raggruppamento per colonne non aggregate job_id e stipendio. Infine, la clausola HAVING specifica che il valore aggregato COUNT(employee_id) deve essere inferiore a 10.

Scenario III: la clausola GROUP BY

WHERE con una clausola GROUP BY

La clausola WHERE deve sempre essere posta prima di GROUP BY. Questo perché WHERE filtra singole righe di dati, non gruppi di righe. La clausola GROUP BY prende le singole righe dal risultato del filtro a livello di riga WHERE per creare gruppi di righe.

Ecco un esempio:

SELECT job_id, SUM(salary) 
FROM hr.employees
WHERE manager_id IN (100, 101, 102, 103)
GROUP BY job_id; 
JOB_IDSUM(SALARY)
AD_VP34000
FI_MGR12008
IT_PROG13800
SA_MAN25000

La query precedente viene valutata come segue:

  1. La clausola WHERE filtra innanzitutto i record con manager_id che non si trovano nell'elenco che segue l'operatore IN.
  2. La clausola GROUP BY raggruppa quindi per job_id i record che hanno superato la condizione WHERE.

La query calcola gli stipendi totali dei dipendenti gestiti da ciascuno dei manager specificati (manager_ids 100, 101, 102, 103). Gli stipendi dei dipendenti che fanno capo ad altri manager non sono inclusi nel calcolo di questo totale.

HAVING con una clausola GROUP BY

La clausola GROUP BY viene spesso utilizzata con le funzioni aggregate. Crea valori di riepilogo per le colonne elencate in GROUP BY.

Al contrario, HAVING segue sempre una clausola GROUP BY, poiché HAVING lavora con i gruppi creati da GROUP BY.

Consideriamo un caso di utilizzo di HAVING in cui la clausola GROUP BY non può essere omessa. Questo è vero se ci sono colonne elencate in SELECT che non sono utilizzate dalle funzioni aggregate, come department_id nell'esempio seguente. Queste colonne non aggregate devono essere elencate in GROUP BY per raggruppare i dati.

SELECT avg(salary), department_id 
FROM hr.emp_details_view
GROUP BY department_id
HAVING avg(salary) < 15000; 
AVG(SALARY)DEPARTMENT_ID
285030
690060
12008100
1250080

Tuttavia, HAVING può essere utilizzato senza l'accompagnamento di GROUP BY. In questo caso, HAVING viene applicato all'intero output di SELECT, trattandolo come un singolo gruppo. Di seguito è riportato un esempio:

SELECT round(avg(salary))
FROM hr.emp_details_view
HAVING avg(salary) < 11000; 
ROUND(AVG(SALARY))
10056

La query restituisce un singolo valore contenente la media di tutti gli stipendi. Si noti che la clausola HAVING pone un limite a questo valore aggregato. Se la media calcolata fosse stata superiore a 11000, la query non avrebbe restituito alcun record.

Il prossimo è un esempio di pseudo-aggregazione, in cui le funzioni di aggregazione vengono utilizzate al posto della clausola GROUP BY:

SELECT MIN(first_name), MIN(department_id), MAX(salary)
FROM hr.emp_details_view
HAVING MIN(salary) > 1000; 
MIN(FIRST_NAME)MIN(DEPARTMENT_ID)MAX(SALARY)
Alexander3017000

La funzione MIN() viene utilizzata sulle colonne first_name e department_id. Non viene richiamata secondo l'uso tipico delle funzioni aggregate, ma piuttosto per evitare un GROUP BY. In questo caso, l'output è solo un singolo record di dati che consiste nel valore minimo di first_name, nel valore minimo di department_id e nel valore massimo del salario, ciascuno dell'intera tabella. Si noti che questi tre valori potrebbero provenire da tre record diversi, come in questo esempio. Inoltre, in questo caso, se il salario minimo dell'intera tabella fosse stato pari o inferiore a 1000, la query non avrebbe restituito alcun record.

Questo particolare esempio è un po' artificioso per poter mostrare qualcosa di più semplice. Sebbene sia meno comune di GROUP BY, la pseudo-aggregazione può essere utilizzata nelle query che trattano tutte le righe incluse come un unico gruppo.

L'uso di HAVING con o senza GROUP BY può variare a seconda del database. Per ulteriori informazioni sulla clausola GROUP BY, consultare l'articolo Clausola GROUP BY.

Scenario IV: Utilizzo congiunto delle clausole WHERE e HAVING

La sintassi per l'utilizzo di WHERE e HAVING in una query segue l'ordine specifico presentato di seguito:

  1. SELECT: seleziona le colonne del database da inviare al risultato.
  2. FROM: elenca le tabelle da utilizzare nella query.
  3. WHERE: filtra i singoli record.
  4. GROUP BY: raggruppa i record in base alle colonne specificate.
  5. HAVING: filtra i gruppi definiti da GROUP BY.
  6. ORDER BY: ordina i record in base alle colonne specificate.

Vediamo un esempio che utilizza tutti questi elementi.

SELECT country_id, city, MIN(salary), MAX(salary) 
FROM hr.emp_details_view
WHERE country_id IN ('US', 'UK')
GROUP BY country_id, city
HAVING MIN(salary) < 15000
ORDER BY country_id; 
COUNTRY_IDCITYMIN(SALARY)MAX(SALARY)
UKOxford1100014000
USSeattle280017000
USSouthlake48009000

La query recupera il minimo salary e il massimo salary per ogni combinazione di city e country_id, quest'ultimo limitato solo agli Stati Uniti e al Regno Unito dalla condizione WHERE.

Supponiamo di avere 10 record con gli Stati Uniti come country_id, e all'interno di questi ci sono 5 record con New York come città e altri 5 con Los Angeles come città. Quando si esegue la query di cui sopra, questi 10 record diventano 2 record:

  • un record per gli Stati Uniti e New York, con i relativi salari minimi e massimi,
  • un altro record per gli Stati Uniti e Los Angeles, anch'esso con i salari minimi e massimi.

Per ulteriori informazioni sulle differenze tra WHERE e HAVING, visitate l'articolo su SQL HAVING vs WHERE.

Scenario V: WHERE e HAVING con le JOIN

L'esempio seguente fa un ulteriore passo avanti e utilizza una JOIN con entrambe le WHERE e HAVING. Unisce i dati di due tabelle sulla base di una colonna comune, employee_id. Le clausole WHERE e HAVING seguono l'ordine di esecuzione appena discusso.

SELECT e.job_id, edv.location_id, SUM(e.salary)
FROM hr.employees e
JOIN hr.emp_details_view edv
ON e.employee_id=edv.employee_id
WHERE e.job_id IN ('IT_PROG', 'SA_MAN')
GROUP BY e.job_id, edv.location_id
HAVING SUM(e.salary) > 5000
ORDER BY e.job_id; 
JOB_IDLOCATION_IDSUM(E.SALARY)
IT_PROG140013800
SA_MAN250025000

Questo esempio seleziona la colonna job_id dalla tabella hr.employee, la colonna location_id dalla tabella hr.emp_details_viewe il totale degli stipendi per ogni combinazione di job_id e location_id. La condizione per unire le tabelle è elencata nell'operatore ON. Questa condizione si basa sulla colonna comune, employee_id.

Successivamente, la clausola WHERE filtra i record solo su job_ids che sono o IT_PROG o SA_MAN. La clausola GROUP BY raggruppa i record in base alla combinazione delle colonne job_id e location_id. Infine, la clausola HAVING filtra il valore aggregato SUM(e.salary) per quelli superiori a 5000.

La clausola ORDER BY ordina i record di output (in ordine crescente per impostazione predefinita) in base alla colonna job_id. Per ulteriori informazioni sulle clausole GROUP BY e ORDER BY, visitate l'articolo GROUP BY vs. ORDER BY.

Differenza tra le clausole WHERE e HAVING in SQL

Ecco una sintesi delle differenze tra le clausole WHERE e HAVING in SQL:

WHERE clauseHAVING clause
FilteringFilters individual rows fetched by SELECT, i.e., the output of the SELECT commandFilters groups of rows created by GROUP BY, i.e., the output of the GROUP BY clause
ConditionsCannot have conditions containing aggregate functionsFocuses on conditions containing aggregate functions
SyntaxWHERE comes before GROUP BY in the syntaxHAVING comes after GROUP BY in the syntax
Order of evaluationWHERE clause is evaluated before GROUP BY in the process flow, before aggregation is performedHAVING clause is evaluated after GROUP BY in the process flow, after aggregation is performed
With/without GROUP BYCan be used with or without GROUP BY, since WHERE and GROUP BY are not relatedShould be used with GROUP BY; otherwise, the output of SELECT is treated as one group

WHERE e HAVING sono una parte importante dell'apprendimento di SQL. Consultate il SQL Basics per sviluppare le vostre competenze in SQL.