2

我们需要在 Oracle 10g 数据库中屏蔽一些个人身份信息。我正在使用的过程基于我们用于 Sybase 的另一个屏蔽脚本(工作正常),但由于 Oracle 和 Sybase 数据库中的信息完全不同,我遇到了一些障碍。

该过程是从 PERSON 表中选择所有数据,放入 PERSON_TRANSFER 表中。然后我们使用一个随机数从 PERSON_TRANSFER 表中选择一个随机名称,然后使用该随机名称更新 PERSON 表。这在 Sybase 中运行良好,因为 PERSON 表中每个人只有一行。

我遇到的问题是,在 Oracle DB 中,每个 PERSON 有多行,每行的名称可能不同,也可能不同,例如

|PERSON|
:-----------------:
|PERSON_ID|SURNAME|
|1        |Purple |
|1        |Purple |
|1        |Pink   | <--
|2        |Gray   |
|2        |Blue   | <--
|3        |Black  |
|3        |Black  |

PERSON_TRANSFER 是该表的副本。该表有数百万行,所以我在这里只给出一个非常基本的例子:)

我当前使用的逻辑只会将所有行更新为与该 PERSON_ID 相同,例如

|PERSON|
:-----------------:
|PERSON_ID|SURNAME|
|1        |Brown  |
|1        |Brown  |
|1        |Brown  | <--
|2        |White  |
|2        |White  | <--
|3        |Red    |
|3        |Red    |

但这是不正确的,因为该 PERSON_ID 的不同名称需要以不同方式屏蔽,例如

|PERSON|
:-----------------:
|PERSON_ID|SURNAME|
|1        |Brown  |
|1        |Brown  |
|1        |Yellow | <--
|2        |White  |
|2        |Green  | <--
|3        |Red    |
|3        |Red    |

如何让脚本分别更新不同的名称,而不是仅仅根据 PERSON_ID 更新它们?我的脚本目前看起来像这样

DECLARE
    v_SURNAME    VARCHAR2(30);

BEGIN

    select pt.SURNAME
    into  v_SURNAME
    from   PERSON_TRANSFER pt
    where   pt.PERSON_ID = (SELECT PERSON_ID FROM
                            ( SELECT PERSON_ID FROM PERSON_TRANSFER
                            ORDER BY dbms_random.value )
                            WHERE rownum = 1);
END;

这会导致错误,因为该随机 PERSON_ID 返回的行太多。

1) 是否有更有效的方法来更新 PERSON 表以便随机分配名称?2) 我如何确保正确屏蔽 PERSON 表,因为对于任何单个 PERSON_ID,各种姓氏都保持不同(或相同,如果它们都相同)?

我希望这是足够的信息。我已经将它简化了一点(该表有更多的列,例如名字、出生日期、TFN 等),希望它使解释更容易。

任何输入/建议/帮助将不胜感激:)

谢谢。

4

1 回答 1

3

复杂性之一是同一姓氏可能出现在 PERSON 表中的不同 person_id 下。您最好使用一个单独的辅助表来保存不同的姓氏(例如,您可以通过从 PERSONS 中选择不同的姓氏来填充它)。

设置:

create table persons (person_id, surname) as (
  select 1, 'Purple' from dual union all
  select 1, 'Purple' from dual union all
  select 1, 'Pink'   from dual union all
  select 2, 'Gray'   from dual union all
  select 2, 'Blue'   from dual union all
  select 3, 'Black'  from dual union all
  select 3, 'Black'  from dual
);
create table mask_names (person_id, surname) as (
  select 1, 'Apple'  from dual union all
  select 2, 'Banana' from dual union all
  select 3, 'Grape'  from dual union all
  select 4, 'Orange' from dual union all
  select 5, 'Pear'   from dual union all
  select 6, 'Plum'   from dual
);
commit;

CTAS 创建 PERSON_TRANSFER:

create table person_transfer (person_id, surname) as (
select ranked.person_id, rand.surname
from   ( select person_id, surname, 
                dense_rank() over (order by surname) as rk
         from   persons
       ) ranked 
       inner join 
       ( select surname, row_number() over (order by dbms_random.value()) as rnd
         from   mask_names
       ) rand
              on ranked.rk = rand.rnd
);
commit;

结果:

SQL> select * from person_transfer order by person_id, surname;

 PERSON_ID SURNAME
---------- -------
         1 Pear
         1 Pear
         1 Plum
         2 Banana
         2 Grape
         3 Apple
         3 Apple

应 OP 的要求添加:范围已扩展 - 现在的要求是surname在原始表 ( PERSONS) 中进行更新。这可以通过merge我之前演示的语句和连接(子)查询来完成。当PERSONS表格有 PK 时,这种方法效果最好,并且确实 OP 说现实生活中的表格PERSONS有这样的 PK,由person_id列和附加列组成,date_from. 在下面的脚本中,我删除persons并重新创建它以包含此附加列。然后我显示查询和结果。

注意 -mask_names仍然需要一张桌子。一个诱人的替代方案是对已经存在的姓氏进行洗牌,persons这样就不需要“帮助”表。唉,这行不通。例如,在一个简单的例子persons中只有一行。要混淆姓氏,必须想出不在原始表中的姓氏。更有趣的是,假设 everyperson_id恰好有两行,具有不同的姓氏,但在每种情况下,这些姓氏都是“约翰”和“玛丽”。只是洗牌这两个名字没有帮助。确实需要一个“帮助”表,例如mask_names.

新设置

drop table persons;

create table persons (person_id, date_from, surname) as (
  select 1, date '2016-01-04', 'Purple' from dual union all
  select 1, date '2016-01-20', 'Purple' from dual union all
  select 1, date '2016-03-20', 'Pink'   from dual union all
  select 2, date '2016-01-24', 'Gray'   from dual union all
  select 2, date '2016-03-21', 'Blue'   from dual union all
  select 3, date '2016-04-02', 'Black'  from dual union all
  select 3, date '2016-02-13', 'Black'  from dual
);

commit;

select * from persons;


 PERSON_ID DATE_FROM  SURNAME
---------- ---------- -------
         1 2016-01-04 Purple
         1 2016-01-20 Purple
         1 2016-03-20 Pink
         2 2016-01-24 Gray
         2 2016-03-21 Blue
         3 2016-04-02 Black
         3 2016-02-13 Black

7 rows selected.

新查询和结果

merge into persons p
  using (
          select ranked.person_id, ranked.date_from, rand.surname
          from ( 
                 select person_id, date_from, surname, 
                        dense_rank() over (order by surname) as rk
                 from   persons
               ) ranked 
          inner join ( 
                 select surname, row_number() over (order by dbms_random.value()) as rnd
                 from   mask_names
               ) rand
          on ranked.rk = rand.rnd
        ) t
  on (p.person_id = t.person_id and p.date_from = t.date_from)
when matched then update
  set p.surname = t.surname;

commit;

select * from persons;

 PERSON_ID DATE_FROM  SURNAME
---------- ---------- -------
         1 2016-01-04 Apple
         1 2016-01-20 Apple
         1 2016-03-20 Orange
         2 2016-01-24 Plum
         2 2016-03-21 Grape
         3 2016-04-02 Banana
         3 2016-02-13 Banana

7 rows selected.
于 2016-07-07T04:45:21.833 回答