SQL Server中的随机选择

时间:2018-06-25 12:43:13

标签: sql sql-server tsql random

我有两组,对于第一组中的每个值,我想从第二组中应用多个随机值。我选择的方法使用第一个选择,第二个选择交叉应用。简化的MWE如下:

DROP TABLE IF EXISTS #S;
CREATE TABLE #S (c CHAR(1));
INSERT INTO #S VALUES ('A'), ('B');
DROP TABLE IF EXISTS #T;
WITH idGen(id) AS (
    SELECT 1 
    UNION ALL 
    SELECT id + 1 FROM idGen WHERE id < 1000
) 
SELECT id INTO #T FROM idGen OPTION(MAXRECURSION 0);
DROP TABLE IF EXISTS #R;
SELECT c, id INTO #R FROM #S
CROSS APPLY (
    SELECT id, ROW_NUMBER() OVER (
        /* 
        -- this gives 100% overlap
        PARTITION BY c
        ORDER BY RAND(CHECKSUM(NEWID()))
        */
        -- this gives the expected ~10% overlap
        ORDER BY RAND(CHECKSUM(NEWID()) + CHECKSUM(c))
    ) AS R
    FROM #T 
) t
WHERE t.R <= 100;
SELECT COUNT(*) AS PercentOverlap -- ~10%
FROM #R rA JOIN #R rB
ON rB.id = rA.id AND rB.c = 'B'
WHERE rA.c = 'A';

虽然此解决方案有效,但我想知道为什么更改为(注释的)分区方法不可行?另外,是否有使用此解决方案的注意事项,以为添加两个校验和看起来有点脏?

在实际问题中,第一组中还有一个计数,其中包含要从第二组中选择的随机值的数量,它代替了上面示例中的静态值100。但是,使用固定值100可以轻松验证预期的重叠。

1 个答案:

答案 0 :(得分:1)

<corners android:bottomLeftRadius="16dp" android:topLeftRadius="16dp" /> <size android:width="140dp" android:height="20dp" /> <solid android:color="#FFFFFFFF" /> 函数是SQL Server中的运行时常量。这意味着通常通常会为查询评估一次。当您将值传递给RAND()时,该值将用作起始种子。

您需要检查执行计划,您将看到优化器将功能评估放在何处。这种情况不会产生预期的结果,很可能是优化器过于积极地对其进行了优化,并将所有“随机性”移出了循环。

此外,将RAND包装到NEWID()CHECKSUM()中也没有意义。 简单的RAND()就足够了。甚至更好的是,该函数旨在产生随机数,例如NEWID()

这两个查询版本看起来都有些奇怪。我会这样写:

CRYPT_GEN_RANDOM()

这会为SELECT c, id INTO #R FROM #S CROSS APPLY ( SELECT TOP(100) -- or #S.SomeField instead of 100 id FROM #T ORDER BY CRYPT_GEN_RANDOM(4) -- generate 4 random bytes, usually it is enough ) AS t ; 中的每一行提供#T中的100条随机行。

实际上,以上查询不是很好。 Optimiser再次看到内部查询(在#S内部)不依赖外部查询,并对其进行了优化。 最终结果是随机行仅被选择一次。

我们需要一些东西来优化运行CROSS APPLY中每一行的内部查询。 一种方法是这样的:

#S

内部查询中的某些内容引用了外部查询中的行。如果您放置SELECT c, id INTO #R FROM #S CROSS APPLY ( SELECT TOP(100) -- or #S.SomeField instead of 100 id FROM #T ORDER BY CRYPT_GEN_RANDOM(4) + CHECKSUM(c) ) AS t ; 而不是常量TOP(#S.SomeField),则不需要TOP(100)

这是第一个变体的计划。您可以看到+ CHECKSUM(c)被扫描了一次(读取了1000行)。

Query 1

这是第二种方案的计划。您可以看到#T被扫描了两次(读取了2000行)。

Query 2