Toto je případ relačního dělení. Přidal jsem značku.
Indexy
Za předpokladu omezení PK nebo UNIQUE na USER_PROPERTY_MAP(property_value_id, user_id)
- sloupce v tomto pořadí, aby mé dotazy byly rychlé. Související:
- Je složený index vhodný také pro dotazy na první pole?
Měli byste mít také index na PROPERTY_VALUE(value, property_name_id, id)
. Opět sloupce v tomto pořadí. Přidejte poslední sloupec id
pouze pokud z něj získáte skenování pouze pro index.
Pro daný počet vlastností
Existuje mnoho způsobů, jak to vyřešit. Ten by měl být jedním z nejjednodušších a nejrychlejších pro přesně dva vlastnosti:
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'
Tabulka PROPERTY_NAME
nenavštívila , protože se zdá, že jste podle svého vzorového dotazu již přeložili názvy vlastností na ID. Jinak byste mohli přidat připojení k PROPERTY_NAME
v každém dílčím dotazu.
V rámci této související otázky jsme sestavili arzenál technik:
- Jak filtrovat výsledky SQL ve vztahu has-many-through
Pro neznámý počet vlastností
@Mike a @Valera mají ve svých odpovědích velmi užitečné dotazy. Aby to bylo ještě dynamické :
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);
Pouze přidávejte / odebírejte řádky z VALUES
výraz. Nebo odstraňte WITH
klauzule a JOIN
pro žádné filtry vlastností vůbec.
Problém s touto třídou dotazů (počítající všechny dílčí shody) je výkon . Můj první dotaz je méně dynamický, ale obvykle podstatně rychlejší. (Stačí otestovat pomocí EXPLAIN ANALYZE
.) Speciálně pro větší stoly a rostoucí počet nemovitostí.
To nejlepší z obou světů?
Toto řešení s rekurzivním CTE by mělo být dobrým kompromisem:rychlé a dynamický:
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 zde
Manuál o rekurzivních CTE.
Přidaná složitost se nevyplácí u malých stolů, kde další režie převáží jakýkoli přínos nebo je rozdíl zanedbatelný. Ale škáluje se mnohem lépe a je stále lepší než „počítací“ techniky s rostoucími tabulkami a rostoucím počtem filtrů vlastností.
Techniky počítání musí navštívit všechny řádků v user_property_map
pro všechny dané filtry vlastností, přičemž tento dotaz (stejně jako první dotaz) může předčasně eliminovat irelevantní uživatele.
Optimalizace výkonu
S aktuální statistikou tabulky (rozumné nastavení, autovacuum
běží), Postgres má znalosti o „nejběžnějších hodnotách“ v každém sloupci a změní pořadí spojení v 1. dotazu nejprve vyhodnotit nejselektivnější filtry vlastností (nebo alespoň ne ty nejméně selektivní). Až do určitého limitu:join_collapse_limit
. Související:
- Postgresql join_collapse_limit a čas pro plánování dotazů
- Proč malá změna ve vyhledávacím dotazu tolik zpomaluje dotaz?
Tento zásah „deus-ex-machina“ není možný pomocí 3. dotazu (rekurzivní CTE). Chcete-li zlepšit výkon (možná hodně), musíte nejprve sami umístit selektivnější filtry. Ale i při objednávání v nejhorším případě bude stále lepší než počet dotazů.
Související:
- Zkontrolujte statistické cíle v PostgreSQL
Mnohem krvavější detaily:
- Částečný index PostgreSQL se nepoužívá, když je vytvořen v tabulce se stávajícími daty
Další vysvětlení v návodu:
- Statistiky používané plánovačem