sql >> Databasteknik >  >> RDS >> Mysql

Hur skapar man en MySQL hierarkisk rekursiv fråga?

För MySQL 8+: använd den rekursiva med syntax.
För MySQL 5.x: använd inline-variabler, sökvägs-ID:n eller självanslutningar.

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;

Värdet som anges i parent_id =19 bör ställas in på id för den förälder du vill välja alla ättlingar till.

MySQL 5.x

För MySQL-versioner som inte stöder Common Table Expressions (upp till version 5.7), skulle du uppnå detta med följande fråga:

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))

Här är en fiol .

Här, värdet som anges i @pv :='19' bör ställas in på id för den förälder du vill välja alla ättlingar till.

Detta fungerar även om en förälder har flera barn. Det krävs dock att varje post uppfyller villkoret parent_id , annars kommer resultaten inte att vara kompletta.

Variabeltilldelningar i en fråga

Den här frågan använder specifik MySQL-syntax:variabler tilldelas och modifieras under dess körning. Vissa antaganden görs om exekveringsordningen:

  • från klausulen utvärderas först. Så det är där @pv initieras.
  • var satsen utvärderas för varje post i den ordning de hämtas från från alias. Så det är här ett villkor ställs för att endast inkludera poster för vilka föräldern redan identifierats som i det efterkommande trädet (alla avkomlingar till den primära föräldern läggs successivt till i @pv ).
  • Villkoren i denna var klausulen utvärderas i ordning, och utvärderingen avbryts när det totala resultatet är säkert. Därför måste det andra villkoret vara på andra plats, eftersom det lägger till id till föräldralistan, och detta bör bara hända om id klarar det första villkoret. längden funktionen anropas bara för att säkerställa att detta villkor alltid är sant, även om pv sträng skulle av någon anledning ge ett falskt värde.

Sammantaget kan man tycka att dessa antaganden är för riskabla att förlita sig på. dokumentationen varnar:

du kanske får de resultat du förväntar dig, men detta är inte garanterat [...] utvärderingsordningen för uttryck som involverar användarvariabler är odefinierad.

Så även om det fungerar konsekvent med ovanstående fråga, kan utvärderingsordningen fortfarande ändras, till exempel när du lägger till villkor eller använder den här frågan som en vy eller underfråga i en större fråga. Det är en "funktion" som kommer att tas bort i framtiden MySQL-version :

Tidigare versioner av MySQL gjorde det möjligt att tilldela ett värde till en användarvariabel i andra satser än SET . Den här funktionen stöds i MySQL 8.0 för bakåtkompatibilitet men kan tas bort i en framtida version av MySQL.

Som nämnts ovan, från MySQL 8.0 och framåt bör du använda den rekursiva with syntax.

Effektivitet

För mycket stora datamängder kan denna lösning bli långsam, eftersom finn_i_uppsättning operation är inte det mest idealiska sättet att hitta ett nummer i en lista, absolut inte i en lista som når en storlek i samma storleksordning som antalet poster som returneras.

Alternativ 1:med rekursiv , anslut med

Fler och fler databaser implementerar SQL:1999 ISO-standarden Med [RECURSIVE] syntax för rekursiva frågor (t.ex. 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+ ). Och från och med version 8.0, stöder MySQL det . Se överst i det här svaret för syntaxen som ska användas.

Vissa databaser har en alternativ, icke-standard syntax för hierarkiska uppslagningar, såsom CONNECT BY klausul tillgänglig på Oracle , DB2 , Informix , CUBRID och andra databaser.

MySQL version 5.7 erbjuder inte en sådan funktion. När din databasmotor tillhandahåller denna syntax eller om du kan migrera till en som gör det, så är det definitivt det bästa alternativet att välja. Om inte, överväg också följande alternativ.

Alternativ 2:Sökvägsliknande identifierare

Saker och ting blir mycket enklare om du tilldelar id värden som innehåller den hierarkiska informationen:en sökväg. Till exempel, i ditt fall kan detta se ut så här:

ID NAMN
19 kategori1
19/1 kategori2
19/1/1 kategori3
19/1/1/1 kategori4

Sedan välj skulle se ut så här:

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

Alternativ 3:Upprepade självanslutningar

Om du vet en övre gräns för hur djupt ditt hierarkiträd kan bli kan du använda en standard sql fråga så här:

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;

Se denna fiol

var condition anger vilken förälder du vill hämta ättlingar till. Du kan utöka den här frågan med fler nivåer efter behov.



  1. SQL-fråga för att hitta saknade rader mellan två relaterade tabeller

  2. FEL 2006 (HY000):MySQL-servern har försvunnit

  3. SQL mellan inte inkluderande

  4. Hur man skapar en användardefinierad postdatatypvariabel i Oracle Database