JavaScript-UDTFs (Benutzerdefinierte Tabellenfunktionen)

Ähnlich wie SQL-UDTFs gibt eine JavaScript-UDTF eine Menge von Zeilen zurück, auf die in der FROM-Klausel einer Abfrage zugegriffen werden kann.

Unter diesem Thema:

Syntax

Eine JavaScript-UDTF wird mit derselben Syntax wie eine Standard-SQL-UDTF definiert, wobei jedoch das Schlüsselwort LANGUAGE hinzugefügt wird. Außerdem wird anstelle eines SQL-Blocks in der Funktionsdefinition der JavaScript-Code übergeben:

CREATE OR REPLACE FUNCTION <name> ( [ <arguments> ] )
  RETURNS TABLE ( <output_columns> )
  LANGUAGE JAVASCRIPT
  AS '<javascript_code>';

Der nächste Abschnitt enthält Details zum Schreiben des Javascript-Codes, der den Hauptteil der UDTF (auch „Definition“ genannt) enthält.

Eine ausführlichere Beschreibung der allgemeinen Syntax für alle UDFs, einschließlich JavaScript UDTFs, finden Sie unter CREATE FUNCTION.

Nutzungshinweise

  • Wenn Sie eine JavaScript-UDTF mit einer PARTITION BY-Klausel verwenden, darf die PARTITION BY-Klausel nur einen Spaltenverweis, aber keinen allgemeinen Ausdruck enthalten. Folgender Befehl wird beispielsweise nicht unterstützt:

    SELECT * FROM udtf_table, TABLE(my_func(col1) OVER (PARTITION BY col2 * 2));

Schreiben von JavaScript-UDTFs

Code-Anforderungen

Der JavaScript-Code muss die folgenden Voraussetzungen erfüllen, damit die UDTF gültig ist:

  • Der Code muss ein einzelnes literales JavaScript-Objekt definieren.

  • Das definierte Objekt muss eine Callback-Funktion mit dem Namen processRow enthalten. Weitere Informationen dazu finden Sie im nächsten Abschnitt.

Wichtig

Wenn der JavaScript-Code diese Anforderungen nicht erfüllt, wird die UDTF trotzdem erstellt. Sie schlägt jedoch fehl, wenn sie in einer Abfrage aufgerufen wird.

Objekt-Callback-Funktionen

Über den JavaScript-Code interagiert Snowflake mit der UDTF, indem während der Ausführung der Abfrage verschiedene Callback-Funktionen aufgerufen werden. Der folgende Skeleton umreißt alle verfügbaren Callbacks und deren erwartete Signatur:

{
   processRow: function (row, rowWriter, context) {/*...*/},
   finalize: function (rowWriter, context) {/*...*/},
   initialize: function (argumentInfo, context) {/*...*/},
}

Beachten Sie, dass nur der processRow-Callback erforderlich ist. Der Rest ist optional.

processRow

Diese Callback-Funktion wird in der Eingabebeziehung einmal für jede Zeile aufgerufen. Die Argumente für die UDTF werden im row-Objekt übergeben. Für jedes der in der CREATE FUNCTION-DDL definierten Argumente, die zum Erstellen der UDTF verwendet wurden, gibt es eine Eigenschaft für das row-Objekt, die überall den gleichen Namen in Großbuchstaben hat. Der Wert dieser Eigenschaft ist der Wert des Arguments für die aktuelle Zeile, der in einen JavaScript-Wert umgewandelt wird.

Das Argument rowWriter wird vom benutzerdefinierten Code zum Erstellen von Ausgabezeilen verwendet. Für das rowWriter-Objekt ist die Einzelfunktion writeRow definiert. Der writeRow-Funktion wird ein Argument übergeben, das Zeilenobjekt. Hierbei handelt es sich um eine einzelne Zeile in der Ausgabetabelle, die als JavaScript-Objekt dargestellt wird. Für jede Spalte, die in der RETURNS-Klausel der CREATE FUNCTION-DDL definiert ist, kann eine entsprechende Eigenschaft für das Zeilenobjekt definiert werden. Der Wert dieser Eigenschaft für das Zeilenobjekt wird zum Wert für die entsprechende Spalte in der Ausgabebeziehung. Ausgabespalten ohne entsprechende Eigenschaft im Zeilenobjekt haben in der Ergebnistabelle den Wert NULL.

finalize

Diese Callback-Funktion wird einmal für jede Partition aufgerufen, nachdem alle Zeilen an processRow übergeben wurden.

Sie kann verwendet werden, um einen beliebigen Status auszugeben, der möglicherweise in processRow mit demselben Zeilenschreiber aggregiert wurde, der an processRow übergeben wird.

initialize

Diese Callback-Funktion wird einmal für jede Partition aufgerufen, bevor processRow aufgerufen wird. Zeilen können mit dieser Methode nicht erzeugt werden.

initialize kann verwendet werden, um einen beliebigen Status für die Ergebnisberechnung einzustellen. Außerdem werden an initialize Metadaten zu den Argumenten der benutzerdefinierten Funktion im argumentInfo-Objekt übergeben. argumentInfo hat eine Eigenschaft für jede Eingabespalte, die in der DDL mit Metadaten zu dieser Spalte definiert ist. Jeder Eintrag ist ein Objekt mit den folgenden Werten:

  • type: Zeichenfolge. Der Typ dieser Spalte.

  • isConst: Boolean. Bei „true“ ist der Wert dieser Spalte konstant (d. h. für jede Zeile der gleiche).

  • constValue: Wenn isConst (wie oben definiert) „true“ ist, enthält dieser Eintrag den konstanten Wert der Spalte. Andernfalls lautet dieses Feld undefined.

Allgemeine Nutzungshinweise für Callback-Funktionen

  • Alle drei Callback-Funktionen benötigen ein context-Objekt. Dies ist für die zukünftige Verwendung reserviert und derzeit leer.

    Vorsicht

    Das Ändern des context-Objekts kann zu undefiniertem Verhalten führen.

  • Zusätzliche Funktionen und Eigenschaften können bei Bedarf für das Objekt definiert werden, das in der UDTF verwendet werden soll.

  • Die Argumente für die Callback-Funktionen sind positionell und können beliebig benannt werden. Für die Zwecke dieses Themas werden die obigen Namen jedoch für die verbleibende Diskussion und die Beispiele verwendet.

Partitionen

JavaScript-UDTFs können mit einer Partition aufgerufen werden. Beispiel:

SELECT * FROM tab1, TABLE(js_udtf(tab1.c1, tab1.c2) OVER (PARTITION BY <expr> ORDER BY <expr>));

Eine Partition besteht aus allen Zeilen, für die die PARTITION BY-Ausdrücke den gleichen Wert ergeben. Die Zeilen werden in der durch den Ausdruck ORDER BY definierten Reihenfolge an processRow übergeben.

Für diese Diskussion wird eine Instanz einer JavaScript-UDTF als eine Instanz des JavaScript-Objekts definiert, das zur Darstellung der Funktion in Snowflake verwendet wird.

Zur Verbesserung der Leistung beim Aufrufen einer JavaScript-UDTF können mehrere UDTF-Instanzen parallel ausgeführt werden. Damit dies funktioniert, müssen die Zeilen in der Eingabe zwischen den verschiedenen Instanzen der Funktion aufgeteilt werden. Beim Aufrufen der JavaScript-UDTF wird die Partitionierung festgelegt:

  • Alle Zeilen einer Partition werden über die Funktion processRow an dieselbe UDTF-Instanz übergeben.

  • initialize und finalize werden jeweils einmal pro Partition aufgerufen.

Beachten Sie, dass zwischen Partitionen und UDTF-Instanzen keine Eins-zu-Eins-Beziehung besteht. Obwohl jede Partition nur von einer UDTF-Instanz verarbeitet wird, gilt die Umkehrung nicht – eine einzige UDTF-Instanz kann mehrere Partitionen verarbeiten. Daher ist es wichtig, initialize und finalize zu verwenden, um Partitionen speziell einzurichten und abzubauen, um beispielsweise zu vermeiden, dass angesammelte Werte von der Verarbeitung einer Partition auf die Verarbeitung einer anderen Partition übertragen werden.

Ergebnisspalten

In diesem Abschnitt werden die Spalten beschrieben, die von einer JavaScript-UDTF zurückgegeben werden:

  • Alle in der RETURNS-Klausel der CREATE FUNCTION DDL definierten Spalten werden in der Ausgabebeziehung zurückgegeben.

  • Alle an die UDTF übergebenen Spalten werden ebenfalls zurückgegeben.

Es wird unterschieden zwischen Zeilen, die im processRow-Callback erzeugt werden, und Zeilen, die von finalize erzeugt werden:

  • Wenn in processRow eine Zeile erzeugt wird, kann Snowflake diese mit einer Eingabe korrelieren, nämlich der Zeile, die als row-Argument an die Funktion übergeben wird. Wenn eine gegebene processRow mehr als eine Zeile erzeugt, werden die Eingabeattribute in jede Ausgabezeile kopiert.

    Bei Zeilen, die in processRow erstellt wurden, werden in der Ausgabebeziehung alle Eingabespalten dupliziert.

  • Im finalize-Callback kann Snowflake keine Korrelation zu einer einzelnen Zeile herstellen, da es keine „aktuelle Zeile“ gibt, mit der korreliert werden kann.

    Für Zeilen, die im finalize-Callback erzeugt werden, werden nur die in der PARTITION BY-Klausel verwendeten Spalten dupliziert (da diese für alle Zeilen in der aktuellen Partition gleich sind). Alle anderen Attribute sind NULL. Wenn keine PARTITION BY-Klausel angegeben ist, sind alle Eingabeattribute NULL.

Aufrufen von JavaScript-UDTFs in Abfragen

Keine Partitionierung

Dieses einfache Beispiel zeigt, wie eine UDTF aufgerufen wird. In diesem Beispiel werden Literalwerte übergeben. Die UDTF gibt lediglich die Parameter in umgekehrter Reihenfolge zurück, in der sie übergeben wurden. In diesem Beispiel wird keine Partitionierung verwendet.

SELECT * FROM TABLE(js_udtf(10.0::FLOAT, 20.0::FLOAT));
+----+----+
|  Y |  X |
|----+----|
| 20 | 10 |
+----+----+

In diesem Beispiel wird eine UDTF aufgerufen, und es werden Werte aus einer anderen Tabelle übergeben. In diesem Beispiel wird die UDTF namens js_udtf für jede Zeile in der Tabelle namens tab1 einmal aufgerufen. Bei jedem Aufruf der Funktion werden Werte aus den Spalten c1 und c2 der aktuellen Zeile übergeben. Wie oben wird die UDTF ohne eine PARTITION BY-Klausel aufgerufen.

SELECT * FROM tab1, TABLE(js_udtf(tab1.c1, tab1.c2)) ;

Wenn keine Partitionierung verwendet wird, partitioniert das Snowflake-Ausführungsmodul die Eingabe selbst anhand mehrerer Faktoren, z. B. der Größe des Warehouse, das die Funktion verarbeitet, und der Kardinalität der Eingabebeziehung. In diesem Modus kann der Benutzercode keine Annahmen über Partitionen treffen. Dies ist am nützlichsten, wenn die Funktion nur Zeilen isoliert betrachten muss, um ihre Ausgabe zu erzeugen, und kein Zustand über Zeilen hinweg aggregiert wird.

Explizite Partitionierung

JavaScript-UDTFs können auch über eine Partition aufgerufen werden. Beispiel:

SELECT * FROM tab1, TABLE(js_udtf(tab1.c1, tab1.c2) OVER (PARTITION BY tab1.c3 ORDER BY tab1.c1));

Explizite Partitionierung mit einer leeren OVER-Klausel

SELECT * FROM tab1, TABLE(js_udtf(tab1.c1, tab1.c2) OVER ());

Eine leere OVER-Klausel bedeutet, dass jede Zeile zu derselben Partition gehört (d. h. die gesamte Eingabebeziehung ist eine Partition).

Bemerkung

Seien Sie vorsichtig, wenn Sie eine JavaScript-UDTF mit einer leeren OVER-Klausel aufrufen, da dies Snowflake auf die Erstellung einer Instanz der Funktion beschränkt und Snowflake die Berechnung daher nicht parallelisieren kann.

Beispiele für JavaScript-UDTFs

Dieser Abschnitt enthält mehrere Beispiel-JavaScript-UDTFs.

Grundlegende Hello World-Beispiele

Die folgende JavaScript-UDTF benötigt keine Parameter und gibt immer die gleichen Werte zurück. Sie wird hauptsächlich zu Illustrationszwecken bereitgestellt:

CREATE OR REPLACE FUNCTION HelloWorld0()
    RETURNS TABLE (OUTPUT_COL VARCHAR)
    LANGUAGE JAVASCRIPT
    AS '{
        processRow: function f(row, rowWriter, context){
           rowWriter.writeRow({OUTPUT_COL: "Hello"});
           rowWriter.writeRow({OUTPUT_COL: "World"});
           }
        }';

SELECT output_col FROM TABLE(HelloWorld0());

Ausgabe:

+------------+
| OUTPUT_COL |
+============+
| Hello      |
+------------+
| World      |
+------------+

Die folgende JavaScript-UDTF dient ebenfalls zur Veranschaulichung, verwendet jedoch einen Eingabeparameter. Beachten Sie, dass JavaScript die Groß-/Kleinschreibung berücksichtigt, aber SQL erzwingt bei Bezeichnern Großbuchstaben. Wenn der JavaScript-Code auf einen SQL-Parameternamen verweist, muss der JavaScript-Code Großbuchstaben verwenden.

Beachten Sie auch, dass auf Funktionsparameter über den Parameter row in der Funktion get_params() zugegriffen wird:

CREATE OR REPLACE FUNCTION HelloHuman(First_Name VARCHAR, Last_Name VARCHAR)
    RETURNS TABLE (V VARCHAR)
    LANGUAGE JAVASCRIPT
    AS '{
        processRow: function get_params(row, rowWriter, context){
           rowWriter.writeRow({V: "Hello"});
           rowWriter.writeRow({V: row.FIRST_NAME});  // Note the capitalization and the use of "row."!
           rowWriter.writeRow({V: row.LAST_NAME});   // Note the capitalization and the use of "row."!
           }
        }';

SELECT V AS Greeting FROM TABLE(HelloHuman('James', 'Kirk'));

Ausgabe:

+------------+
|  GREETING  |
+============+
| Hello      |
+------------+
| James      |
+------------+
| Kirk       |
+------------+

Grundlegende Beispiele zur Veranschaulichung der Callback-Funktionen

Die folgende JavaScript-UDTF zeigt alle API-Callback-Funktionen und verschiedene Ausgabespalten. Es werden einfach alle Zeilen in der vorliegenden Form zurückgegeben und die Anzahl der Zeichen angegeben, die in jeder Partition angezeigt werden. Außerdem wird veranschaulicht, wie der Status über eine Partition mit der Referenz THIS gemeinsam genutzt wird. Beachten Sie, dass das Beispiel einen initialize-Callback verwendet, um den Zähler mit null zu initialisieren. Dies ist erforderlich, da eine bestimmte Funktionsinstanz zur Verarbeitung mehrerer Partitionen verwendet werden kann:

-- set up for the sample
CREATE TABLE parts (p FLOAT, s STRING);

INSERT INTO parts VALUES (1, 'michael'), (1, 'kelly'), (1, 'brian');
INSERT INTO parts VALUES (2, 'clara'), (2, 'maggie'), (2, 'reagan');

-- creation of the UDTF
CREATE OR REPLACE FUNCTION "CHAR_SUM"(INS STRING)
    RETURNS TABLE (NUM FLOAT)
    LANGUAGE JAVASCRIPT
    AS '{
    processRow: function (row, rowWriter, context) {
      this.ccount = this.ccount + 1;
      this.csum = this.csum + row.INS.length;
      rowWriter.writeRow({NUM: row.INS.length});
    },
    finalize: function (rowWriter, context) {
     rowWriter.writeRow({NUM: this.csum});
    },
    initialize: function(argumentInfo, context) {
     this.ccount = 0;
     this.csum = 0;
    }}';

Die folgende Abfrage veranschaulicht das Aufrufen der CHAR_SUM-UDTF für die Tabelle parts ohne Partitionierung:

SELECT * FROM parts, TABLE(char_sum(s));

Ausgabe:

+--------+---------+-----+
| P      | S       | NUM |
+--------+---------+-----+
| 1      | michael | 7   |
| 1      | kelly   | 5   |
| 1      | brian   | 5   |
| 2      | clara   | 5   |
| 2      | maggie  | 6   |
| 2      | reagan  | 6   |
| [NULL] | [NULL]  | 34  |
+--------+---------+-----+

Wenn keine Partitionierung angegeben ist, definiert Snowflake automatisch Partitionen. In diesem Beispiel wird aufgrund der geringen Anzahl von Zeilen nur eine Partition erstellt (d. h. es wird nur ein Aufruf von finalize ausgeführt). Beachten Sie, dass in der letzten Zeile für die Eingabespalten NULL-Werte verwendet werden.

Gleiche Abfrage, jedoch mit expliziter Partitionierung:

SELECT * FROM parts, TABLE(char_sum(s) OVER (PARTITION BY p));

Ausgabe:

+--------+---------+-----+
| P      | S       | NUM |
+--------+---------+-----+
| 1      | michael | 7   |
| 1      | kelly   | 5   |
| 1      | brian   | 5   |
| 1      | [NULL]  | 17  |
| 2      | clara   | 5   |
| 2      | maggie  | 6   |
| 2      | reagan  | 6   |
| 2      | [NULL]  | 17  |
+--------+---------+-----+

Dieses Beispiel wird über die Spalte p partitioniert, wodurch zwei Partitionen entstehen. Für jede Partition wird im finalize-Callback eine einzelne Zeile zurückgegeben. Dies ergibt insgesamt zwei Zeilen, die durch den NULL-Wert in der Spalte s unterschieden werden. Da p die PARTITION BY-Spalte ist, haben die in finalize erstellten Zeilen den Wert p, der die aktuelle Partition definiert.

Erweiterte Beispiele mit Tabellenwerten und anderen UDTFs als Eingabe

Diese einfache UDTF konvertiert einen „Bereich“ von IP-Adressen in eine vollständige Liste von IP-Adressen. Die Eingabe besteht aus den ersten 3 Segmenten der IP-Adresse (z. B. '192.168.1') und dann aus Anfang und Ende des Bereichs, der zum Generieren des letzten Segments (z. B. 42 und 45) verwendet wird:

CREATE OR REPLACE FUNCTION range_to_values(PREFIX VARCHAR, RANGE_START FLOAT, RANGE_END FLOAT)
    RETURNS TABLE (IP_ADDRESS VARCHAR)
    LANGUAGE JAVASCRIPT
    AS $$
      {
        processRow: function f(row, rowWriter, context)  {
          var suffix = row.RANGE_START;
          while (suffix <= row.RANGE_END)  {
            rowWriter.writeRow( {IP_ADDRESS: row.PREFIX + "." + suffix} );
            suffix = suffix + 1;
            }
          }
      }
      $$;

SELECT * FROM TABLE(range_to_values('192.168.1', 42::FLOAT, 45::FLOAT));

Ausgabe:

+--------------+
| IP_ADDRESS   |
+==============+
| 192.168.1.42 |
+--------------+
| 192.168.1.43 |
+--------------+
| 192.168.1.44 |
+--------------+
| 192.168.1.45 |
+--------------+

Aufbauend auf dem vorherigen Beispiel können Sie einzelne IP-Adressen für mehr als einen Bereich berechnen. Diese nächste Anweisung erstellt eine Tabelle mit Bereichen, die zum Erweitern auf einzelne IP-Adressen verwendet werden können. Die Abfrage gibt dann die Zeilen aus der Tabelle in die range_to_values()-UDTF ein, um die einzelnen IP-Adressen zurückzugeben:

CREATE TABLE ip_address_ranges(prefix VARCHAR, range_start INTEGER, range_end INTEGER);
INSERT INTO ip_address_ranges (prefix, range_start, range_end) VALUES
    ('192.168.1', 42, 44),
    ('192.168.2', 10, 12),
    ('192.168.2', 40, 40)
    ;

SELECT rtv.ip_address
  FROM ip_address_ranges AS r, TABLE(range_to_values(r.prefix, r.range_start::FLOAT, r.range_end::FLOAT)) AS rtv;

Ausgabe:

+--------------+
| IP_ADDRESS   |
+==============+
| 192.168.1.42 |
+--------------+
| 192.168.1.43 |
+--------------+
| 192.168.1.44 |
+--------------+
| 192.168.2.10 |
+--------------+
| 192.168.2.11 |
+--------------+
| 192.168.2.12 |
+--------------+
| 192.168.2.40 |
+--------------+

Achtung

In diesem Beispiel ist die in der FROM-Klausel verwendete Syntax identisch mit der Syntax einer inneren Verknüpfung (d. h. FROM t1, t2). Die ausgeführte Operation ist jedoch keine echte innere Verknüpfung. Das tatsächliche Verhalten ist, dass die Funktion range_to_values() mit den Werten aus jeder Zeile der Tabelle ip_address changes aufgerufen wird. Mit anderen Worten: Folgendes wäre gleichbedeutend:

for input_row in ip_address_ranges:
  output_row = range_to_values(input_row.prefix, input_row.range_start, input_row.range_end)

Das Konzept der Übergabe von Werten an eine UDTF kann auf mehrere UDTFs erweitert werden. Im nächsten Beispiel wird eine UDTF mit dem Namen fake_ipv4_to_ipv6() erstellt, die die IPV4-Adresse in IPV6-Adressen „konvertiert“. Die Abfrage ruft die Funktion dann als Teil einer komplexeren Anweisung auf, die eine andere UDTFbeinhaltet:

-- Example UDTF that "converts" an IPV4 address to a range of IPV6 addresses.
-- (for illustration purposes only and is not intended for actual use)
CREATE OR REPLACE FUNCTION fake_ipv4_to_ipv6(ipv4 VARCHAR)
    RETURNS TABLE (IPV6 VARCHAR)
    LANGUAGE JAVASCRIPT
    AS $$
      {
        processRow: function f(row, rowWriter, context)  {
          rowWriter.writeRow( {IPV6: row.IPV4 + "." + "000.000.000.000"} );
          rowWriter.writeRow( {IPV6: row.IPV4 + "." + "..."} );
          rowWriter.writeRow( {IPV6: row.IPV4 + "." + "FFF.FFF.FFF.FFF"} );
          }
      }
      $$;

SELECT ipv6 FROM TABLE(fake_ipv4_to_ipv6('192.168.3.100'));

Ausgabe:

+-------------------------------+
| IPV6                          |
+===============================+
| 192.168.3.100.000.000.000.000 |
+-------------------------------+
| 192.168.3.100....             |
+-------------------------------+
| 192.168.3.100.FFF.FFF.FFF.FFF |
+-------------------------------+

Die folgende Abfrage verwendet die zuvor erstellten fake_ipv4_to_ipv6- und range_to_values()-UDTFs mit Eingabe aus der Tabelle ip_address changes. Mit anderen Worten, sie beginnt mit einem Set von IP-Adressbereichen, konvertiert diese in einzelne IPV4-Adressen und nimmt dann jede IPV4-Adresse und „konvertiert“ diese wiederum in einen Bereich von IPV6-Adressen:

SELECT rtv6.ipv6
  FROM ip_address_ranges AS r,
       TABLE(range_to_values(r.prefix, r.range_start::FLOAT, r.range_end::FLOAT)) AS rtv,
       TABLE(fake_ipv4_to_ipv6(rtv.ip_address)) AS rtv6
  WHERE r.prefix = '192.168.2'  -- limits the output for this example
  ;

Ausgabe:

+------------------------------+
| IPV6                         |
+==============================+
| 192.168.2.10.000.000.000.000 |
+------------------------------+
| 192.168.2.10....             |
+------------------------------+
| 192.168.2.10.FFF.FFF.FFF.FFF |
+------------------------------+
| 192.168.2.11.000.000.000.000 |
+------------------------------+
| 192.168.2.11....             |
+------------------------------+
| 192.168.2.11.FFF.FFF.FFF.FFF |
+------------------------------+
| 192.168.2.12.000.000.000.000 |
+------------------------------+
| 192.168.2.12....             |
+------------------------------+
| 192.168.2.12.FFF.FFF.FFF.FFF |
+------------------------------+
| 192.168.2.40.000.000.000.000 |
+------------------------------+
| 192.168.2.40....             |
+------------------------------+
| 192.168.2.40.FFF.FFF.FFF.FFF |
+------------------------------+

Beachten Sie, dass in diesem Beispiel zweimal die Join-Syntax verwendet wurde, aber keine der Operationen eine echte Verknüpfung war: Beide waren Aufrufe einer UDTF, wobei die Ausgabe einer Tabelle oder eine andere UDTF als Eingabe verwendet wurde.

Bei einem echten INNER JOIN muss die Reihenfolge nicht beachtet werden. Beispielsweise sind die folgenden Anweisungen identisch:

table1 INNER JOIN table2 ON ...
table2 INNER JOIN table1 ON ...

Die Übergabe von Werten an eine UDTF ist keine echte Verknüpfung, und bei den Operationen wird die Reihenfolge nicht beachtet. Die folgende Abfrage ist beispielsweise identisch mit dem vorherigen Beispiel, außer dass die Reihenfolge der UDTFs in der FROM-Klausel umgekehrt wird:

SELECT rtv6.ipv6
  FROM ip_address_ranges AS r,
       TABLE(fake_ipv4_to_ipv6(rtv.ip_address)) AS rtv6,
       TABLE(range_to_values(r.prefix, r.range_start::FLOAT, r.range_end::FLOAT)) AS rtv
 WHERE r.prefix = '192.168.2'  -- limits the output for this example
  ;

Die Abfrage schlägt mit der folgenden Fehlermeldung fehl:

SQL compilation error: error line 3 at position 35 invalid identifier 'RTV.IP_ADDRESS'

Der Bezeichner rtv.ip_address ist ungültig, da er vor seiner Verwendung nicht definiert wurde. Bei einer echten Verknüpfung würde dies nicht passieren, aber bei der Verarbeitung von UDTFs mithilfe der Join-Syntax kann dieser Fehler möglicherweise auftreten.

Testen Sie als Nächstes eine Anweisung, die die Übergabe von Eingabewerten an eine UDTF mit einer echten Verknüpfung kombiniert. Denken Sie jedoch daran, dass die Eingabe in eine UDTF und INNER JOIN die gleiche Syntax verwenden, was verwirrend sein kann:

-- First, create a small table of IP address owners.
-- This table uses only IPv4 addresses for simplicity.
DROP TABLE ip_address_owners;
CREATE TABLE ip_address_owners (ip_address VARCHAR, owner_name VARCHAR);
INSERT INTO ip_address_owners (ip_address, owner_name) VALUES
  ('192.168.2.10', 'Barbara Hart'),
  ('192.168.2.11', 'David Saugus'),
  ('192.168.2.12', 'Diego King'),
  ('192.168.2.40', 'Victoria Valencia')
  ;

-- Now join the IP address owner table to the IPv4 addresses.
SELECT rtv.ip_address, ipo.owner_name
  FROM ip_address_ranges AS r,
       TABLE(range_to_values(r.prefix, r.range_start::FLOAT, r.range_end::FLOAT)) AS rtv,
       ip_address_owners AS ipo
 WHERE ipo.ip_address = rtv.ip_address AND
      r.prefix = '192.168.2'   -- limits the output for this example
  ;

Ausgabe:

+--------------+-------------------+
| IP_ADDRESS   | OWNER_NAME        |
+==============+===================+
| 192.168.2.10 | Barbara Hart      |
+--------------+-------------------+
| 192.168.2.11 | David Saugus      |
+--------------+-------------------+
| 192.168.2.12 | Diego King        |
+--------------+-------------------+
| 192.168.2.40 | Victoria Valencia |
+--------------+-------------------+

Achtung

Das vorstehende Beispiel funktioniert wie beschrieben. Beim Kombinieren von UDTFs mit echten Verknüpfungen sollten Sie jedoch vorsichtig sein, da dies zu nicht deterministischem und/oder unerwartetem Verhalten führen kann.

Beachten Sie außerdem, dass sich dieses Verhalten in Zukunft möglicherweise ändert.