sql >> Databáze >  >> RDS >> Mysql

Jak vytvořit hierarchický rekurzivní dotaz MySQL?

Pro MySQL 8+: použijte rekurzivní with syntaxe.
Pro MySQL 5.x: použijte vložené proměnné, ID cesty nebo vlastní spojení.

MySQL 8+

with recursive cte (id, name, parent_id) as (
  select     id,
             name,
             parent_id
  from       products
  where      parent_id = 19
  union all
  select     p.id,
             p.name,
             p.parent_id
  from       products p
  inner join cte
          on p.parent_id = cte.id
)
select * from cte;

Hodnota uvedená v parent_id = 19 by měl být nastaven na id rodiče, jehož chcete vybrat všechny potomky.

MySQL 5.x

U verzí MySQL, které nepodporují Common Table Expressions (až do verze 5.7), byste toho dosáhli pomocí následujícího dotazu:

select  id,
        name,
        parent_id 
from    (select * from products
         order by parent_id, id) products_sorted,
        (select @pv := '19') initialisation
where   find_in_set(parent_id, @pv)
and     length(@pv := concat(@pv, ',', id))

Zde je housle .

Zde je hodnota zadaná v @pv := '19' by měl být nastaven na id rodiče, jehož chcete vybrat všechny potomky.

To bude fungovat i v případě, že rodič má více děti. Je však nutné, aby každý záznam splňoval podmínku parent_id < id , jinak nebudou výsledky úplné.

Přiřazení proměnných uvnitř dotazu

Tento dotaz používá specifickou syntaxi MySQL:proměnné jsou přiřazovány a upravovány během jeho provádění. Existují určité předpoklady ohledně pořadí provádění:

  • from klauzule se hodnotí jako první. Takže tam je @pv se inicializuje.
  • where klauzule je vyhodnocena pro každý záznam v pořadí načtení z from aliasy. Zde je tedy nastavena podmínka, aby byly zahrnuty pouze záznamy, u kterých byl rodič již identifikován jako ve stromu potomků (všichni potomci primárního rodiče jsou postupně přidáváni do @pv ).
  • Podmínky v tomto where klauzule jsou hodnoceny v pořadí a hodnocení je přerušeno, jakmile je celkový výsledek jistý. Druhá podmínka proto musí být na druhém místě, protože přidává id do nadřazeného seznamu a mělo by k tomu dojít pouze v případě id splňuje první podmínku. length funkce je volána pouze proto, aby se ujistil, že tato podmínka je vždy pravdivá, i když pv řetězec by z nějakého důvodu poskytl falešnou hodnotu.

Celkově vzato mohou být tyto předpoklady příliš riskantní, než aby se na ně spoléhalo. dokumentace varuje:

můžete získat očekávané výsledky, ale to není zaručeno [...] pořadí vyhodnocení výrazů zahrnujících uživatelské proměnné není definováno.

Takže i když to funguje konzistentně s výše uvedeným dotazem, pořadí vyhodnocení se může stále změnit, například když přidáte podmínky nebo použijete tento dotaz jako pohled nebo dílčí dotaz ve větším dotazu. Jedná se o „funkci“, která bude v budoucnu odstraněna Vydání MySQL :

Předchozí verze MySQL umožňovala přiřadit hodnotu uživatelské proměnné v jiných příkazech než SET . Tato funkce je podporována v MySQL 8.0 kvůli zpětné kompatibilitě, ale v budoucí verzi MySQL bude odstraněna.

Jak je uvedeno výše, od MySQL 8.0 byste měli používat rekurzivní with syntaxe.

Účinnost

U velmi velkých souborů dat může být toto řešení pomalé, protože find_in_set operace není nejideálnějším způsobem, jak najít číslo v seznamu, rozhodně ne v seznamu, který dosahuje velikosti řádově stejné jako počet vrácených záznamů.

Alternativa 1:with recursive , connect by

Stále více databází implementuje SQL:1999 ISO standard WITH [RECURSIVE] syntaxe pro rekurzivní dotazy (např. Postgres 8.4+ , SQL Server 2005+ , DB2 , Oracle 11gR2+ , SQLite 3.8.4+ , Firebird 2.1+ , H2 , HyperSQL 2.1.0+ , Teradata , MariaDB 10.2.2+ ). A od verze 8.0 to podporuje i MySQL . Použitou syntaxi naleznete v horní části této odpovědi.

Některé databáze mají alternativní, nestandardní syntaxi pro hierarchické vyhledávání, jako je CONNECT BY klauzule dostupná na Oracle , DB2 , Informix , CUBRID a další databáze.

MySQL verze 5.7 takovou funkci nenabízí. Když váš databázový stroj poskytuje tuto syntaxi nebo můžete migrovat na takový, který ano, pak je to určitě nejlepší volba. Pokud ne, zvažte také následující alternativy.

Alternativa 2:Identifikátory stylu cesty

Věci budou mnohem jednodušší, pokud přiřadíte id hodnoty, které obsahují hierarchické informace:cesta. Ve vašem případě by to mohlo vypadat například takto:

ID NAME
19 kategorie1
19/1 kategorie2
19/1/1 kategorie3
19/1/1/1 kategorie4

Poté select bude vypadat takto:

select  id,
        name 
from    products
where   id like '19/%'

Alternativa 3:Opakovaná vlastní připojení

Pokud znáte horní hranici hloubky vašeho hierarchického stromu, můžete použít standardní sql dotaz takto:

select      p6.parent_id as parent6_id,
            p5.parent_id as parent5_id,
            p4.parent_id as parent4_id,
            p3.parent_id as parent3_id,
            p2.parent_id as parent2_id,
            p1.parent_id as parent_id,
            p1.id as product_id,
            p1.name
from        products p1
left join   products p2 on p2.id = p1.parent_id 
left join   products p3 on p3.id = p2.parent_id 
left join   products p4 on p4.id = p3.parent_id  
left join   products p5 on p5.id = p4.parent_id  
left join   products p6 on p6.id = p5.parent_id
where       19 in (p1.parent_id, 
                   p2.parent_id, 
                   p3.parent_id, 
                   p4.parent_id, 
                   p5.parent_id, 
                   p6.parent_id) 
order       by 1, 2, 3, 4, 5, 6, 7;

Podívejte se na tyto hudle

where podmínka určuje, kterého rodiče chcete získat potomky. Tento dotaz můžete podle potřeby rozšířit o další úrovně.



  1. balíček selhává – nelze najít klientskou knihovnu PostgreSQL (libpq)

  2. Záznam vrácený z funkce má zřetězené sloupce

  3. Referenční hodnota sériového sloupce v jiném sloupci při stejném INSERT

  4. MySQL Pořadí podle čísla, Null poslední