Une approche pourrait être d'utiliser une table #temp pour les valeurs et également d'introduire une colonne équijoin factice pour permettre une jointure de hachage. Par exemple:
-- Create a #temp table with a dummy column to match the hash join
-- and the actual column you want
CREATE TABLE #values (dummy INT NOT NULL, Col0 CHAR(1) NOT NULL)
INSERT INTO #values (dummy, Col0)
VALUES (0, 'A'),
(0, 'B'),
(0, 'C')
GO
-- A similar query, but with a dummy equijoin condition to allow for a hash join
SELECT v.Col0,
CASE v.Col0
WHEN 'A' THEN cs.DataA
WHEN 'B' THEN cs.DataB
WHEN 'C' THEN cs.DataC
END AS Col1
FROM ColumnstoreTable cs
JOIN #values v
-- Join your dummy column to any numeric column on the columnstore,
-- multiplying that column by 0 to ensure a match to all #values
ON v.dummy = cs.DataA * 0
Plan de performance et de requête
Cette approche génère un plan de requête comme le suivant, et la correspondance de hachage est effectuée en mode batch:
Si je remplace l' SELECT
instruction par un SUM
de l' CASE
instruction afin d'éviter d'avoir à diffuser toutes ces lignes sur la console, puis d'exécuter la requête sur une vraie table columnstore de ligne de 100MM que je traîne, je vois des performances assez bonnes pour générer les 300MM requis Lignes:
CPU time = 33803 ms, elapsed time = 4363 ms.
Et le plan réel montre une bonne parallélisation de la jointure de hachage.
Remarques sur la parallélisation de jointure de hachage lorsque toutes les lignes ont la même valeur
Les performances de cette requête dépendent fortement de chaque thread du côté sonde de la jointure ayant accès à la table de hachage complète (par opposition à une version partitionnée de hachage, qui mapperait toutes les lignes à un seul thread étant donné qu'il n'y a qu'une seule valeur distincte pour la dummy
colonne).
Heureusement, cela est vrai dans ce cas (comme nous pouvons le voir par le manque d' Parallelism
opérateur du côté de la sonde) et devrait être vrai de manière fiable car le mode batch crée une seule table de hachage qui est partagée entre les threads. Par conséquent, chaque thread peut prendre ses lignes dans le Columnstore Index Scan
et les faire correspondre à cette table de hachage partagée. Dans SQL Server 2012, cette fonctionnalité était beaucoup moins prévisible car un déversement provoquait le redémarrage de l'opérateur en mode Row, perdant à la fois l'avantage du mode batch et nécessitant également un Repartition Streams
opérateur du côté sonde de la jointure, ce qui entraînerait un biais de thread dans ce cas . Permettre aux déversements de rester en mode batch est une amélioration majeure dans SQL Server 2014.
À ma connaissance, le mode ligne n'a pas cette capacité de table de hachage partagée. Toutefois, dans certains cas, généralement avec une estimation de moins de 100 lignes côté génération, SQL Server créera une copie distincte de la table de hachage pour chaque thread (identifiable par le Distribute Streams
début de la jointure de hachage). Cela peut être très puissant, mais il est beaucoup moins fiable que le mode batch car il dépend de vos estimations de cardinalité et SQL Server essaie d'évaluer les avantages par rapport au coût de construction d'une copie complète de la table de hachage pour chaque thread.
UNION ALL: une alternative plus simple
Paul White a souligné qu'une autre option, potentiellement plus simple, serait d'utiliser UNION ALL
pour combiner les lignes pour chaque valeur. C'est probablement votre meilleur pari en supposant qu'il est facile pour vous de construire dynamiquement ce SQL. Par exemple:
SELECT 'A' AS Col0, c.DataA AS Col1
FROM ColumnstoreTable c
UNION ALL
SELECT 'B' AS Col0, c.DataB AS Col1
FROM ColumnstoreTable c
UNION ALL
SELECT 'C' AS Col0, c.DataC AS Col1
FROM ColumnstoreTable c
Cela donne également un plan capable d'utiliser le mode batch et offre des performances encore meilleures que la réponse d'origine. (Bien que dans les deux cas, les performances soient assez rapides pour que toute sélection ou écriture des données dans une table devienne rapidement le goulot d'étranglement.) L' UNION ALL
approche évite également de jouer à des jeux comme la multiplication par 0. Parfois, il vaut mieux penser simple!
CPU time = 8673 ms, elapsed time = 4270 ms.