PL / SQL:需要比较plsql表中每个字段的数据

问题描述 投票:1回答:1

我需要创建一个将收集作为输入并将每个字段(大约50列)的数据与登台表数据逐行比较的过程。

业务逻辑:

  1. 每当登台表列值与对应的集合变量值不匹配时,我就需要将'FAIL'更新到登台表STATUS列中,并将该行的原因更新到REASON列中。

  2. 如果匹配,则需要在“状态”列中更新'SUCCESS'

每个调用的有效负载约为500行。

我已经在示例脚本下面创建了:

PKG规格:

CREATE OR REPLACE
PACKAGE process_data
IS
TYPE pass_data_rec
IS
  record
  (
    p_eid employee.eid%type,
    p_ename employee.ename%type,
    p_salary employee.salary%type,
    p_dept employee.dept%type 
  );

type p_data_tab IS TABLE OF pass_data_rec INDEX BY binary_integer;
PROCEDURE comp_data(inpt_data IN p_data_tab);
END;

PKG正文:

  CREATE OR REPLACE
    PACKAGE body process_data
    IS
    PROCEDURE comp_data (inpt_data IN p_data_tab)
    IS
      status VARCHAR2(10);
      reason VARCHAR2(1000);
      cnt1   NUMBER;
      v_eid employee_copy.eid%type;
      v_ename employee_copy.ename%type;
    BEGIN
      FOR i IN 1..inpt_data.count
      LOOP
        SELECT ec1.eid,ec1.ename,COUNT(*) over () INTO v_eid,v_ename,cnt1
        FROM employee_copy ec1
        WHERE ec1.eid = inpt_data(i).p_eid;
        IF cnt1 > 0 THEN
          IF (v_eid=inpt_data(i).p_eid AND v_ename = inpt_data(i).p_ename) THEN
            UPDATE employee_copy SET status = 'SUCCESS' WHERE eid = inpt_data(i).p_eid;
          ELSE
            UPDATE employee_copy SET status = 'FAIL' WHERE eid = inpt_data(i).p_eid;
          END IF;
        ELSE
          NULL;
        END IF;
      END LOOP;
      COMMIT;
      status :='success';
    EXCEPTION
    WHEN OTHERS THEN
      status:= 'fail';
      --reason:=sqlerrm;
    END;
    END;

但是在这种方法中,我有以下提到的问题。

  1. 需要为每个列值声明所有局部变量。
  2. 需要使用'and'运算符比较所有变量数据。不确定是否正确,因为如果有50列,则条件将变得很沉重。

    IF (v_eid=inpt_data(i).p_eid AND v_ename = inpt_data(i).p_ename) THEN

  3. 当该行的任何列数据不匹配(第一个不匹配的列名)时,都需要更新REASON列,这种方式我无法实现。

请提出任何其他实现此要求的好方法。

编辑:

我的终端只有一张桌子,即目标桌子。输入将从任何其他来源作为集合对象。

oracle plsql collections associative-array
1个回答
1
投票

修订版答案您可以将记录加载到t temp表中,但是除非您需要其他处理,否则没有必要。 AFAIK无法在不逐列浏览的情况下识别有问题的列(仅第一列)。但是,您不必担心必须声明变量。您可以声明一个定义为%rowtype的变量,该变量使您可以按名称访问每一列。循环访问一组数据以查找偶发错误是不好的(恕我直言),可使用SQL一次消除好错误。它在这里可用。即使您的输入是一个数组,我们也可以通过使用TABLE运算符将其用作表,该运算符允许将数组(集合)当作数据库表使用。因此,MINUS运算符可以使用。以下例程将设置适当的状态,并为输入数组中的每个条目标识第一个未匹配的列。它恢复为软件包规范中的原始定义,但替换了comp_data过程。

create or replace package body process_data
is
    procedure comp_data (inpt_data in p_data_tab)
    is
      -- define local array to hold status and reason for ecah.
      type status_reason_r is record
           ( eid    employee_copy.eid%type 
           , status employee_copy.status%type
           , reason employee_copy.reason%type
           );          
      type status_reason_t is
           table of status_reason_r
           index by pls_integer;
      status_reason status_reason_t := status_reason_t();

      -- define error array to contain the eid for each that have a mismatched column  
      type error_eids_t is table of employee_copy.eid%type ;
      error_eids error_eids_t; 
      current_matched_indx pls_integer;

      /*
        Helper function to identify 1st mismatched column in error row.
        Here is where we slug our way through each column to find the first column
        value mismatch. Note: There is actually validate the column sequence, but 
        for purpose here we'll proceed in the input data type definition.
      */
      function identify_mismatch_column(matched_indx_in pls_integer)
        return varchar2
      is
          employee_copy_row employee_copy%rowtype;
          mismatched_column employee_copy.reason%type;
      begin
          select * 
            into employee_copy_row
            from employee_copy
           where employee_copy.eid = inpt_data(matched_indx_in).p_eid;

          -- now begins the task of finding the mismatched column.
          if employee_copy_row.ename !=  inpt_data(matched_indx_in).p_ename
          then 
             mismatched_column := 'employee_copy.ename';
          elsif employee_copy_row.salary !=  inpt_data(matched_indx_in).p_salary 
          then  
             mismatched_column := 'employee_copy.salary';
          elsif employee_copy_row.dept !=  inpt_data(matched_indx_in).p_dept 
          then 
             mismatched_column := 'employee_copy.dept'; 
         -- elsif continue until ALL columns tested
          end if; 

          return  mismatched_column;

      exception
          -- NO_DATA_FOUND is the one error that cannot actually be reported in the customer_copy table.
          -- It occurs when an eid exista in the input data but does not exist in customer_copy.
          when NO_DATA_FOUND 
          then 
              dbms_output.put_line( 'Employee (eid)=' 
                                  || inpt_data(matched_indx_in).p_eid
                                  || ' does not exist in employee_copy table.'
                                  );
              return 'employee_copy.eid ID is NOT in table';
      end identify_mismatch_column;

      /*   
        Helper function to find specified eid in the initial inpt_data array
        Since the resulting array of mismatching eid derive from a select without sort
        there is no guarantee the index values actually match. Nor can we sort to build 
        the error array, as there is no way to know the order of eid in the initial array.
        The following helper identifies the index value in the input array for the specified 
        eid in error.
      */
      function match_indx(eid_in employee_copy.eid%type)
        return pls_integer
      is
          l_at        pls_integer := 1;
          l_searching boolean     := true;
      begin
          while l_at <= inpt_data.count
          loop 
             exit when eid_in = inpt_data(l_at).p_eid;
             l_at := l_at + 1; 
          end loop; 
          if l_at > inpt_data.count
          then  
             raise_application_error( -20199, 'Internal error: Find index for ' || eid_in ||' not found');
          end if; 
          return l_at;
      end match_indx;


    -- Main     
    begin
      -- initialize status table for each input enter 
      -- additionally this results is a status_reason table in a 1:1 with the input array.
      for i in 1..inpt_data.count
      loop
        status_reason(i).eid    := inpt_data(i).p_eid;
        status_reason(i).status :='SUCCESS';
      end loop;

      /*
         We can assume the majority of data in the input array is valid meaning the columns match.
         We'll eliminate all value rows by selecting each and then MINUSing those that do match on 
         each column. To accomplish this cast the input with TABLE function allowing it's use in SQL.
         Following produces an array of eids that have at least 1 column mismatch.
      */        
      select p_eid
        bulk collect into error_eids 
        from (select p_eid, p_ename, p_salary, p_dept from TABLE(inpt_data) 
              minus
              select eid, ename, salary, dept from employee_copy
             )  exs;

      /*
         The error_eids array now contains the eid for each miss matched data item.
         Mark the status as failed, then begin the long hard process of identifying 
         the first column causing the mismatch.
         The following loop used the nested functions to slug the way through. 
         This keeps the main line logic clear.
      */
      for i in 1 .. error_eids.count  -- if all inpt_data rows match then count is 0, we bypass the enttire loop
      loop
         current_matched_indx                       := match_indx(error_eids(i)); 
         status_reason(current_matched_indx).status := 'FAIL';
         status_reason(current_matched_indx).reason := identify_mismatch_column(current_matched_indx);
      end loop; 

      -- update employee_copy with appropriate status for each row in the input data.
      -- Except for any cid that is in the error eid table but doesn't exist in the customer_copy table.
      forall i in inpt_data.first .. inpt_data.last 
          update employee_copy
             set status = status_reason(i).status
               , reason = status_reason(i).reason
           where eid = inpt_data(i).p_eid;

    end comp_data;
end process_data;

[如果您不熟悉它们,可能还需要研究其他几种技术:

  1. 嵌套功能。该过程中定义和使用了2个功能。
  2. 批量处理。那是批量收集并永久使用。

祝你好运。

原始答案不必比较每一列,也不必通过串联来构建字符串。正如您指出的,比较50列会变得很繁重。因此,让DBMS承担大部分工作。使用MINUS运算符可以完全满足您的需求。

... MINUS运算符,该运算符仅返回由第一个查询,但第二个查询没有。

使用此任务仅需要2次更新:1标记为“ fail”,1标记为“ success”。因此,尝试:

create table e( e_id integer
              , col1 varchar2(20)
              , col2 varchar2(20)
              ); 
create table stage ( e_id integer
                   , col1 varchar2(20)
                   , col2 varchar2(20)
                   , status varchar2(20)
                   , reason varchar2(20)
                   );

-- create package spec and body
create or replace package process_data
is   
    procedure comp_data;
end process_data; 

create or replace package body process_data
is
    package body process_data   
    procedure comp_data 
    is
    begin  
        update stage 
           set status='failed'
             , reason='No matching e row'
         where e_id in ( select e_id 
                          from (select e_id, col1, col2 from stage
                                except
                                select e_id, col1, col2 from e
                               )  exs                     
                       );
        update stage 
           set status='success'
         where status is null; 
    end comp_data;
end process_data;   

-- test 
-- populate tables  
insert into e(e_id, col1, col2)  
   select (1,'ABC','def')       from dual union all
   select (2,'No','Not any')    from dual union all      
   select (3,'ok', 'best ever') from dual union all
   select (4,'xx','zzzzzz')     from dual;

insert into stage(e_id, col1, col2)
   select (1,'ABC','def')         from dual union all
   select (2,'No','Not any more') from dual union all
   select (4,'yy', 'zzzzzz')      from dual union all
   select (5,'no e','nnnnn')      from dual;

-- run procedure

begin 
    process_data.comp_date; 
end; 

-- check results
select * from stage;

不要问。是的,您希望在MINUS操作中涉及的每个查询中比较您必须列出每一列。我知道文档链接很旧(10gR2),但实际上查找Oracle文档是一件很痛苦的事情。但是MINUS运算符在19c中的功能仍然相同;

© www.soinside.com 2019 - 2024. All rights reserved.