> ## Documentation Index
> Fetch the complete documentation index at: https://clickhouse.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Travailler avec les tableaux dans ClickHouse

> Guide d’introduction à l’utilisation des tableaux dans ClickHouse

export const RunnableCode = ({children, run = false, showStats = true}) => {
  const [results, setResults] = useState(null);
  const [error, setError] = useState(null);
  const [loading, setLoading] = useState(false);
  const [showResults, setShowResults] = useState(false);
  const [stats, setStats] = useState(null);
  const [isDark, setIsDark] = useState(false);
  const [hoveredRow, setHoveredRow] = useState(-1);
  const codeRef = useRef(null);
  useEffect(() => {
    if (typeof window !== "undefined") {
      const check = () => setIsDark(document.documentElement.classList.contains("dark"));
      check();
      const observer = new MutationObserver(check);
      observer.observe(document.documentElement, {
        attributes: true,
        attributeFilter: ["class"]
      });
      return () => observer.disconnect();
    }
  }, []);
  useEffect(() => {
    if (codeRef.current) {
      const block = codeRef.current.querySelector(".code-block");
      if (block) {
        block.style.marginBottom = "0";
        block.style.marginTop = "0";
        block.style.borderBottomLeftRadius = "0";
        block.style.borderBottomRightRadius = "0";
      }
    }
  });
  const getSqlText = () => {
    if (!codeRef.current) return "";
    const code = codeRef.current.querySelector("code");
    return (code || codeRef.current).textContent.trim();
  };
  const executeQuery = async () => {
    const sql = getSqlText();
    if (!sql) return;
    setLoading(true);
    setError(null);
    setResults(null);
    setShowResults(true);
    try {
      const cleanQuery = sql.replace(/;$/, "").trim();
      const params = new URLSearchParams({
        query: cleanQuery,
        default_format: "JSONCompact",
        result_overflow_mode: "break",
        read_overflow_mode: "break",
        allow_experimental_analyzer: "1"
      });
      const res = await fetch(`https://sql-clickhouse.clickhouse.com/?${params.toString()}`, {
        method: "POST",
        headers: {
          Authorization: `Basic ${btoa(`demo:`)}`
        }
      });
      const text = await res.text();
      if (!res.ok) {
        setError(text || `HTTP ${res.status}`);
        setLoading(false);
        return;
      }
      const json = JSON.parse(text);
      setResults(json);
      setStats(json.statistics || null);
    } catch (err) {
      setError(err.message || "Échec de l'exécution de la requête");
    }
    setLoading(false);
  };
  useEffect(() => {
    if (run) executeQuery();
  }, []);
  const formatRows = n => {
    if (n >= 1e9) return `${(n / 1e9).toFixed(1)}B`;
    if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
    if (n >= 1e3) return `${(n / 1e3).toFixed(1)}K`;
    return String(n);
  };
  const formatBytes = b => {
    if (b >= 1e9) return `${(b / 1e9).toFixed(2)} GB`;
    if (b >= 1e6) return `${(b / 1e6).toFixed(2)} MB`;
    if (b >= 1e3) return `${(b / 1e3).toFixed(2)} KB`;
    return `${b} B`;
  };
  const isNumericType = type => {
    return (/^(UInt|Int|Float|Decimal)/).test(type);
  };
  const isHyperlink = value => {
    return typeof value === "string" && (/^https?:\/\//).test(value);
  };
  const computeColumnExtremes = (meta, data) => {
    const extremes = {};
    for (let i = 0; i < meta.length; i++) {
      if (isNumericType(meta[i].type)) {
        let min = Infinity, max = -Infinity;
        for (const row of data) {
          const v = Number(row[i]);
          if (!isNaN(v)) {
            if (v < min) min = v;
            if (v > max) max = v;
          }
        }
        if (max > -Infinity) {
          extremes[i] = {
            min,
            max
          };
        }
      }
    }
    return extremes;
  };
  const computeColumnWidths = (meta, data) => {
    const lengths = meta.map((col, i) => {
      const headerLen = col.name.length + col.type.length + 1;
      let maxData = 0;
      for (const row of data) {
        const v = row[i];
        const len = v === null ? 4 : String(v).length;
        if (len > maxData) maxData = len;
      }
      return Math.max(headerLen, maxData);
    });
    const total = lengths.reduce((s, l) => s + l, 0);
    return lengths.map(l => `${(l / total * 100).toFixed(1)}%`);
  };
  const copyResultsAsTSV = () => {
    if (!results || !results.meta || !results.data) return;
    const header = results.meta.map(col => col.name).join("\t");
    const rows = results.data.map(row => row.map(cell => cell === null ? "NULL" : String(cell)).join("\t"));
    const tsv = [header, ...rows].join("\n");
    navigator.clipboard.writeText(tsv);
  };
  const borderColor = isDark ? "rgba(255,255,255,0.15)" : "#e5e7eb";
  const bgColor = isDark ? "rgba(255,255,255,0.05)" : "#f9fafb";
  const headerBg = isDark ? "#2a2a2a" : "#f3f4f6";
  const textColor = isDark ? "#e5e7eb" : "#1f2937";
  const mutedColor = isDark ? "#d1d5db" : "#6b7280";
  const accentColor = isDark ? "#FAFF69" : "#323232";
  const accentTextColor = isDark ? "#000" : "#fff";
  const barColor = isDark ? "#35372f" : "#d2d2d2";
  const cellBg = isDark ? "#1f201b" : "#ffffff";
  const cellBgHover = isDark ? "lch(15.8 0 0)" : "#f0f0f0";
  const extremes = results && results.meta && results.data ? computeColumnExtremes(results.meta, results.data) : {};
  const colWidths = results && results.meta && results.data ? computeColumnWidths(results.meta, results.data) : [];
  const getCellBarStyle = (cell, ci, ri) => {
    if (cell === null) return null;
    const colMeta = results.meta[ci];
    if (!isNumericType(colMeta.type) || !extremes[ci] || results.data.length <= 1 || extremes[ci].max <= 0) return null;
    const ratio = 100 * Number(cell) / extremes[ci].max;
    const bg = ri === hoveredRow ? cellBgHover : cellBg;
    return {
      background: `linear-gradient(to right, ${barColor} 0%, ${barColor} ${ratio}%, ${bg} ${ratio}%, ${bg} 100%)`
    };
  };
  const renderCell = (cell, ci) => {
    if (cell === null) {
      return <span style={{
        color: mutedColor,
        fontStyle: "italic"
      }}>NULL</span>;
    }
    const value = String(cell);
    if (isHyperlink(value)) {
      return <a href={value} target="_blank" rel="noopener noreferrer" style={{
        color: accentColor,
        textDecoration: "underline",
        cursor: "pointer"
      }}>
          {value}
        </a>;
    }
    return value;
  };
  return <div className="not-prose" style={{
    margin: "1rem 0",
    width: "100%",
    boxSizing: "border-box",
    contain: "inline-size"
  }}>
      {}
      <div>
        <div ref={codeRef}>{children}</div>

        {}
        <div style={{
    display: "flex",
    justifyContent: "space-between",
    alignItems: "center",
    padding: "6px 12px",
    backgroundColor: headerBg,
    borderWidth: "0 1px 1px 1px",
    borderStyle: "solid",
    borderColor: isDark ? "rgba(255,255,255,0.1)" : "rgba(11,11,11,0.1)",
    borderRadius: "0 0 4px 4px"
  }}>
          <div style={{
    display: "flex",
    alignItems: "center",
    gap: "12px"
  }}>
            {results && <button onClick={() => setShowResults(!showResults)} style={{
    background: "none",
    border: "none",
    cursor: "pointer",
    color: mutedColor,
    fontSize: "12px",
    padding: "2px 4px"
  }}>
                {showResults ? "▼ Masquer les résultats" : "▶ Afficher les résultats"}
              </button>}
            {showStats && stats && <span style={{
    fontSize: "11px",
    color: mutedColor,
    fontStyle: "italic"
  }}>
                {formatRows(stats.rows_read)} lignes lues, {formatBytes(stats.bytes_read)} en {stats.elapsed.toFixed(3)}s
              </span>}
          </div>
          <button onClick={() => executeQuery()} disabled={loading} style={{
    display: "flex",
    alignItems: "center",
    gap: "6px",
    padding: "4px 14px",
    borderRadius: "4px",
    border: "none",
    cursor: loading ? "wait" : "pointer",
    backgroundColor: accentColor,
    color: accentTextColor,
    fontSize: "12px",
    fontWeight: 600
  }}>
            {loading ? <span>En cours...</span> : <>
                <span style={{
    fontSize: "10px"
  }}>▶</span>
                <span>Exécuter</span>
              </>}
          </button>
        </div>
      </div>

      {}
      {showResults && <div className="not-prose" style={{
    marginTop: "8px",
    maxHeight: "350px",
    overflow: "auto",
    border: `1px solid ${borderColor}`,
    borderRadius: "4px"
  }}>
          <div>
            {loading && <div style={{
    padding: "24px",
    textAlign: "center",
    color: mutedColor
  }}>Exécution de la requête...</div>}

            {error && <div style={{
    padding: "12px 16px",
    color: "#ef4444",
    backgroundColor: isDark ? "rgba(239,68,68,0.1)" : "#fef2f2",
    fontSize: "13px",
    fontFamily: "monospace",
    whiteSpace: "pre-wrap"
  }}>
                {error}
              </div>}

            {results && results.meta && results.data && <div style={{
    display: "grid",
    gridTemplateColumns: colWidths.join(" "),
    width: "100%",
    fontSize: "13px",
    fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace'
  }}>
                {results.meta.map((col, i) => <div key={`h-${i}`} style={{
    position: "sticky",
    top: 0,
    zIndex: 1,
    padding: "6px 12px",
    textAlign: isNumericType(col.type) && results.meta.length > 1 ? "right" : "left",
    backgroundColor: headerBg,
    borderBottom: `1px solid ${borderColor}`,
    color: textColor,
    fontWeight: 600,
    fontSize: "12px",
    whiteSpace: "nowrap",
    overflow: "hidden",
    textOverflow: "ellipsis"
  }}>
                    {col.name}
                    <span style={{
    color: mutedColor,
    fontWeight: 400,
    marginLeft: "4px",
    fontSize: "10px"
  }}>{col.type}</span>
                  </div>)}
                {results.data.map((row, ri) => row.map((cell, ci) => <div key={`${ri}-${ci}`} onMouseEnter={() => setHoveredRow(ri)} onMouseLeave={() => setHoveredRow(-1)} style={{
    padding: "4px 12px",
    color: textColor,
    whiteSpace: "nowrap",
    overflow: "hidden",
    textOverflow: "ellipsis",
    textAlign: isNumericType(results.meta[ci].type) && results.meta.length > 1 ? "right" : "left",
    borderBottom: `1px solid ${borderColor}`,
    backgroundColor: ri === hoveredRow ? cellBgHover : ri % 2 === 0 ? "transparent" : bgColor,
    transition: "background-color 0.1s",
    ...getCellBarStyle(cell, ci, ri)
  }}>
                      {renderCell(cell, ci)}
                    </div>))}
              </div>}

            {results && results.data && <div style={{
    display: "flex",
    justifyContent: "space-between",
    alignItems: "center",
    padding: "4px 12px",
    fontSize: "11px",
    color: mutedColor,
    borderTop: `1px solid ${borderColor}`,
    backgroundColor: headerBg
  }}>
                <span>
                  {results.rows} ligne{results.rows !== 1 ? "s" : ""}
                </span>
                <button onClick={copyResultsAsTSV} style={{
    background: "none",
    border: "none",
    cursor: "pointer",
    color: mutedColor,
    fontSize: "11px",
    padding: "2px 6px",
    borderRadius: "3px"
  }} onMouseEnter={e => e.target.style.color = textColor} onMouseLeave={e => e.target.style.color = mutedColor}>
                  ⧉ Copier en TSV
                </button>
              </div>}
          </div>
        </div>}
    </div>;
};

> Dans ce guide, vous apprendrez à utiliser les tableaux dans ClickHouse, ainsi que quelques-unes des [fonctions de tableau](/docs/fr/reference/functions/regular-functions/array-functions) les plus couramment utilisées.

<div id="array-basics">
  ## Introduction aux tableaux
</div>

Un tableau est une structure de données en mémoire qui regroupe des valeurs.
On appelle ces valeurs les *éléments* du tableau, et chaque élément peut être désigné par un indice, qui indique sa position dans cet ensemble.

Dans ClickHouse, les tableaux peuvent être créés à l'aide de la fonction [`array`](/docs/fr/reference/data-types/array) :

```sql theme={null}
array(T)
```

Ou bien, en utilisant `[]` :

```sql theme={null}
[]
```

Par exemple, vous pouvez créer un tableau de nombres :

```sql theme={null}
SELECT array(1, 2, 3) AS numeric_array
```

```response theme={null}
┌─numeric_array─┐
│ [1,2,3]       │
└───────────────┘
```

Ou un tableau de chaînes :

```sql theme={null}
SELECT array('hello', 'world') AS string_array
```

```response theme={null}
┌─string_array──────┐
│ ['hello','world'] │
└───────────────────┘
```

Ou un tableau de types imbriqués tels que des [tuples](/docs/fr/reference/data-types/tuple):

```sql theme={null}
SELECT array(tuple(1, 2), tuple(3, 4))
```

```response theme={null}
┌─[(1, 2), (3, 4)]─┐
│ [(1,2),(3,4)]    │
└──────────────────┘
```

Vous pourriez être tenté de créer un tableau contenant différents types, comme ceci :

```sql theme={null}
SELECT array('Hello', 'world', 1, 2, 3)
```

Cependant, les éléments d’un tableau doivent toujours avoir un super-type commun, c’est-à-dire le plus petit type de données capable de représenter sans perte des valeurs de deux types ou plus, afin de pouvoir les utiliser ensemble.
S’il n’existe pas de super-type commun, une exception sera renvoyée lorsque vous tenterez de constituer le tableau :

```sql theme={null}
Received exception:
Code: 386. DB::Exception: There is no supertype for types String, String, UInt8, UInt8, UInt8 because some of them are String/FixedString/Enum and some of them are not: In scope SELECT ['Hello', 'world', 1, 2, 3]. (NO_COMMON_TYPE)
```

lors de la création de tableaux à la volée, ClickHouse choisit le type le plus spécifique compatible avec tous les éléments.
Par exemple, si vous créez un tableau d’entiers et de nombres à virgule flottante, un supertype flottant est choisi :

```sql theme={null}
SELECT [1::UInt8, 2.5::Float32, 3::UInt8] AS mixed_array, toTypeName([1, 2.5, 3]) AS array_type;
```

```response theme={null}
┌─mixed_array─┬─array_type─────┐
│ [1,2.5,3]   │ Array(Float64) │
└─────────────┴────────────────┘
```

<Accordion title="Création de tableaux de types différents">
  Vous pouvez utiliser le paramètre `use_variant_as_common_type` pour modifier le comportement par défaut décrit ci-dessus.
  Cela permet d’utiliser le type [Variant](/docs/fr/reference/data-types/variant) comme type de résultat pour les fonctions `if`/`multiIf`/`array`/`map` lorsqu’il n’existe aucun type commun entre les types d’argument.

  Par exemple :

  ```sql theme={null}
  SELECT
      [1, 'ClickHouse', ['Another', 'Array']] AS array,
      toTypeName(array)
  SETTINGS use_variant_as_common_type = 1;
  ```

  ```response theme={null}
  ┌─array────────────────────────────────┬─toTypeName(array)────────────────────────────┐
  │ [1,'ClickHouse',['Another','Array']] │ Array(Variant(Array(String), String, UInt8)) │
  └──────────────────────────────────────┴──────────────────────────────────────────────┘
  ```

  Vous pouvez ensuite extraire du tableau les valeurs par nom de type :

  ```sql theme={null}
  SELECT
      [1, 'ClickHouse', ['Another', 'Array']] AS array,
      array.UInt8,
      array.String,
      array.`Array(String)`
  SETTINGS use_variant_as_common_type = 1;
  ```

  ```response theme={null}
  ┌─array────────────────────────────────┬─array.UInt8───┬─array.String─────────────┬─array.Array(String)─────────┐
  │ [1,'ClickHouse',['Another','Array']] │ [1,NULL,NULL] │ [NULL,'ClickHouse',NULL] │ [[],[],['Another','Array']] │
  └──────────────────────────────────────┴───────────────┴──────────────────────────┴─────────────────────────────┘
  ```
</Accordion>

La notation d’indexation `[]` offre un moyen pratique d’accéder aux éléments d’un tableau.
Dans ClickHouse, il est important de savoir que l’indice d’un tableau commence toujours à **1**.
Cela peut différer d’autres langages de programmation auxquels vous êtes habitué, où les tableaux sont indexés à partir de zéro.

Par exemple, étant donné un tableau, vous pouvez en sélectionner le premier élément en écrivant :

```sql theme={null}
WITH array('hello', 'world') AS string_array
SELECT string_array[1];
```

```response theme={null}
┌─arrayElement⋯g_array, 1)─┐
│ hello                    │
└──────────────────────────┘
```

Il est également possible d'utiliser des indices négatifs.
Vous pouvez ainsi sélectionner des éléments par rapport au dernier élément :

```sql theme={null}
WITH array('hello', 'world') AS string_array
SELECT string_array[-1];
```

```response theme={null}
┌─arrayElement⋯g_array, -1)─┐
│ world                     │
└───────────────────────────┘
```

Bien que les tableaux soient indexés à partir de 1, vous pouvez tout de même accéder à l’élément en position 0.
La valeur renvoyée sera la *valeur par défaut* du type de tableau.
Dans l’exemple ci-dessous, une chaîne vide est renvoyée, car il s’agit de la valeur par défaut du type de données String :

```sql theme={null}
WITH ['hello', 'world', 'arrays are great aren\'t they?'] AS string_array
SELECT string_array[0]
```

```response theme={null}
┌─arrayElement⋯g_array, 0)─┐
│                          │
└──────────────────────────┘
```

<div id="array-functions">
  ## Fonctions de tableau
</div>

ClickHouse propose de nombreuses fonctions utiles pour manipuler les tableaux.
Dans cette section, nous allons passer en revue quelques-unes des plus utiles, en commençant par les plus simples pour aller vers les plus complexes.

<div id="length-arrayEnumerate-indexOf-has-functions">
  ### fonctions length, arrayEnumerate, indexOf, has\*
</div>

La fonction `length` renvoie le nombre d’éléments du tableau :

```sql theme={null}
WITH array('learning', 'ClickHouse', 'arrays') AS string_array
SELECT length(string_array);
```

```response theme={null}
┌─length(string_array)─┐
│                    3 │
└──────────────────────┘
```

Vous pouvez également utiliser la fonction [`arrayEnumerate`](/docs/fr/reference/functions/regular-functions/array-functions#arrayEnumerate) pour retourner un tableau contenant les indices des éléments :

```sql theme={null}
WITH array('learning', 'ClickHouse', 'arrays') AS string_array
SELECT arrayEnumerate(string_array);
```

```response theme={null}
┌─arrayEnumerate(string_array)─┐
│ [1,2,3]                      │
└──────────────────────────────┘
```

Si vous souhaitez trouver la position d’une valeur particulière, vous pouvez utiliser la fonction `indexOf` :

```sql theme={null}
SELECT indexOf([4, 2, 8, 8, 9], 8);
```

```response theme={null}
┌─indexOf([4, 2, 8, 8, 9], 8)─┐
│                           3 │
└─────────────────────────────┘
```

Notez que cette fonction renverra le premier indice trouvé s’il existe plusieurs valeurs identiques dans le tableau.
Si les éléments de votre tableau sont triés par ordre croissant, vous pouvez utiliser la fonction [`indexOfAssumeSorted`](/docs/fr/reference/functions/regular-functions/array-functions#indexOfAssumeSorted).

Les fonctions `has`, `hasAll` et `hasAny` sont utiles pour déterminer si un tableau contient une valeur donnée.
Prenons l’exemple suivant :

```sql theme={null}
WITH ['Airbus A380', 'Airbus A350', 'Airbus A220', 'Boeing 737', 'Boeing 747-400'] AS airplanes
SELECT
    has(airplanes, 'Airbus A350') AS has_true,
    has(airplanes, 'Lockheed Martin F-22 Raptor') AS has_false,
    hasAny(airplanes, ['Boeing 737', 'Eurofighter Typhoon']) AS hasAny_true,
    hasAny(airplanes, ['Lockheed Martin F-22 Raptor', 'Eurofighter Typhoon']) AS hasAny_false,
    hasAll(airplanes, ['Boeing 737', 'Boeing 747-400']) AS hasAll_true,
    hasAll(airplanes, ['Boeing 737', 'Eurofighter Typhoon']) AS hasAll_false
FORMAT Vertical;
```

```response theme={null}
has_true:     1
has_false:    0
hasAny_true:  1
hasAny_false: 0
hasAll_true:  1
hasAll_false: 0
```

<div id="exploring-flight-data-with-array-functions">
  ## Exploration des données de vol avec les fonctions de tableau
</div>

Jusqu’ici, les exemples étaient assez simples.
L’utilité des tableaux apparaît vraiment lorsqu’ils sont appliqués à un jeu de données réel.

Nous allons utiliser le [jeu de données ontime](/docs/fr/get-started/sample-datasets/ontime), qui contient des données de vol du Bureau of Transportation Statistics.
Vous trouverez ce jeu de données dans le [SQL playground](https://sql.clickhouse.com/?query_id=M4FSVBVMSHY98NKCQP8N4K).

Nous avons choisi ce jeu de données, car les tableaux sont souvent bien adaptés aux données de séries temporelles et peuvent aider à simplifier
des requêtes autrement complexes.

<Tip>
  Cliquez sur le bouton "play" ci-dessous pour exécuter les requêtes directement dans la documentation et voir le résultat en direct.
</Tip>

<div id="grouparray">
  ### groupArray
</div>

Ce jeu de données comporte de nombreuses colonnes, mais nous allons nous concentrer sur un sous-ensemble de colonnes.
Exécutez la requête ci-dessous pour voir à quoi ressemblent les données :

<RunnableCode>
  ```sql theme={null}
  -- SELECT
  -- *
  -- FROM ontime.ontime LIMIT 100

  SELECT
      FlightDate,
      Origin,
      OriginCityName,
      Dest,
      DestCityName,
      DepTime,
      DepDelayMinutes,
      ArrTime,
      ArrDelayMinutes
  FROM ontime.ontime LIMIT 5
  ```
</RunnableCode>

Examinons les 10 aéroports les plus fréquentés des États-Unis un jour donné choisi au hasard, par exemple '2024-01-01'.
Nous voulons savoir combien de vols partent de chaque aéroport.
Nos données contiennent une ligne par vol, mais il serait pratique de pouvoir regrouper les données par aéroport d'origine et de rassembler les destinations dans un tableau.

Pour ce faire, nous pouvons utiliser la fonction d'agrégation [`groupArray`](/docs/fr/reference/functions/aggregate-functions/groupArray), qui prend les valeurs de la colonne spécifiée pour chaque ligne et les regroupe dans un tableau.

Exécutez la requête ci-dessous pour voir comment cela fonctionne :

<RunnableCode>
  ```sql theme={null}
  SELECT
      FlightDate,
      Origin,
      groupArray(toStringCutToZero(Dest)) AS Destinations
  FROM ontime.ontime
  WHERE Origin IN ('ATL', 'ORD', 'DFW', 'DEN', 'LAX', 'JFK', 'LAS', 'CLT', 'SFO', 'SEA') AND FlightDate='2024-01-01'
  GROUP BY FlightDate, Origin
  ORDER BY length(Destinations)
  ```
</RunnableCode>

La fonction [`toStringCutToZero`](/docs/fr/reference/functions/regular-functions/type-conversion-functions#toStringCutToZero) dans la requête ci-dessus sert à supprimer les caractères nuls qui apparaissent après le code à 3 lettres de certains aéroports.

Avec les données sous cette forme, nous pouvons facilement classer les aéroports les plus fréquentés en calculant la longueur des tableaux « Destinations » agrégés :

<RunnableCode>
  ```sql highlight={7} theme={null}
  WITH
      '2024-01-01' AS date,
      busy_airports AS (
      SELECT
      FlightDate,
      Origin,
      groupArray(toStringCutToZero(Dest)) AS Destinations
      FROM ontime.ontime
      WHERE Origin IN ('ATL', 'ORD', 'DFW', 'DEN', 'LAX', 'JFK', 'LAS', 'CLT', 'SFO', 'SEA')
      AND FlightDate = date
      GROUP BY FlightDate, Origin
      ORDER BY length(Destinations)
      )
  SELECT
      Origin,
      length(Destinations) AS outward_flights
  FROM busy_airports
  ORDER BY outward_flights DESC
  ```
</RunnableCode>

<div id="arraymap">
  ### arrayMap et arrayZip
</div>

Nous avons vu dans la requête précédente que l’aéroport international de Denver était celui qui comptait le plus de vols au départ pour la journée choisie.
Voyons maintenant combien de ces vols étaient à l’heure, retardés de 15 à 30 minutes ou retardés de plus de 30 minutes.

Dans ClickHouse, de nombreuses fonctions de tableau sont ce que l’on appelle des ["fonctions d’ordre supérieur"](/docs/fr/reference/functions/regular-functions/overview#higher-order-functions) et acceptent une fonction lambda comme premier paramètre.
La fonction [`arrayMap`](/docs/fr/reference/functions/regular-functions/array-functions#arrayMap) en est un exemple : elle renvoie un nouveau tableau à partir du tableau fourni en appliquant une fonction lambda à chaque élément du tableau d’origine.

Exécutez la requête ci-dessous, qui utilise la fonction `arrayMap`, pour voir quels vols étaient retardés ou à l’heure.
Pour chaque paire origine/destination, elle affiche le numéro de queue et le statut de chaque vol :

<RunnableCode>
  ```sql theme={null}
  WITH arrayMap(
                d -> if(d >= 30, 'DELAYED', if(d >= 15, 'WARNING', 'ON-TIME')),
                groupArray(DepDelayMinutes)
      ) AS statuses

  SELECT
      Origin,
      toStringCutToZero(Dest) AS Destination,
      arrayZip(groupArray(Tail_Number), statuses) as tailNumberStatuses
  FROM ontime.ontime
  WHERE Origin = 'DEN'
    AND FlightDate = '2024-01-01'
    AND DepTime IS NOT NULL
    AND DepDelayMinutes IS NOT NULL
  GROUP BY ALL
  ```
</RunnableCode>

Dans la requête ci-dessus, la fonction `arrayMap` prend un tableau à un seul élément `[DepDelayMinutes]` et applique la fonction lambda `d -> if(d >= 30, 'DELAYED', if(d >= 15, 'WARNING', 'ON-TIME'` pour le catégoriser.
Ensuite, le premier élément du tableau résultant est extrait avec `[DepDelayMinutes][1]`.
La fonction [`arrayZip`](/docs/fr/reference/functions/regular-functions/array-functions#arrayZip) combine le tableau `Tail_Number` et le tableau `statuses` en un seul tableau.

<div id="arrayfilter">
  ### arrayFilter
</div>

Ensuite, nous allons examiner uniquement le nombre de vols retardés de 30 minutes ou plus pour les aéroports `DEN`, `ATL` et `DFW` :

<RunnableCode>
  ```sql highlight={4} theme={null}
  SELECT
      Origin,
      OriginCityName,
      length(arrayFilter(d -> d >= 30, groupArray(ArrDelayMinutes))) AS num_delays_30_min_or_more
  FROM ontime.ontime
  WHERE Origin IN ('DEN', 'ATL', 'DFW')
      AND FlightDate = '2024-01-01'
  GROUP BY Origin, OriginCityName
  ORDER BY num_delays_30_min_or_more DESC
  ```
</RunnableCode>

Dans la requête ci-dessus, nous passons une fonction lambda comme premier argument à la fonction [`arrayFilter`](/docs/fr/reference/functions/regular-functions/array-functions#arrayFilter).
Cette fonction lambda prend elle-même le retard en minutes (`d`) et renvoie `1` si la condition est remplie, sinon `0`.

```sql theme={null}
d -> d >= 30
```

<div id="arraysort-and-arrayintersect">
  ### arraySort et arrayIntersect
</div>

Ensuite, nous allons déterminer quelles paires de grands aéroports américains desservent le plus de destinations en commun à l'aide des fonctions [`arraySort`](/docs/fr/reference/functions/regular-functions/array-functions#arraySort) et [`arrayIntersect`](/docs/fr/reference/functions/regular-functions/array-functions#arrayIntersect).
`arraySort` prend un tableau et trie ses éléments par défaut dans l'ordre croissant, même s'il est également possible de lui passer une fonction lambda pour définir l'ordre de tri.
`arrayIntersect` prend plusieurs tableaux et renvoie un tableau contenant les éléments présents dans tous les tableaux.

Exécutez la requête ci-dessous pour voir ces deux fonctions de tableau en action :

<RunnableCode>
  ```sql highlight={4,12} theme={null}
  WITH airport_routes AS (
      SELECT 
          Origin,
          arraySort(groupArray(DISTINCT toStringCutToZero(Dest))) AS destinations
      FROM ontime.ontime
      WHERE FlightDate = '2024-01-01'
      GROUP BY Origin
  )
  SELECT 
      a1.Origin AS airport1,
      a2.Origin AS airport2,
      length(arrayIntersect(a1.destinations, a2.destinations)) AS common_destinations
  FROM airport_routes a1
  CROSS JOIN airport_routes a2
  WHERE a1.Origin < a2.Origin
      AND a1.Origin IN ('DEN', 'ATL', 'DFW', 'ORD', 'LAS')
      AND a2.Origin IN ('DEN', 'ATL', 'DFW', 'ORD', 'LAS')
  ORDER BY common_destinations DESC
  LIMIT 10
  ```
</RunnableCode>

La requête fonctionne en deux grandes étapes.
D'abord, elle crée un jeu de données temporaire appelé `airport_routes` à l'aide d'une expression de table commune (CTE), qui examine tous les vols du 1er janvier 2024 et construit, pour chaque aéroport d'origine, une liste triée de toutes les destinations uniques qu'il dessert.
Dans le jeu de résultats `airport_routes`, par exemple, DEN peut avoir un tableau contenant toutes les villes qu'il dessert, comme `['ATL', 'BOS', 'LAX', 'MIA', ...]`, et ainsi de suite.

Dans la deuxième étape, la requête prend cinq grands hubs aéroportuaires américains (`DEN`, `ATL`, `DFW`, `ORD` et `LAS`) et compare toutes les paires possibles.
Elle le fait à l'aide d'un CROSS JOIN, qui crée toutes les combinaisons de ces aéroports.
Ensuite, pour chaque paire, elle utilise la fonction `arrayIntersect` pour identifier les destinations qui apparaissent dans les listes des deux aéroports.
La fonction `length` compte le nombre de destinations qu'ils ont en commun.

La condition `a1.Origin < a2.Origin` garantit que chaque paire n'apparaît qu'une seule fois.
Sans cela, vous obtiendriez à la fois JFK-LAX et LAX-JFK comme résultats distincts, ce qui serait redondant puisqu'ils représentent la même comparaison.
Enfin, la requête trie les résultats pour montrer quelles paires d'aéroports ont le plus grand nombre de destinations communes et ne renvoie que les 10 premières.
Cela met en évidence les grands hubs dont les réseaux de liaisons se recoupent le plus, ce qui peut indiquer des marchés concurrentiels où plusieurs compagnies aériennes desservent les mêmes paires de villes, ou des hubs qui desservent des régions géographiques similaires et pourraient donc servir de points de correspondance alternatifs pour les voyageurs.

<div id="arrayReduce">
  ### arrayReduce
</div>

Puisque nous examinons les retards, utilisons une autre fonction de tableau d'ordre supérieur, `arrayReduce`, pour trouver le retard moyen et le retard maximal
pour chaque itinéraire au départ de l'aéroport international de Denver :

<RunnableCode>
  ```sql highlight={5-6} theme={null}
  SELECT
      Origin,
      toStringCutToZero(Dest) AS Destination,
      groupArray(DepDelayMinutes) AS delays,
      round(arrayReduce('avg', groupArray(DepDelayMinutes)), 2) AS avg_delay,
      round(arrayReduce('max', groupArray(DepDelayMinutes)), 2) AS worst_delay
  FROM ontime.ontime
  WHERE Origin = 'DEN'
      AND FlightDate = '2024-01-01'
      AND DepDelayMinutes IS NOT NULL
  GROUP BY Origin, Destination
  ORDER BY avg_delay DESC
  ```
</RunnableCode>

Dans l'exemple ci-dessus, nous avons utilisé `arrayReduce` pour déterminer les retards moyen et maximal de différents vols au départ de `DEN`.
`arrayReduce` applique une fonction d'agrégation, spécifiée dans le premier paramètre de la fonction, aux éléments du tableau fourni, spécifié dans le second paramètre de la fonction.

<div id="arrayJoin">
  ### arrayJoin
</div>

Les fonctions classiques de ClickHouse ont la propriété de renvoyer le même nombre de lignes qu'elles en reçoivent.
Il existe toutefois une fonction intéressante et unique qui enfreint cette règle, et qui mérite d'être connue : la fonction `arrayJoin`.

`arrayJoin` « déplie » un tableau en créant une ligne distincte pour chaque élément.
Cela s'apparente aux fonctions SQL `UNNEST` ou `EXPLODE` dans d'autres bases de données.

Contrairement à la plupart des fonctions de tableau, qui renvoient des tableaux ou des valeurs scalaires, `arrayJoin` modifie fondamentalement le jeu de résultats en multipliant le nombre de lignes.

Considérez la requête ci-dessous, qui renvoie un tableau de valeurs de 0 à 100 par pas de 10.
On peut considérer que ce tableau représente différents temps de retard : 0 minute, 10 minutes, 20 minutes, etc.

<RunnableCode>
  ```sql theme={null}
  WITH range(0, 100, 10) AS delay
  SELECT delay
  ```
</RunnableCode>

Nous pouvons écrire une requête utilisant `arrayJoin` pour déterminer combien de retards il y a eu jusqu'à ce nombre de minutes entre deux aéroports.
La requête ci-dessous crée un histogramme montrant la distribution des retards de vol de Denver (DEN) à Miami (MIA) le 1er janvier 2024, en utilisant des buckets de retard cumulatifs :

<RunnableCode>
  ```sql theme={null}
  WITH range(0, 100, 10) AS delay,
      toStringCutToZero(Dest) AS Destination

  SELECT
      'Up to ' || arrayJoin(delay) || ' minutes' AS delayTime,
      countIf(DepDelayMinutes >= arrayJoin(delay)) AS flightsDelayed
  FROM ontime.ontime
  WHERE Origin = 'DEN' AND Destination = 'MIA' AND FlightDate = '2024-01-01'
  GROUP BY delayTime
  ORDER BY flightsDelayed DESC
  ```
</RunnableCode>

Dans la requête ci-dessus, nous renvoyons un tableau de retards à l'aide d'une clause CTE (clause `WITH`).
`Destination` convertit le code de destination en chaîne de caractères.

Nous utilisons `arrayJoin` pour déplier le tableau de retards en lignes distinctes.
Chaque valeur du tableau `delay` devient sa propre ligne avec l'alias `del`,
et nous obtenons 10 lignes : une pour `del=0`, une pour `del=10`, une pour `del=20`, etc.
Pour chaque seuil de retard (`del`), la requête compte combien de vols avaient des retards supérieurs ou égaux à ce seuil
à l'aide de `countIf(DepDelayMinutes >= del)`.

`arrayJoin` a également un équivalent en SQL : `ARRAY JOIN`.
La requête ci-dessus est reproduite ci-dessous avec l'équivalent SQL à des fins de comparaison :

<RunnableCode>
  ```sql theme={null}
  WITH range(0, 100, 10) AS delay, 
       toStringCutToZero(Dest) AS Destination

  SELECT    
      'Up to ' || del || ' minutes' AS delayTime,
      countIf(DepDelayMinutes >= del) flightsDelayed
  FROM ontime.ontime
  ARRAY JOIN delay AS del
  WHERE Origin = 'DEN' AND Destination = 'MIA' AND FlightDate = '2024-01-01'
  GROUP BY ALL
  ORDER BY flightsDelayed DESC
  ```
</RunnableCode>

<div id="next-steps">
  ## Étapes suivantes
</div>

Félicitations ! Vous avez appris à utiliser les tableaux dans ClickHouse, de la création et de l’indexation de tableaux simples à des fonctions puissantes comme `groupArray`, `arrayFilter`, `arrayMap`, `arrayReduce` et `arrayJoin`.
Pour aller plus loin, consultez la référence complète des fonctions de tableau afin de découvrir d’autres fonctions comme `arrayFlatten`, `arrayReverse` et `arrayDistinct`.
Vous pouvez également vous intéresser à des structures de données connexes, comme les [`tuples`](/docs/fr/reference/data-types/tuple#creating-tuples) et les types [JSON](/docs/fr/reference/data-types/newjson) et [Map](/docs/fr/reference/data-types/map), qui s’utilisent très bien avec les tableaux.
Exercez-vous à appliquer ces concepts à vos propres jeux de données, et testez différentes requêtes dans le SQL playground ou sur d’autres jeux de données d’exemple.

Les tableaux sont une fonctionnalité fondamentale de ClickHouse qui permettent d’effectuer des requêtes analytiques efficaces. À mesure que vous vous familiariserez avec les fonctions de tableau, vous constaterez qu’elles peuvent considérablement simplifier des aggregations complexes et l’analyse de séries temporelles.
Pour aller encore plus loin sur les tableaux, nous vous recommandons la vidéo YouTube ci-dessous de Mark, notre expert data :

<Frame>
  <iframe src="https://www.youtube.com/embed/7jaw3J6U_h8?si=6NiEJ7S1odU-VVqX" title="Lecteur vidéo YouTube" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen />
</Frame>
