问题
I'm stuck, I don't know what I'm doing wrong, I need some help!
Given a table PERSON which maps to a person:
create table person
(
ID integer,
registration_number varchar(9),
primary_number varchar(9),
women_act varchar(1)
);
Given a table CONSOLIDATED_NUMBERS which specifies a mapping between multiple entries from PERSON:
create table consolidated_numbers
(
SECONDARY_NUMBER varchar(9),
person_id integer
);
Given a table TRANSACTION_HISTORY which keeps a record of all activity associated with a given person from the PERSON table (note, reason column below, lines up with valid_code above)
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');
Given a table CODE which tracks valid codes:
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');
The desire is for when a personX from PERSON does something such that it updates the TRANSACTION_HISTORY table, then all people in PERSON associated with personX, as mapped in table CONSOLIDATED_NUMBERS, should be updated to have their women_act column set to 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`;
My challenge right now is to set the banner to all the consolidated numbers. Right now if I set the banner to a primary number, the banner will show on all the secondary numbers; however if I set the banner on the secondary number, it will not show on the primary.
Please help!
回答1:
Don't use a trigger for this. Most of the conditions that you've coded into the nested IFs (of your trigger) can probably be done via foreign key constraints and check constraints. Also, you do not need to store the 'X' for WOMAN_ACT anywhere, as it is a "derived value" ie you can obtain or generate it when querying your data. Maybe the following example (based on your original tables and data) will help you to find a solution. Please read the comments in the code.
DDL code
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
);
Test data
-- 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;
/
The following VIEW will pick up person_ids from the HISTORY_TRANSACTION tables, add and 'X' to every one of them, and also pick up all persons who are "associated" with (or: mapped to) these ids from CONSOLIDATED_NUMBERS, and also add an 'X' to their ids. (Side note: it seems that your PERSON table contains a recursive relationship, so one could write a recursive query. However, you will have a reason for modelling the CONSOLIDATED_NUMBERS table, so we will be using a JOIN here.)
VIEW
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 |
+---------+---------+
Now, let's remove/add some data, and run a few tests (see also: 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 |
+---------+---------+
Another test before we go into more details:
-- 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 the view to the person table
-- 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 |
+----+-------------------+--------------+---------+
The outer join is needed for PERSON_IDs that have no corresponding rows in the HISTORY_TRANSACTION table eg
-- 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
If - as stated in your comment - you must store a value in the WOMAN_ACT column (although it apparently is a "derived value"), you could write a package that contains procedures for all required DML operations - still without using a trigger. However, without knowing the full story it is hard to decide whether this would be the best way forward. The following example uses a small package containing procedures for setting WOMAN_ACT values of the PERSON table, and a trigger that fires after INSERTs/UPDATEs(table: HISTORY_TRANSACTIONS). DBfiddle here.
PERSON table
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
PACKAGE
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 ;
/
PACKAGE BODY
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 ;
/
TRIGGER
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 ;
/
TESTING: see DBfiddle
来源:https://stackoverflow.com/questions/63241108/oracle-updating-records-with-1-to-many-table-relationship-in-a-trigger