IN, NOT IN, GLOBAL IN et GLOBAL NOT IN sont traités séparément, car leurs fonctionnalités sont particulièrement riches.
Le côté gauche de l’opérateur est soit une colonne unique, soit un Tuple.
Exemples :
SELECT entre crochets.
Pour des raisons de compatibilité historique, lorsque le côté droit est une expression tuple unique, celle-ci peut être interprétée soit comme un ensemble de valeurs, soit comme une valeur de tuple, selon le côté gauche de l’opérateur IN. Si le côté gauche est une valeur scalaire, ClickHouse traite les éléments de cette expression tuple unique côté droit comme des valeurs IN distinctes :
Query
Response
SELECT 1 IN (1, 2). Si le côté gauche est également un tuple, le côté droit est interprété comme un ensemble de valeurs de tuple :
Query
Response
tuple unique. Un côté gauche scalaire ne peut pas être mis en correspondance avec un côté droit contenant plusieurs valeurs de tuple :
Query
Response
IN.
Dans ce cas, il convertit la valeur du côté droit vers le type du côté gauche, comme si la fonction accurateCastOrNull était appliquée au côté droit.
Cela signifie que le type de données devient Nullable et, si la conversion
ne peut pas être effectuée, renvoie NULL.
Exemple
Query
Response
UserID IN users), cela est équivalent à la sous-requête UserID IN (SELECT * FROM users). Utilisez cette syntaxe lorsque vous travaillez avec des données externes envoyées avec la requête. Par exemple, la requête peut être envoyée avec un ensemble d’identifiants utilisateur chargés dans la table temporaire ‘users’, qui doit être filtrée.
Si le côté droit de l’opérateur est un nom de table utilisant le moteur Set (un ensemble de données préparé toujours conservé en RAM), l’ensemble de données ne sera pas recréé à chaque requête.
La sous-requête peut spécifier plusieurs colonnes pour filtrer les tuples.
Exemple :
Query
IN doivent être du même type.
L’opérateur IN et la sous-requête peuvent apparaître dans n’importe quelle partie de la requête, y compris dans les fonctions d’agrégation et les fonctions lambda.
Exemple :
Query
Response
IN est toujours exécutée une seule fois sur un seul serveur. Il n’existe pas de sous-requêtes dépendantes.
Traitement des NULL
IN considère que le résultat d’une opération avec NULL est toujours égal à 0, que NULL se trouve à droite ou à gauche de l’opérateur. Les valeurs NULL ne sont incluses dans aucun ensemble de données, ne correspondent pas les unes aux autres et ne peuvent pas être comparées si transform_null_in = 0.
Voici un exemple avec la table t_null :
SELECT x FROM t_null WHERE y IN (NULL,3) renvoie le résultat suivant :
y = NULL est exclue du résultat de la requête. En effet, ClickHouse ne peut pas déterminer si NULL est inclus dans l’ensemble (NULL,3), renvoie donc 0 comme résultat de l’opération, et SELECT exclut cette ligne de la sortie finale.
Sous-requêtes distribuées
IN avec des sous-requêtes (similaires aux opérateurs JOIN) : IN / JOIN classique et GLOBAL IN / GLOBAL JOIN. Elles se distinguent par leur mode d’exécution dans le cadre du traitement des requêtes distribuées.
N’oubliez pas que les algorithmes décrits ci-dessous peuvent se comporter différemment selon le paramètre
distributed_product_mode des paramètres de session.IN standard, la requête est envoyée aux serveurs distants, et chacun d’eux exécute les sous-requêtes présentes dans la clause IN ou JOIN.
Lors de l’utilisation de GLOBAL IN / GLOBAL JOIN, toutes les sous-requêtes sont d’abord exécutées pour GLOBAL IN / GLOBAL JOIN, et les résultats sont collectés dans des tables temporaires. Ces tables temporaires sont ensuite transmises à chaque serveur distant, où les requêtes sont exécutées à partir de ces données temporaires.
Pour GLOBAL ... JOIN, le côté de la jointure calculé en tant que sous-requête dépend du type de jointure : pour les jointures LEFT et INNER, c’est la table de droite qui est calculée ; pour les jointures RIGHT, c’est la table de gauche qui est calculée à la place, car la table de droite est le côté préservé et doit être lue depuis les segments.
Pour une requête non distribuée, utilisez les opérateurs IN / JOIN classiques.
Soyez vigilant lors de l’utilisation de sous-requêtes dans les clauses IN / JOIN dans le cadre du traitement distribué des requêtes.
Examinons quelques exemples. Supposons que chaque serveur du cluster dispose d’une table local_table normale. Chaque serveur possède également une table distributed_table de type Distributed, qui couvre l’ensemble des serveurs du cluster.
Pour une requête adressée à la distributed_table, celle-ci sera envoyée à tous les serveurs distants et exécutée sur chacun d’eux via la local_table.
Par exemple, la requête
IN :
- Calcul de l’intersection des audiences de deux sites.
IN sera collecté sur chaque serveur de manière indépendante, uniquement à partir des données stockées localement sur chacun des serveurs.
Cela fonctionnera correctement et de manière optimale si vous avez anticipé ce cas et réparti les données sur les serveurs du cluster de façon à ce que les données d’un même UserID résident entièrement sur un seul serveur. Dans ce cas, toutes les données nécessaires seront disponibles localement sur chaque serveur. Dans le cas contraire, le résultat sera inexact. Nous désignons cette variante de la requête par “local IN”.
Pour corriger le comportement de la requête lorsque les données sont réparties aléatoirement sur les serveurs du cluster, vous pouvez spécifier distributed_table dans une sous-requête. La requête se présenterait alors comme suit :
GLOBAL IN plutôt que IN. Voyons comment cela fonctionne pour la requête :
_data1 sera envoyée à chaque serveur distant avec la requête (le nom de la table temporaire dépend de l’implémentation).
C’est plus efficace que d’utiliser un IN normal. Cependant, gardez les points suivants à l’esprit :
- Lors de la création d’une table temporaire, les données ne sont pas dédupliquées. Pour réduire le volume de données transmis sur le réseau, indiquez DISTINCT dans la sous-requête. (Vous n’avez pas besoin de le faire avec un
INnormal.) - La table temporaire sera envoyée à tous les serveurs distants. La transmission ne tient pas compte de la topologie du réseau. Par exemple, si 10 serveurs distants se trouvent dans un centre de données très éloigné du serveur à l’origine de la requête, les données seront envoyées 10 fois sur la liaison vers ce centre de données distant. Essayez d’éviter les jeux de données volumineux lorsque vous utilisez
GLOBAL IN. - Lors de la transmission des données aux serveurs distants, les limitations de bande passante réseau ne sont pas configurables. Vous risquez de surcharger le réseau.
- Essayez de répartir les données entre les serveurs afin de ne pas avoir à utiliser
GLOBAL INrégulièrement. - Si vous devez souvent utiliser
GLOBAL IN, planifiez l’emplacement du cluster ClickHouse de sorte qu’un même groupe de répliques ne soit pas réparti sur plusieurs centres de données, et qu’il dispose d’un réseau rapide entre eux, afin qu’une requête puisse être traitée entièrement dans un seul centre de données.
GLOBAL IN, si cette table locale n’est disponible que sur le serveur à l’origine de la requête et que vous souhaitez utiliser ses données sur des serveurs distants.
Sous-requêtes distribuées et max_rows_in_set
max_rows_in_set et max_bytes_in_set pour contrôler la quantité de données transférées lors de requêtes distribuées.
C’est particulièrement important si la requête GLOBAL IN renvoie une grande quantité de données. Prenons l’instruction SQL suivante :
some_predicate n’est pas assez sélectif, il renverra un volume important de données, ce qui entraînera des problèmes de performances. Dans ce cas, il est judicieux de limiter le volume de données transférées via le réseau. Notez également que set_overflow_mode est défini sur throw (par défaut), ce qui signifie qu’une exception est levée lorsque ces seuils sont atteints.
Sous-requêtes distribuées et max_parallel_replicas
M est compris entre 1 et 3, selon la réplique sur laquelle la requête locale s’exécute.
Ces paramètres affectent chaque table de la famille MergeTree dans la requête et ont le même effet que l’application de SAMPLE 1/3 OFFSET (M-1)/3 à chaque table.
Par conséquent, l’ajout du paramètre max_parallel_replicas ne produira des résultats corrects que si les deux tables utilisent le même schéma de réplication et si UserID, ou l’une de ses sous-clés, sert de clé d’échantillonnage. En particulier, si local_table_2 n’a pas de clé d’échantillonnage, les résultats seront incorrects. La même règle s’applique à JOIN.
Une solution de contournement, si local_table_2 ne remplit pas ces conditions, consiste à utiliser GLOBAL IN ou GLOBAL JOIN.
Si une table n’a pas de clé d’échantillonnage, des options plus souples pour parallel_replicas_custom_key peuvent être utilisées, ce qui peut produire un comportement différent et plus optimal.