如何使用另一个表中的随机行更新表的每一行

时间:2015-04-20 23:27:36

标签: sql sql-server tsql random sql-server-2008-r2

我正在构建我的第一个去识别脚本,并遇到了我的方法问题。

我有一个表dbo.pseudonyms,其firstname列填充了200行数据。此列中包含200行的每一行都有一个值(none为null)。该表还有一个id列(int,主键,非null),数字为1-200。

我想要做的是,在一个声明中,重新填充我的整个USERS表,并为我firstname表中每行随机选择pseudonyms个数据。

使用ABS(Checksum(NewId())) % 200生成随机数以便选择我。每次我做SELECT ABS(Checksum(NewId())) % 200时,我都会得到一个数值,我会寻找一个很好的,没有间歇性的不稳定行为。

但是,当我在以下声明中使用此公式时:

SELECT pn.firstname 
FROM DeIdentificationData.dbo.pseudonyms pn 
WHERE pn.id = ABS(Checksum(NewId())) % 200

我得到了非常间歇性的结果。我说大约30%的结果返回从表中挑出的一个名称(这是预期的结果),大约30%的结果返回多个结果(这令人费解,没有重复的id列值),大约30%返回NULL(即使firstname列中有没有空行)

我确实为这个具体问题寻找了一段时间,但到目前为止无济于事。我假设问题与使用此公式作为指针有关,但我不知道如何做到这一点。

思想?

1 个答案:

答案 0 :(得分:1)

为什么问题中的查询会返回意外结果

您的原始查询从Pseudonyms中选择。服务器扫描表格的每一行,从该行中选择ID,生成一个随机数,将生成的数字与ID进行比较。

如果偶然生成的特定行的编号恰好与该行的ID相同,则在结果集中返回此行。偶然生成的数字很可能永远不会与ID相同,并且生成的数字会多次与ID重合。

更详细一点:

  • 服务器选择ID=1行。
  • 生成一个随机数,比如25。为什么不?一个不错的随机数。
  • 1 = 25?否=>不返回此行。
  • 服务器选择ID=2行。
  • 生成一个随机数,比如125。为什么不?一个不错的随机数。
  • 2 = 125?否=>不返回此行。
  • 依旧......

<强> Here is a complete solution on SQL Fiddle

示例数据

DECLARE @VarPseudonyms TABLE (ID int IDENTITY(1,1), PseudonymName varchar(50) NOT NULL);
DECLARE @VarUsers TABLE (ID int IDENTITY(1,1), UserName varchar(50) NOT NULL);

INSERT INTO @VarUsers (UserName)
SELECT TOP(1000)
    'UserName' AS UserName
FROM sys.all_objects
ORDER BY sys.all_objects.object_id;

INSERT INTO @VarPseudonyms (PseudonymName)
SELECT TOP(200)
    'PseudonymName'+CAST(ROW_NUMBER() OVER(ORDER BY sys.all_objects.object_id) AS varchar) AS PseudonymName
FROM sys.all_objects
ORDER BY sys.all_objects.object_id;

Users有1000行,每行有UserName个。表格Pseudonyms有200行,不同的PseudonymNames

SELECT * FROM @VarUsers;
ID   UserName
--   --------
1    UserName
2    UserName
...
999  UserName
1000 UserName

SELECT * FROM @VarPseudonyms;
ID   PseudonymName
--   -------------
1    PseudonymName1
2    PseudonymName2
...
199  PseudonymName199
200  PseudonymName200

首次尝试

起初我尝试了直接的方法。对于Users中的每一行,我想从Pseudonyms得到一个随机行:

SELECT
    U.ID
    ,U.UserName
    ,CA.PseudonymName
FROM
    @VarUsers AS U
    CROSS APPLY
    (
        SELECT TOP(1)
            P.PseudonymName
        FROM @VarPseudonyms AS P
        ORDER BY CRYPT_GEN_RANDOM(4)
    ) AS CA
;

事实证明,优化器太聪明了,这产生了一些随机的,但每个PseudonymName都是User,这不是我的预期:

ID   UserName   PseudonymName
1    UserName   PseudonymName181
2    UserName   PseudonymName181
...
999  UserName   PseudonymName181
1000 UserName   PseudonymName181

所以,我稍微调整了这个方法,并首先为Users中的每一行生成一个随机数。然后,我使用生成的数字,使用PseudonymID中的每一行Users找到CROSS APPLY

CTE_Users有一个额外的列,其中包含1到200的随机数。在CTE_Joined中,我们为每个PseudonymsUser中选择一行。 最后,我们UPDATE原始Users表。

最终解决方案

WITH
CTE_Users
AS
(
    SELECT
        U.ID
        ,U.UserName
        ,1 + 200 * (CAST(CRYPT_GEN_RANDOM(4) as int) / 4294967295.0 + 0.5) AS rnd
    FROM @VarUsers AS U
)
,CTE_Joined
AS
(
    SELECT
        CTE_Users.ID
        ,CTE_Users.UserName
        ,CA.PseudonymName
    FROM
        CTE_Users
        CROSS APPLY
        (
            SELECT P.PseudonymName
            FROM @VarPseudonyms AS P
            WHERE P.ID = CAST(CTE_Users.rnd AS int)
        ) AS CA
)
UPDATE CTE_Joined
SET UserName = PseudonymName;

<强>结果

SELECT * FROM @VarUsers;
ID   UserName
1    PseudonymName41
2    PseudonymName132
3    PseudonymName177
...
998  PseudonymName60
999  PseudonymName141
1000 PseudonymName157

<强> SQL Fiddle