Mit rekursiven CTEs arbeiten

In GoogleSQL for BigQuery enthält eine WITH-Klausel einen oder mehrere allgemeine Tabellenausdrücke (CTEs) mit temporären Tabellen, auf die Sie in einem Abfrageausdruck verweisen können. CTEs können nicht rekursiv, rekursiv oder beides sein. Das Keyword RECURSIVE aktiviert die Rekursion in der WITH-Klausel (WITH RECURSIVE).

Eine rekursive CTE kann auf sich selbst, eine vorherige CTE oder eine nachfolgende CTE verweisen. Ein nicht rekursiver CTE kann nur auf vorherige CTEs und nicht auf sich selbst verweisen. Rekursive CTEs werden kontinuierlich ausgeführt, bis keine neuen Ergebnisse gefunden werden. Nicht rekursive CTEs werden einmal ausgeführt. Aus diesen Gründen werden häufig rekursive CTEs für die Abfrage hierarchischer Daten und Diagrammdaten verwendet.

Betrachten Sie beispielsweise ein Diagramm, in dem jede Zeile einen Knoten darstellt, der mit anderen Knoten verknüpft werden kann. Sie benötigen einen rekursiven CTE in der Abfrage (WITH RECURSIVE), um den transitiven Abschluss aller erreichbaren Knoten von einem bestimmten Startknoten zu ermitteln, ohne die maximale Anzahl von Hops zu kennen. Die rekursive Abfrage würde mit dem Basisfall des Startknotens beginnen und jeder Schritt würde die neuen, nicht erkannten Knoten berechnen, die von allen bisher gesehenen Knoten bis zum vorherigen Schritt erreicht werden können. Die Abfrage ist abgeschlossen, wenn keine neuen Knoten gefunden werden können.

Rekursive CTEs können jedoch rechenintensiv sein. Bevor Sie diese verwenden, lesen Sie diese Anleitung und den Abschnitt WITH-Klausel der GoogleSQL-Referenzdokumentation.

Rekursiven CTE erstellen

Verwenden Sie zum Erstellen eines rekursiven CTE in GoogleSQL die WITH RECURSIVE-Klausel, wie im folgenden Beispiel gezeigt:

WITH RECURSIVE
  CTE_1 AS (
    (SELECT 1 AS iteration UNION ALL SELECT 1 AS iteration)
    UNION ALL
    SELECT iteration + 1 AS iteration FROM CTE_1 WHERE iteration < 3
  )
SELECT iteration FROM CTE_1
ORDER BY 1 ASC

Das vorherige Beispiel liefert folgende Ergebnisse:

/*-----------*
 | iteration |
 +-----------+
 | 1         |
 | 1         |
 | 2         |
 | 2         |
 | 3         |
 | 3         |
 *-----------*/

Ein rekursiver CTE enthält einen Basisbegriff, einen Union-Operator und einen rekursiven Begriff. Der Basisbegriff führt die erste Iteration des rekursiven Union-Vorgangs aus. Der rekursive Begriff führt die verbleibenden Iterationen aus und muss einen Selbstverweis auf den rekursiven CTE enthalten. Nur der rekursive Begriff kann einen Selbstverweis enthalten.

Im vorherigen Beispiel enthält der rekursive CTE die folgenden Komponenten:

  • Rekursiver CTE-Name: CTE_1
  • Basisbegriff: SELECT 1 AS iteration
  • Union-Operator: UNION ALL
  • Rekursiver Begriff: SELECT iteration + 1 AS iteration FROM CTE_1 WHERE iteration < 3

Weitere Informationen zur rekursiven CTE-Syntax, den Regeln und Beispielen finden Sie in der WITH-Klausel in der GoogleSQL-Referenzdokumentation.

Erreichbarkeit in einem gerichteten azyklischen Graphen (Directed Acyclic Graph, DAG) prüfen

Mit einer rekursiven Abfrage können Sie die Erreichbarkeit in einem gerichteten azyklischen Graphen (Directed Acyclic Graph, DAG) prüfen. Mit der folgenden Abfrage werden alle Knoten ermittelt, die vom Knoten 5 aus in einem Diagramm namens GraphData erreichbar sind:

WITH RECURSIVE
  GraphData AS (
    --    1          5
    --   / \        / \
    --  2 - 3      6   7
    --      |       \ /
    --      4        8
    SELECT 1 AS from_node, 2 AS to_node UNION ALL
    SELECT 1, 3 UNION ALL
    SELECT 2, 3 UNION ALL
    SELECT 3, 4 UNION ALL
    SELECT 5, 6 UNION ALL
    SELECT 5, 7 UNION ALL
    SELECT 6, 8 UNION ALL
    SELECT 7, 8
  ),
  R AS (
    (SELECT 5 AS node)
    UNION ALL
    (
      SELECT GraphData.to_node AS node
      FROM R
      INNER JOIN GraphData
        ON (R.node = GraphData.from_node)
    )
  )
SELECT DISTINCT node FROM R ORDER BY node;

Das vorherige Beispiel liefert folgende Ergebnisse:

/*------*
 | node |
 +------+
 | 5    |
 | 6    |
 | 7    |
 | 8    |
 *------*/

Fehler beim Iterationslimit beheben

Rekursive CTEs können zu einer unendlichen Rekursion führen, die auftritt, wenn der rekursive Begriff kontinuierlich ausgeführt wird, ohne eine Beendigungsbedingung zu erfüllen. Für das Beenden von unendlichen Wiederholungen wird ein Limit von Iterationen für jeden rekursiven CTE erzwungen. Für BigQuery beträgt das Iterationslimit 500 Iterationen. Sobald ein rekursiver CTE die maximale Anzahl von Iterationen erreicht hat, wird die CTE-Ausführung mit einem Fehler abgebrochen.

Dieses Limit besteht, da die Berechnung eines rekursiven CTE teuer sein kann und das Ausführen eines CTE mit einer großen Anzahl von Iterationen viele Systemressourcen verbraucht und sehr viel länger dauert.

Bei Abfragen, die das Iterationslimit erreichen, fehlt in der Regel eine ordnungsgemäße Beendigungsbedingung, sodass eine Endlosschleife entsteht oder rekursive CTEs in unangemessenen Szenarien verwendet werden.

Wenn ein Fehler mit einem Rekursionslimit auftritt, lesen Sie die Vorschläge in diesem Abschnitt.

Auf unendliche Rekursion prüfen

Achten Sie darauf, dass der rekursive Begriff nach der Ausführung einer bestimmten Anzahl von Iterationen ein leeres Ergebnis liefern kann, um eine unbegrenzte Rekursion zu verhindern.

Eine Möglichkeit zur Prüfung der unendlichen Rekursion besteht darin, den rekursiven CTE in einen TEMP TABLE mit einer REPEAT-Schleife für die ersten 100 Iterationen zu konvertieren:

DECLARE current_iteration INT64 DEFAULT 0;

CREATE TEMP TABLE recursive_cte_name AS
SELECT base_expression, current_iteration AS iteration;

REPEAT
  SET current_iteration = current_iteration + 1;
  INSERT INTO recursive_cte_name
    SELECT recursive_expression, current_iteration
    FROM recursive_cte_name
    WHERE termination_condition_expression
      AND iteration = current_iteration - 1
      AND current_iteration < 100;
  UNTIL NOT EXISTS(SELECT * FROM recursive_cte_name WHERE iteration = current_iteration)
END REPEAT;

Ersetzen Sie die folgenden Werte:

  • recursive_cte_name: Der rekursive CTE zum Debuggen.
  • base_expression: Der Basisbegriff des rekursiven CTEs.
  • recursive_expression: Der rekursive Begriff des rekursiven CTEs.
  • termination_condition_expression: Der Beendigungsausdruck des rekursiven CTEs.

Betrachten Sie beispielsweise den folgenden rekursiven CTE namens TestCTE:

WITH RECURSIVE
  TestCTE AS (
    SELECT 1 AS n
    UNION ALL
    SELECT n + 3 FROM TestCTE WHERE MOD(n, 6) != 0
  )

In diesem Beispiel werden die folgenden Werte verwendet:

  • recursive_cte_name: TestCTE
  • base_expression: SELECT 1
  • recursive_expression: n + 3
  • termination_condition_expression: MOD(n, 6) != 0

Mit dem folgenden Code wird daher TestCTE auf unendliche Rekursion getestet:

DECLARE current_iteration INT64 DEFAULT 0;

CREATE TEMP TABLE TestCTE AS
SELECT 1 AS n, current_iteration AS iteration;

REPEAT
SET current_iteration = current_iteration + 1;

INSERT INTO TestCTE
SELECT n + 3, current_iteration
FROM TestCTE
WHERE
  MOD(n, 6) != 0
  AND iteration = current_iteration - 1
  AND current_iteration < 10;

UNTIL
  NOT EXISTS(SELECT * FROM TestCTE WHERE iteration = current_iteration)
    END REPEAT;

-- Print the number of rows produced by each iteration

SELECT iteration, COUNT(1) AS num_rows
FROM TestCTE
GROUP BY iteration
ORDER BY iteration;

-- Examine the actual result produced for a specific iteration

SELECT * FROM TestCTE WHERE iteration = 2;

Das vorherige Beispiel liefert die folgenden Ergebnisse, die die Iterations-ID und die Anzahl der Zeilen enthalten, die während dieses Durchlaufs erstellt wurden:

/*-----------+----------*
 | iteration | num_rows |
 +-----------+----------+
 | 0         | 1        |
 | 1         | 1        |
 | 2         | 1        |
 | 3         | 1        |
 | 4         | 1        |
 | 5         | 1        |
 | 6         | 1        |
 | 7         | 1        |
 | 8         | 1        |
 | 9         | 1        |
 | 10        | 1        |
 *-----------+----------*/

Dies sind die tatsächlichen Ergebnisse, die während der Iteration 2 erstellt wurden:

/*----------+-----------*
 | n        | iteration |
 +----------+-----------+
 | 7        | 2         |
 *----------+-----------*/

Wenn die Anzahl der Zeilen immer größer als null ist, was in diesem Beispiel der Fall ist, hat die Stichprobe wahrscheinlich eine unendliche Rekursion.

Angemessene Verwendung des rekursiven CTE überprüfen

Prüfen Sie, ob Sie den rekursive CTE in einem geeigneten Szenario verwenden. Rekursive CTEs können teuer sein, da sie zum Abfragen von Hierarchiedaten und Graphdaten verwendet werden. Wenn Sie diese beiden Arten von Daten nicht abfragen, sollten Sie Alternativen in Betracht ziehen, z. B. die Verwendung der LOOP-Anweisung mit einem nicht rekursiven CTE.

Rekursiven CTE in mehrere rekursive CTEs aufteilen

Wenn Sie der Meinung sind, dass Ihre rekursive CTE mehr als die maximal zulässige Iteration benötigt, können Sie Ihre rekursive CTE in mehrere rekursive CTEs aufteilen.

Sie können einen rekursiven CTE mit einer Abfragestruktur wie der folgenden aufteilen:

WITH RECURSIVE
  CTE_1 AS (
    SELECT base_expression
    UNION ALL
    SELECT recursive_expression FROM CTE_1 WHERE iteration < 500
  ),
  CTE_2 AS (
    SELECT * FROM CTE_1 WHERE iteration = 500
    UNION ALL
    SELECT recursive_expression FROM CTE_2 WHERE iteration < 500 * 2
  ),
  CTE_3 AS (
    SELECT * FROM CTE_2 WHERE iteration = 500 * 2
    UNION ALL
    SELECT recursive_expression FROM CTE_3 WHERE iteration < 500 * 3
  ),
  [, ...]
SELECT * FROM CTE_1
UNION ALL SELECT * FROM CTE_2 WHERE iteration > 500
UNION ALL SELECT * FROM CTE_3 WHERE iteration > 500 * 2
[...]

Ersetzen Sie die folgenden Werte:

  • base_expression: Der Basisbegriffsausdruck für den aktuellen CTE.
  • recursive_expression: Der rekursive Begriffsausdruck für den aktuellen CTE.

Der folgende Code teilt einen CTE beispielsweise in drei verschiedene CTEs auf:

WITH RECURSIVE
  CTE_1 AS (
    SELECT 1 AS iteration
    UNION ALL
    SELECT iteration + 1 AS iteration FROM CTE_1 WHERE iteration < 10
  ),
  CTE_2 AS (
    SELECT * FROM CTE_1 WHERE iteration = 10
    UNION ALL
    SELECT iteration + 1 AS iteration FROM CTE_2 WHERE iteration < 10 * 2
  ),
  CTE_3 AS (
    SELECT * FROM CTE_2 WHERE iteration = 10 * 2
    UNION ALL
    SELECT iteration + 1 AS iteration FROM CTE_3 WHERE iteration < 10 * 3
  )
SELECT iteration FROM CTE_1
UNION ALL
SELECT iteration FROM CTE_2 WHERE iteration > 10
UNION ALL
SELECT iteration FROM CTE_3 WHERE iteration > 20
ORDER BY 1 ASC

Im vorherigen Beispiel werden 500 Iterationen durch zehn Iterationen ersetzt, sodass die Ergebnisse der Abfrage schneller angezeigt werden. Die Abfrage erzeugt 30 Zeilen, aber jeder rekursive CTE iteriert nur zehnmal. Die Ausgabe sieht in etwa so aus:

/*-----------*
 | iteration |
 +-----------+
 | 2         |
 | ...       |
 | 30        |
 *-----------*/

Sie können die vorherige Abfrage bei viel größeren Iterationen testen.

Anstelle eines rekursiven CTE eine Schleife verwenden

Um Iterationslimits zu vermeiden, sollten Sie eine Schleife anstelle eines rekursiven CTE verwenden. Sie können eine Schleife mit einer von mehreren Schleifenanweisungen erstellen, z. B. LOOP, REPEAT oder WHILE. Weitere Informationen zu Schleifen.

Rekursives Limit ändern

Wenn Sie der Meinung sind, dass die folgenden Faktoren zutreffen, wenden Sie sich an den Kundensupport, um das rekursive Limit zu erhöhen:

  • Es gibt einen relevanten Grund dafür, dass der rekursive CTE mehr als 500 Iterationen ausführt.
  • Sie sind mit einer viel längeren Ausführung zufrieden.

Beachten Sie, dass das Erhöhen des rekursiven Limits potenzielle Risiken mit sich bringt:

  • Ihr CTE kann mit einer anderen Fehlermeldung fehlschlagen, z. B. Speicher überschritten oder Timeout.
  • Wenn Ihr Projekt das On-Demand-Preismodell verwendet, schlägt Ihr CTE möglicherweise mit einem Fehler der Abrechnungsstufe fehl, bis Sie zum kapazitätsbasierten Preismodell wechseln.
  • Ein rekursiver CTE mit einer großen Anzahl von Iterationen verbraucht viele Ressourcen. Das kann sich auf andere Abfragen auswirken, die in derselben Reservierung ausgeführt werden, da sie um gemeinsam genutzte Ressourcen konkurrieren.

Preise

Wenn Sie On-Demand-Abrechnung verwenden, erfolgt die Abrechnung von BigQuery entsprechend der Anzahl der Byte, die während der Ausführung einer Abfrage mit einem rekursiven CTE verarbeitet werden.

Weitere Informationen zu Abfragegrößenberechnung.

Kontingente

Informationen zu rekursiven CTE-Kontingenten und -Limits finden Sie unter Kontingente und Limits.