PL / SQL: необходимо сравнить данные для каждого поля в таблице в plsql - PullRequest
1 голос
/ 22 февраля 2020

Мне нужно создать процедуру, которая будет принимать сбор в качестве входных данных и сравнивать данные с данными промежуточной таблицы построчно для каждого поля (около 50 столбцов).

Бизнес-логика c:

  1. всякий раз, когда значение столбца промежуточной таблицы будет не соответствовать соответствующему значению переменной коллекции, тогда мне нужно обновить 'FAIL' в столбец STATUS промежуточной таблицы и указать причину в столбце REASON для этого строка.

  2. При совпадении необходимо обновить 'УСПЕХ' в столбце СОСТОЯНИЕ.

Полезная нагрузка составит около 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. Нужно сравнить все переменные данные с помощью оператора «и». Не уверен, что это правильный путь или нет, потому что если есть 50 столбцов, то если условие станет очень тяжелым.

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

  3. Необходимо обновить столбец REASON, когда любые несоответствия данных столбца (первое несоответствующее имя столбца) для этой строки, в этом подходе я не могу достичь.

Пожалуйста, предложите любой другой хороший способ выполнить это требование.

Редактировать:

На моем конце есть только одна таблица, т.е. целевая таблица. Ввод поступит из любого другого источника в качестве объекта коллекции.

1 Ответ

1 голос
/ 22 февраля 2020

ПЕРЕСМОТРЕННЫЙ ответ Вы можете загрузить записи в временную таблицу, но если вам не нужна дополнительная обработка, в этом нет необходимости. AFAIK нет способа идентифицировать поврежденный столбец (только первый), не пробираясь через столбец за столбцом. Однако ваша другая проблема, связанная с объявлением переменной, не обязательна. Вы можете объявить одну переменную, определенную как % rowtype , которая дает вам доступ к каждому столбцу по имени. Циклировать по массиву данных, чтобы найти случайную ошибку, просто плохо (imho) с SQL, доступным для устранения хороших за один раз oop. И это доступно здесь. Несмотря на то, что ваши входные данные являются массивом, мы можем использовать его в качестве таблицы с помощью оператора TABLE, который допускает массив (коллекцию), как если бы это была таблица базы данных. Таким образом, оператор МИНУС может быть занят. Следующая процедура установит соответствующий статус и определит первый пропущенный столбец для каждой записи во входном массиве. Он возвращается к исходному определению в пакете spe c, но заменяет процедуру 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 столбцов становится довольно тяжелым. Так что пусть СУБД сделает большую часть подъема. Использование оператора MINUS делает именно то, что вам нужно.

... оператор MINUS, который возвращает только уникальные строки, возвращенные первым запросом, но не вторым.

При использовании этой задачи требуется только 2 обновления: 1, чтобы отметить «провал», и 1, чтобы отметить «успех». Поэтому попробуйте:

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;

Не спрашивайте. Да, вы должны перечислить каждый столбец , который вы будете sh сравнивать в каждом из запросов, участвующих в операции MINUS. Я знаю, что ссылка на документацию старая (10gR2), но на самом деле найти документацию Oracle - непростая задача. Но оператор МИНУС все еще функционирует в 19c;

...