【问题标题】:How to batch replace strings in a field in SQL如何批量替换SQL中字段中的字符串
【发布时间】:2019-01-28 10:08:31
【问题描述】:

背景和目标

我正在尝试对我的数据表中的一些产品编号进行伪匿名化。请参阅下面的示例代码。产品编号是 10 个数字,对于表格来说可能是唯一的,也可能不是唯一的。

由于我可能希望链接到其他表,因此我想使用一种非随机方式对数据进行伪匿名化。

系统是 SQLite 3.10.1。但是,任何类型的带 SQL 的 DBMS 都可以。

我的限制是:

  • 保持和原来一样的长度
  • 将每个数字换成另一个数字或一个字母

我已采取的措施

我实际上是要检查每一个可能的数字并按如下方式更新它。但是,这感觉是一种非常低效的方法。

UPDATE test
SET pseudo_num = replace(pseudo_num, '0', 'B');
UPDATE test
SET pseudo_num = replace(pseudo_num, '1', 'T');
UPDATE test
SET pseudo_num = replace(pseudo_num, '2', 'A');
UPDATE test
SET pseudo_num = replace(pseudo_num, '3', 'A');
UPDATE test
SET pseudo_num = replace(pseudo_num, '4', 'D');
UPDATE test
SET pseudo_num = replace(pseudo_num, '5', '3');
UPDATE test
SET pseudo_num = replace(pseudo_num, '6', '2');
UPDATE test
SET pseudo_num = replace(pseudo_num, '7', '4');
UPDATE test
SET pseudo_num = replace(pseudo_num, '8', 'X');
UPDATE test
SET pseudo_num = replace(pseudo_num, '9', 'L');

问题

  1. 是否有更快的方法来做到这一点,例如通过批量更换?
  2. 是否有另一种标准方法来进行伪匿名化,我可以利用它来保持在我上面概述的限制范围内?

创建数据表的示例代码

CREATE TABLE test (
  prod_num varchar(14),
  owner varchar(255) default NULL,
  prod_date varchar(255)
);

INSERT INTO test (prod_num,owner,prod_date) VALUES ("260619275","Kieran","Feb 10, 2018"),("316556232","Steven","Jan 6, 2020"),("625302534","Oliver","Feb 10, 2018"),("811424845","Jeremy","Apr 12, 2018"),("060961216","Quinlan","Jul 19, 2019"),("713794360","Stuart","Nov 1, 2019"),("553381666","George","Jan 8, 2019"),("978519361","Macon","Nov 26, 2018"),("352718969","Raphael","Jul 21, 2019"),("803299478","Byron","Nov 26, 2019");
INSERT INTO test (prod_num,owner,prod_date) VALUES ("696124452","Dalton","Jul 17, 2018"),("892088485","Keane","Jul 9, 2018"),("817054190","Dillon","Apr 23, 2018"),("500170097","Fitzgerald","Feb 11, 2019"),("663252252","Thomas","Apr 10, 2018"),("061983557","Alan","May 12, 2018"),("492057435","Jarrod","Apr 16, 2018"),("837802495","Shad","Mar 22, 2019"),("725698187","Mark","Jul 22, 2018"),("153352349","Akeem","Feb 19, 2018");

ALTER TABLE test 
ADD pseudo_num NVARCHAR(20);

UPDATE test 
SET pseudo_num = prod_num;

【问题讨论】:

  • 替换有什么整体逻辑吗?您使用的是什么版本的 SQL?
  • 我不确定用已知数字(或字母)替换数字是否是一个好的匿名化,因为它是可逆的(除了 A,它是 2 或 3)
  • 当然是可逆的,但我认为没关系 - 它应该是伪匿名的,而不是完全的
  • 如果您使用的是 SQLite,那么您需要 SQLite 中的解决方案。

标签: sql performance sqlite


【解决方案1】:

您可以使用散列(或加密)函数将产品编号转换为具有相同长度的字符和数字的字符串。相同的产品编号也获得相同的哈希/值:

TSQL 示例:

-- preview (old and new prod_num)
SELECT prod_num, RIGHT(CONVERT(VARCHAR(32), HASHBYTES('SHA1', prod_num), 2), LEN(prod_num)) 
FROM test;

-- the UPDATE
UPDATE test SET pseudo_num = RIGHT(CONVERT(VARCHAR(32), HASHBYTES('SHA1', prod_num), 2), LEN(prod_num));

demo on dbfiddle.uk

MySQL 示例:

-- preview (old and new prod_num)
SELECT prod_num, UPPER(RIGHT(MD5(prod_num), LENGTH(prod_num))) 
FROM test;

-- the UPDATE
UPDATE test SET pseudo_num = UPPER(RIGHT(MD5(prod_num), LENGTH(prod_num)));

demo on dbfiddle.uk

Oracle 示例:

-- preview (old and new prod_num)
SELECT prod_num, SUBSTR(STANDARD_HASH(prod_num, 'MD5'), LENGTH(prod_num) * -1) pseudo_prod_num 
FROM test;

-- the UPDATE
UPDATE test SET pseudo_num = SUBSTR(STANDARD_HASH(prod_num, 'MD5'), LENGTH(prod_num) * -1);

demo on dbfiddle.uk

PostgreSQL 示例:

-- preview (old and new prod_num)
SELECT prod_num, UPPER(RIGHT(MD5(prod_num), LENGTH(prod_num))) 
FROM test;

-- the UPDATE
UPDATE test SET pseudo_num = UPPER(RIGHT(MD5(prod_num), LENGTH(prod_num)));

demo on dbfiddle.uk

【讨论】:

  • 这种转换是否可重现,所以如果我想在另一个表上完成它并且存在相同的prod_num,它将产生相同的pseudo_num
  • 是的。相同的 prod_num 是相同的哈希。如果您查看预览,您可以检查这一点。相同的产品编号具有相同的哈希值。在所有大型 DBMS 上都应该有一个散列函数。
  • 如何使用 PostgreSQL 和 Oracle 做同样的事情?
【解决方案2】:

您可以尝试在此处使用连接来进行替换。如果您没有包含从旧pseduo_num 映射到新的正式表,那么我们可以尝试使用 CTE。

WITH map AS (
    SELECT '0' AS pseudo_num, 'B' AS output UNION ALL
    SELECT '1', 'T' UNION ALL
    SELECT '2', 'A' UNION ALL
    SELECT '3', 'A' UNION ALL
    SELECT '4', 'D' UNION ALL
    SELECT '5', '3' UNION ALL
    SELECT '6', '2' UNION ALL
    SELECT '7', '4' UNION ALL
    SELECT '8', 'X' UNION ALL
    SELECT '9', 'L'
),
cte AS (
    SELECT t.pseudo_num, m.output
    FROM test t
    INNER JOIN map m
        ON t.pseudo_num = m.psuedo_num
)

UPDATE cte
SET pseudo_num = output;

【讨论】:

    【解决方案3】:

    您说“任何类型的带有 SQL 的 DBMS 都可以”,所以这是针对 Postgres 的:

    在 Postgres 中,您可以使用 translate() 函数:

    UPDATE test
      SET pseudo_num = translate(pseudo_num, '0123456789', 'BTAAD324XL');
    

    在线示例:https://rextester.com/OIMBB72939

    【讨论】:

      【解决方案4】:

      在 Mariadb 上:

      alter table test add primary key (prod_num);
      replace into test(prod_num, owner, prod_date, pseudo_num)
      select 
          prod_num,
          owner,
          prod_date,
          replace(
              replace(
                  replace(
                      replace(
                          replace(
                              replace(
                                  replace(
                                      replace(
                                          replace(
                                              replace(prod_num,'0','B')
                                          ,'1','T')
                                      ,'2','A')
                                  ,'3','A')
                              ,'4','D')
                          ,'5','3')
                      ,'6','2')
                  ,'7','4')
              ,'8','X')
          ,'9','L') as pseudo_num
      from test;
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-04-08
        • 1970-01-01
        • 2015-07-17
        • 1970-01-01
        • 1970-01-01
        • 2012-03-26
        • 2022-01-22
        相关资源
        最近更新 更多