Oracle:使用临时表的纯 PL/SQL 数据提取和匿名化,只读权限

Oracle: Pure PL/SQL data extraction and anonymization using temporary tables, read-only permissions

我正在尝试创建一个 PL/SQL 脚本,该脚本从 oracle 生产数据库中提取根 "object" 以及所有子项和其他相关信息。目的是创建一组测试数据以重现生产中遇到的问题。由于数据保护法,数据在提取时需要匿名 - 对象名称、某些类型的 ID 和货币金额需要替换。

我试图创建一个或多个临时 t运行slation tables,其中将包含原始值和匿名版本。然后我会将真实数据与 t运行slation tables 结合起来,并在需要的地方输出匿名值。

DECLARE
  rootId integer := 123456;

  TYPE anonTableRow IS RECORD 
  (
    id NUMBER,
    fieldC NUMBER,
    anonymizedFieldC NUMBER
  );

  TYPE anonTable IS TABLE OF anonTableRow;
  anonObject anonTable;
BEGIN

  FOR cursor_row IN 
  (
    select 
     id,
     fieldC,
     1234 -- Here I would create anonymized values based on rowNum or something similar
    from 
    prodTable
    where id = rootId
  ) 
  LOOP       
    i := i + 1;
    anonObject(i) := cursor_row; 
  END LOOP;

  FOR cursor_row IN 
  (
    select 
    prod_table.id,
    prod_table.fieldB,
    temp_table.anonymizedFieldC fieldC,
    prod_table.fieldD
    from 
    prod_table
    inner join table(temp_table) on prod_table.id = temp_table.id
    where prod_table.id = 123456789
  ) 
  LOOP       
   dbms_output.put_line('INSERT INTO prod_table VALUES (' || cursor_row.id || ', ' || cursor_row.fieldB || ', ' || cursor_row.fieldC || ', , ' || cursor_row.fieldD);
  END LOOP;
END;
/

但是我 运行 使用这种方法遇到了几个问题 - 似乎几乎不可能将 oracle PL/SQL tables 与真实数据库 tables 结合起来。我对生产数据库的访问受到严格限制,因此我无法创建全局临时 tables,无法在 PL/SQL 之外声明类型或任何类似的东西。

我尝试声明自己的 PL/SQL 类型失败,出现问题 mentioned in this question - 由于权限有限,该解决方案对我不起作用。

是否有一种纯粹的 PL/SQL 方式,不需要花哨的权限即可实现上述目标?

请注意:上面的代码示例已经简化了很多,实际上不需要单独的 t运行slation table - 实际上我需要在几个不同的查询中访问原始值和 t运行slated 值,所以我宁愿不必在任何地方都使用 "recalculate" t运行slations。

如果您的数据已正确规范化,那么我想这应该只对内部 ID 是必需的(尽管不确定为什么需要翻译它们)。

以下代码应该适合您,将映射保留在 Associative Arrays:

DECLARE
  TYPE t_number_mapping IS TABLE OF PLS_INTEGER INDEX BY PLS_INTEGER;

  mapping_field_c   t_number_mapping;
BEGIN
  -- Prepare mapping
  FOR cur IN (
    SELECT 101 AS field_c FROM dual UNION ALL SELECT 102 FROM dual -- test-data
  ) LOOP
    mapping_field_c(cur.field_c) := mapping_field_c.COUNT;  -- first entry mapped to 1
  END LOOP;

  -- Use mapping
  FOR cur IN (
    SELECT 101 AS field_c FROM dual UNION ALL SELECT 102 FROM dual -- test-data
  ) LOOP
    -- You can use the mapping when generating the `INSERT` statement
    dbms_output.put_line( cur.field_c || ' mapped to ' || mapping_field_c(cur.field_c) );
  END LOOP;
END;

输出:

101 mapped to 1
102 mapped to 2

如果这不是永久性的生产代码,"borrowing" 现有的集合类型如何 - 例如一个在 SYS 中定义,您可以访问。

使用您的架构中的此脚本,您可以生成一个 SQL Plus 脚本来描述所有 SYS-owned 类型:

select 'desc ' || type_name from all_types
where typecode = 'COLLECTION'
and owner = 'SYS';

运行 生成的脚本将显示您可以访问的所有内容的结构。这个看起来可能适合例如:

SQL> desc KU$_PARAMVALUES1010
 KU$_PARAMVALUES1010 TABLE OF SYS.KU$_PARAMVALUE1010
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 PARAM_NAME                                         VARCHAR2(30)
 PARAM_OP                                           VARCHAR2(30)
 PARAM_TYPE                                         VARCHAR2(30)
 PARAM_LENGTH                                       NUMBER
 PARAM_VALUE_N                                      NUMBER
 PARAM_VALUE_T                                      VARCHAR2(4000)

当然,您不能保证该类型在数据库升级后仍然存在或保持不变或可供您访问,因此我在一开始就提出警告。

实现此目标的更通用方法。 在我的示例中,我使用了 xquery flwor 表达式和 dbms_xmlstore。必须了解 xquery。

create table mask_user_objects as select * from user_objects where rownum <0;

declare 
   v_s_table varchar2(30) := 'USER_OBJECTS';   --uppercase!!!
   v_d_table varchar2(30) := 'MASK_USER_OBJECTS';  --uppercase!!!
   v_mask_columns xmltype := xmltype('<COLS><OBJECT_NAME>XXXX</OBJECT_NAME>
                                            <DATA_OBJECT_ID>-1</DATA_OBJECT_ID>
                                            <OBJECT_TYPE/>                                            
                                      </COLS>');  --uppercase!!!   
   insCtx DBMS_XMLSTORE.ctxType;
   r NUMBER;
   v_source_table xmltype;
   v_cursor sys_refcursor; 
begin 
   open v_cursor  for 'select * from '||v_s_table||' where rownum <100 ';  
   v_source_table := xmltype(v_cursor);
   close v_cursor; 
   -- Load source table into xmltype. 
   insCtx := DBMS_XMLSTORE.newContext(v_d_table); -- Get saved context
  for rec in ( 
   select tt.column_value from  xmltable('
                  let $col := $anomyze/COLS
                  for $i in  $doc/ROWSET/ROW           
                   let $row := $i  
                   return <ROWSET>             
                            <ROW>                                         
                                {                                                                
                                 for $x in $row/* 
                                  return if(
                                       exists($col/*[name() = $x/name()] )                                                                                             
                                  ) then element{$x/name()}{ $col/*[name() = $x/name()]/text() }  
                                    else element{$x/name()}{$x/text()}                   
                                }                   
                            </ROW>             
                         </ROWSET>
                  '
                  passing v_source_table as "doc"
                       ,  v_mask_columns as "anomyze"
                  ) tt) loop
                  null;
             r := DBMS_XMLSTORE.insertXML(insCtx,  rec.column_value);
end loop;
 DBMS_XMLSTORE.closeContext(insCtx);      

end;