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