【问题标题】:UPDATE a column in every row with a random unique number使用随机唯一编号更新每一行中的一列
【发布时间】:2018-03-12 11:41:42
【问题描述】:

我有一个大约 20k 行的小表。在该表中有一个名为 random_uid (INT NOT NULL) 的列。我想用一个随机的唯一编号更新所有 20k 行。

由于我的表很小,我认为我不需要使用字符串或 UUID,所以我选择了

SELECT FLOOR(RAND() * 100000000) AS random_num
FROM table1 
WHERE "random_num" NOT IN (SELECT random_uid FROM table1)
LIMIT 1;

我的问题是我无法从同一个表中更新和选择,所以我在创建 UPDATE 查询时遇到了麻烦。

编辑: 我对上述随机性没有问题,因为我没有将它用于任何安全目的,只是为每一行创建唯一 ids,而不仅仅是增加。由于用于验证同一数字不存在于另一行中的选择,我不能使用UPDATE,这就是问题所在。

【问题讨论】:

  • 随机且独一无二。棘手。
  • 伪随机且唯一(在小型数据库中)听起来更合理,不是吗。
  • 请记住RAND() 会产生相当可预测的数字,尤其是当您将其限制在相对较小的值范围内时。如果你需要这些是真正不可预测的,你将不得不使用不同的生成器。
  • 这不是为了任何与安全相关的事情,所以我不认为这是一个问题,除非我忽略了一些问题。
  • 如果您创建一个具有自动递增 id 的新表,然后以随机顺序将所有行插入其中?

标签: mysql random sql-update


【解决方案1】:

对 2M 条记录进行了 100 次迭代的测试。测试成功。

UPDATE IGNORE table1 SET random_uid = ( RAND( ) * ( SELECT countID
FROM (
SELECT MAX(random_uid) + COUNT(1) + 1 countID
FROM table1) AS t3)
) + ( 
SELECT maxID
FROM (SELECT MAX( random_uid ) maxID FROM table1) AS t)

【讨论】:

  • 也试着解释你为什么在那里使用INNER JOIN。例如限制尝试更新非唯一列等。
  • 很抱歉没有提及,我会更新我的帖子以反映这一点,但我使用了 NOT IN 因为我需要数字是唯一的。以上不保证这一点。
  • 那仍然不能保证不冲突。正如您现在所拥有的,两个不同的行可能在 random_uid 列中具有相同的数字。当我问这个问题时,我最初想要的是如何将上述查询“某种”放入 UPDATE 查询中。
  • random_uid 字段的最大值是多少?
  • 我没有接受答案的原因是因为我测试了它,它在工作时会生成伪随机数并更新列,数字不是唯一的!
【解决方案2】:

这是一个简单的方法。我用 512 行填充了一个测试表,然后这样做:

mysql> set @i = 0;

mysql> update table1 set random_num = @i:=@i+1 order by rand();

mysql> select * from table1 limit 10;
+----+------------+
| id | random_num |
+----+------------+
|  1 |        345 |
|  2 |        108 |
|  3 |         18 |
|  4 |        247 |
|  6 |        202 |
|  7 |        275 |
|  8 |        289 |
|  9 |        121 |
| 13 |        237 |
| 14 |        344 |
+----+------------+

数字现在随机分配到行,但每一行都有一个唯一的值。

不过,在为随后插入的行分配值时,它不会那么随机。

【讨论】:

  • 在我看来,这是最适合这个问题的答案。谢谢!
【解决方案3】:

您可以使用更新触发器修改现有行,并使用插入触发器为新行生成随机数。在触发器主体中,您生成一个随机数并检查它是否已存在于表中。您在循环中执行此操作,并在找到新(唯一)数字后立即退出循环。

更新触发器

DELIMITER //
create trigger table1_before_update
before update on table1 for each row 
begin
    declare rnd_num integer;
    loop1: loop
        set rnd_num := floor(rand() * 100000000);
        if not exists (select * from table1 where random_num = rnd_num) then
            set new.random_num = rnd_num;
            leave loop1;
        end if;
    end loop;
end//
DELIMITER ;

您可以使用以下命令更新表中的所有行:

update table1 set random_num = null where 1 = 1;

请注意,random_num 列必须可以为空。但它可以是UNIQUE。所以你可以定义为random_num int null unique

由于您只需要执行此步骤一次,因此您现在可以删除该触发器。

插入触发器

DELIMITER //
create trigger table1_before_insert
before insert on table1 for each row
begin
    declare rnd_num integer;
    loop1: loop
        set rnd_num := floor(rand() * 100000000);
        if not exists (select * from table1 where random_num = rnd_num) then
            set new.random_num = rnd_num;
            leave loop1;
        end if;
    end loop;
end//
DELIMITER ;

INSERT 触发器具有相同的主体。插入新行时,不需要设置random_num 列。触发器会处理它。它甚至适用于批量插入:

insert into table1 (data) values
    ('data1'),
    ('data2'),
    ('data3'),
    ('data4'),
    ('data5');

演示:http://rextester.com/ZIDG57947

请注意,我在演示中使用FLOOR(RAND() * 10) 来演示小范围内的唯一性。但是 - 你不应该尝试插入比可能的唯一数字更多的行:-)

对于 20K 行和 100M 可能的唯一编号,循环将需要每行 1.0002(平均)迭代。

【讨论】:

    【解决方案4】:

    您可以生成前 N 个整数的随机序列并用它更新您的表(其中 N 是表中的行数)。

    Update table1 as st join (Select id, rnd_id
    from (Select @rn3:=@rn3+1 as rowid, id from (select @rn3:=-1) as t4 cross join table1) as t5
    join 
    (Select @rn2:=@rn2+1 as rowid, rnd_id from (SELECT @rn2:=-1) as t1 cross join
    (Select @rn1:=@rn1+1 as rnd_id from (SELECT @rn1:=-1) as t3 cross join table1 order by Rand()) as t2) as t6
    on t5.rowid=t6.rowid) as t7 on st.id=t7.id set st.random_id=t7.rnd_id;
    

    解释:

    (Select @rn1:=@rn1+1 as rnd_id from (SELECT @rn1:=-1) as t3 cross join table1 order by Rand()) as t2
    

    构建 N 个数字的随机序列。我们使用每行递增的变量。 (SELECT @rn1:=-1) as t3 cross join 等价于 set @rn1:=-1; 我们使用交叉连接技巧将两个语句放在一行中。所以这会生成从 0 到 N-1 的序列并用order by Rand() 打乱它

    我们用一个行号来增加这个表

    (Select @rn2:=@rn2+1 as rowid, rnd_id from (SELECT @rn2:=-1) as t1 cross join ...
    

    我们以类似的方式在原始表中增加行号:

    (Select @rn3:=@rn3+1 as rowid, id from (select @rn3:=-1) as t4 cross join table1) as t5
    

    我们使用行号连接这两个部分:

    on t5.rowid=t6.rowid
    

    我们有效地构建了一个表,其中一列包含 id,另一列包含问题中提到的 random_uid(称为 rnd_id)。此时我们可以继续更新,用我们的新 rnd_id 表扩充表,并将原始表中的 random_uid(这里称为 random_id)设置为等于 rnd_id:

    Update table1 as st join ... as t7 on st.id=t7.id set st.random_id=t7.rnd_id;
    

    关于在同一个表中使用更新和选择的麻烦,我认为诀窍是为表使用不同的别名。参考MySql - Update table using select statment from same table

    这解决了为所有表填充 random_uid 的问题。在我的情况下,当我添加一行时,我只添加一个等于表中元素数的 random_uid,所以 N(因为我从 0 开始)。在我的情况下这已经足够了,但通常不是,这取决于您的限制。

    【讨论】:

      【解决方案5】:

      借用 @BillKarwin ,UUID 提供了一种在数据库中获取随机和唯一字段的方法,并且 Mysql8 有一些部分但完全可行的支持。

      要存储它们,您的字段需要是 VARCHAR(37)。确保您也为该字段分配了唯一约束。通过将它们打包成 16 字节大小的二进制文件可以更有效地存储它们,但这超出了本文的范围——网上还有其他帖子解释了如何打包 uuid。

      SET @i = UUID(); 
      UPDATE table1 set uuid_field = @i := UUID(); 
      SELECT * from table1 LIMIT 10;
      

      经过上述查询,每个 uuid_field 都有一个通用唯一的 id。 您还需要有一个触发器来填充插入时列的值。假设该字段名为 'uuid_field' 并且是一个 varchar,这里是触发器:

      CREATE TRIGGER trigger_name
      BEFORE INSERT ON table1
      FOR EACH ROW
      SET new.uuid_field = UUID();
      

      【讨论】:

        【解决方案6】:

        可能最简单的方法是重复运行此查询:

        UPDATE table1
        SET random_uid = FLOOR(RAND() * 100000000);
        

        在每一轮之间,你可以调用:

        SELECT random_uid, COUNT(*) FROM table1 GROUP BY random_uid HAVING COUNT(*) > 1
        

        查看是否有重复。

        如果您在 MySQL Workbench 中工作,您可以创建一个临时过程来为您执行此操作,如下所示:

        DELIMITER ;;
        DROP PROCEDURE IF EXISTS __SET_UNIQUE_IDS__;;
        CREATE PROCEDURE __SET_UNIQUE_IDS__()
        BEGIN
            while_loop: WHILE 1 = 1 DO
                UPDATE table1 SET random_uid = FLOOR(RAND() * 100000000);
                IF NOT EXISTS (SELECT random_uid FROM table1 GROUP BY random_uid HAVING COUNT(*) > 1) THEN
                    LEAVE while_loop;
                END IF;
            END WHILE;
        END
        ;;
        CALL __SET_UNIQUE_IDS__();;
        DROP PROCEDURE __SET_UNIQUE_IDS__;;
        DELIMITER ;
        

        这实际上只是一种蛮力的方式,您可以通过各种方式优化性能,但这可以快速完成工作。我真的建议以另一种方式这样做,例如使用 UUID。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-09-28
          • 2015-12-27
          • 2012-08-25
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多