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

Bruger samme kolonne flere gange i WHERE-sætning

Dette er et tilfælde af relationel opdeling. Jeg tilføjede tagget.

Indekser

Forudsat en PK eller UNIQUE begrænsning på USER_PROPERTY_MAP(property_value_id, user_id) - kolonner i denne rækkefølge for at gøre mine forespørgsler hurtige. Relateret:

  • Er et sammensat indeks også godt til forespørgsler i det første felt?

Du bør også have et indeks på PROPERTY_VALUE(value, property_name_id, id) . Igen, kolonner i denne rækkefølge. Tilføj den sidste kolonne id kun hvis du får kun indeksscanninger ud af det.

For et givet antal ejendomme

Der er mange måder at løse det på. Dette burde være en af ​​de enkleste og hurtigste for præcis to egenskaber:

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øger ikke tabellen PROPERTY_NAME , da du tilsyneladende allerede har løst ejendomsnavne til ID'er ifølge din eksempelforespørgsel. Ellers kan du tilføje en deltagelse til PROPERTY_NAME i hver underforespørgsel.

Vi har samlet et arsenal af teknikker under dette relaterede spørgsmål:

  • Sådan filtreres SQL-resultater i en har-mange-gennem-relation

For et ukendt antal ejendomme

@Mike og @Valera har meget nyttige spørgsmål i deres respektive svar. For at gøre dette endnu mere dynamisk :

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

Tilføj/fjern kun rækker fra VALUES udtryk. Eller fjern WITH klausulen og JOIN for ingen egenskabsfiltre overhovedet.

problemet med denne klasse af forespørgsler (tæller alle delvise matches) er ydeevne . Min første forespørgsel er mindre dynamisk, men typisk betydeligt hurtigere. (Bare test med EXPLAIN ANALYZE .) Især til større borde og et voksende antal ejendomme.

Det bedste fra begge verdener?

Denne løsning med en rekursiv CTE burde være et godt kompromis:hurtig og 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 her

Manualen om rekursive CTE'er.

Den ekstra kompleksitet betaler sig ikke for små borde, hvor de ekstra omkostninger opvejer enhver fordel, eller forskellen er ubetydelig til at begynde med. Men den skalerer meget bedre og er i stigende grad overlegen i forhold til "tælle"-teknikker med væksttabeller og et voksende antal ejendomsfiltre.

Tælleteknikker skal besøge alle rækker i user_property_map for alle givne egenskabsfiltre, mens denne forespørgsel (såvel som den første forespørgsel) kan eliminere irrelevante brugere tidligt.

Optimering af ydeevne

Med aktuel tabelstatistik (rimelige indstillinger, autovacuum kører), har Postgres viden om "mest almindelige værdier" i hver kolonne og vil omarrangere joinforbindelser i 1. forespørgsel at evaluere de mest selektive egenskabsfiltre først (eller i det mindste ikke de mindst selektive). Op til en vis grænse:join_collapse_limit . Relateret:

  • Postgresql join_collapse_limit og tid til forespørgselsplanlægning
  • Hvorfor bremser en lille ændring i søgetermen forespørgslen så meget?

Denne "deus-ex-machina"-intervention er ikke mulig med 3. forespørgsel (rekursiv CTE). For at hjælpe på ydeevnen (muligvis meget) skal du først selv placere mere selektive filtre. Men selv med de værste tilfælde vil den stadig overgå optællingsforespørgsler.

Relateret:

  • Tjek statistikmål i PostgreSQL

Meget mere blodige detaljer:

  • PostgreSQL delvist indeks er ubrugt, når det oprettes på en tabel med eksisterende data

Mere forklaring i manualen:

  • Statistik brugt af planlæggeren


  1. Videregivelse af kolonnenavne dynamisk for en postvariabel i PostgreSQL

  2. ClusterControl - Advanced Backup Management - mariabackup del I

  3. PostgreSQL Anonymisering On Demand

  4. Udforskning af lavprioritetslås-venteindstillinger i SQL Server 2014 CTP1