sql >> Databasteknik >  >> RDS >> PostgreSQL

Använder samma kolumn flera gånger i WHERE-satsen

Detta är ett fall av relationell uppdelning. Jag lade till taggen.

Index

Antag en PK- eller UNIQUE-begränsning på USER_PROPERTY_MAP(property_value_id, user_id) - kolumner i denna ordning för att göra mina frågor snabba. Relaterat:

  • Är ett sammansatt index också bra för frågor i det första fältet?

Du bör också ha ett index på PROPERTY_VALUE(value, property_name_id, id) . Återigen, kolumner i denna ordning. Lägg till den sista kolumnen id bara om du får enbart index-skanningar av den.

För ett givet antal fastigheter

Det finns många sätt att lösa det. Detta borde vara en av de enklaste och snabbaste för exakt två egenskaper:

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'

Besöker inte tabellen PROPERTY_NAME , eftersom du verkar ha löst egendomsnamn till ID redan, enligt din exempelfråga. Annars kan du lägga till en anslutning till PROPERTY_NAME i varje underfråga.

Vi har samlat en arsenal av tekniker under denna relaterade fråga:

  • Hur man filtrerar SQL-resultat i en har-många-genom-relation

För ett okänt antal fastigheter

@Mike och @Valera har mycket användbara frågor i sina respektive svar. För att göra detta ännu mer dynamiskt :

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);

Lägg bara till/ta bort rader från VALUES uttryck. Eller ta bort WITH satsen och JOIN för inga egenskapsfilter överhuvudtaget.

problemet med den här klassen av frågor (räknas alla partiella matchningar) är prestanda . Min första fråga är mindre dynamisk, men vanligtvis betydligt snabbare. (Testa bara med EXPLAIN ANALYZE .) Speciellt för större bord och ett växande antal fastigheter.

Bäst av två världar?

Denna lösning med en rekursiv CTE borde vara en bra kompromiss:snabb och dynamisk:

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 här

Manualen om rekursiva CTE.

Den ökade komplexiteten lönar sig inte för små bord där den extra omkostnaden uppväger alla fördelar eller skillnaden är försumbar till att börja med. Men den skalar mycket bättre och är allt mer överlägsen "räknetekniker" med växande tabeller och ett växande antal egenskapsfilter.

Räknetekniker måste besöka alla rader i user_property_map för alla givna egenskapsfilter, medan den här frågan (liksom den första frågan) kan eliminera irrelevanta användare tidigt.

Optimera prestanda

Med aktuell tabellstatistik (rimliga inställningar, autovacuum löpning), har Postgres kunskap om "vanligaste värden" i varje kolumn och kommer att omordna kopplingar i första frågan att utvärdera de mest selektiva egenskapsfiltren först (eller åtminstone inte de minst selektiva). Upp till en viss gräns:join_collapse_limit . Relaterat:

  • Postgresql join_collapse_limit och tid för frågeplanering
  • Varför saktar en liten förändring i söktermen ned frågan så mycket?

Denna "deus-ex-machina"-intervention är inte möjlig med tredje frågan (rekursiv CTE). För att hjälpa prestanda (möjligen mycket) måste du först placera mer selektiva filter själv. Men även med den värsta beställningen kommer den fortfarande att överträffa räkningsfrågor.

Relaterat:

  • Kontrollera statistikmål i PostgreSQL

Mycket mer blodiga detaljer:

  • PostgreSQL partiellt index används inte när det skapas i en tabell med befintliga data

Mer förklaring i manualen:

  • Statistik som används av planeraren


  1. Hur man schemalägger en lagrad procedur i MySQL

  2. SQLite grupperar efter/antal timmar, dagar, veckor, år

  3. JDBC SQLServerException:Den här drivrutinen är inte konfigurerad för integrerad autentisering.

  4. Rumsmigrering Ändra tabell lägger inte till ny kolumn och migrerar blir anropade igen och igen