Одним из подходов может быть использование таблицы #temp для значений, а также введение пустого столбца equijoin, чтобы разрешить хеш-соединение. Например:
-- 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
Производительность и план запросов
Этот подход дает план запроса, подобный следующему, и совпадение хеша выполняется в пакетном режиме:
Если я заменим этот SELECT
оператор SUM
на CASE
оператор, чтобы избежать необходимости передавать все эти строки на консоль, а затем выполнить запрос к реальной таблице хранилищ строк в 100 ММ, которая у меня лежит, я вижу довольно хорошую производительность для создания необходимых 300 ММ. Количество строк:
CPU time = 33803 ms, elapsed time = 4363 ms.
И фактический план показывает хорошее распараллеливание хеш-соединения.
Замечания о распараллеливании хеш-соединений, когда все строки имеют одинаковое значение
Производительность этого запроса сильно зависит от того, имеет ли каждый поток на стороне зонда соединения доступ к полной хэш-таблице (в отличие от версии с хэш-секционированием, которая отображает все строки в один поток, учитывая, что существует только одно отдельное значение для dummy
колонки).
К счастью, в этом случае это верно (как мы видим по отсутствию Parallelism
оператора на стороне зонда) и должно быть достоверно верно, поскольку в пакетном режиме создается одна хеш-таблица, которая используется несколькими потоками. Поэтому каждый поток может взять свои строки из Columnstore Index Scan
и сопоставить их с этой общей хеш-таблицей. В SQL Server 2012 эта функциональность была гораздо менее предсказуемой, поскольку разлив привел к тому, что оператор перезапустился в режиме строки, что утратило преимущество пакетного режима, а также потребовало Repartition Streams
оператора на стороне соединения, которая в этом случае вызовет перекос потока , Разрешение разливов оставаться в пакетном режиме является серьезным улучшением в SQL Server 2014.
Насколько мне известно, в режиме строк эта общая таблица хеш-функций отсутствует. Однако в некоторых случаях, как правило, с оценкой менее 100 строк на стороне сборки, SQL Server будет создавать отдельную копию хеш-таблицы для каждого потока (идентифицируемого по Distribute Streams
началу в хеш-соединении). Это может быть очень мощным, но гораздо менее надежным, чем пакетный режим, поскольку это зависит от ваших оценок количества элементов, и SQL Server пытается оценить преимущества по сравнению со стоимостью создания полной копии хэш-таблицы для каждого потока.
UNION ALL: более простая альтернатива
Пол Уайт отметил, что другим, и, возможно, более простым вариантом будет использование UNION ALL
объединения строк для каждого значения. Вероятно, это ваш лучший выбор, если предположить, что вам легко построить этот SQL динамически. Например:
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
Это также дает план, который может использовать пакетный режим и обеспечивает даже лучшую производительность, чем первоначальный ответ. (Хотя в обоих случаях производительность достаточно высока, поэтому любой выбор или запись данных в таблицу быстро становится узким местом.) Этот UNION ALL
подход также позволяет избежать игр, таких как умножение на 0. Иногда лучше думать просто!
CPU time = 8673 ms, elapsed time = 4270 ms.