ORACLE обновление записей с отношением 1 ко многим таблицам в триггере - PullRequest
1 голос
/ 04 августа 2020

Я застрял, я не знаю, что делаю не так, мне нужна помощь!

Дана таблица PERSON , которая соответствует человеку:

create table person
(
    ID integer,
    registration_number varchar(9),
    primary_number varchar(9),
    women_act varchar(1)
);

Учитывая таблицу CONSOLIDATED_NUMBERS , которая определяет сопоставление между несколькими записями от PERSON:

create table consolidated_numbers
(
    SECONDARY_NUMBER varchar(9),
    person_id integer
);

Учитывая таблицу TRANSACTION_HISTORY , в которой хранится запись все действия, связанные с данным человеком из таблицы PERSON (примечание, столбец причины ниже, строки с valid_code выше)

create table history_transaction
(
    reason varchar(2),
    person_id integer,
    type_id integer,
    action_date date
);
    insert into person (ID,registration_number,primary_number) values(132, '000000001', null);
    insert into person (ID,registration_number,primary_number) values (151, '000000002', '000000001');
insert into consolidated_numbers (SECONDARY_NUMBER,person_id) values ('000000002', 132);
insert into history_transaction (reason,person_id,type_id,action_date) values ('A1', 132, 1420, DATE '2019-01-01');

Данная таблица CODE , который отслеживает действительные коды:

    create table code
    (
        valid_code varchar(2)
    );
    insert into code (valid_code) values ('A1');
    insert into code (valid_code) values ('T1');
    insert into code (valid_code) values ('N2');

Желание состоит в том, чтобы, когда personX от PERSON делает что-то такое, что обновляет таблицу TRANSACTION_HISTORY, а затем всех людей в PERSON, связанных с personX, как показано в Таблица CONSOLIDATED_NUMBERS должна быть обновлена, чтобы в столбце women_act было установлено значение X.

    create or replace TRIGGER trans_hist_trg
        AFTER
    INSERT OR
    UPDATE OF reason
        ON history_transaction
    FOR EACH ROW
    DECLARE
    v_exists   VARCHAR2
    (1);
    v_valid    code.valid_code%TYPE;
    v_person_id     person.id%TYPE;

    BEGIN
        IF(INSERTING) THEN
        v_person_id := :NEW.person_id;
    ELSE
        v_person_id := :OLD.person_id;
    END
    IF;
    BEGIN
        SELECT women_act
        INTO  v_exists
        FROM person
        WHERE id = v_person_id;
        EXCEPTION
        WHEN NO_DATA_FOUND THEN
           v_exists := NULL;
    END;

    SELECT valid_code
    INTO v_valid
    FROM code
    WHERE valid_code = :NEW.reason;

    IF v_exists IS NULL AND :NEW.type_id IN
    (120,140,1420,1440,160,180,150,1520,1540,1560) THEN
    IF :NEW.reason NOT IN
    ('T1','A1') OR
    (:NEW.reason IN
    ('T1','A1') AND :NEW.action_date >= '01-JAN-00') THEN
    BEGIN
        SELECT valid_code
        INTO v_valid
        FROM code
        WHERE valid_code = :NEW.reason;
        EXCEPTION
                WHEN NO_DATA_FOUND THEN
                    v_exists := null;
    END;
    IF v_valid IS NOT NULL THEN
    UPDATE person
                SET women_act = 'X'
                WHERE (id = v_person_id
        OR id IN (SELECT DISTINCT id
        FROM person
        WHERE registration_number IN (SELECT DISTINCT SECONDARY_NUMBER
        FROM consolidated_numbers
        WHERE person_id = v_person_id)));
    END IF;
   END IF;    
  END IF;            
 
END trans_hist_trg;
   ---validate records
    select * from person;
    select * from consolidated_numbers;
    select * from history_transaction;
    select * from code;

    --update reocord to activate trigger 
    update history_transaction
      set reason = 'A1'
      where person_id = 132;

    --should update 2 records (132 and 151 with x. Initially they were null)
    select * from `person`;

Моя задача прямо сейчас - установить баннер для всех консолидированных чисел. Прямо сейчас, если я установлю для баннера основной номер, баннер будет отображаться на всех дополнительных номерах; однако, если я установлю баннер на дополнительный номер, он не будет отображаться на основном.

Пожалуйста, помогите!

1 Ответ

2 голосов
/ 06 августа 2020

Не используйте для этого триггер. Большинство условий, которые вы закодировали во вложенных IF (вашего триггера), вероятно, можно выполнить с помощью ограничений внешнего ключа и проверочных ограничений. Кроме того, вам не нужно нигде хранить «X» для WOMAN_ACT, поскольку это «производное значение» ie, которое вы можете получить или сгенерировать при запросе ваших данных. Возможно, следующий пример (основанный на ваших исходных таблицах и данных) поможет вам найти решение. Прочтите комментарии в коде.

DDL-код

create table person (
  id number primary key
, registration_number varchar2(9) unique
, primary_number varchar2(9)
-- , women_act varchar2(1)   <- not needed!
); 
  
create table consolidated_numbers (
  secondary_number varchar2(9) references person( registration_number )
, person_id number references person( id )
); 

create table code (
  valid_code varchar2(2) primary key
);

-- CHECK constraint added to allow only certain TYPE_IDs
create table history_transaction (
  reason varchar2(2) references code( valid_code ) -- valid REASONSs enforced by FK constraint
, person_id number references person( id )
, type_id number check (
    type_id in (
      120, 140, 1420, 1440, 160, 180, 150, 1520, 1540, 1560  -- only allow these type_ids
    )
  )
, action_date date
);

Тестовые данные

-- INSERT your initial test data
begin
  insert into person (ID,registration_number,primary_number) values(132, '000000001', null);
  insert into person (ID,registration_number,primary_number) values (151, '000000002', '000000001');
  insert into consolidated_numbers (SECONDARY_NUMBER,person_id) values ('000000002', 132);
  insert into code (valid_code) values ('A1');
  insert into code (valid_code) values ('T1');
  insert into code (valid_code) values ('N2');
  insert into history_transaction (reason,person_id,type_id,action_date)
    values ('A1', 132, 1420, DATE '2019-01-01');
  commit ;
end;
/

Следующие VIEW выберет идентификаторы person_ids из таблиц HISTORY_TRANSACTION, добавит и 'X' к каждой из них, а также выберет всех лиц, которые «связаны» с этими идентификаторами (или сопоставлены) из CONSOLIDATED_NUMBERS, а также добавит 'X 'к своим идентификаторам. (Примечание: кажется, что ваша таблица PERSON содержит рекурсивные отношения, поэтому можно написать рекурсивный запрос. Однако у вас будет причина для моделирования таблицы CONSOLIDATED_NUMBERS, поэтому мы будем использовать здесь JOIN.)

ПРОСМОТР

create or replace view personx
as
with PID as (
  select distinct person_id
  from history_transaction
)
select person_id, 'X' as woman_act  -- [Q1] all person_ids from history_transaction
from PID
union
select P.id, 'X' as woman_act       -- [Q2] all person_ids associated with ids from Q1
from person P
  join consolidated_numbers C
    on P.registration_number = C.secondary_number
    and C.person_id in (
      select person_id from PID
    )
;

-- with your initial test data, we get:
select * from personx ;
+---------+---------+
|PERSON_ID|WOMAN_ACT|
+---------+---------+
|132      |X        |
|151      |X        |
+---------+---------+

Теперь давайте удалим / добавим некоторые данные и запустим несколько тестов (см. Также: DBfiddle ):

-- test 1
delete from history_transaction ;
select * from personx ;
-- result: no rows selected -> OK

-- test 2
insert into history_transaction (reason,person_id,type_id,action_date) 
  values ('A1', 132, 1420, DATE '2019-01-01');
  
select * from personx ;
+---------+---------+
|PERSON_ID|WOMAN_ACT|
+---------+---------+
|132      |X        |
|151      |X        |
+---------+---------+

-- test 3: add more associations
begin   
-- new: person 345 associated with person 132
  insert into person (ID,registration_number,primary_number) values (345, '000000345', '000000001');
  insert into consolidated_numbers (SECONDARY_NUMBER,person_id) values ('000000345', 132);
  commit ;
end ;
/

select * from personx ;
+---------+---------+
|PERSON_ID|WOMAN_ACT|
+---------+---------+
|132      |X        |
|151      |X        |
|345      |X        |
+---------+---------+

Еще один тест перед тем, как мы go в подробностях:

-- test 4
-- add more associations 
-- no entry in history_transactions for person(id) 1000        
begin   
  insert into person (ID,registration_number,primary_number) values(1000, '000000777', null);
  insert into person (ID,registration_number,primary_number) values (2000, '000000778', '000000777');
  insert into consolidated_numbers (SECONDARY_NUMBER,person_id) values ('000000778', 1000);
  commit ;
end ;
/   

-- output must be the same as before -> result OK
select * from personx ;
+---------+---------+
|PERSON_ID|WOMAN_ACT|
+---------+---------+
|132      |X        |
|151      |X        |
|345      |X        |
+---------+---------+

JOIN представление таблицы person

-- test 5
-- add an entry from person 1000 into the history_transaction table
insert into history_transaction (reason,person_id,type_id,action_date) 
    values ('N2', 1000, 1420, sysdate);  

select * from personx ;
+---------+---------+
|PERSON_ID|WOMAN_ACT|
+---------+---------+
|132      |X        |
|151      |X        |
|345      |X        |
|1000     |X        |
|2000     |X        |
+---------+---------+

-- test 5: show more details
select P.id, P.registration_number, P.primary_number, PX.woman_act
from personx PX right join person P on PX.person_id = P.id ;

+----+-------------------+--------------+---------+
|ID  |REGISTRATION_NUMBER|PRIMARY_NUMBER|WOMAN_ACT|
+----+-------------------+--------------+---------+
|132 |000000001          |NULL          |X        |
|151 |000000002          |000000001     |X        |
|345 |000000345          |000000001     |X        |
|1000|000000777          |NULL          |X        |
|2000|000000778          |000000777     |X        |
+----+-------------------+--------------+---------+

Внешнее соединение необходим для PERSON_ID, у которых нет соответствующих строк в таблице HISTORY_TRANSACTION, например,

-- test 6
-- add more associations
-- no entry in history_transactions for person(id) 10000!
begin
  insert into person (ID,registration_number,primary_number) values(10000, '000007777', null);
  insert into person (ID,registration_number,primary_number) values (20000, '000007778', '000007777');
  insert into consolidated_numbers (SECONDARY_NUMBER,person_id) values ('000007778', 10000);
  commit ;
end ;
/

-- after TEST 6 data have been inserted:
select P.id, P.registration_number, P.primary_number, PX.woman_act
from personx PX right join person P on PX.person_id = P.id ;

+-----+-------------------+--------------+---------+
|ID   |REGISTRATION_NUMBER|PRIMARY_NUMBER|WOMAN_ACT|
+-----+-------------------+--------------+---------+
|132  |000000001          |NULL          |X        |
|151  |000000002          |000000001     |X        |
|345  |000000345          |000000001     |X        |
|1000 |000000777          |NULL          |X        |
|2000 |000000778          |000000777     |X        |
|20000|000007778          |000007777     |NULL     |
|10000|000007777          |NULL          |NULL     |
+-----+-------------------+--------------+---------+

EDIT

Если - как указано в вашем комментарии - вы должны сохранить значение в В столбце WOMAN_ACT (хотя, по-видимому, это «производное значение»), вы можете написать пакет, содержащий процедуры для всех требуемых операций DML, но без использования триггера. Однако, не зная всей истории, трудно решить, будет ли это наилучшим путем вперед. В следующем примере используется небольшой пакет, содержащий процедуры для установки значений WOMAN_ACT таблицы PERSON и триггер, который срабатывает после INSERT / UPDATE (таблица: HISTORY_TRANSACTIONS). DBfiddle здесь .

таблица PERSON

create table person (
  id number primary key
, registration_number varchar2(9) unique
, primary_number varchar2(9)
, woman_act varchar2(1) check ( woman_act in ( null, 'X' ) )
);
-- all other tables: same as before

ПАКЕТ

create or replace package pxpkg
is
  -- find out whether a certain id (table: PERSON) is a "parent" or a "child"
  function isparent( id_ number ) return boolean ;
  -- set 'X' values: id_ is a "parent"
  procedure setx_parentchildren( id_ number ) ;
  -- set 'X' values: id_ is a "child" 
  procedure setx_childsiblings( id_ number ) ;
end pxpkg ;
/

ТЕЛО УПАКОВКИ

create or replace package body pxpkg
is
  function isparent( id_ number )
  return boolean
  is
    secondarynumbers pls_integer := 0 ;
  begin
    select count(*) into secondarynumbers
    from consolidated_numbers
    where person_id = id_ ;
    if secondarynumbers = 0 then
      return false ;
    else
      return true ;
    end if ;
  end isparent ;
--
  procedure setx_parentchildren ( id_ number )
  is
  begin
    update person
    set woman_act = 'X'
    where id in ( 
      select id from person where id = id_ -- parent id
      union
      select id from person 
      where primary_number = ( 
        select registration_number from person where id = id_ -- parent id
      )
    ) ;
  end setx_parentchildren ;
--
  procedure setx_childsiblings ( id_ number )
  is
  begin
    update person
    set woman_act = 'X'
    where id in ( 
      with PID as (
        select id, primary_number from person
        where id = id_                    -- current id
          and primary_number is not null  -- child ids only
      )
      select id from PID
      union
      select id 
      from person 
      where registration_number in ( select primary_number from PID )
         or primary_number in ( select primary_number from PID )
    ) ;
  end setx_childsiblings ;
end pxpkg ;
/

ТРИГГЕР

create or replace trigger pxtrigger
after insert or update on history_transaction
for each row
begin
  if pxpkg.isparent( :new.person_id ) then
    pxpkg.setx_parentchildren( :new.person_id )  ;
  else
    pxpkg.setx_childsiblings( :new.person_id )  ;
  end if ;
end pxtrigger ;
/

ТЕСТИРОВАНИЕ: см. DBfiddle

...