sql >> Databáze >  >> RDS >> PostgreSQL

Vícenásobné použití stejného sloupce v klauzuli WHERE

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


  1. Jak počítat slova v MySQL / náhradě regulárních výrazů?

  2. Dotaz na výpočet kumulativního i celkového SUMU nad mzdou

  3. Chyba syntaxe tabulky Postgres

  4. Proaktivní kontroly stavu serveru SQL, část 1:Místo na disku