04 ago 2025·7 min di lettura

Ricerca ovunque con PostgreSQL: full-text, trigrammi e indici parziali

Scopri come progettare una ricerca “ovunque” con PostgreSQL per schermate interne scegliendo full-text, indici trigram e indici parziali per risultati veloci.

Ricerca ovunque con PostgreSQL: full-text, trigrammi e indici parziali

Che cosa significa davvero “search everywhere” per gli strumenti interni

Su una schermata interna, “search everywhere” di solito significa: “Aiutami a trovare velocemente il record esatto che ho in mente, anche se non lo ricordo perfettamente.” Le persone non stanno navigando: cercano di arrivare subito a un cliente, a un ticket, a una fattura o a un dispositivo.

Per questo una ricerca lenta sembra peggiore di una pagina lenta. Il caricamento di una pagina avviene una volta. La ricerca avviene molte volte di seguito, spesso mentre qualcuno è al telefono o fa triage. Se i risultati richiedono 2–3 secondi, gli utenti cambiano la query, cancellano, provano un altro termine e si finisce con più carico e più frustrazione.

Da una singola casella, gli utenti si aspettano un insieme di comportamenti: corrispondenze parziali ("alex" trova "Alexander"), tolleranza per piccoli refusi ("microsfot" trova ancora "Microsoft"), un ordinamento sensato dei “migliori risultati” (ID o email esatti in cima), un po’ di preferenza per la recenza e filtri che si applicano di default (ticket aperti, clienti attivi).

La parte difficile è che un input spesso nasconde più intenti. Un agente può incollare un numero di ticket, digitare un frammento di nome, cercare un’email o inserire un numero di telefono. Ogni intento richiede una strategia diversa, indici diversi e talvolta una regola di ranking diversa.

Quindi non iniziare dagli indici. Comincia elencando i pochi intenti di ricerca che i tuoi utenti hanno davvero e separa i campi di identità (ID, email) dai campi fuzzy (nomi, oggetti) e dal testo lungo (note).

Inizia nominando i dati e i comportamenti di ricerca

Prima di scegliere un indice, scrivi cosa digitano effettivamente le persone. “PostgreSQL search everywhere” suona come una sola funzionalità, ma in realtà è spesso un mix di ricerche molto diverse.

Gli strumenti interni mescolano identificatori “duri” (order ID, numero ticket, codice fattura) con testo “morbido” (nome cliente, email, note, tag). Quei gruppi si comportano diversamente in PostgreSQL, quindi trattarli tutti allo stesso modo è una strada veloce verso query lente.

Poi separa i comportamenti:

  • Ricerca esatta: qualcuno che cerca TCK-104883 si aspetta un risultato preciso.
  • Ricerca fuzzy: chi digita john smth vuole una corrispondenza permissiva su nomi (e magari email) e si aspetta di scorrere una lista corta.
  • Ricerca guidata da filtri: chi seleziona “Status = Open” e “Assigned to = Me” sta soprattutto filtrando; la casella di testo è secondaria.

Decidi presto se i risultati devono essere ordinati per rilevanza (migliori prima) o semplicemente filtrati. Il ranking conta per note e descrizioni lunghe. Per ID e email, il ranking spesso pare casuale e aggiunge costo.

Una breve checklist è di solito sufficiente:

  • Quali campi vengono cercati quotidianamente?
  • Quali input sono esatti (ID, codici), fuzzy (nomi) o testo lungo (note)?
  • Quali filtri si applicano quasi sempre?
  • Serve un ordinamento “migliore corrispondenza” o qualsiasi match è accettabile?
  • Quanto crescerà la tabella: migliaia, centinaia di migliaia o milioni?

Se definisci queste decisioni dall’inizio, le scelte sugli indici smettono di sembrare un tiro a caso.

Il baseline: corrispondenze esatte e perché spesso ILIKE danneggia

Blocca prima i guadagni facili. Per molte schermate interne, un semplice indice B-tree già dà risultati istantanei per corrispondenze esatte come ID, numeri d’ordine, email e riferimenti esterni.

Se le persone incollano un valore esatto, assicurati che la query sia davvero esatta. WHERE id = ... o WHERE email = ... possono essere estremamente veloci con un indice normale. Un indice unico su email ripaga spesso due volte: velocità e qualità dei dati.

I problemi iniziano quando “search everywhere” si trasforma silenziosamente in ILIKE. Una query come name ILIKE '%ann%' ha un wildcard iniziale, quindi PostgreSQL non può usare un B-tree normale. Finisce per controllare molte righe e rallenta prevedibilmente con la crescita della tabella.

La ricerca per prefisso può funzionare, ma solo quando il pattern è ancorato all’inizio: name ILIKE 'ann%'. Anche qui i dettagli contano (collation, gestione delle maiuscole, e se hai indicizzato la stessa espressione che interroghi). Se la tua UI deve essere case-insensitive, un approccio comune è interrogare lower(name) e creare un indice corrispondente su lower(name).

Aiuta anche mettersi d’accordo su cosa significhi “reattivo”:

  • Circa 200 ms o meno per il lavoro del database su cache calda
  • Sotto 1 secondo end-to-end includendo rete e rendering
  • Nessuno stato di caricamento visibile per ricerche comuni

Con obiettivi simili è più facile decidere se restare su corrispondenze esatte e prefisso o se è il momento di full-text o indici trigram.

Quando la full-text è lo strumento giusto

La full-text è la scelta migliore quando le persone digitano linguaggio naturale e si aspettano che il sistema trovi gli elementi giusti, non solo corrispondenze esatte. Pensa a messaggi di ticket, note interne, descrizioni lunghe, articoli della knowledge base e log delle chiamate.

Il grande vantaggio è il ranking. Invece di restituire una lunga lista dove il miglior risultato è sepolto, la full-text può ordinare per rilevanza. Negli strumenti interni questo conta: qualcuno ha bisogno di una risposta in pochi secondi, non dopo aver scansionato 50 righe.

A grandi linee, la full-text ha tre parti mobili:

  • Un tsvector (il testo ricercabile, memorizzato o generato)
  • Un tsquery (ciò che l'utente ha digitato, convertito in query)
  • Una configurazione di lingua (come vengono normalizzate le parole)

La configurazione di lingua è dove il comportamento diventa visibile. PostgreSQL rimuove stop word comuni (come “the” o “and”) e applica lo stemming, così “pay”, “paid” e “payment” possono corrispondere. Questo è ottimo per note e messaggi, ma può sorprendere quando si cerca una parola corta e comune e non si ottiene nulla.

I sinonimi sono un altro punto decisionale. Aiutano quando la tua azienda usa parole diverse per la stessa cosa (per esempio, “refund” vs “chargeback”), ma richiedono manutenzione. Mantieni la lista di sinonimi corta e basata su cosa digitano davvero support e ops.

Un esempio pratico: cercare “can’t login after reset” dovrebbe tirare fuori ticket dove il messaggio dice “cannot log in after password reset”, anche se la formulazione differisce. Questo comportamento “trova il rilevante” è ciò per cui è pensata la full-text e spesso è preferibile tentare di far comportare ILIKE come un motore di ricerca.

Quando gli indici trigram vincono

Possiedi il tuo stack
Mantieni il controllo completo con l'export del sorgente per il self-hosting e personalizzazioni approfondite.
Esporta sorgente

Gli indici trigram sono una scelta forte quando gli utenti digitano frammenti, fanno refusi o ricordano solo “qualcosa del genere”. Brillano su campi di testo brevi dove la full-text è troppo rigida: nomi di persone, nomi di aziende, soggetti di ticket, SKU, numeri d'ordine e codici prodotto.

Un trigram è un blocco di 3 caratteri. PostgreSQL confronta due stringhe contando quanti trigram condividono. Per questo può collegare "Jon Smth" a "John Smith", o "ACM" a "ACME", e trovare risultati quando la query è nel mezzo di una parola.

Spesso questa è la strada più veloce per una casella “PostgreSQL search everywhere” indulgente quando il compito è “trova la riga giusta”, non “trova documenti su un argomento”.

Dove supera la full-text

La full-text è ottima per testo lungo e per ranking semantico, ma non gestisce naturalmente stringhe parziali e piccoli refusi su campi brevi. La ricerca trigram è costruita per quel tipo di fuzziness.

Mantieni ragionevole il costo in scrittura

Gli indici trigram sono più grandi e aggiungono overhead sulle scritture, quindi sii selettivo. Indicizza le colonne che le persone usano davvero:

  • Nome, email, azienda, username
  • Identificatori brevi (SKU, codice, riferimento)
  • Un campo titolo conciso (non un lungo campo note/commenti)

Se riesci a nominare i campi esatti che il tuo team digita nella casella di ricerca, di solito puoi mantenere l'indicizzazione trigram piccola e veloce.

Indici parziali per i filtri che le persone usano davvero

Estendi la ricerca con l'AI
Aggiungi integrazioni AI quando ti servono ricerche interne più intelligenti o workflow di triage ticket.
Costruisci con AI

Una casella “search everywhere” di solito ha default nascosti. Le persone cercano dentro uno workspace, su elementi attivi e con i cancellati esclusi. Se quei filtri sono presenti in quasi ogni richiesta, rendi il caso comune veloce indicizzando solo le righe che li soddisfano.

Un indice parziale è un indice normale con una clausola WHERE. PostgreSQL lo mantiene più piccolo perché conserva solo le voci per le righe che ti interessano di più. Questo spesso significa meno pagine da leggere e migliori hit di cache.

Target comuni per indici parziali includono righe attive (status = 'active'), soft delete (deleted_at IS NULL), scoping per tenant e finestre “recenti” (per esempio ultimi 90 giorni).

La chiave è far combaciare l'indice con la tua UI. Se la schermata nasconde sempre i cancellati, le tue query dovrebbero sempre includere deleted_at IS NULL e il tuo partial index dovrebbe usare la stessa condizione. Piccole incongruenze, come usare is_deleted = false in un posto e deleted_at IS NULL in un altro, possono impedire al planner di usare l'indice.

Gli indici parziali funzionano anche insieme a full-text e trigram. Per esempio, indicizzare la ricerca testuale solo per righe non cancellate mantiene le dimensioni dell'indice sotto controllo.

Trade-off: gli indici parziali aiutano meno per query rare. Se qualcuno occasionalmente cerca tra i record cancellati o in tutti i workspace, PostgreSQL può ricadere su un piano più lento. Gestiscilo con un percorso admin-only separato, o aggiungi un secondo indice solo se la query rara diventa comune.

Mescolare approcci senza trasformare la ricerca in un mistero

La maggior parte dei team finisce per mescolare tecniche perché una casella di ricerca deve coprire intenti diversi. L'obiettivo è rendere chiaro l'ordine delle operazioni così i risultati sembrino prevedibili.

Una semplice priorità aiuta, che la implementi come query separate o come una query con logica CASE chiara.

Una scala di priorità prevedibile

Inizia stretti, poi diventa più permissivo solo se necessario:

  • Prima corrispondenza esatta (ID, email, numero ticket, SKU) usando indici B-tree
  • Poi match per prefisso dove ha senso
  • Poi match trigram per refusi e frammenti su nomi e titoli
  • Infine full-text per note più lunghe, descrizioni e contenuti liberi

Se mantieni la stessa scala, gli utenti imparano cosa “significa” la casella. Non penseranno più che il sistema sia rotto quando “12345” trova un ticket istantaneamente mentre “refund policy” ricerca nel testo più lungo.

Filtra prima, poi fuzzy

La ricerca fuzzy diventa costosa quando deve considerare l'intera tabella. Restringi il set di candidati con i filtri che le persone usano davvero (status, team assegnato, intervallo di date, account), poi esegui trigram o full-text su quello che resta. Anche un indice trigram veloce può sembrare lento se gli chiedi di valutare milioni di righe.

Vale anche la pena scrivere una regola in una frase che i non tecnici possano comprendere, per esempio: “Corrispondi prima il numero ticket esatto, poi il nome cliente con tolleranza agli errori, poi cerca nelle note.” Quella definizione condivisa previene discussioni su perché una riga è apparsa.

Passo dopo passo: scegli un approccio e implementalo in sicurezza

Trasforma le regole di ricerca in UI
Progetta tabelle nel Data Designer, quindi integra flussi di ricerca esatti, fuzzy e full-text.
Inizia a costruire

Una casella “search everywhere” veloce è un insieme di piccole decisioni. Scrivile prima e il lavoro sul database diventa più semplice.

  1. Definisci gli input. È solo una casella, o una casella più filtri (status, owner, intervallo di date)?
  2. Scegli i tipi di match per campo. ID e codici vogliono corrispondenze esatte. Nomi ed email spesso richiedono prefisso o fuzzy. Note lunghe e descrizioni stanno meglio con la ricerca in linguaggio naturale.
  3. Aggiungi gli indici giusti e conferma che vengono usati. Crea l'indice, poi controlla la query reale con EXPLAIN (ANALYZE, BUFFERS).
  4. Aggiungi ranking o ordinamento che combaci con l'intento. Se gli utenti digitano “invoice 1042”, le corrispondenze esatte dovrebbero risalire. Se digitano un nome sbagliato, il ranking per similarità dovrebbe vincere.
  5. Testa con query reali. Prova refusi, termini molto corti (come “al”), testo lungo incollato, input vuoto e modalità “solo filtri”.

Per distribuire in sicurezza, cambia una cosa alla volta e rendi semplice il rollback. Per nuovi indici su tabelle grandi, preferisci CREATE INDEX CONCURRENTLY così non blocchi le scritture. Se possibile, rilascia dietro feature flag e confronta la latenza prima e dopo.

Un pattern pratico per “PostgreSQL search everywhere” è: prima corrispondenza esatta (veloce e precisa), poi matching trigram per campi “umani” dove si sbaglia, e full-text per testo lungo che beneficia del ranking.

Un esempio realistico: una casella di ricerca in un pannello admin support

Immagina un pannello admin di support dove il team ha una casella di ricerca ma si aspetta che trovi clienti, ticket e anche note. Questo è il classico problema “un input, molti significati”.

La prima vittoria è rendere l'intento visibile senza aggiungere attrito. Se la query sembra un’email o un numero di telefono, trattala come lookup cliente. Se sembra un ID ticket (per esempio, "TKT-10482"), instradala direttamente ai ticket. Tutto il resto ricade sulla ricerca testuale su oggetto ticket e note.

Per lookup cliente, gli indici trigram di solito sembrano i migliori. Nomi e aziende sono disordinati e le persone digitano frammenti. Un indice trigram può rendere veloci ricerche come “jon smi” o “acm”.

Per le note dei ticket, usa la full-text. Le note sono frasi vere e di solito vuoi corrispondenze rilevanti, non solo “contiene questa sottostringa”. Il ranking aiuta quando dozzine di ticket menzionano la stessa parola chiave.

I filtri contano più di quanto molti team si aspettino. Se gli agenti vivono in “open tickets”, aggiungi un indice parziale che copra soltanto le righe aperte. Fai lo stesso per “clienti attivi”. Mantiene gli indici più piccoli e rende il percorso comune veloce.

Le query molto corte meritano regole, altrimenti il database fa lavoro costoso per rumore:

  • 1–2 caratteri: mostra ticket aperti recenti e clienti aggiornati di recente
  • 3+ caratteri: esegui trigram per i campi cliente e full-text per il testo dei ticket
  • Nessun intento chiaro: mostra una lista mista, ma limita ogni gruppo (per esempio 10 clienti e 10 ticket)

Errori comuni che rendono la ricerca lenta o confusa

Prototipa una ricerca multi-intento
Prototipa una singola casella di ricerca che instradi ID, email e nomi al percorso di query corretto.
Crea prototipo

La maggior parte dei problemi “perché la ricerca è lenta?” è auto-inflitta. L'obiettivo non è indicizzare tutto, ma indicizzare ciò che le persone fanno davvero.

Una trappola comune è aggiungere indici su molte colonne “nel caso serva”. Le letture possono migliorare, ma ogni insert e update ha lavoro extra. In strumenti interni dove i record cambiano tutto il giorno (ticket, ordini, utenti), la velocità in scrittura conta.

Un altro errore è usare la full-text quando ciò che serve è una ricerca tollerante ai refusi su nomi o email. La full-text è ottima per documenti e descrizioni. Non è una bacchetta magica per “Jon” vs “John” o “gmail.con” vs “gmail.com.” Quello è spesso territorio trigram.

I filtri possono anche rompere silenziosamente il piano. Se la maggior parte delle ricerche avviene con un filtro fisso (come status = 'open' o org_id = 42), il miglior indice potrebbe essere un partial index che corrisponde a quella condizione. Se te lo dimentichi, PostgreSQL potrebbe scansionare molte più righe di quanto ti aspetti.

Alcuni errori ricorrenti:

  • Aggiungere molti indici senza misurare il costo in scrittura
  • Aspettarsi che la full-text si comporti come un autocomplete tollerante ai refusi
  • Ignorare come i filtri comuni cambiano quale indice può essere usato
  • Testare su dati piccoli e puliti invece che su dati realistici (frequenza dei termini vs ID comuni)
  • Ordinare per una colonna senza indice di supporto, costringendo una sort lenta

Esempio: una schermata di support cerca ticket per oggetto, nome cliente e numero ticket, poi ordina per ultima attività. Se latest_activity_at non è indicizzato per il sottoinsieme filtrato (per esempio ticket aperti), quella sort può annullare la velocità guadagnata dall'indice di ricerca.

Controlli rapidi prima del rilascio

Rilascia un pannello admin reale
Crea un pannello admin per support o ops che trova rapidamente ticket, clienti e note.
Crea app admin

Prima di dichiarare finita una funzione “search everywhere”, sii concreto sul comportamento che prometti.

  • Le persone cercano un record con un identificatore esatto (numero ticket, email)?
  • Si aspettano matching fuzzy per refusi?
  • Vogliono risultati ordinati per rilevanza da note e descrizioni più lunghe?

Se mescoli le modalità, decidi quale prevale in caso di conflitto.

Poi identifica i 2–3 campi che guidano la maggior parte delle ricerche. Se l'80% delle ricerche è per email, nome e ID ticket, ottimizza prima quelli e considera il resto secondario.

Una breve checklist pre-ship:

  • Conferma la modalità principale di match per campo (lookup esatto, fuzzy o testo indicizzato)
  • Elenca i filtri che gli utenti applicano quotidianamente e assicurati che gli indici corrispondano a quelle combinazioni
  • Decidi come gestire ricerche molto corte e input vuoto (per esempio richiedere 2–3 caratteri per la ricerca fuzzy; mostrare “recenti” per l'input vuoto)
  • Rendi l'ordinamento spiegabile: più recenti, miglior match testuale o una semplice regola combinata

Infine, testa con dimensioni e tempi realistici, non solo per correttezza. Una query che sembra istantanea con 1.000 righe può rallentare con 1.000.000.

Passi successivi: trasforma il piano in una schermata di ricerca interna veloce

Una casella di ricerca resta veloce quando il team concorda su cosa deve fare. Scrivi le regole in linguaggio semplice: cosa significa “corrisponde” (esatto, prefisso, tolleranza ai refusi), quali campi vengono cercati e come i filtri cambiano il set di risultati.

Mantieni un piccolo set di test con ricerche reali e usalo come suite di regressione. Dieci-venti query sono in genere sufficienti: un paio di nomi comuni, qualche email parziale, un refuso, un frammento di nota lungo e un caso di “nessun risultato”. Esegui questi test prima e dopo le modifiche così il lavoro sulle performance non rompe silenziosamente la rilevanza.

Se costruisci strumenti interni con AppMaster (appmaster.io), aiuta definire quelle regole di ricerca insieme al modello dati e alla logica di business, così il comportamento UI e le scelte sul database non divergono man mano che i requisiti cambiano.

FAQ

Cosa significa di solito “search everywhere” in uno strumento interno?

Trattala come “trova rapidamente il record esatto che intendo”, non come navigazione. Inizia annotando le poche intenzioni reali degli utenti (ricerca per ID, ricerca per nome/email con errori, ricerca in note lunghe) e i filtri predefiniti che usano quasi sempre. Quelle decisioni ti diranno quali query eseguire e quali indici valgono il costo.

Perché `ILIKE '%...%'` rende lenta la ricerca?

ILIKE '%term%' ha un carattere jolly iniziale, quindi PostgreSQL in genere non può usare un normale indice B-tree e finisce per scansionare molte righe. Può andare bene su tabelle piccole, ma rallenta sensibilmente con la crescita dei dati. Se ti serve matching su sottostringhe o tolleranza agli errori, pianifica trigram o full-text invece di aspettarti che ILIKE scalI.

Qual è il modo più veloce per gestire ricerche esatte come ID o email?

Usa confronti esatti come WHERE id = $1 o WHERE email = $1 e appoggiali a un indice B-tree (spesso unico per email o codici). Le ricerche esatte sono le più economiche e rendono i risultati prevedibili. Se l'utente incolla un numero di ticket o un'email completa, instrada prima su questa strada.

Come faccio una ricerca prefisso case-insensitive senza rompere gli indici?

Preferisci un pattern di prefisso come name ILIKE 'ann%' e mantieni coerenza con l'indice. Per comportamento case-insensitive affidabile, molti team interrogano lower(name) e creano un indice sulla stessa espressione così il planner lo può usare. Se non puoi ancorare il pattern all'inizio, la ricerca per prefisso non basta.

Quando dovrei usare gli indici trigram per una casella di ricerca?

Usa l'indicizzazione trigram quando gli utenti digitano frammenti, fanno piccoli errori o ricordano solo “qualcosa di simile”, specialmente su campi brevi come nomi, soggetti, codici e username. Funziona bene per match a metà stringa e near-match come refuso. Sii selettivo sulle colonne da indicizzare perché gli indici trigram sono più grandi e aggiungono overhead in scrittura.

Quando la full-text di PostgreSQL è la scelta migliore?

Usa la full-text quando le persone cercano frasi o parole chiave in contenuti più lunghi come note, messaggi, descrizioni o knowledge-base. Il suo grande vantaggio è il ranking di rilevanza, così i migliori risultati salgono in cima invece di costringere l'utente a scorrere una lunga lista. Aspettati comportamenti di lingua come stemming e rimozione di stop-word, utili per il testo ma che possono sorprendere su parole molto brevi e comuni.

Come aiutano gli indici parziali le schermate “search everywhere"?

Aggiungi indici parziali quando la maggior parte delle ricerche include gli stessi filtri, come deleted_at IS NULL, status = 'open' o un vincolo tenant/workspace. Poiché l'indice copre solo il sottoinsieme comune, resta più piccolo e spesso più veloce nei carichi reali. Assicurati che le tue query usino esattamente la stessa condizione del partial index, altrimenti PostgreSQL potrebbe ignorarlo.

Come posso combinare ricerca esatta, trigram e full-text senza confondere gli utenti?

Usa una scala di priorità coerente così i risultati risultano stabili: prima esatto per ID/email, poi prefisso dove ha senso, poi trigram per nomi/titoli permissivi, e infine full-text per note e descrizioni lunghe. Applica i filtri predefiniti subito per ridurre le righe che la ricerca fuzzy deve considerare. Questo mantiene prestazioni e rilevanza non casuali con l'aumentare dei dati.

Cosa dovrei fare con ricerche di 1–2 caratteri o input vuoto?

Stabilisci regole semplici come richiedere 3+ caratteri prima di eseguire la ricerca fuzzy e usa query brevi per mostrare record recenti o frequentemente usati. Input molto brevi generano rumore e possono innescare lavoro costoso a basso valore. Decidi anche come gestire l'input vuoto in modo che l'interfaccia non colpisca il database con query “match everything”.

Come valido le performance e rilascio in sicurezza le modifiche alla ricerca?

Crea l'indice e poi verifica la query reale con EXPLAIN (ANALYZE, BUFFERS) su dati realistici, non solo su un dataset di sviluppo. Rilascia le modifiche una per volta e tieni facile il rollback; su tabelle grandi, costruisci nuovi indici con CREATE INDEX CONCURRENTLY per non bloccare le scritture. Se costruisci la schermata in AppMaster (appmaster.io), definisci le regole di ricerca insieme al modello dati e alla logica di business così il comportamento UI resta coerente con il cambiare dei requisiti.

Facile da avviare
Creare qualcosa di straordinario

Sperimenta con AppMaster con un piano gratuito.
Quando sarai pronto potrai scegliere l'abbonamento appropriato.

Iniziare