【问题标题】:How to update each row of a table with a random row from another table如何用另一个表中的随机行更新表的每一行
【发布时间】:2015-06-27 21:45:55
【问题描述】:

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

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

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

要生成用于挑选的随机数,我使用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 列中有 no 个空行)

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

想法?

【问题讨论】:

  • 使用适当的软件(MySQL、Oracle、DB2...)和版本标记数据库问题很有帮助,例如sql-server-2014。语法和功能的差异通常会影响答案。如果您使用的是 SQL Server,您可能需要考虑CRYPT_GEN_RANDOMExample.
  • 更新了标签,谢谢!
  • ABS(Checksum(NewId())) 按行重新评估。你可能想要rand,但不是。
  • 我喜欢 crypt_gen_random,很好找。但是我不确定如何将我的随机数范围限制在 1 - 200 之间?
  • Martin 我想要得到的是一个新的随机值,用于我拉取并更新我的 USER 表的每一行。如果没有对每行重新评估公式,这是否意味着我会为每一行设置相同的值?

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


【解决方案1】:

更简单的方法:

UPDATE u
SET u.FirstName = p.Name
FROM Users u
CROSS APPLY (
    SELECT TOP(1) p.Name
    FROM pseudonyms p
    WHERE u.Id IS NOT NULL -- must be some unique identifier on Users
    ORDER BY NEWID()
) p

完整示例来自:https://stackoverflow.com/a/36185100/6620329

【讨论】:

    【解决方案2】:

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

    您的原始查询从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
    ;
    

    事实证明,优化器太聪明了,这会产生一些随机的,但对于每个 UserPseudonymName 都是相同的,这不是我所期望的:

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

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

    CTE_Users 有一个额外的列,随机数从 1 到 200。在CTE_Joined 中,我们从Pseudonyms 中为每个User 选择一行。 最后我们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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-11-23
      • 1970-01-01
      • 2020-12-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多