Tabellarische JavaScript-UDFs (UDTFs)

Sie können den Handler für eine benutzerdefinierte Tabellenfunktion (UDTF) in JavaScript schreiben.

Der Handler-Code verarbeitet die mit dem UDTF-Aufruf übergeben Zeilen und gibt ein tabellarisches Ergebnis zurück. Die empfangenen Zeilen werden partitioniert, entweder implizit von Snowflake oder explizit in der Syntax des Funktionsaufrufs. Sie verwenden Callback-Funktionen, die Sie schreiben, um einzelne Zeilen sowie die Partitionen, in die sie gruppiert sind, zu verarbeiten.

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 unter Objekt-Callback-Funktionen.

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.

Bemerkung

Tabellarische Funktionen (UDTFs) haben einen Grenzwert von 500 Eingabeargumenten und 500 Ausgabespalten.

Objekt-Callback-Funktionen

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

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

Beachten Sie, dass nur processRow() erforderlich ist. Die anderen Funktionen sind optional.

processRow()

Diese Callback-Funktion wird in der Eingabebeziehung einmal für jede Zeile aufgerufen. Die Argumente für processRow() werden im row-Objekt übergeben. Bei allen in der CREATE FUNCTION-Anweisung definierten Argumenten, die zum Erstellen der UDTF verwendet wurden, gibt es ein row-Objekt mit einer Eigenschaft, die denselben Namen in Großbuchstaben hat. Der Wert dieser Eigenschaft ist der Wert des Arguments für die aktuelle Zeile. (Der Wert wird in einen JavaScript-Wert umgewandelt.)

Das Argument rowWriter wird vom benutzerdefinierten Code zum Erstellen von Ausgabezeilen verwendet. Das Objekt rowWriter definiert eine einzelne Funktion: writeRow(). Der writeRow()-Funktion wird ein Zeilenobjekt als Argument übergeben. Hierbei handelt es sich um eine einzelne Zeile in der Ausgabetabelle, die durch ein JavaScript-Objekt repräsentiert wird. Für jede Spalte, die in der RETURNS-Klausel des CREATE FUNCTION-Befehls 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 finalize() wird einmal aufgerufen, nachdem alle Zeilen an processRow() übergeben wurden. (Wenn die Daten in Partitionen gruppiert sind, dann wird finalize() einmal für jede Partition aufgerufen, nachdem alle Zeilen in dieser Partition an processRow() übergeben wurden.)

Diese Callback-Funktion kann verwendet werden, um einen beliebigen Status auszugeben, der möglicherweise in processRow() mit demselben rowWriter-Objekt aggregiert wurde, das an processRow() übergeben wird.

Bemerkung

Snowflake unterstützt zwar große Partitionen mit Timeouts, die so eingestellt sind, dass sie erfolgreich verarbeitet werden können, aber bei besonders großen Partitionen kann es zu Zeitüberschreitungen kommen (z. B. wenn finalize zu lange für den Abschluss braucht). Wenden Sie sich an den Snowflake-Support, wenn Sie den Timeout-Schwellenwert für bestimmte Nutzungsszenarios anpassen möchten.

initialize()

Diese Callback-Funktion wird einmal für jede Partition aufgerufen, bevor processRow() aufgerufen wird.

Verwenden Sie initialize(), um während der Ergebnisberechnung einen beliebigen Status einzustellen.

Der Parameter argumentInfo der initialize()-Funktion enthält Metadaten zu den Argumenten der benutzerdefinierten Funktion. Wenn zum Beispiel die UDF wie folgt definiert ist:

CREATE FUNCTION f(argument_1 INTEGER, argument_2 VARCHAR) ...
Copy

Dann enthält argumentInfo Informationen zu argument_1 und argument_2.

argumentInfo hat eine Eigenschaft für jedes dieser Argumente. Jede Eigenschaft ist ein Objekt mit den folgenden Werten:

  • type: String. Der Typ dieses Arguments.

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

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

Die initialize()-Funktion kann keine Ausgabezeilen erzeugen.

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

In vielen Situationen möchten Sie vielleicht Zeilen in Partitionen gruppieren. Die Partitionierung hat zwei wesentliche Vorteile:

  • Sie können Zeilen auf der Basis eines gemeinsamen Merkmals gruppieren. Auf diese Weise können Sie alle Zeilen innerhalb der Gruppe gemeinsam verarbeiten und jede Gruppe unabhängig voneinander weiterverarbeiten.

  • Das ermöglicht Snowflake eine Aufteilung des Workloads, wodurch sich die Parallelisierung und damit die Leistung verbessern lässt.

Sie können zum Beispiel Daten zu Aktienkursen in eine Gruppe pro Aktie partitionieren. Alle Aktienkurse einer einzelnen Firma können zusammen verarbeitet werden, und die Gruppen der verschiedenen Firmen werden unabhängig voneinander verarbeitet.

Mit der folgenden Anweisung wird die UDTF namens js_udtf() auf einzelnen Partitionen aufgerufen. Jede Partition enthält alle Zeilen, für die der Ausdruck PARTITION BY denselben Wert ergibt (z. B. dasselbe Aktiensymbol).

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

Wenn Sie einen Partitionsausdruck zur Verwendung mit einer UDTF angeben, führt Snowflake folgende Aufrufe aus:

  • initialize() einmal für jede Partition.

  • processRow() einmal für jede einzelne Zeile in dieser Partition.

  • finalize() einmal für jede Partition (nach Verarbeitung der letzten Zeile in dieser Partition).

Möglicherweise möchten Sie die Zeilen auch in einer bestimmten Reihenfolge verarbeiten. Wenn Sie z. B. den gleitenden Durchschnitt eines Aktienkurses über die Zeit berechnen möchten, dann sortieren Sie die Aktienkurse nach Zeitstempel (und führen eine Partitionierung nach Aktie oder Firma aus). Im folgenden Beispiel wird gezeigt, wie Sie dies tun können:

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

Wenn Sie eine ORDER BY-Klausel angeben, werden die Zeilen in der durch den ORDER BY-Ausdruck festgelegten Reihenfolge verarbeitet. Die Zeilen werden in der durch den ORDER BY-Ausdruck definierten Reihenfolge an processRow() übergeben.

In den meisten Fällen verbessert die Partitionierung von Daten fast automatisch die Möglichkeiten zur Parallelisierung und führt damit zu einer Steigerung der Leistung. Snowflake führt normalerweise mehrere UDTF-Instanzen parallel aus. (Für diese Diskussion wird eine Instanz einer JavaScript-UDTF als eine Instanz des JavaScript-Objekts definiert, das zur Repräsentation der Funktion in Snowflake verwendet wird.) Jede Partition von Zeilen wird an eine einzelne Instanz der UDTF übergeben.

Beachten Sie, dass aber zwischen Partitionen und UDTF-Instanzen nicht zwingend eine 1:1-Beziehung besteht. Obwohl jede Partition nur von einer UDTF-Instanz verarbeitet wird, gilt die Umkehrung nicht – eine UDTF-Instanz kann mehrere Partitionen verarbeiten. Daher ist es wichtig, initialize() und finalize() zu verwenden, um jede Partitionen speziell einzurichten und wieder aufzuheben, um beispielsweise zu vermeiden, dass akkumulierte 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 des CREATE FUNCTION-Befehls 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 ein gegebener processRow()-Aufruf 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

Wenn Sie eine UDTF in der FROM-Klausel einer Abfrage aufrufen, geben Sie den Namen und die Argumente der UDTF innerhalb der Klammern an, die dem Schlüsselwort TABLE folgen.

Verwenden Sie beim Aufrufen einer UDTF das Schlüsselwort TABLE in etwa wie folgt:

SELECT ...
  FROM TABLE ( udtf_name (udtf_arguments) )
Copy

Bemerkung

Weitere Informationen zum Aufrufen von UDFs und UDTFs finden Sie unter Aufrufen einer UDF.

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 |
+----+----+
Copy

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

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

Explizite Partitionierung mit einer leeren OVER-Klausel

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

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());
Copy

Ausgabe:

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

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'));
Copy

Ausgabe:

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

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;
    }}';
Copy

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

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

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  |
+--------+---------+-----+
Copy

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

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  |
+--------+---------+-----+
Copy

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

Ausgabe:

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

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;
Copy

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 |
+--------------+
Copy

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

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'));
Copy

Ausgabe:

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

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
  ;
Copy

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 |
+------------------------------+
Copy

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
  ;
Copy

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
  ;
Copy

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 |
+--------------+-------------------+
Copy

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.