Gli array in PostgreSQL ti permettono di salvare tanti valori in una sola cella della tabella. È super comodo quando vuoi raggruppare dati collegati, tipo una lista di tag per un articolo o le categorie di un prodotto. Ma appena inizi a cercare, filtrare o incrociare gli array, le performance possono andare a farsi benedire. Ecco perché l’indicizzazione degli array ti salva la vita. Gli indici ti permettono di velocizzare operazioni come:
- verificare se un array contiene un elemento specifico,
- trovare array che contengono determinati elementi,
- verificare se ci sono elementi in comune tra array.
Operatori per lavorare con gli array
Prima di buttarti a creare indici, vediamo gli operatori base per lavorare con gli array:
@> (contains) — controlla se un array contiene tutti gli elementi di un altro array.
SELECT *
FROM courses
WHERE tags @> ARRAY['SQL'];
Qui cerchiamo i corsi che hanno il tag "SQL".
<@ (is contained by) — controlla se un array è contenuto in un altro.
SELECT *
FROM courses
WHERE ARRAY['PostgreSQL', 'SQL'] <@ tags;
Qui cerchiamo i corsi i cui tag includono tutti gli elementi dell’array ARRAY['PostgreSQL', 'SQL'].
&& (overlap) — controlla se ci sono elementi in comune tra due array.
SELECT *
FROM courses
WHERE tags && ARRAY['NoSQL', 'Big Data'];
Questa query trova i corsi che hanno almeno uno dei tag "NoSQL" o "Big Data".
Come aiuta l’indicizzazione?
Immagina di avere una tabella courses con milioni di righe e di lanciare una query con uno degli operatori sopra. Senza indice, PostgreSQL deve controllare ogni riga una per una — roba che può durare una vita (soprattutto se hai la pazienza di un dev che aspetta la compilazione).
Con gli indici, tutto questo si evita. PostgreSQL offre due tipi di indici adatti agli array:
GIN(Generalized Inverted Index) — la scelta migliore per gli array.BTREE— usato per confrontare array interi.
Esempio: Creiamo una tabella con array e testiamo
Creiamo una tabella semplice con array per provare tutto in pratica.
CREATE TABLE courses (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
tags TEXT[] NOT NULL
);
Aggiungiamo qualche riga:
INSERT INTO courses (name, tags)
VALUES
('Basi di SQL', ARRAY['SQL', 'PostgreSQL', 'Basi di dati']),
('Lavorare con Big Data', ARRAY['Hadoop', 'Big Data', 'NoSQL']),
('Sviluppo in Python', ARRAY['Python', 'Web', 'Dati']),
('Corso su PostgreSQL', ARRAY['PostgreSQL', 'Advanced', 'SQL']);
Ecco come appare la tabella:
| id | name | tags |
|---|---|---|
| 1 | Basi di SQL | {SQL, PostgreSQL, Basi di dati} |
| 2 | Lavorare con Big Data | {Hadoop, Big Data, NoSQL} |
| 3 | Sviluppo in Python | {Python, Web, Dati} |
| 4 | Corso su PostgreSQL | {PostgreSQL, Advanced, SQL} |
Senza indice: ricerca lenta
Ora immaginiamo di voler trovare tutti i corsi che hanno il tag SQL.
EXPLAIN ANALYZE
SELECT *
FROM courses
WHERE tags @> ARRAY['SQL'];
La query funziona, ma se hai tanti dati sarà lentissima. PostgreSQL farà uno scan sequenziale (Sequential Scan), cioè controllerà ogni riga della tabella.
Risultato della query:
| id | name | tags |
|---|---|---|
| 1 | Basi di SQL | {SQL, PostgreSQL, Basi di dati} |
| 4 | Corso su PostgreSQL | {PostgreSQL, Advanced, SQL} |
Creazione indice GIN
Per velocizzare la ricerca, creiamo un indice di tipo GIN:
CREATE INDEX idx_courses_tags
ON courses USING GIN (tags);
Riproviamo la stessa query:
EXPLAIN ANALYZE
SELECT *
FROM courses
WHERE tags @> ARRAY['SQL'];
Ora PostgreSQL userà l’indice GIN appena creato, e il tempo di risposta cala di brutto.
Prima si usava lo scan sequenziale (Seq Scan), ora nel piano di esecuzione vedrai Bitmap Index Scan:
| Step | Rows | Cost | Info |
|---|---|---|---|
| Bitmap Index Scan | N | basso | su indice idx_courses_tags |
| Bitmap Heap Scan | N | basso | seleziona le righe dalla tabella |
I valori precisi di Rows e Cost dipendono dai dati, ma la cosa importante è che ora l’indice viene usato.
Come funzionano gli operatori con gli indici?
Esempio 1: Operatore @>
Query:
SELECT *
FROM courses
WHERE tags @> ARRAY['SQL'];
L’indice GIN è perfetto per questo operatore. Postgres controlla velocemente quali righe hanno l’elemento richiesto e ti dà il risultato.
Risultato della query:
| id | name | tags |
|---|---|---|
| 1 | Basi di SQL | {SQL, PostgreSQL, Basi di dati} |
| 4 | Corso su PostgreSQL | {PostgreSQL, Advanced, SQL} |
@> si legge "contiene" — questa query restituisce tutti i corsi che hanno SQL nell’array tags.
Esempio 2: Operatore &&
Query:
SELECT *
FROM courses
WHERE tags && ARRAY['NoSQL', 'Big Data'];
Questo operatore controlla se ci sono elementi in comune tra gli array: restituisce le righe dove tags si incrocia almeno con un elemento dell’array passato.
L’indice GIN fa di nuovo la magia — la ricerca è veloce anche con tanti dati.
Risultato della query:
| id | name | tags |
|---|---|---|
| 2 | Lavorare con Big Data | {Hadoop, Big Data, NoSQL} |
si legge "ha incrocio" — la condizione è vera se almeno uno dei tag coincide.
Indicizzazione e ottimizzazione
Quando lavori con gli array, segui questi consigli:
- Usa indici
GINper cercare dentro gli array. Sono molto più veloci dello scan sequenziale. - Aggiungi indici solo sulle colonne che usi spesso nelle query. Gli indici occupano spazio e rallentano gli insert, quindi non indicizzare tutto a caso.
- Profiling delle query con
EXPLAINeEXPLAIN ANALYZE, così vedi se davvero l’indice viene usato.
Esempi: creare indici per gli array
Vediamo come creare indici per tipi specifici di operazioni sugli array e perché serve davvero nella pratica.
Indice per operatore @>
Supponiamo di avere già questa tabella courses:
| id | name | tags |
|---|---|---|
| 1 | Basi di SQL | {SQL, PostgreSQL, Basi di dati} |
| 2 | Lavorare con Big Data | {Hadoop, Big Data, NoSQL} |
| 3 | Sviluppo in Python | {Python, Web, Dati} |
| 4 | Corso su PostgreSQL | {PostgreSQL, Advanced, SQL} |
Per velocizzare le query con l’operatore @> (array contiene elemento), creiamo un indice GIN:
CREATE INDEX idx_courses_tags_gin
ON courses USING GIN (tags);
Ora facciamo la query:
SELECT *
FROM courses
WHERE tags @> ARRAY['SQL'];
Risultato:
| id | name | tags |
|---|---|---|
| 1 | Basi di SQL | {SQL, PostgreSQL, Basi di dati} |
| 4 | Corso su PostgreSQL | {PostgreSQL, Advanced, SQL} |
Indice per operatori @>, <@, e &&
La tabella è la stessa dell’esempio precedente.
Visto che gli operatori @>, <@ e && funzionano tutti bene con gli indici GIN, puoi creare un unico indice universale che velocizza tutte queste query:
CREATE INDEX idx_tags
ON courses USING GIN (tags);
Esempi di query e risultati:
@>— controlla se l’array contiene gli elementi indicati:
SELECT *
FROM courses
WHERE tags @> ARRAY['SQL'];
| id | name | tags |
|---|---|---|
| 1 | Basi di SQL | {SQL, PostgreSQL, Basi di dati} |
| 4 | Corso su PostgreSQL | {PostgreSQL, Advanced, SQL} |
<@— controlla se l’array è contenuto in un altro array:
SELECT *
FROM courses
WHERE tags <@ ARRAY['SQL', 'PostgreSQL', 'Advanced', 'Big Data', 'NoSQL', 'Python'];
| id | name | tags |
|---|---|---|
| 1 | Basi di SQL | {SQL, PostgreSQL, Basi di dati} |
| 2 | Lavorare con Big Data | {Hadoop, Big Data, NoSQL} |
| 3 | Sviluppo in Python | {Python, Web, Dati} |
| 4 | Corso su PostgreSQL | {PostgreSQL, Advanced, SQL} |
&&— controlla se ci sono elementi in comune tra array:
SELECT *
FROM courses
WHERE tags && ARRAY['NoSQL', 'Big Data'];
| id | name | tags |
|---|---|---|
| 2 | Lavorare con Big Data | {Hadoop, Big Data, NoSQL} |
Proviamo qualcosa di più tosto
Facciamo una query che restituisce i corsi dove tra i tag c’è almeno un incrocio con la lista ['Python', 'SQL', 'NoSQL']:
SELECT *
FROM courses
WHERE tags && ARRAY['Python', 'SQL', 'NoSQL'];
Output:
| id | name | tags |
|---|---|---|
| 1 | Basi di SQL | {SQL,PostgreSQL,Basi di dati} |
| 2 | Lavorare con Big Data | {Hadoop,Big Data,NoSQL} |
| 3 | Sviluppo in Python | {Python,Web,Dati} |
Con l’indice GIN questa query vola, anche con milioni di righe.
Errori tipici con gli array
Indice non usato: se nel risultato di EXPLAIN vedi Seq Scan, controlla che l’indice sia stato creato e che l’operatore che usi supporti davvero l’indicizzazione.
Uso raro dell’array: se la colonna array viene usata poco nelle query o viene aggiornata spesso, l’indice può solo occupare spazio senza portare vantaggi.
Indici inutili: gli indici occupano spazio su disco e rallentano le scritture, quindi crea solo quelli che ti servono davvero e che userai nelle query.
Ora hai tutti gli strumenti per lavorare in modo efficace con gli array in PostgreSQL — velocizza le query con gli operatori @>, <@, && e gli indici GIN. Non aver paura di provarli sui tuoi dati!
GO TO FULL VERSION