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