web-dev-qa-db-fra.com

Aplatissement du tableau SQL: Pourquoi CROSS JOIN UNNEST ne joint-il pas chaque valeur imbriquée à chaque ligne?

Cette question ne consiste pas à résoudre un problème particulier, mais à comprendre ce qui se passe réellement dans les coulisses d'un idiome SQL commun utilisé pour aplatir les tableaux. Il y a de la magie dans les coulisses et je veux jeter un œil derrière le rideau de sucre syntaxique et voir ce qui se passe.

Prenons le tableau suivant t1:

t1

Supposons maintenant que nous ayons une fonction appelée FLATTEN qui prend une colonne de type tableau et décompresse chacun des tableaux de cette colonne de sorte qu'il nous reste une ligne pour chaque valeur de chaque tableau - si nous exécutons SELECT FLATTEN(numbers_array) AS flattened_numbers FROM t1, nous nous attendrions à ce qui suit, que nous appellerons t2

t2

En SQL, CROSS JOIN combine les lignes de deux tables en combinant chaque ligne de la première table avec chaque ligne de la deuxième table. Donc, si nous exécutons SELECT id, flattened.flattened_numbers from t1 CROSS JOIN flattened, Nous obtenons

enter image description here

Aplatir n'est plus qu'une fonction imaginaire, et comme vous pouvez le voir, il n'est pas très utile de le combiner avec un CROSS JOIN, car chacune des valeurs d'origine de la colonne id est mélangée avec flattened_numbers De chacune des lignes d'origine. Tout est mélangé car nous n'avons pas de clause WHERE qui sélectionne uniquement les lignes du CROSS JOIN Que nous voulons.

Le modèle que les gens utilisent pour aplatir les tableaux ressemble à ceci: SELECT id, flattened_numbers FROM t1 CROSS JOIN UNNEST(sequences.some_numbers) AS flattened_numbers, qui produit

enter image description here

Mais je ne comprends pas pourquoi le modèle CROSS JOIN UNNEST Fonctionne réellement. Étant donné que CROSS JOIN Ne comprend pas de clause WHERE, je m'attendrais à ce qu'elle se comporte exactement comme la fonction FLATTEN décrite ci-dessus, où chaque valeur non imbriquée est combinée avec chaque ligne de t1.

Quelqu'un peut-il "décompresser" ce qui se passe réellement dans le modèle CROSS JOIN UNNEST Qui garantit que chaque ligne n'est jointe qu'avec ses propres valeurs imbriquées (et non avec les valeurs imbriquées des autres lignes)?

17
conradlee

La meilleure façon d'y penser est de regarder ce qui se passe ligne par ligne. Configuration de certaines données d'entrée, nous avons:

WITH t1 AS (
  SELECT 1 AS id, [0, 1] AS numbers_array UNION ALL
  SELECT 2, [2, 4, 5]
)
...

(J'utilise un troisième élément pour la deuxième rangée pour rendre les choses plus intéressantes). Si nous le sélectionnons, nous obtenons une sortie qui ressemble à ceci:

WITH t1 AS (
  SELECT 1 AS id, [0, 1] AS numbers_array UNION ALL
  SELECT 2, [2, 4, 5]
)
SELECT * FROM t1;
+----+---------------+
| id | numbers_array |
+----+---------------+
| 1  | [0, 1]        |
| 2  | [2, 4, 5]     |
+----+---------------+

Parlons maintenant de l'imbrication. La fonction UNNEST prend un tableau et renvoie une table de valeurs du type d'élément du tableau. Alors que la plupart des tables BigQuery sont des tables SQL définies comme une collection de colonnes, une table de valeurs contient des lignes de type value . Pour numbers_array, UNNEST(numbers_array) renvoie une table de valeurs dont le type de valeur est INT64, Car numbers_array Est un tableau dont le type d'élément est INT64. Cette table de valeurs contient tous les éléments de numbers_array Pour la ligne actuelle de t1.

Pour la ligne avec un id de 1, le contenu de la table de valeurs retournée par UNNEST(numbers_array) est:

+-----+
| f0_ |
+-----+
| 0   |
| 1   |
+-----+

C'est la même chose que ce que nous obtenons avec la requête suivante:

SELECT * FROM UNNEST([0, 1]);

UNNEST([0, 1]) dans ce cas signifie "créer une table de valeurs à partir des valeurs INT640 et 1".

De même, pour la ligne avec un id de 2, le contenu de la table de valeurs retournée par UNNEST(numbers_array) est:

+-----+
| f0_ |
+-----+
| 2   |
| 4   |
| 5   |
+-----+

Parlons maintenant de la façon dont CROSS JOIN S'intègre dans l'image. Dans la plupart des cas, vous utilisez CROSS JOIN Entre deux tables non corrélées. En d'autres termes, le contenu du tableau à droite de CROSS JOIN N'est pas défini par le contenu actuel du tableau à gauche.

Dans le cas des tableaux et de UNNEST, cependant, le contenu de la table de valeurs produite par UNNEST(numbers_array) change en fonction de la ligne actuelle de t1. Lorsque nous joignons les deux tables, nous obtenons le produit croisé de la ligne actuelle de t1 Avec toutes les lignes de UNNEST(numbers_array). Par exemple:

WITH t1 AS (
  SELECT 1 AS id, [0, 1] AS numbers_array UNION ALL
  SELECT 2, [2, 4, 5]
)
SELECT id, number
FROM t1
CROSS JOIN UNNEST(numbers_array) AS number;
+----+--------+
| id | number |
+----+--------+
| 1  | 0      |
| 1  | 1      |
| 2  | 2      |
| 2  | 4      |
| 2  | 5      |
+----+--------+

numbers_array A deux éléments dans la première ligne et trois éléments dans le second, donc nous obtenons 2 + 3 = 5 Lignes dans le résultat de la requête.

Pour répondre à la question de savoir en quoi cela diffère d'aplatir le numbers_array Et puis d'effectuer un CROSS JOIN, Regardons le résultats de cette requête:

WITH t1 AS (
  SELECT 1 AS id, [0, 1] AS numbers_array UNION ALL
  SELECT 2, [2, 4, 5]
), t2 AS (
  SELECT number
  FROM t1
  CROSS JOIN UNNEST(numbers_array) AS number
)
SELECT number
FROM t2;
+--------+
| number |
+--------+
| 0      |
| 1      |
| 2      |
| 4      |
| 5      |
+--------+

Dans ce cas, t2 Est une table SQL avec une colonne nommée number avec ces valeurs. Si nous effectuons un CROSS JOIN Entre t1 Et t2, Nous obtenons un vrai produit croisé de toutes les lignes:

WITH t1 AS (
  SELECT 1 AS id, [0, 1] AS numbers_array UNION ALL
  SELECT 2, [2, 4, 5]
), t2 AS (
  SELECT number
  FROM t1
  CROSS JOIN UNNEST(numbers_array) AS number
)
SELECT id, numbers_array, number
FROM t1
CROSS JOIN t2;
+----+---------------+--------+
| id | numbers_array | number |
+----+---------------+--------+
| 1  | [0, 1]        | 0      |
| 1  | [0, 1]        | 1      |
| 1  | [0, 1]        | 2      |
| 1  | [0, 1]        | 4      |
| 1  | [0, 1]        | 5      |
| 2  | [2, 4, 5]     | 0      |
| 2  | [2, 4, 5]     | 1      |
| 2  | [2, 4, 5]     | 2      |
| 2  | [2, 4, 5]     | 4      |
| 2  | [2, 4, 5]     | 5      |
+----+---------------+--------+

Alors, quelle est la différence entre cela et la requête précédente avec CROSS JOIN UNNEST(numbers_array)? Dans ce cas, le contenu de t2 Ne change pas pour chaque ligne de t1. Pour la première ligne de t1, Il y a cinq lignes dans t2. Pour la deuxième ligne de t1, Il y a cinq lignes dans t2. Par conséquent, le CROSS JOIN Entre les deux renvoie au total 5 + 5 = 10 Lignes.

10
Elliott Brossard