Un filtro come “mostra tutti i clienti il cui stato non è attivo” sembra semplice, ma in SQL nasconde alcune differenze importanti. In questo articolo mostro come usare l’operatore di disuguaglianza, quando scegliere <> o !=, perché i valori NULL possono cambiare il risultato e quali errori evitare nelle query reali.
La regola essenziale per filtrare i valori diversi
-
<>è la forma più portabile e riconosciuta dallo standard SQL. -
!=è accettato da molti database, ma non è la scelta più sicura quando si lavora su più sistemi. - Un confronto con
NULLnon restituisce vero o falso, ma un valore sconosciuto. - Per escludere più valori può essere più leggibile usare
NOT INo condizioni ben raggruppate. - Il confronto deve rispettare tipo di dato, maiuscole, spazi e regole di confronto del database.

Come si esprime il diverso in SQL
Per verificare che una colonna abbia un valore diverso da quello indicato si usa soprattutto l’operatore <>. La struttura di base è molto semplice:
SELECT *
FROM clienti
WHERE stato <> 'inattivo';La query restituisce i clienti il cui stato non coincide con inattivo. Non significa però “tutti i clienti che hanno uno stato diverso”, perché le righe con valore NULL vengono escluse dal risultato, un dettaglio che spesso causa report incompleti.
In molti ambienti è possibile scrivere anche !=:
SELECT *
FROM prodotti
WHERE categoria != 'Servizi';Il risultato pratico è normalmente lo stesso. Io preferisco <> nei progetti destinati a database diversi, perché è la notazione più vicina allo standard SQL e rende più chiara la portabilità della query.
<> e != sono davvero equivalenti
Su MySQL, PostgreSQL, SQL Server e molti altri sistemi, <> e != vengono interpretati come “diverso da”. La documentazione di Microsoft Learn segnala però che != è una forma non ISO, mentre <> è la scelta più coerente con lo standard.
| Forma | Significato | Quando preferirla |
|---|---|---|
<> |
Diverso da | Query portabili e codice SQL condiviso |
!= |
Diverso da | Script legati a un database che lo supporta |
NOT IN |
Non incluso in un insieme | Esclusione di più valori |
IS DISTINCT FROM |
Diverso anche considerando i NULL
|
Confronti precisi tra valori eventualmente mancanti |
Il manuale di PostgreSQL considera entrambe le forme operatori di disuguaglianza, mentre MySQL documenta esplicitamente la coppia <> e !=. La differenza più importante, quindi, non è quasi mai il risultato della singola query, ma la compatibilità futura del codice.
Se stai scrivendo una query per un solo progetto SQL Server o MySQL, != difficilmente creerà problemi. Se invece il codice può passare da PostgreSQL a SQL Server, oppure alimenta strumenti di business intelligence con connettori diversi, sceglierei senza esitazione <>.
Il caso che confonde più spesso riguarda NULL
In SQL, NULL non indica necessariamente zero, una stringa vuota o un valore predefinito. Indica che il dato è mancante o sconosciuto. Per questo il confronto seguente non trova le righe senza categoria:
SELECT *
FROM prodotti
WHERE categoria <> 'Servizi';Quando categoria vale NULL, il database non può stabilire se quel valore sia diverso da Servizi. Il risultato del confronto diventa quindi UNKNOWN, e la clausola WHERE conserva soltanto le condizioni valutate come vere.
Se vuoi includere anche i prodotti senza categoria, devi dichiararlo esplicitamente:
SELECT *
FROM prodotti
WHERE categoria <> 'Servizi'
OR categoria IS NULL;Questa è, nella pratica, la correzione più utile. Molti report apparentemente “sbagliati” non hanno un problema nell’operatore diverso, ma una gestione implicita dei valori mancanti.
Leggi anche: SQL Server PIVOT per trasformare righe in colonne
Confrontare NULL con NULL
La condizione categoria = NULL non funziona come ci si aspetta, e lo stesso vale per categoria <> NULL. Per controllare la presenza o l’assenza del valore bisogna usare IS NULL e IS NOT NULL.
SELECT *
FROM clienti
WHERE email IS NOT NULL;Quando serve confrontare due colonne trattando due NULL come equivalenti, la sintassi dipende dal database. PostgreSQL e alcuni sistemi moderni supportano IS DISTINCT FROM, mentre MySQL dispone dell’operatore null-safe <=>. In una query destinata a più piattaforme, conviene verificare prima la compatibilità del dialetto SQL.
Come escludere più valori senza creare condizioni fragili
Per scartare più valori puoi concatenare diverse condizioni con AND:
SELECT *
FROM ordini
WHERE stato <> 'annullato'
AND stato <> 'test';La logica è corretta, ma la query diventa poco comoda da mantenere quando i valori da escludere aumentano. In quel caso preferisco NOT IN:
SELECT *
FROM ordini
WHERE stato NOT IN ('annullato', 'test');Le due condizioni sono equivalenti per valori normali. Il comportamento cambia però se nella colonna compare NULL oppure se la lista usata da NOT IN contiene un NULL: l’intera valutazione può diventare sconosciuta e non restituire le righe attese.
Quando la lista arriva da una sottoquery, controllo sempre che non produca valori nulli:
SELECT *
FROM ordini o
WHERE o.cliente_id NOT IN (
SELECT cliente_id
FROM clienti_bloccati
WHERE cliente_id IS NOT NULL
);In molti casi NOT EXISTS è più robusto, soprattutto con dati incompleti:
SELECT *
FROM ordini o
WHERE NOT EXISTS (
SELECT 1
FROM clienti_bloccati cb
WHERE cb.cliente_id = o.cliente_id
);Non considero NOT IN una scelta sbagliata in assoluto. Per una lista breve e fissa è leggibile e perfettamente adeguato; con sottoquery, valori mancanti e grandi volumi, NOT EXISTS merita invece una valutazione più attenta.
Attenzione ai tipi di dato e alle stringhe
Il confronto è affidabile quando i due lati hanno tipi compatibili. Confrontare una colonna numerica con una stringa può attivare conversioni automatiche, generare errori o produrre risultati diversi tra database.
SELECT *
FROM ordini
WHERE totale <> '100';Se totale è numerico, è più corretto usare un numero senza apici:
SELECT *
FROM ordini
WHERE totale <> 100;Su MySQL alcune conversioni implicite tra stringhe e numeri sono consentite, ma questa comodità può nascondere dati sporchi come '100 EUR' o stringhe vuote. Io preferisco rendere il tipo evidente e usare CAST() quando la conversione è davvero necessaria.
Anche le stringhe richiedono attenzione. Il risultato può dipendere da collation, maiuscole, minuscole, accenti e spazi finali. Una condizione come nome <> 'Luca' potrebbe comportarsi diversamente in un database case-sensitive rispetto a uno case-insensitive.
Prima di correggere la query, controlla quindi il tipo della colonna e le regole di confronto. Cambiare l’operatore senza capire la struttura del dato risolve raramente il problema alla radice.
Gli errori pratici che alterano il risultato
Un errore frequente riguarda la combinazione di AND e OR. Questa query può essere interpretata diversamente da quanto immagini:
SELECT *
FROM clienti
WHERE stato <> 'inattivo'
OR paese = 'Italia'
AND segmento = 'Business';In genere AND ha precedenza su OR, quindi il database legge la condizione come:
WHERE stato <> 'inattivo'
OR (paese = 'Italia' AND segmento = 'Business')Se l’obiettivo è filtrare solo i clienti italiani appartenenti al segmento Business e con stato diverso da inattivo, servono le parentesi:
SELECT *
FROM clienti
WHERE stato <> 'inattivo'
AND paese = 'Italia'
AND segmento = 'Business';Un secondo errore consiste nell’usare l’operatore diverso per cercare un testo parzialmente differente. Per esempio, email <> '%@azienda.it' non significa “email che non termina con azienda.it”: per i modelli testuali devi usare NOT LIKE.
SELECT *
FROM utenti
WHERE email NOT LIKE '%@azienda.it';Infine, non trasformare automaticamente la colonna con funzioni come LOWER() o COALESCE() senza valutare gli indici. La condizione può diventare più difficile da ottimizzare, soprattutto su tabelle con milioni di righe. Prima di intervenire sulle prestazioni, verifico il piano di esecuzione e la distribuzione reale dei dati.
Una scelta semplice che resta corretta nel tempo
Per un confronto diretto tra valori non nulli, la mia regola è lineare: usa <>, scrivi i valori con il tipo corretto e aggiungi una condizione separata se vuoi includere i NULL.
Quando devi escludere un insieme di valori, scegli NOT IN per liste brevi e controllate, oppure valuta NOT EXISTS se entrano in gioco sottoquery e dati mancanti. Le parentesi nelle condizioni miste e una verifica del piano di esecuzione fanno spesso più differenza di qualsiasi micro-ottimizzazione sintattica.
Il vero punto non è ricordare due simboli, ma capire che SQL lavora con tre stati logici e con regole diverse per tipi, stringhe e valori assenti. Una query che considera questi dettagli produce risultati più affidabili e report molto più facili da difendere davanti a chi deve prendere decisioni sui dati.
