Dit is een geval van relationele verdeling. Ik heb het label toegevoegd.
Indexen
Uitgaande van een PK- of UNIQUE-beperking op USER_PROPERTY_MAP(property_value_id, user_id)
- kolommen in deze volgorde om mijn vragen snel te maken. Gerelateerd:
- Is een samengestelde index ook goed voor zoekopdrachten op het eerste veld?
U moet ook een index hebben op PROPERTY_VALUE(value, property_name_id, id)
. Nogmaals, kolommen in deze volgorde. Voeg de laatste kolom toe id
alleen als u er alleen-index scans van krijgt.
Voor een bepaald aantal woningen
Er zijn veel manieren om het op te lossen. Dit zou een van de eenvoudigste en snelste moeten zijn voor precies twee eigenschappen:
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'
Geen bezoek aan tabel PROPERTY_NAME
, aangezien u volgens uw voorbeeldquery al eigenschapnamen naar ID's lijkt te hebben omgezet. Anders zou je een join kunnen toevoegen aan PROPERTY_NAME
in elke subquery.
We hebben een arsenaal aan technieken verzameld onder deze gerelateerde vraag:
- SQL-resultaten filteren in een heeft-veel-door-relatie
Voor een onbekend aantal woningen
@Mike en @Valera hebben zeer nuttige vragen in hun respectievelijke antwoorden. Om dit nog dynamisch te maken :
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);
Alleen rijen toevoegen/verwijderen van de VALUES
uitdrukking. Of verwijder de WITH
clausule en de JOIN
voor geen eigenschapsfilters helemaal niet.
Het probleem met deze klasse van zoekopdrachten (waarbij alle gedeeltelijke overeenkomsten worden geteld) is prestaties . Mijn eerste query is minder dynamisch, maar meestal aanzienlijk sneller. (Test gewoon met EXPLAIN ANALYZE
.) Vooral voor grotere tafels en een groeiend aantal eigendommen.
Het beste van twee werelden?
Deze oplossing met een recursieve CTE zou een goed compromis moeten zijn:snel en dynamisch:
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 hier
De handleiding over recursieve CTE's.
De extra complexiteit loont niet voor kleine tafels waar de extra overhead opweegt tegen enig voordeel of het verschil te verwaarlozen is om mee te beginnen. Maar het schaalt veel beter en is in toenemende mate superieur aan 'teltechnieken' met groeiende tabellen en een groeiend aantal eigenschapsfilters.
Teltechnieken moeten alles bezoeken rijen in user_property_map
voor alle gegeven eigenschapsfilters, terwijl deze zoekopdracht (evenals de eerste zoekopdracht) irrelevante gebruikers vroegtijdig kan elimineren.
Prestaties optimaliseren
Met huidige tabelstatistieken (redelijke instellingen, autovacuum
actief), heeft Postgres kennis over "meest voorkomende waarden" in elke kolom en zal de joins opnieuw ordenen in de 1e zoekopdracht om eerst de meest selectieve eigenschapsfilters te evalueren (of in ieder geval niet de minst selectieve). Tot een bepaalde limiet:join_collapse_limit
. Gerelateerd:
- Postgresql join_collapse_limit en tijd voor het plannen van query's
- Waarom vertraagt een kleine verandering in de zoekterm de zoekopdracht zo veel?
Deze "deus-ex-machina"-interventie is niet mogelijk met de 3e vraag (recursieve CTE). Om de prestaties (misschien veel) te helpen, moet je eerst zelf selectievere filters plaatsen. Maar zelfs in het slechtste geval zal het nog steeds beter presteren dan het tellen van zoekopdrachten.
Gerelateerd:
- Controleer statistische doelen in PostgreSQL
Veel meer bloederige details:
- PostgreSQL gedeeltelijke index ongebruikt wanneer gemaakt op een tabel met bestaande gegevens
Meer uitleg in de handleiding:
- Statistieken gebruikt door de planner