我想为另一个表的每一行选择一个表的随机值。
我有以下代码:
SELECT T1.COL1,(SELECT TOP 1 t2.COL2 FROM T2 ORDER BY NEWID())FROM T1
我理解它不起作用的原因。它从T2中选择一个随机值,但从T1中选择的每一行都是相同的。
谢谢。
答案 0 :(得分:5)
这是SQL Server的棘手部分。在优化查询方面有点过于激进。您可以使用关联子句来阻止这种情况:
SELECT T1.COL1,
(SELECT TOP 1 t2.COL2 FROM T2 where t1.col1 is not null ORDER BY NEWID() )
FROM T1
添加的子句where t1.col1 is not null
将强制SQL Server计算每行的子查询。如果没有这个,就像在原始查询中一样,子查询被计算一次,然后被缓存。
答案 1 :(得分:3)
Gordon建议的查询是正确的,它会产生预期的结果,但如果你的表有多个行,那么效率很低。
我会在这里重复一遍:
SELECT T1.COL1,
(SELECT TOP 1 t2.COL2 FROM T2 where t1.col1 is not null ORDER BY NEWID() )
FROM T1
基本上,它为T1
表的每一行运行子查询。子查询读取整个T2
表,对整个表进行排序,选择一行并丢弃其余的表。 T2
表格的扫描次数与T1
中的行数一样多。
查询可以为t2.COL2
的不同行返回相同的值T1
。
如果这是“随机选择”的要求和定义的一部分,那么你就无法做很多事情。
但是,如果没有这样的要求并且允许在没有重复的情况下逐行映射两个表,那么通过仅扫描源表一次就可以更快地完成它。
在我的测试SQL Server 2008中,我有一个表Numbers
,其中包含100,000行,编号为1到100,000,表Calendar
的日期为2000-01-01到2037-12-31( 13880行)。
所以,我写了两个问题:
行号
WITH
CTE1
AS
(
SELECT
T1.dt
,ROW_NUMBER() OVER (ORDER BY dt) AS rn1
FROM dbo.Calendar AS T1
)
,CTE2
AS
(
SELECT
T2.Number
,ROW_NUMBER() OVER (ORDER BY NEWID()) AS rn2
FROM dbo.Numbers AS T2
)
SELECT
CTE1.dt
,CTE2.Number
FROM
CTE1
INNER JOIN CTE2 ON CTE1.rn1 = CTE2.rn2
;
<强>子查询强>
SELECT T1.dt,
(SELECT TOP 1 t2.Number FROM dbo.Numbers AS T2 where t1.dt is not null ORDER BY NEWID() )
FROM dbo.Calendar AS T1
;
我在SQL Sentry Plan Explorer中运行它们并比较它们的执行计划和性能:
从这个屏幕截图中可以看出,RowNumber查询在233毫秒内完成,而子查询变体在289,950毫秒内完成。大约1200倍。
当您查看执行计划时,显而易见的是:
<强> ROWNUMBER 强>
您可以看到两个表都被扫描一次并且合并在一起。
<强>子查询强>
此处Numbers
表被扫描并排序13880次。
我保持RowNumber变体很简单,以说明这个概念。如果T1
的行数超过T2
,则无法按预期工作。
很容易修复它,但是在计算行号之前,需要多次将T2
交叉连接到自身以生成足够的行。