如何在SQL中的select语句中分配随机值?

时间:2018-05-18 02:16:19

标签: sql sql-server

我想为另一个表的每一行选择一个表的随机值。

我有以下代码:

SELECT T1.COL1,(SELECT TOP 1 t2.COL2 FROM T2 ORDER BY NEWID())FROM T1

我理解它不起作用的原因。它从T2中选择一个随机值,但从T1中选择的每一行都是相同的。

谢谢。

2 个答案:

答案 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中运行它们并比较它们的执行计划和性能:

stats

从这个屏幕截图中可以看出,RowNumber查询在233毫秒内完成,而子查询变体在289,950毫秒内完成。大约1200倍。

当您查看执行计划时,显而易见的是:

<强> ROWNUMBER

rownumber

您可以看到两个表都被扫描一次并且合并在一起。

<强>子查询

subquery

此处Numbers表被扫描并排序13880次。

我保持RowNumber变体很简单,以说明这个概念。如果T1的行数超过T2,则无法按预期工作。

很容易修复它,但是在计算行号之前,需要多次将T2交叉连接到自身以生成足够的行。