Tabellenfunktionen
Ich führe sehr schnelle, komplexe Datenbankmigrationen durch, um meinen Lebensunterhalt zu verdienen, wobei ich SQL sowohl als Client- als auch als Serversprache verwende (keine andere Sprache wird verwendet), die alle serverseitig ausgeführt werden, wo der Code selten aus der Datenbank-Engine auftaucht. Tabellenfunktionen spielen in meiner Arbeit eine RIESIGE Rolle . Ich verwende keine "Cursor", da sie zu langsam sind, um meine Leistungsanforderungen zu erfüllen, und alles, was ich tue, ist ergebnisorientiert. Tabellenfunktionen waren eine immense Hilfe für mich, indem sie die Verwendung von Cursorn vollständig eliminierten, eine sehr hohe Geschwindigkeit erreichten und dramatisch dazu beigetragen haben, das Codevolumen zu reduzieren und die Einfachheit zu verbessern.
Kurz gesagt, Sie verwenden eine Abfrage die auf zwei (oder mehr) Tabellenfunktionen verweist, um die Daten von einer Tabellenfunktion zur nächsten zu übergeben. Das ausgewählte Abfrageergebnis, das die Tabellenfunktionen aufruft, dient als Kanal, um die Daten von einer Tabellenfunktion zur nächsten zu übergeben. Auf der DB2-Plattform / -Version, an der ich arbeite, und es scheint basierend auf einem kurzen Blick auf das 9.1 Postgres-Handbuch, dass dort dasselbe zutrifft, können Sie nur eine einzelne Zeile von Spaltenwerten als Eingabe an einen der Tabellenfunktionsaufrufe übergeben. wie Sie festgestellt haben. Da der Tabellenfunktionsaufruf jedoch mitten in der Verarbeitung der Ergebnismenge einer Abfrage erfolgt, erzielen Sie den gleichen Effekt, wenn Sie eine ganze Ergebnismenge an jeden Tabellenfunktionsaufruf übergeben, obwohl die Daten in der Datenbank-Engine übergeben werden nur jeweils eine Zeile für jede Tabellenfunktion.
Tabellenfunktionen akzeptieren eine Reihe von Eingabespalten und geben eine einzelne Ergebnismenge zurück an die aufrufende Abfrage (d. h. select), die die Funktion aufgerufen hat. Die von einer Tabellenfunktion zurückgegebenen Ergebnismengenspalten werden Teil der Ergebnismenge der aufrufenden Abfrage und stehen daher als Eingabe für die nächste Tabellenfunktion zur Verfügung , auf die später in derselben Abfrage verwiesen wird, normalerweise als nachfolgender Join. Die Ergebnisspalten der ersten Tabellenfunktion werden als Eingabe (jeweils eine Zeile) an die zweite Tabellenfunktion übergeben, die ihre Ergebnismengenspalten in die Ergebnismenge der aufrufenden Abfrage zurückgibt. Sowohl die erste als auch die zweite Ergebnismengenspalte der Tabellenfunktion sind jetzt Teil der Ergebnismenge der aufrufenden Abfrage und stehen nun als Eingabe (jeweils eine Zeile) für eine dritte Tabellenfunktion zur Verfügung. Jeder Tabellenfunktionsaufruf erweitert die Ergebnismenge der aufrufenden Abfrage über die zurückgegebenen Spalten. Dies kann so lange dauern, bis Sie an Grenzen für die Breite einer Ergebnismenge stoßen, die wahrscheinlich von einer Datenbank-Engine zur nächsten variiert.
Betrachten Sie dieses Beispiel (das möglicherweise nicht den Syntaxanforderungen oder -funktionen von Postgres entspricht, da ich an DB2 arbeite). Dies ist eines von vielen Designmustern, in denen ich Tabellenfunktionen verwende, es ist eines der einfacheren, das meiner Meinung nach sehr anschaulich ist, und eines, von dem ich erwarte, dass es eine breite Anziehungskraft haben würde if Tabellenfunktionen wurden im Mainstream stark genutzt (meines Wissens sind sie es nicht, aber ich denke, sie verdienen mehr Aufmerksamkeit, als sie bekommen).
In diesem Beispiel sind die verwendeten Tabellenfunktionen:VALIDATE_TODAYS_ORDER_BATCH, POST_TODAYS_ORDER_BATCH und DATA_WAREHOUSE_TODAYS_ORDER_BATCH. In der DB2-Version, an der ich arbeite, packen Sie die Tabellenfunktion in „TABLE(place table function call and parameters here)“, aber nach einem kurzen Blick in ein Postgres-Handbuch scheint es, dass Sie den „TABLE( )“-Wrapper weglassen. P>
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
- Wenn die Tabelle TODAYS_ORDER_BATCH 1.000.000 Zeilen enthält, wird VALIDATE_TODAYS_ORDER_BATCH 1.000.000 Mal aufgerufen, einmal für jede Zeile.
- Wenn 900.000 Zeilen die Validierung innerhalb von VALIDATE_TODAYS_ORDER_BATCH bestehen, wird POST_TODAYS_ORDER_BATCH 900.000 Mal aufgerufen.
- Wenn nur 850.000 Zeilen erfolgreich gepostet werden, müssen für VALIDATE_TODAYS_ORDER_BATCH einige Schlupflöcher geschlossen werden, LOL, und DATA_WAREHOUSE_TODAYS_ORDER_BATCH wird 850.000 Mal aufgerufen.
- Wenn 850.000 Zeilen erfolgreich in das Data Warehouse gelangt sind (d. h. es wurden keine zusätzlichen Ausnahmen generiert), wird die Tabelle TODAYS_ORDER_PROCESSING_EXCEPTIONS mit 1.000.000 - 850.000 =150.000 Ausnahmezeilen gefüllt.
Die Tabellenfunktionsaufrufe in diesem Beispiel geben nur eine einzelne Spalte zurück, aber sie könnten viele Spalten zurückgeben. Beispielsweise könnte die Tabellenfunktion, die eine Bestellzeile validiert, den Grund zurückgeben, warum die Validierung einer Bestellung fehlgeschlagen ist.
Bei diesem Entwurf wird praktisch das gesamte Geschwätz zwischen einer HLL und der Datenbank eliminiert, da der HLL-Anforderer die Datenbank auffordert, den gesamten Stapel in EINER Anforderung zu verarbeiten. Dies führt zu einer Verringerung von Millionen von SQL-Anforderungen an die Datenbank, zu einer RIESIGEN Entfernung von Millionen von HLL-Prozedur- oder Methodenaufrufen und bietet als Ergebnis eine RIESIGE Laufzeitverbesserung. Im Gegensatz dazu würde Legacy-Code, der häufig eine einzelne Zeile gleichzeitig verarbeitet, normalerweise 1.000.000 SQL-Abrufanforderungen senden, 1 für jede Zeile in TODAYS_ORDER_BATCH, plus mindestens 1.000.000 HLL- und/oder SQL-Anforderungen zu Validierungszwecken, plus mindestens 1.000.000 HLL und /oder SQL-Requests für Buchungszwecke plus 1.000.000 HLL- und/oder SQL-Requests für das Senden der Bestellung an das Data Warehouse. Zugegeben, mit diesem Tabellenfunktionsdesign werden innerhalb der Tabellenfunktionen SQL-Anforderungen an die Datenbank gesendet, aber wenn die Datenbank Anforderungen an sich selbst stellt (d. h. aus einer Tabellenfunktion), werden die SQL-Anforderungen viel schneller bedient (insbesondere im Vergleich zu ein Legacy-Szenario, in dem der HLL-Anforderer eine einzelne Zeilenverarbeitung von einem Remote-System aus durchführt, im schlimmsten Fall über ein WAN - OMG, bitte tun Sie das nicht).
Sie können leicht auf Leistungsprobleme stoßen, wenn Sie eine Tabellenfunktion verwenden, um "eine Ergebnismenge abzurufen" und diese Ergebnismenge dann mit anderen Tabellen zu verknüpfen. In diesem Fall kann der SQL-Optimierer nicht vorhersagen, welche Gruppe von Zeilen von der Tabellenfunktion zurückgegeben wird, und kann daher die Verknüpfung mit nachfolgenden Tabellen nicht optimieren. Aus diesem Grund verwende ich sie selten zum Abrufen einer Ergebnismenge, es sei denn, ich weiß, dass die Ergebnismenge eine sehr kleine Anzahl von Zeilen sein wird, wodurch kein Leistungsproblem verursacht wird, oder ich muss keine Verknüpfungen mit nachfolgenden Tabellen herstellen.
Meiner Meinung nach ist ein Grund, warum Tabellenfunktionen zu wenig genutzt werden, der, dass sie oft nur als Werkzeug zum Abrufen einer Ergebnismenge wahrgenommen werden, was oft eine schlechte Leistung erbringt, sodass sie als „schlechtes“ Werkzeug abgeschrieben werden.
Tabellenfunktionen sind äußerst nützlich, um mehr Funktionalität auf den Server zu übertragen, um den größten Teil des Geschwätzes zwischen dem Datenbankserver und Programmen auf entfernten Systemen zu eliminieren und sogar um Geschwätz zwischen dem Datenbankserver und externen Programmen auf demselben Server zu eliminieren. Sogar das Chatten zwischen Programmen auf demselben Server verursacht mehr Overhead, als vielen Menschen bewusst ist, und vieles davon ist unnötig. Das Herzstück der Leistungsfähigkeit von Tabellenfunktionen liegt darin, sie zu verwenden, um Aktionen innerhalb der Ergebnissatzverarbeitung auszuführen.
Es gibt fortgeschrittenere Entwurfsmuster für die Verwendung von Tabellenfunktionen, die auf dem obigen Muster aufbauen, mit denen Sie die Ergebnissatzverarbeitung noch weiter maximieren können, aber dieser Beitrag ist für die meisten bereits eine Menge zu verinnerlichen.