Un enfoque podría ser usar una tabla #temp para los valores y también introducir una columna de equijoin ficticia para permitir una unión hash. Por ejemplo:
-- 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 rendimiento y consulta
Este enfoque produce un plan de consulta como el siguiente, y la coincidencia hash se realiza en modo por lotes:
Si reemplazo la SELECT
declaración con una SUM
de la CASE
declaración para evitar tener que transmitir todas esas filas a la consola y luego ejecutar la consulta en una tabla de almacén de columnas de fila real de 100MM que tengo por ahí, veo un rendimiento bastante bueno para generar los 300MM necesarios filas:
CPU time = 33803 ms, elapsed time = 4363 ms.
Y el plan real muestra una buena paralelización de la unión hash.
Notas sobre la paralelización de combinación hash cuando todas las filas tienen el mismo valor
El rendimiento de esta consulta depende en gran medida de que cada subproceso en el lado de la sonda de la unión tenga acceso a la tabla hash completa (a diferencia de una versión particionada hash, que asignaría todas las filas a un solo subproceso dado que solo hay un valor distinto para la dummy
columna).
Afortunadamente, esto es cierto en este caso (como podemos ver por la falta de un Parallelism
operador en el lado de la sonda) y debería ser cierto de manera confiable porque el modo por lotes crea una única tabla hash que se comparte entre los subprocesos. Por lo tanto, cada subproceso puede tomar sus filas del Columnstore Index Scan
y combinarlas con esa tabla hash compartida. En SQL Server 2012, esta funcionalidad era mucho menos predecible porque un derrame causó que el operador se reiniciara en modo Fila, perdiendo el beneficio del modo por lotes y también requiriendo un Repartition Streams
operador en el lado de la sonda de la unión, lo que causaría un sesgo de hilo en este caso . Permitir que los derrames permanezcan en modo por lotes es una mejora importante en SQL Server 2014.
Que yo sepa, el modo de fila no tiene esta capacidad de tabla hash compartida. Sin embargo, en algunos casos, generalmente con una estimación de menos de 100 filas en el lado de la compilación, SQL Server creará una copia separada de la tabla hash para cada subproceso (identificable por el Distribute Streams
inicio de la unión hash). Esto puede ser muy poderoso, pero es mucho menos confiable que el modo por lotes, ya que depende de sus estimaciones de cardinalidad y SQL Server está tratando de evaluar los beneficios versus el costo de construir una copia completa de la tabla hash para cada hilo.
UNION ALL: una alternativa más simple
Paul White señaló que otra opción, y potencialmente más simple, sería utilizar UNION ALL
para combinar las filas para cada valor. Esta es probablemente su mejor opción, suponiendo que sea fácil para usted construir este SQL dinámicamente. Por ejemplo:
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
Esto también produce un plan que puede utilizar el modo por lotes y proporciona un rendimiento aún mejor que la respuesta original. (Aunque en ambos casos el rendimiento es lo suficientemente rápido como para que cualquier selección o escritura de datos en una tabla se convierta rápidamente en el cuello de botella). El UNION ALL
enfoque también evita jugar juegos como multiplicar por 0. ¡A veces es mejor pensar de manera simple!
CPU time = 8673 ms, elapsed time = 4270 ms.