Passer au contenu principal
Une structure de données imbriquée s’apparente à une table à l’intérieur d’une cellule. Les paramètres d’une structure de données imbriquée — les noms et les types des colonnes — sont spécifiés de la même manière que dans une requête CREATE TABLE. Chaque ligne de table peut correspondre à un nombre quelconque de lignes dans une structure de données imbriquée.
Évitez d’utiliser des points dans les noms de colonnesLes noms de colonnes contenant des points, les colonnes partageant un préfixe commun se terminant par un point, ainsi que les colonnes de type Array, peuvent chacun être interprétés comme faisant partie d’une structure Nested aplatie lorsque flatten_nested = 1 (valeur par défaut). Cela peut entraîner une validation inattendue de la longueur des tableaux lors des insertions, ainsi que des restrictions de renommage.Évitez, si possible, d’utiliser des points dans les noms de colonnes. Utilisez des traits de soulignement (_) ou un autre séparateur à la place des points dans les noms de colonnes, sauf si vous avez volontairement besoin de la sémantique Nested.
Exemple :
CREATE TABLE test.visits(
  CounterID UInt32,
  StartDate Date,
  Sign Int8,
  IsNew UInt8,
  VisitID UInt64,
  UserID UInt64,
  Goals Nested(
    ID UInt32,
    Serial UInt32,
    EventTime DateTime,
    Price Int64,
    OrderID String,
    CurrencyID UInt32
  )
)
ENGINE = CollapsingMergeTree(Sign)
ORDER BY (StartDate, intHash32(UserID), (CounterID, StartDate, intHash32(UserID), VisitID));

INSERT INTO test.visits
(CounterID, StartDate, Sign, IsNew, VisitID, UserID, Goals.ID, Goals.Serial, Goals.EventTime, Goals.Price, Goals.OrderID, Goals.CurrencyID)
VALUES
    (101500, '2014-03-17', 1, 1, 1001, 100001, [1073752, 591325, 591325], [1, 2, 3], ['2014-03-17 16:38:10', '2014-03-17 16:38:48', '2014-03-17 16:42:27'], [0, 0, 0], ['', '', ''], [0, 0, 0]),
    (101500, '2014-03-17', 1, 0, 1002, 100002, [1073752], [1], ['2014-03-17 00:28:25'], [0], [''], [0]),
    (101500, '2014-03-17', 1, 0, 1003, 100003, [1073752], [1], ['2014-03-17 10:46:20'], [0], [''], [0]),
    (101500, '2014-03-17', 1, 1, 1004, 100004, [1073752, 591325, 591325, 591325], [1, 2, 3, 4], ['2014-03-17 13:59:20', '2014-03-17 22:17:55', '2014-03-17 22:18:07', '2014-03-17 22:18:51'], [0, 0, 0, 0], ['', '', '', ''], [0, 0, 0, 0]),
    (101500, '2014-03-17', 1, 0, 1005, 100005, [], [], [], [], [], []),
    (101500, '2014-03-17', 1, 0, 1006, 100006, [1073752, 591325, 591325], [1, 2, 3], ['2014-03-17 11:37:06', '2014-03-17 14:07:47', '2014-03-17 14:36:21'], [0, 0, 0], ['', '', ''], [0, 0, 0]),
    (101500, '2014-03-17', 1, 0, 1007, 100007, [], [], [], [], [], []),
    (101500, '2014-03-17', 1, 0, 1008, 100008, [], [], [], [], [], []),
    (101500, '2014-03-17', 1, 1, 1009, 100009, [591325, 1073752], [1, 2], ['2014-03-17 00:46:05', '2014-03-17 00:46:05'], [0, 0], ['', ''], [0, 0]),
    (101500, '2014-03-17', 1, 1, 1010, 100010, [1073752, 591325, 591325, 591325], [1, 2, 3, 4], ['2014-03-17 13:28:33', '2014-03-17 13:30:26', '2014-03-17 18:51:21', '2014-03-17 18:51:45'], [0, 0, 0, 0], ['', '', '', ''], [0, 0, 0, 0]);
L’instruction DDL CREATE TABLE ci-dessus déclare la structure de données imbriquée Goals, qui contient des données sur les conversions, c’est-à-dire les objectifs atteints. Chaque ligne de la table ‘visits’ correspond à zéro conversion ou plus. Lorsque le paramètre flatten_nested est défini sur 0 (flatten_nested=1 par défaut), des niveaux d’imbrication arbitraires sont pris en charge. Dans la plupart des cas, lorsque vous travaillez avec une structure de données imbriquée, ses colonnes sont désignées par des noms de colonne séparés par un point. Ces colonnes forment un tableau de types correspondants. Tous les tableaux de colonnes d’une même structure de données imbriquée ont la même longueur. Par exemple :
SELECT
    Goals.ID,
    Goals.EventTime
FROM test.visits
WHERE CounterID = 101500 AND length(Goals.ID) < 5
ORDER BY VisitID
LIMIT 10
    ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
    ┃ Goals.ID                       ┃ Goals.EventTime                                                                           ┃
    ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
 1. │ [1073752,591325,591325]        │ ['2014-03-17 16:38:10','2014-03-17 16:38:48','2014-03-17 16:42:27']                       │
    ├────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────┤
 2. │ [1073752]                      │ ['2014-03-17 00:28:25']                                                                   │
    ├────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────┤
 3. │ [1073752]                      │ ['2014-03-17 10:46:20']                                                                   │
    ├────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────┤
 4. │ [1073752,591325,591325,591325] │ ['2014-03-17 13:59:20','2014-03-17 22:17:55','2014-03-17 22:18:07','2014-03-17 22:18:51'] │
    ├────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────┤
 5. │ []                             │ []                                                                                        │
    ├────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────┤
 6. │ [1073752,591325,591325]        │ ['2014-03-17 11:37:06','2014-03-17 14:07:47','2014-03-17 14:36:21']                       │
    ├────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────┤
 7. │ []                             │ []                                                                                        │
    ├────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────┤
 8. │ []                             │ []                                                                                        │
    ├────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────┤
 9. │ [591325,1073752]               │ ['2014-03-17 00:46:05','2014-03-17 00:46:05']                                             │
    ├────────────────────────────────┼───────────────────────────────────────────────────────────────────────────────────────────┤
10. │ [1073752,591325,591325,591325] │ ['2014-03-17 13:28:33','2014-03-17 13:30:26','2014-03-17 18:51:21','2014-03-17 18:51:45'] │
    └────────────────────────────────┴───────────────────────────────────────────────────────────────────────────────────────────┘
Il est plus simple de considérer une structure de données imbriquée comme un ensemble de plusieurs tableaux de colonnes de même longueur.

Filtrage des colonnes Nested dans WHERE

Comme chaque colonne d’une structure Nested est stockée sous forme d’Array, le fait d’y faire référence dans une clause WHERE renvoie l’intégralité du tableau pour chaque ligne, et non un élément individuel. Vous ne pouvez pas comparer directement une colonne Nested à une valeur scalaire ; vous devez donc utiliser des array functions. Par exemple, cette requête ne se contente pas de ne renvoyer aucune ligne de façon silencieuse : elle déclenche une exception, car Goals.ID a pour type Array(UInt32) et equals(Array(UInt32), UInt32) n’est pas une comparaison valide :
-- WRONG: compares the entire Array to a scalar
SELECT * FROM test.visits
WHERE Goals.ID = 591325;
Code: 43. DB::Exception: Illegal types of arguments (`Array(UInt32)`, `UInt32`)
of function `equals`. (ILLEGAL_TYPE_OF_ARGUMENT)
Utilisez has pour vérifier si un tableau contient une valeur donnée :
-- Find visits that have at least one goal with ID 591325
SELECT CounterID, VisitID, Goals.ID
FROM test.visits
WHERE has(Goals.ID, 591325);
Utilisez arrayExists si la condition est plus complexe :
-- Find visits that have at least one goal with ID greater than 1000000
SELECT CounterID, VisitID, Goals.ID
FROM test.visits
WHERE arrayExists(id -> id > 1000000, Goals.ID);
Vous pouvez filtrer selon la longueur des tableaux avec length ou exclure les tableaux vides avec notEmpty :
-- Visits with at least 3 goals
SELECT CounterID, VisitID, Goals.ID
FROM test.visits
WHERE length(Goals.ID) >= 3;

-- Visits with at least one goal (non-empty array)
SELECT CounterID, VisitID, Goals.ID
FROM test.visits
WHERE notEmpty(Goals.ID);
Pour filtrer des éléments individuels d’une structure imbriquée plutôt que des lignes entières, utilisez ARRAY JOIN pour déployer d’abord les tableaux. Après ARRAY JOIN, chaque élément devient une ligne distincte ; la clause WHERE s’applique donc à des valeurs scalaires. Pour plus d’informations, consultez la clause ARRAY JOIN. Exemple :
SELECT
    Goal.ID,
    Goal.EventTime
FROM test.visits
ARRAY JOIN Goals AS Goal
WHERE CounterID = 101500 AND length(Goals.ID) < 5
ORDER BY VisitID, Goal.Serial
LIMIT 10
    ┏━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┓
    ┃ Goal.ID ┃      Goal.EventTime ┃
    ┡━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━┩
 1. │ 1073752 │ 2014-03-17 16:38:10 │
    ├─────────┼─────────────────────┤
 2. │  591325 │ 2014-03-17 16:38:48 │
    ├─────────┼─────────────────────┤
 3. │  591325 │ 2014-03-17 16:42:27 │
    ├─────────┼─────────────────────┤
 4. │ 1073752 │ 2014-03-17 00:28:25 │
    ├─────────┼─────────────────────┤
 5. │ 1073752 │ 2014-03-17 10:46:20 │
    ├─────────┼─────────────────────┤
 6. │ 1073752 │ 2014-03-17 13:59:20 │
    ├─────────┼─────────────────────┤
 7. │  591325 │ 2014-03-17 22:17:55 │
    ├─────────┼─────────────────────┤
 8. │  591325 │ 2014-03-17 22:18:07 │
    ├─────────┼─────────────────────┤
 9. │  591325 │ 2014-03-17 22:18:51 │
    ├─────────┼─────────────────────┤
10. │ 1073752 │ 2014-03-17 11:37:06 │
    └─────────┴─────────────────────┘
Vous ne pouvez pas exécuter SELECT sur l’ensemble d’une structure de données imbriquée. Vous pouvez seulement énumérer explicitement les colonnes individuelles qui la composent.

Insertion de données

Pour une requête INSERT, vous devez transmettre séparément tous les tableaux des colonnes composantes d’une structure de données imbriquée (comme s’il s’agissait de tableaux de colonnes distincts). Lors de l’insertion, le système vérifie qu’ils ont tous la même longueur. Chaque sous-colonne imbriquée est indiquée dans la liste des colonnes à l’aide de la notation par points (Goals.ID, Goals.Serial, …), et les valeurs correspondantes sont des tableaux :
INSERT INTO test.visits
    (CounterID, StartDate, Sign, IsNew, VisitID, UserID,
     Goals.ID, Goals.Serial, Goals.EventTime, Goals.Price, Goals.OrderID, Goals.CurrencyID)
VALUES
    -- A visit with two goals: each nested sub-column gets an array of length 2
    (101500, '2014-03-18', 1, 1, 2001, 200001,
     [1073752, 591325], [1, 2],
     ['2014-03-18 10:00:00', '2014-03-18 10:05:00'],
     [100, 200], ['order_a', 'order_b'], [1, 2]),
    -- A visit with no goals: all nested sub-columns get empty arrays
    (101500, '2014-03-18', 1, 0, 2002, 200002,
     [], [], [], [], [], []);
Tous les tableaux de sous-colonnes imbriquées au sein d’une même ligne doivent avoir la même longueur. Des longueurs différentes provoquent une erreur :
-- ERROR: Goals.ID has 2 elements, but Goals.Serial has 1
INSERT INTO test.visits
    (CounterID, StartDate, Sign, IsNew, VisitID, UserID,
     Goals.ID, Goals.Serial, Goals.EventTime, Goals.Price, Goals.OrderID, Goals.CurrencyID)
VALUES
    (101500, '2014-03-18', 1, 1, 2003, 200003,
     [1073752, 591325], [1],
     ['2014-03-18 12:00:00'], [0], [''], [0]);
Pour une requête DESCRIBE, les colonnes d’une structure de données imbriquée sont répertoriées séparément de la même façon.

Limites d’ALTER

Les requêtes ALTER sur les structures de données imbriquées présentent les limites suivantes : L’ajout de sous-colonnes fonctionne normalement. Vous pouvez ajouter une nouvelle sous-colonne à une structure Nested existante :
ALTER TABLE test.visits ADD COLUMN Goals.Revenue Float64;
La suppression des sous-colonnes fonctionne pour chaque sous-colonne individuellement :
ALTER TABLE test.visits DROP COLUMN Goals.Revenue;
La modification du type d’une sous-colonne fonctionne et déclenche une mutation (réécriture des données) :
ALTER TABLE test.visits MODIFY COLUMN Goals.Price Int32;
Le renommage est soumis à certaines restrictions. Vous pouvez renommer une sous-colonne au sein de la même structure imbriquée :
-- OK: stays within the Goals structure
ALTER TABLE test.visits RENAME COLUMN Goals.Price TO Goals.Amount;
Cependant, vous ne pouvez pas :
  • Renommer la structure imbriquée entière (par exemple, Goals en Conversions).
  • Déplacer une sous-colonne vers une autre structure imbriquée (par exemple, Goals.ID vers OtherNested.ID).
  • Déplacer une sous-colonne hors d’une structure imbriquée ou l’y déplacer (par exemple, Goals.ID vers GoalID, ou inversement).
Dernière modification le 25 juin 2026