sql >> Database teknologi >  >> RDS >> PostgreSQL

Sådan vælger du ved hjælp af WITH RECURSIVE-sætning

Lad os først og fremmest prøve at forenkle og tydeliggøre algoritmebeskrivelsen givet på manualsiden. For at forenkle det overvej kun union all i med rekursiv klausul for nu (og union senere):

MED RECURSIVE pseudo-entity-name(column-names) AS (Initial-SELECTUNION ALL Rekursive-SELECT ved hjælp af pseudo-entity-name)Ydre-SELECT ved hjælp af pseudo-entity-name 

For at tydeliggøre det, lad os beskrive forespørgselsudførelsesprocessen i pseudokode:

working-recordset =resultat af Initial-SELECTtilføj arbejdsrecordset til tomt ydre-recordsetwhile( working-recordset is not tom ) start new working-recordset =resultat af Recursive-SELECT tager tidligere arbejdsrecordsæt som pseudo- enhedsnavn tilføj working-recordset til ydre-recordsetendoverall-result =resultat af Outer-SELECT tager ydre-recordset som pseudo-entitetsnavn 

Eller endnu kortere - Databasemotoren udfører indledende valg og tager dens resultatrækker som arbejdssæt. Derefter udfører den gentagne gange rekursiv udvælgelse på arbejdssættet, hver gang den erstatter indholdet af arbejdssættet med opnået forespørgselsresultat. Denne proces slutter, når et tomt sæt returneres af rekursivt valg. Og alle resultatrækker, der først gives ved indledende udvælgelse og derefter ved rekursiv udvælgelse, samles og føres til ydre udvalg, hvilket resultat bliver det overordnede forespørgselsresultat.

Denne forespørgsel beregner faktoriel af 3:

MED REKURSIV faktoriel(F,n) AS (VÆLG 1 F, 3 nUNION ALLE VÆLG F*n F, n-1 n fra faktoriel hvor n>1)VÆLG F fra faktoriel hvor n=1 

Indledende vælg SELECT 1 F, 3 n giver os startværdier:3 for argument og 1 for funktionsværdi.
Rekursivt vælg SELECT F*n F, n-1 n fra factorial hvor n>1 angiver, at hver gang vi skal gange sidste funktionsværdi med sidste argumentværdi og formindske argumentværdien.
Databasemotoren udfører det sådan her:

Først og fremmest udfører den initail select, som giver den oprindelige tilstand af arbejdende recordset:

F | n--+--1 | 3

Derefter transformerer den fungerende postsæt med rekursiv forespørgsel og opnår sin anden tilstand:

F | n--+--3 | 2

Derefter tredje tilstand:

F | n--+--6 | 1

I den tredje tilstand er der ingen række, der følger efter n>1 tilstand i rekursivt valg, så det arbejdende sæt er loop exits.

Ydre recordset indeholder nu alle rækkerne, returneret af initial og rekursiv select:

F | n--+--1 | 33 | 26 | 1

Ydre udvalg bortfiltrerer alle mellemresultater fra det ydre rekordsæt og viser kun den endelige faktorværdi, som bliver det overordnede forespørgselsresultat:

F --6 

Og lad os nu overveje tabellen forest(id,parent_id,name) :

id | forældre_id | navn---+-----------+-----------------1 | | punkt 12 | 1 | underpunkt 1.13 | 1 | underpunkt 1.24 | 1 | underpunkt 1.35 | 3 | underpunkt 1.2.16 | | punkt 27 | 6 | underpunkt 2.18 | | punkt 3

'Udvider hele træet ' betyder her sortering af træelementer i menneskelig læsbar dybde-første rækkefølge, mens de beregner deres niveauer og (måske) stier. Begge opgaver (med korrekt sorterings- og beregningsniveau eller sti) kan ikke løses i én (eller endda et konstant antal) SELECT uden at bruge WITH RECURSIVE-sætning (eller Oracle CONNECT BY-klausul, som ikke understøttes af PostgreSQL). Men denne rekursive forespørgsel gør jobbet (nå, det gør næsten, se noten nedenfor):

MED RECURSIVE fulltree(id,parent_id,niveau,navn,sti) AS ( SELECT id, parent_id, 1 som niveau, navn, navn||'' som sti fra skov, hvor parent_id er nullUNION ALL SELECT t. id, t.parent_id, ft.level+1 som niveau, t.name, ft.path||' / '||t.name som sti fra skov t, fulltree ft hvor t.parent_id =ft.id)SELECT * fra fuldtræ rækkefølge efter sti

Databasemotoren udfører det sådan her:

For det første udfører den initail select, som giver alle elementer på højeste niveau (rødder) fra skov tabel:

id | forældre_id | niveau | navn | sti---+-----------+-------+-------------------+------ ----------------------------------1 | | 1 | punkt 1 | punkt 18 | | 1 | punkt 3 | punkt 36 | | 1 | punkt 2 | punkt 2

Derefter udfører den rekursivt valg, som giver alle elementer på 2. niveau fra skov tabel:

id | forældre_id | niveau | navn | sti---+-----------+-------+-------------------+------ ----------------------------------2 | 1 | 2 | underpunkt 1.1 | punkt 1 / underpunkt 1.13 | 1 | 2 | underpunkt 1.2 | punkt 1 / underpunkt 1.24 | 1 | 2 | underpunkt 1.3 | punkt 1 / underpunkt 1.37 | 6 | 2 | underpunkt 2.1 | punkt 2 / underpunkt 2.1

Derefter udfører den rekursivt valg igen og henter elementer på 3d-niveau:

id | forældre_id | niveau | navn | sti---+-----------+-------+-------------------+------ ----------------------------------5 | 3 | 3 | underpunkt 1.2.1 | punkt 1 / underpunkt 1.2 / underpunkt 1.2.1

Og nu udfører den rekursivt valg igen og prøver at hente elementer på 4. niveau, men der er ingen af ​​dem, så løkken afsluttes.

Det ydre SELECT indstiller den korrekte rækkefølge, der kan læses af mennesker, og sorterer på stikolonnen:

id | forældre_id | niveau | navn | sti---+-----------+-------+-------------------+------ ----------------------------------1 | | 1 | punkt 1 | punkt 12 | 1 | 2 | underpunkt 1.1 | punkt 1 / underpunkt 1.13 | 1 | 2 | underpunkt 1.2 | punkt 1 / underpunkt 1.25 | 3 | 3 | underpunkt 1.2.1 | punkt 1 / underpunkt 1.2 / underpunkt 1.2.14 | 1 | 2 | underpunkt 1.3 | punkt 1 / underpunkt 1.36 | | 1 | punkt 2 | punkt 27 | 6 | 2 | underpunkt 2.1 | punkt 2 / underpunkt 2.18 | | 1 | punkt 3 | punkt 3

BEMÆRK :Den resulterende rækkefølge forbliver kun korrekt, så længe der ikke er nogen tegnsætningstegn, der står foran / i varenavnene. Hvis vi omdøber Punkt 2 i Vare 1 * , vil den bryde rækkefølgen, der står mellem Punkt 1 og dens efterkommere.
Mere stabil løsning er at bruge tabulatortegn (E'\t' ) som sti-separator i forespørgslen (som kan erstattes af mere læsbar sti-separator senere:i ydre valg, før displaining til mennesker eller osv.). Tab-separerede stier bevarer den korrekte rækkefølge, indtil der er faner eller kontroltegn i emnenavnene - som nemt kan kontrolleres og udelukkes uden tab af brugervenlighed.

Det er meget enkelt at ændre sidste forespørgsel for at udvide et hvilket som helst vilkårligt undertræ - du behøver kun at erstatte betingelsen forælder_id er null med perent_id=1 (for eksempel). Bemærk, at denne forespørgselsvariant returnerer alle niveauer og stier i forhold til Vare 1 .

Og nu om typiske fejl . Den mest bemærkelsesværdige typiske fejl, der er specifik for rekursive forespørgsler, er at definere dårlige stopbetingelser i rekursivt udvalg, hvilket resulterer i uendelig looping.

For eksempel, hvis vi udelader hvor n>1 betingelse i faktoreksempel ovenfor, vil udførelse af rekursivt udvalg aldrig give et tomt sæt (fordi vi ikke har nogen betingelse for at filtrere en enkelt række ud), og looping vil fortsætte uendeligt.

Det er den mest sandsynlige årsag til, at nogle af dine forespørgsler hænger (den anden uspecifikke, men stadig mulige årsag er meget ineffektiv udvælgelse, som udføres i begrænset, men meget lang tid).

Der er ikke mange REKURSIVE-specifikke retningslinjer for forespørgsler at nævne, så vidt jeg ved. Men jeg vil gerne foreslå (temmelig indlysende) trin for trin rekursiv forespørgselsopbygningsprocedure.

  • Byg og fejlfind dit første valg separat.

  • Pak den ind med stilladser MED REKURSIV konstruktion
    og begynd at bygge og fejlfinde dit rekursive udvalg.

Den anbefalede stilladskonstruktion er som denne:

MED RECURSIVE rec(  ) AS ( UNION ALL )SELECT * from rec limit 1000 

Denne enkleste ydre markering vil udlæse hele det ydre postsæt, der, som vi ved, indeholder alle outputrækker fra indledende udvælgelse og hver udførelse af rekusriv udvælgelse i en loop i deres originale outputrækkefølge - ligesom i eksemplerne ovenfor! grænse 1000 del forhindrer hængning, og erstatter den med overdimensioneret output, hvor du vil være i stand til at se det mistede stoppunkt.

  • Efter debugging indledende og rekursiv skal du bygge og fejlsøge dit ydre udvalg.

Og nu den sidste ting at nævne - forskellen i at bruge union i stedet for union all i med rekursiv klausul. Det introducerer rækkeentydighedsbegrænsning, hvilket resulterer i to ekstra linjer i vores eksekveringspseudokode:

working-recordset =resultat af Initial-SELECTdiscard duplikerede rækker fra working-recordset /*union-specific*/append working-recordset til tom ydre-recordsetwhile( working-recordset is not tom) start new working-recordset =resultat af Rekursiv-SELECT, der tager tidligere arbejds-recordsæt som pseudo-entitetsnavn, kasser duplikerede rækker og rækker, der har dubletter i ydre-recordset fra working-recordset /*union-specific*/ føj arbejds-recordset til ydre-recordsetendoverall-result =resultat af, at Outer-SELECT tager det ydre-recordsæt som pseudo-entitetsnavn 



  1. Måde man prøve flere SELECT'er, indtil et resultat er tilgængeligt?

  2. Sådan installeres, sikres og ydeevneindstilling af MariaDB-databaseserveren

  3. Hvad er MariaDB Enterprise, og hvordan administreres det med ClusterControl?

  4. Hvordan udtrækkes år og måned fra dato i PostgreSQL uden at bruge to_char()-funktionen?