PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Utilizzo della stessa colonna più volte nella clausola WHERE

Questo è un caso di divisione relazionale. Ho aggiunto il tag.

Indici

Assumendo un vincolo PK o UNIQUE su USER_PROPERTY_MAP(property_value_id, user_id) - colonne in questo ordine per velocizzare le mie query. Correlati:

  • Un indice composito va bene anche per le query sul primo campo?

Dovresti anche avere un indice su PROPERTY_VALUE(value, property_name_id, id) . Ancora una volta, colonne in questo ordine. Aggiungi l'ultima colonna id solo se ottieni scansioni solo indice.

Per un determinato numero di proprietà

Ci sono molti modi per risolverlo. Questo dovrebbe essere uno dei più semplici e veloci per esattamente due proprietà:

SELECT u.*
FROM   users             u
JOIN   user_property_map up1 ON up1.user_id = u.id
JOIN   user_property_map up2 USING (user_id)
WHERE  up1.property_value_id =
      (SELECT id FROM property_value WHERE property_name_id = 1 AND value = '101')
AND    up2.property_value_id =
      (SELECT id FROM property_value WHERE property_name_id = 2 AND value = '102')
-- AND    u.user_name = 'user1'  -- more filters?
-- AND    u.city = 'city1'

Non sto visitando la tabella PROPERTY_NAME , poiché sembra che tu abbia già risolto i nomi delle proprietà in ID, secondo la tua query di esempio. Altrimenti potresti aggiungere un join a PROPERTY_NAME in ogni sottoquery.

Abbiamo assemblato un arsenale di tecniche sotto questa domanda correlata:

  • Come filtrare i risultati SQL in una relazione ha molti-attraverso

Per un numero imprecisato di proprietà

@Mike e @Valera hanno domande molto utili nelle rispettive risposte. Per renderlo ancora più dinamico :

WITH input(property_name_id, value) AS (
      VALUES  -- provide n rows with input parameters here
        (1, '101')
      , (2, '102')
      -- more?
      ) 
SELECT *
FROM   users u
JOIN  (
   SELECT up.user_id AS id
   FROM   input
   JOIN   property_value    pv USING (property_name_id, value)
   JOIN   user_property_map up ON up.property_value_id = pv.id
   GROUP  BY 1
   HAVING count(*) = (SELECT count(*) FROM input)
   ) sub USING (id);

Aggiungi/rimuovi solo righe da VALUES espressione. Oppure rimuovi il WITH clausola e il JOIN per nessun filtro proprietà affatto.

Il problema con questa classe di query (contando tutte le corrispondenze parziali) è performance . La mia prima query è meno dinamica, ma in genere notevolmente più veloce. (Basta testare con EXPLAIN ANALYZE .) Soprattutto per tavoli più grandi e un numero crescente di proprietà.

Il meglio di entrambi i mondi?

Questa soluzione con un CTE ricorsivo dovrebbe essere un buon compromesso:veloce e dinamico:

WITH RECURSIVE input AS (
   SELECT count(*)     OVER () AS ct
        , row_number() OVER () AS rn
        , *
   FROM  (
      VALUES  -- provide n rows with input parameters here
        (1, '101')
      , (2, '102')
      -- more?
      ) i (property_name_id, value)
   )
 , rcte AS (
   SELECT i.ct, i.rn, up.user_id AS id
   FROM   input             i
   JOIN   property_value    pv USING (property_name_id, value)
   JOIN   user_property_map up ON up.property_value_id = pv.id
   WHERE  i.rn = 1

   UNION ALL
   SELECT i.ct, i.rn, up.user_id
   FROM   rcte              r
   JOIN   input             i ON i.rn = r.rn + 1
   JOIN   property_value    pv USING (property_name_id, value)
   JOIN   user_property_map up ON up.property_value_id = pv.id
                              AND up.user_id = r.id
   )
SELECT u.*
FROM   rcte  r
JOIN   users u USING (id)
WHERE  r.ct = r.rn;          -- has all matches

dbfiddle qui

Il manuale sui CTE ricorsivi.

La complessità aggiunta non paga per tavoli piccoli in cui le spese generali aggiuntive superano qualsiasi vantaggio o la differenza è trascurabile per cominciare. Ma si adatta molto meglio ed è sempre più superiore alle tecniche di "conteggio" con tabelle in aumento e un numero crescente di filtri di proprietà.

Le tecniche di conteggio devono visitare tutte righe in user_property_map per tutti i filtri di proprietà specificati, mentre questa query (così come la prima query) può eliminare in anticipo gli utenti irrilevanti.

Ottimizzazione delle prestazioni

Con le statistiche attuali della tabella (impostazioni ragionevoli, autovacuum in esecuzione), Postgres conosce i "valori più comuni" in ogni colonna e riordinerà i join nella 1a query valutare prima i filtri di proprietà più selettivi (o almeno non quelli meno selettivi). Fino a un certo limite:join_collapse_limit . Correlati:

  • Postgresql join_collapse_limit e tempo per la pianificazione delle query
  • Perché una leggera modifica nel termine di ricerca rallenta così tanto la query?

Questo intervento "deus-ex-machina" non è possibile con la 3a query (CTE ricorsiva). Per migliorare le prestazioni (forse molto) devi prima posizionare tu stesso filtri più selettivi. Ma anche con l'ordine nel peggiore dei casi, supererà comunque il conteggio delle query.

Correlati:

  • Controlla i target delle statistiche in PostgreSQL

Dettagli molto più cruenti:

  • Indice parziale PostgreSQL non utilizzato quando viene creato su una tabella con dati esistenti

Maggiori spiegazioni nel manuale:

  • Statistiche utilizzate dal pianificatore