Tabelfunktioner
Jeg udfører meget højhastigheds, komplekse databasemigreringer til livets ophold, ved at bruge SQL som både klient- og serversprog (der bruges ikke andet sprog), alt sammen på serversiden, hvor koden sjældent dukker op fra databasemotoren. Bordfunktioner spiller en KÆMPE rolle i mit arbejde . Jeg bruger ikke "cursorer", da de er for langsomme til at opfylde mine præstationskrav, og alt hvad jeg gør er resultatsætorienteret. Tabelfunktioner har været en enorm hjælp for mig til fuldstændig at eliminere brugen af markører, opnå meget høj hastighed og har bidraget dramatisk til at reducere kodevolumen og forbedre enkelheden.
Kort sagt, du bruger en forespørgsel der refererer til to (eller flere) tabelfunktioner for at overføre data fra en tabelfunktion til den næste. Det udvalgte forespørgselsresultatsæt, der kalder tabelfunktionerne, fungerer som kanal til at overføre data fra en tabelfunktion til den næste. På DB2-platformen/-versionen jeg arbejder på, og det ser ud til, baseret på et hurtigt kig i 9.1 Postgres-manualen, at det samme er tilfældet der, kan du kun sende en enkelt række kolonneværdier som input til nogen af tabelfunktionskaldene, som du har opdaget. Men fordi tabelfunktionskaldet sker midt i en forespørgsels resultatsætbehandling, opnår du den samme effekt ved at overføre et helt resultatsæt til hvert tabelfunktionskald, selvom dataene i databasemotorens VVS sendes videre. kun én række ad gangen til hver tabelfunktion.
Tabelfunktioner accepterer én række inputkolonner og returnerer et enkelt resultatsæt tilbage i den kaldende forespørgsel (dvs. vælg), der kaldte funktionen. Resultatsætkolonnerne, der er sendt tilbage fra en tabelfunktion, bliver en del af den kaldende forespørgsels resultatsæt og er derfor tilgængelige som input til den næste tabelfunktion , refereret senere i den samme forespørgsel, typisk som en efterfølgende joinforbindelse. Den første tabelfunktions resultatkolonner føres som input (en række ad gangen) til den anden tabelfunktion, som returnerer dens resultatsætkolonner til den kaldende forespørgsels resultatsæt. Både den første og anden tabelfunktions resultatsætkolonner er nu en del af den kaldende forespørgsels resultatsæt og er nu tilgængelige som input (en række ad gangen) til en tredje tabelfunktion. Hvert tabelfunktionskald udvider den kaldende forespørgsels resultatsæt via de kolonner, det returnerer. Dette kan fortsætte, indtil du begynder at nå grænser for bredden af et resultatsæt, hvilket sandsynligvis varierer fra den ene databasemotor til den næste.
Overvej dette eksempel (som muligvis ikke matcher Postgres' syntakskrav eller muligheder, da jeg arbejder på DB2). Dette er et af mange designmønstre, hvor jeg bruger tabelfunktioner, er et af de mere simple, som jeg synes er meget illustrativt, og et som jeg forventer ville have bred appel hvis bordfunktioner var i stor almindelig brug (så vidt jeg ved er de ikke, men jeg synes, de fortjener mere opmærksomhed, end de får).
I dette eksempel er tabelfunktionerne i brug:VALIDATE_TODAYS_ORDER_BATCH, POST_TODAYS_ORDER_BATCH og DATA_WAREHOUSE_TODAYS_ORDER_BATCH. På den DB2-version, jeg arbejder på, pakker du tabelfunktionen inde i "TABLE( placer tabelfunktionskald og parametre her )", men baseret på et hurtigt kig på en Postgres-manual ser det ud til, at du udelader "TABLE( )"-indpakningen.
create table TODAYS_ORDER_PROCESSING_EXCEPTIONS as (
select TODAYS_ORDER_BATCH.*
,VALIDATION_RESULT.ROW_VALID
,POST_RESULT.ROW_POSTED
,WAREHOUSE_RESULT.ROW_WAREHOUSED
from TODAYS_ORDER_BATCH
cross join VALIDATE_TODAYS_ORDER_BATCH ( ORDER_NUMBER, [either pass the remainder of the order columns or fetch them in the function] )
as VALIDATION_RESULT ( ROW_VALID ) --example: 1/0 true/false Boolean returned
left join POST_TODAYS_ORDER_BATCH ( ORDER_NUMBER, [either pass the remainder of the order columns or fetch them in the function] )
as POST_RESULT ( ROW_POSTED ) --example: 1/0 true/false Boolean returned
on ROW_VALIDATED = '1'
left join DATA_WAREHOUSE_TODAYS_ORDER_BATCH ( ORDER_NUMBER, [either pass the remainder of the order columns or fetch them in the function] )
as WAREHOUSE_RESULT ( ROW_WAREHOUSED ) --example: 1/0 true/false Boolean returned
on ROW_POSTED = '1'
where coalesce( ROW_VALID, '0' ) = '0' --Capture only exceptions and unprocessed work.
or coalesce( ROW_POSTED, '0' ) = '0' --Or, you can flip the logic to capture only successful rows.
or coalesce( ROW_WAREHOUSED, '0' ) = '0'
) with data
- Hvis tabellen TODAYS_ORDER_BATCH indeholder 1.000.000 rækker, vil VALIDATE_TODAYS_ORDER_BATCH blive kaldt 1.000.000 gange, én gang for hver række.
- Hvis 900.000 rækker består valideringen i VALIDATE_TODAYS_ORDER_BATCH, vil POST_TODAYS_ORDER_BATCH blive kaldt 900.000 gange.
- Hvis kun 850.000 rækker kan sendes, skal VALIDATE_TODAYS_ORDER_BATCH lukke nogle smuthuller LOL, og DATA_WAREHOUSE_TODAYS_ORDER_BATCH vil blive kaldt 850.000 gange.
- Hvis 850.000 rækker kom ind i datavarehuset (dvs. der blev ikke genereret yderligere undtagelser), vil tabellen TODAYS_ORDER_PROCESSING_EXCEPTIONS blive udfyldt med 1.000.000 - 850.000 =150.000 undtagelsesrækker.
Tabelfunktionskaldene i dette eksempel returnerer kun en enkelt kolonne, men de kan returnere mange kolonner. For eksempel kan tabelfunktionen, der validerer en ordrerække, returnere årsagen til, at en ordre mislykkedes ved validering.
I dette design er stort set al chatter mellem en HLL og databasen elimineret, da HLL-anmoderen beder databasen om at behandle hele batchen i EN anmodning. Dette resulterer i en reduktion på millioner af SQL-anmodninger til databasen, i en KÆMPE fjernelse af millioner af HLL procedure- eller metodekald, og som et resultat giver det en KÆMPE runtime forbedring. I modsætning hertil vil ældre kode, som ofte behandler en enkelt række ad gangen, typisk sende 1.000.000 hente SQL-anmodninger, 1 for hver række i TODAYS_ORDER_BATCH, plus mindst 1.000.000 HLL- og/eller SQL-anmodninger til valideringsformål, plus mindst 1.000 og 000 HLL. /eller SQL-anmodninger til bogføringsformål, plus 1.000.000 HLL- og/eller SQL-anmodninger for at sende ordren til datavarehuset. Indrømmet, ved at bruge dette tabelfunktionsdesign inde i tabelfunktionerne sendes SQL-anmodninger til databasen, men når databasen sender anmodninger til sig selv (dvs. inde fra en tabelfunktion), serviceres SQL-anmodningerne meget hurtigere (især i forhold til et ældre scenarie, hvor HLL-anmoderen udfører enkeltrækkebehandling fra et fjernsystem, med det værste tilfælde over et WAN - OMG, lad vær med at gøre det).
Du kan nemt løbe ind i ydeevneproblemer, hvis du bruger en tabelfunktion til at "hente et resultatsæt" og derefter forbinder det resultatsæt med andre tabeller. I så fald kan SQL-optimeringsværktøjet ikke forudsige, hvilket sæt rækker der returneres fra tabelfunktionen, og den kan derfor ikke optimere joinforbindelsen til efterfølgende tabeller. Af den grund bruger jeg dem sjældent til at hente et resultatsæt, medmindre jeg ved, at resultatsættet vil være et meget lille antal rækker, hvilket derfor ikke forårsager et ydeevneproblem, eller jeg behøver ikke at slutte mig til efterfølgende tabeller.
Efter min mening er en grund til, at tabelfunktioner underudnyttes, at de ofte opfattes som kun et værktøj til at hente et resultatsæt, som ofte yder dårligt, så de bliver afskrevet som et "dårligt" værktøj at bruge.
Tabelfunktioner er uhyre nyttige til at skubbe mere funktionalitet over til serveren, til at eliminere det meste af chatten mellem databaseserveren og programmer på fjernsystemer, og endda til at eliminere chatter mellem databaseserveren og eksterne programmer på den samme server. Selv chat mellem programmer på den samme server medfører mere overhead, end mange mennesker er klar over, og meget af det er unødvendigt. Hjertet af kraften ved tabelfunktioner ligger i at bruge dem til at udføre handlinger i resultatsætbehandlingen.
Der er mere avancerede designmønstre til brug af tabelfunktioner, der bygger på ovenstående mønster, hvor du kan maksimere behandling af resultatsæt endnu mere, men dette indlæg er meget for de fleste at absorbere allerede.