一种方法可能是对值使用#temp表,还引入一个虚拟的等值连接列以允许哈希连接。例如:
-- 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
语句,以避免必须将所有这些行流式传输到控制台,然后在我所躺着的真正的100MM行列存储表上运行查询,那么我看到生成必要的300MM的性能相当不错行数:
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.