Clickhouse - des fonctions de fenĂȘtre qui n'existent pas ...

J'ai commencĂ© Ă  travailler avec des bases de donnĂ©es en colonnes avec BigQuery. Quand j'ai dĂ» "dĂ©mĂ©nager" chez Clickhouse, j'ai Ă©tĂ© dĂ©sagrĂ©ablement surpris par le manque de fonctions de fenĂȘtre Ă  part entiĂšre. Il existe, bien sĂ»r, de nombreuses fonctions pour travailler avec des tableaux, des fonctions d'ordre supĂ©rieur et d'autres fonctions (une fonction exĂ©cutantDifferenceStartingWithFirstValue en vaut la peine). Le gagnant de 1999 pour le titre du mot le plus long DonaudampfschifffahrtsgesellschaftskapitĂ€nswitwe vient immĂ©diatement Ă  l'esprit. Ce qui signifie en traduction de l'allemand "la veuve du capitaine de la compagnie maritime sur le Danube".



La recherche de «fonctions de fenĂȘtre dans Clickhouse» ne renvoie pas de rĂ©sultats significatifs. Cet article est une tentative de rĂ©sumer des donnĂ©es dispersĂ©es sur Internet, des exemples avec ClickHouseMeetup et ma propre expĂ©rience.



Fonctions de fenĂȘtre - syntaxe



Permettez-moi de vous rappeler la syntaxe des fonctions de fenĂȘtre et le type de rĂ©sultat que nous obtenons. Dans les exemples, nous utiliserons le dialecte Standart SQL Google BigQuery. Voici un lien vers la documentation sur les fonctions de fenĂȘtre (elles sont appelĂ©es fonction analytique dans la documentation - des sons de traduction plus prĂ©cis comme des fonctions analytiques). Et voici la liste des fonctions elle-mĂȘme.



La syntaxe générale ressemble à ceci:



analytic_function_name ( [ argument_list ] ) OVER over_clause
over_clause:
  { named_window | ( [ window_specification ] ) }
window_specification:
  [ named_window ]
  [ PARTITION BY partition_expression [, ...] ]
  [ ORDER BY expression [ { ASC | DESC }  ] [, ...] ]
  [ window_frame_clause ]
window_frame_clause:
  { rows_range } { frame_start | frame_between }
rows_range:
  { ROWS | RANGE }


Prenons les choses Ă©tape par Ă©tape:



  1. La fonction window est appliquée au jeu d'enregistrements défini dans l'expression over_clause,
  2. Le jeu d'enregistrements est dĂ©fini par la clause PARTITION BY. Ici, vous pouvez lister un ou plusieurs champs par lesquels le jeu d'enregistrements sera dĂ©terminĂ©. Fonctionne de la mĂȘme maniĂšre que GROUP BY.

    Le tri des enregistrements dans un ensemble est spécifié à l'aide de ORDER BY.
  3. Vous pouvez en outre restreindre un ensemble prĂ©dĂ©fini d'enregistrements en tant que fenĂȘtre. La fenĂȘtre peut ĂȘtre dĂ©finie de maniĂšre statique. Par exemple, vous pouvez prendre 5 enregistrements comme fenĂȘtre, 2 avant et 2 aprĂšs l'enregistrement actuel et l'enregistrement actuel lui-mĂȘme. Cela ressemblera Ă  ceci: RANGÉES ENTRE 2 PRÉCÉDENT ET 2 SUIVANTES.

    Un exemple de construction pour spĂ©cifier une fenĂȘtre dĂ©finie dynamiquement ressemble Ă  ceci - RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW. Cette construction dĂ©finit une fenĂȘtre du premier Ă  l'enregistrement en cours selon l'ordre de tri spĂ©cifiĂ©.


A titre d'exemple, considérons le calcul de la somme cumulée (exemple de la documentation):



SELECT item, purchases, category, SUM(purchases)
  OVER (
    PARTITION BY category
    ORDER BY purchases
    ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
  ) AS total_purchases
FROM Produce


RĂ©sultat:



+-------------------------------------------------------+
| item      | purchases  | category   | total_purchases |
+-------------------------------------------------------+
| orange    | 2          | fruit      | 2               |
| apple     | 8          | fruit      | 10              |
| leek      | 2          | vegetable  | 2               |
| cabbage   | 9          | vegetable  | 11              |
| lettuce   | 10         | vegetable  | 21              |
| kale      | 23         | vegetable  | 44              |
+-------------------------------------------------------+


Que peut-on faire dans Clickhouse



Essayons de rĂ©pĂ©ter cet exemple dans ClickHouse. Bien sĂ»r, ClickHouse a les fonctions runningAccumulate , arrayCumSum et groupArrayMovingSum . Mais dans le premier cas, vous devez dĂ©terminer l'Ă©tat dans une sous-requĂȘte ( plus de dĂ©tails ), et dans le second cas, la fonction renvoie un tableau, qui doit ensuite ĂȘtre dĂ©veloppĂ©.



Nous construirons la requĂȘte la plus gĂ©nĂ©rale. La demande elle-mĂȘme pourrait ressembler Ă  ceci:



SELECT
   items,
   summ as purchases,
   category,
   sumArray(cum_summ) as total_purchases
FROM (SELECT
         category,
         groupArray(item) AS items,
         groupArray(purchases) AS summ,
         arrayMap(x -> arraySlice(summ, 1, x), arrayEnumerate(summ)) AS cum_summ
     FROM (SELECT
               item,
               purchases,
               category
           FROM produce
           ORDER BY category, purchases)
     GROUP BY category)
   ARRAY JOIN items, summ, cum_summ
GROUP BY category, items, summ
ORDER BY category, purchases


Prenons les choses Ă©tape par Ă©tape:



  1. Tout d'abord, nous construisons une sous-requĂȘte, dans laquelle le tri des donnĂ©es requis a lieu (catĂ©gorie ORDER BY, achats). Le tri doit correspondre aux champs des expressions PARTITION BY et ORDER BY de la fonction de fenĂȘtre.
  2. , , PARTITION BY. item .

    purchases , summ .
  3. — ArrayMap. , func arr.

    arr — [1, 2, 
, length(summ)], arrayEnumerate.

    func — arraySlice(summ, 1, x), x — arr, . summ x. , cum_sum , , .



    ArrayMap arrayEnumerate , , . ( 3), ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING.



    arrayMap(x -> arraySlice(summ, if(x-1 > 0, x-1, 1), if(x-1 > 0, 3, 2)), arrayEnumerate(summ))


    , . 2 ClickHouse:



    • — [edited] — . [/edited]. , , arrayMap arrayFilter. . — ( — ) (alias) arrayMap, arrayFilter .
    • — . , , arrayReverse arraySlice.


  4. La derniÚre étape consiste à développer les tableaux dans une table à l'aide de ARRAY JOIN. Nous devons également appliquer la fonction d'agrégation de somme avec le modificateur -Array (en conséquence, la fonction d'agrégation ressemble à sumArray) au résultat renvoyé par la fonction ArrayMap.


Production



Il est possible d'Ă©muler le fonctionnement des fonctions de fenĂȘtre dans ClickHouse. Pas trĂšs rapide et pas trĂšs joli. En bref, le pipeline se compose de 3 Ă©tapes:



  1. RequĂȘte triĂ©e. Cette Ă©tape prĂ©pare le jeu d'enregistrements.
  2. Regroupement en baies et exĂ©cution d'opĂ©rations sur les baies. Cette Ă©tape dĂ©finit la fenĂȘtre de notre fonction de fenĂȘtre.
  3. Expansion dans une table à l'aide de fonctions d'agrégation.



All Articles