Implement an update algorithm for Data Vault in PL / SQL - plsql

I'm currently working on modeling a data warehouse using Data Vault modeling.
My problem is the following: I want to define for each satellite the last state according to a business key.
Here are the elements and the algorithm that I want to put in place:
I have two tables.
The first table (SAT) contains a history of events whose primary key is composed of a business key and a date of update of this business key. This table is powered in real time.
The second table (LAST_SAT) contains the last state of each event whose primary key consists only of a business key. The update date is the maximum update date of the first table. This table is fed once a day.
Here is the algorithm that I want implemented in PL / SQL:
For each satellite pair (SAT), and last state satellite (LAST_SAT):
Identify the last update date of LAST SAT (if the return is zero because the table is empty, then value 01/01/1900): MAX_DATE
Select the data in the SAT table updated after MAX_DATE.
Compare selected data (2) with LAST_SAT data to update LAST_SAT with new and updated data.
3.a. Delete data from LAST_SAT present in SAT (according to business key)
3.b. Insert the selected data (2) in LAST_SAT in an orderly fashion.
Here is the program I started writing in PL / SQL, but my level is too low to achieve this.
/* Formatted on 29/07/2019 15:25:07 (QP5 v5.185.11230.41888) */
DECLARE
v_maxVal VARCHAR2 (200);
requete VARCHAR2 (200);
TYPE TabCur IS REF CURSOR;
v_tab_cursor TabCur;
BEGIN
FOR sat IN (SELECT table_name, column_name
FROM USER_TAB_COLUMNS
WHERE column_name LIKE '%UPDATE%' AND table_name = 'S_SALE'
)
LOOP
EXECUTE IMMEDIATE 'SELECT MAX(' ||sat.column_name || ') FROM ' || 'LS_'|| substr(sat.table_name,1,29) INTO v_maxVal;
requete := 'SELECT * FROM '|| sat.table_name || ' WHERE '|| sat.column_name || ' >= ''' || v_maxVal || '''';
OPEN v_tab_cursor FOR requete;
LOOP
FETCH v_tab_cursor INTO rec;
EXIT WHEN v_tab_cursor%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(rec.CONT_CONT_CONTRAT_REF);
END LOOP;
CLOSE v_tab_cursor;
--DBMS_OUTPUT.put_line (sat.table_name || ' -> ' || sat.column_name || ' : ' || v_maxVal);
--DBMS_OUTPUT.put_line (sat.table_name || ' : ' || v_maxVal);
END LOOP;
END;
Can you help me in my goal?
Thank you in advance,
Guillaume.

Related

Oracle trigger execution on a particular condition

My requirement is that I have a table where there are 4 columns(val1,val1_date,new_val1,new_val1_date) in it.
The issue is that when new_val1 gets updated along with new_val1_date,I need to update columns val1,val1_date accordingly.
For that I have created a function.
Now when new_val1_date is equal to sysdate then I want to execute the trigger.
My trigger is not giving me proper result.
Can anyone help me in that.
create or replace TRIGGER MY_Trigger
AFTER INSERT OR UPDATE ON my_table
FOR EACH ROW
WHEN (TO_DATE(NEW.new_val1_date,'dd/mon/yyyy')<=TO_DATE(sysdate,'dd/mon/yyyy'))
declare
V_CD_ERROR NUMBER;
V_DS_ERROR VARCHAR2(500);
begin
My_UpdateProc(V_CD_ERROR,V_DS_ERROR);
DBMS_OUTPUT.PUT_LINE('V_CD_ERROR: ' || V_CD_ERROR || ' V_DS_ERROR ' || V_DS_ERROR);
end;
Oracle Date columns store the timestamp, so unless your inserted value matches sysdate down to the second, it won't have the expected result.
You need to "trunc" the dates so the timestamp is set to midnight for both values. Then it will evaluate based on the date.
Also, the evaluation should be done inside the begin/end block of the trigger.
create or replace TRIGGER MY_Trigger
AFTER INSERT OR UPDATE ON my_table
FOR EACH ROW
declare
V_CD_ERROR NUMBER;
V_DS_ERROR VARCHAR2(500);
begin
if (trunc(NEW.new_val1_date) = trunc(sysdate)) then
My_UpdateProc(V_CD_ERROR,V_DS_ERROR);
DBMS_OUTPUT.PUT_LINE('V_CD_ERROR: ' || V_CD_ERROR || ' V_DS_ERROR ' || V_DS_ERROR);
end if;
end;

I'm having trouble writing a cursor/loop. I'm not sure why it's not working

My code is here. I keep getting errors for the select statement. I've already created a waitlist table and a sequence. I inserted values into waitlist, however; I had to get the sname from the students table since waitlist does not have sname in the table. I'm trying to display RankingSid, Snum, sname, time(such as 1 pm or 2 pm).
create or replace procedure getWaiting(
p_callnum waitlist.callnum%type) as
cursor cwaiting is
select RankingSid, waitlist.snum, students.sname, to_char(time, 'hh AM')time
from waitlist, students
where p_callnum=callnum
and waitlist.snum=students.snum;
begin
For EachStudent in cwaiting loop
insert into TestTable values (EachStudent.RankingSid, Eachstudent.snum, EachStudent.sname, EachStudent.time);
dbms_output.put_line(eachstudent.rankingsid || eachstudent.snum || eachstudent.sname, eachstudent.time);
end loop;
end;
/
There are a number of syntax errors in your code, the biggest being your insert statement you need to reference your cursor variable for the insert in the same way that you did for the dbms_output. I do not have your table definitions to test the code but the below should be pretty close to what you need.
create or replace procedure getWaiting(
p_callnum waitlist.callnum%type) as
cursor cwaiting is
select RankingSid, waitlist.snum, students.sname, to_char(time, 'hh AM')time
from waitlist, students
where p_callnum=callnum
and waitlist.snum=students.snum;
begin
For EachStudent in cwaiting loop
insert into TestTable values (EachStudent.RankingSid, Eachstudent.snum, EachStudent.sname, EachStudent.time);
dbms_output.put_line(eachstudent.rankingsid || eachstudent.snum || eachstudent.sname || eachstudent.time);
end loop;
end;
/
I did it another way and would like to know how to change it..insert table wasn't working the other way. I was wondering if i'd have to create instead of inserting
create or replace procedure getWaiting(
p_callnum waitlist.callnum%type) as
begin
For eachRec in (select waitlist.rankingsid, students.snum, students.sname, to_char(time, 'hh:mi:ss AM') RequestedTime
from students, waitlist
where p_callnum=callnum
and students.snum=waitlist.snum
order by rankingsid)
Loop
dbms_output.put_line('Rank Number '|| eachRec.rankingsid ||' Student Name ' ||eachRec.sname ||' Student Number ' || eachRec.snum ||' Wait List Date ' || eachRec.RequestedTime);
end Loop;
end;
/

pl/sql displaying all columns of a table

employee table schema
employee(id, name, company, salary);
procedure created to display all the column values
create or replace procedure p1
IS
BEGIN
select * from employee;
END;
/
exe p1;
However, this does not display the data.
Your PL/SQL block is not valid, and won't even run. You need to either return the data back to the client, or if you're using SQL*Plus use dbms_output.put_line to print the query resultset.
create or replace procedure p1
IS
BEGIN
DBMS_OUTPUT.ENABLE;
FOR emp_rec in select * from employee LOOP
dbms_output.put_line('EMployee id: || emp_rec.emp_id || ' Name: ' || emp_rec ename);
END LOOP
END;
/
Change the column name suitably

PL/SQL Trigger gets a mutating table error

My trigger wants to check if a 'new' manager supervises no more than 5 employees.
Manager supervising only 5 people are in BLOCKED_MANAGER table(ssn,numberofemployees).
Finally, every update is recorded in SUPERLOG table(date,user,old_manager,new_manager).
I get no compiling error about the trigger, but when I update a superssn I get this error:
SQL> update employee set superssn='666666607' where ssn='111111100';
update employee set superssn='666666607' where ssn='111111100'
*
ERROR at line 1:
ORA-04091: Table FRANK.EMPLOYEE is mutating, the trigger/function
can't read it
ORA-06512: a "FRANK.TLOG", line 20
ORA-04088: error during execution of trigger 'FRANK.TLOG'
How can I solve this trigger? Thank you
create or replace trigger tlog
before update of superssn on employee
for each row
declare
t1 exception;
n number:=0;
cont number:=0;
empl varchar2(16);
cursor cur is (select ssn from blocked_manager where ssn is not null);
begin
open cur;
loop
fetch cur into empl;
exit when cur%notfound;
if(:new.superssn = empl) then
n:=1;
end if;
end loop;
close cur;
if n=1 then
raise t1;
end if;
select count(*) into cont from employee group by superssn having superssn=:new.superssn;
if(cont=4) then
insert into blocked_manager values(:new.superssn,5);
end if;
insert into superlog values(sysdate,user,:old.superssn, :new.superssn );
exception
when t1 then
raise_application_error(-20003,'Manager '||:new.superssn||' has already 5 employees');
end;
Probably the quickest way around this is to use a carefully constructed statement trigger instead of a row trigger. Row triggers have the phrase FOR EACH ROW in them, are invoked for each row which is modified (based on the BEFORE/AFTER INSERT, BEFORE/AFTER UPDATE, and BEFORE/AFTER DELETE constraints on the trigger), can see the appropriate :NEW and :OLD values, and are subject to the "can't look at the table on which the trigger is defined" rule. Statement triggers are invoked at the appropriate time for each statement which is executed, can't see row values, but aren't subject to the limits on looking at the particular table on which they're defined. So for the portions of your logic which don't need to work with :NEW or :OLD values a trigger such as this might prove useful:
CREATE OR REPLACE TRIGGER EMPLOYEE_S_BU
BEFORE UPDATE ON EMPLOYEE
-- Note: no BEFORE EACH ROW phrase, so this is a statement trigger
BEGIN
-- The following FOR loop should insert rows into BLOCKED_MANAGER for all
-- supervisors which have four or more employees under them and who are not
-- already in BLOCKED_MANAGER.
FOR aRow IN (SELECT e.SUPERSSN, COUNT(e.SUPERSSN) AS EMP_COUNT
FROM EMPLOYEE e
LEFT OUTER JOIN BLOCKED_MANAGER b
ON b.SSN = e.SUPERSSN
WHERE b.SSN IS NULL
GROUP BY e.SUPERSSN
HAVING COUNT(e.SUPERSSN) >= 4)
LOOP
INSERT INTO BLOCKED_MANAGER
(SSN, EMPLOYEE_COUNT)
VALUES
(aRow.SUPERSSN, aRow.EMP_COUNT);
END LOOP;
-- Remove rows from BLOCKED_MANAGER for managers who supervise fewer
-- than four employees.
FOR aRow IN (SELECT e.SUPERSSN, COUNT(e.SUPERSSN) AS EMP_COUNT
FROM EMPLOYEE e
INNER JOIN BLOCKED_MANAGER b
ON b.SSN = e.SUPERSSN
GROUP BY e.SUPERSSN
HAVING COUNT(e.SUPERSSN) <= 3)
LOOP
DELETE FROM BLOCKED_MANAGER
WHERE SSN = aRow.SUPERSSN;
END LOOP;
-- Finally, if any supervisor has five or more employees under them,
-- raise an exception. Note that we go directly to EMPLOYEE to determine
-- the number of employees supervised.
FOR aRow IN (SELECT SUPERSSN, COUNT(*) AS EMP_COUNT
FROM EMPLOYEE
GROUP BY SUPERSSN
HAVING COUNT(*) >= 5)
LOOP
-- If we get here we've found a supervisor with 5 (or more) employees.
-- Raise an exception
RAISE_APPLICATION_ERROR(-20000, 'Found supervisor ' || aRow.SUPERSSN ||
' supervising ' || aRow.EMP_COUNT ||
' employees');
END LOOP;
END EMPLOYEE_S_BU;
Note that if you get rid of the BLOCKED_MANAGER table (which this trigger still maintains, although I don't know if it's truly necessary) the logic gets cut down considerably.
You'll still need a row trigger to handle the logging, but as that's just a matter of cutting down your existing trigger I'll leave that to you. :-)
Share and enjoy.
As you have discovered, you cannot select from the same table that a row-level trigger is defined against; it causes a table mutating exception.
In order to properly create this validation using a trigger a procedure should be created to obtain user-specified locks so the validation can be correctly serialized in a multi-user environment.
PROCEDURE request_lock
(p_lockname IN VARCHAR2
,p_lockmode IN INTEGER DEFAULT dbms_lock.x_mode
,p_timeout IN INTEGER DEFAULT 60
,p_release_on_commit IN BOOLEAN DEFAULT TRUE
,p_expiration_secs IN INTEGER DEFAULT 600)
IS
-- dbms_lock.allocate_unique issues implicit commit, so place in its own
-- transaction so it does not affect the caller
PRAGMA AUTONOMOUS_TRANSACTION;
l_lockhandle VARCHAR2(128);
l_return NUMBER;
BEGIN
dbms_lock.allocate_unique
(lockname => p_lockname
,lockhandle => p_lockhandle
,expiration_secs => p_expiration_secs);
l_return := dbms_lock.request
(lockhandle => l_lockhandle
,lockmode => p_lockmode
,timeout => p_timeout
,release_on_commit => p_release_on_commit);
IF (l_return not in (0,4)) THEN
raise_application_error(-20001, 'dbms_lock.request Return Value ' || l_return);
END IF;
-- Must COMMIT an autonomous transaction
COMMIT;
END request_lock;
This procedure can then be used in a compound trigger (assuming at least Oracle 11, this will need to be split into individual triggers in earlier versions)
CREATE OR REPLACE TRIGGER too_many_employees
FOR INSERT OR UPDATE ON employee
COMPOUND TRIGGER
-- Table to hold identifiers of inserted/updated employee supervisors
g_superssns sys.odcivarchar2list;
BEFORE STATEMENT
IS
BEGIN
-- Reset the internal employee supervisor table
g_superssns := sys.odcivarchar2list();
END BEFORE STATEMENT;
AFTER EACH ROW
IS
BEGIN
-- Store the inserted/updated supervisors of employees
IF ( ( INSERTING
AND :new.superssn IS NOT NULL)
OR ( UPDATING
AND ( :new.superssn <> :old.superssn
OR :new.superssn IS NOT NULL AND :old.superssn IS NULL) ) )
THEN
g_superssns.EXTEND;
g_superssns(g_superssns.LAST) := :new.superssn;
END IF;
END AFTER EACH ROW;
AFTER STATEMENT
IS
CURSOR csr_supervisors
IS
SELECT DISTINCT
sup.column_value superssn
FROM TABLE(g_superssns) sup
ORDER BY sup.column_value;
CURSOR csr_constraint_violations
(p_superssn employee.superssn%TYPE)
IS
SELECT count(*) employees
FROM employees
WHERE pch.superssn = p_superssn
HAVING count(*) > 5;
r_constraint_violation csr_constraint_violations%ROWTYPE;
BEGIN
-- Check if for any inserted/updated employee there exists more than
-- 5 employees for the same supervisor. Serialise the constraint for each
-- superssn so concurrent transactions do not affect each other
FOR r_supervisor IN csr_supervisors LOOP
request_lock('TOO_MANY_EMPLOYEES_' || r_supervisor.superssn);
OPEN csr_constraint_violations(r_supervisor.superssn);
FETCH csr_constraint_violations INTO r_constraint_violation;
IF csr_constraint_violations%FOUND THEN
CLOSE csr_constraint_violations;
raise_application_error(-20001, 'Supervisor ' || r_supervisor.superssn || ' now has ' || r_constraint_violation.employees || ' employees');
ELSE
CLOSE csr_constraint_violations;
END IF;
END LOOP;
END AFTER STATEMENT;
END;
You do not need the blocked_manager table to manage this constraint. This information can be derived from the employee table.
Or in versions earlier than Oracle 11i:
CREATE OR REPLACE PACKAGE employees_trg
AS
-- Table to hold identifiers of inserted/updated employee supervisors
g_superssns sys.odcivarchar2list;
END employees_trg;
CREATE OR REPLACE TRIGGER employee_biu
BEFORE INSERT OR UPDATE ON employee
IS
BEGIN
-- Reset the internal employee supervisor table
employees_trg.g_superssns := sys.odcivarchar2list();
END;
CREATE OR REPLACE TRIGGER employee_aiur
AFTER INSERT OR UPDATE ON employee
FOR EACH ROW
IS
BEGIN
-- Store the inserted/updated supervisors of employees
IF ( ( INSERTING
AND :new.superssn IS NOT NULL)
OR ( UPDATING
AND ( :new.superssn <> :old.superssn
OR :new.superssn IS NOT NULL AND :old.superssn IS NULL) ) )
THEN
employees_trg.g_superssns.EXTEND;
employees_trg.g_superssns(employees_trg.g_superssns.LAST) := :new.superssn;
END IF;
END;
CREATE OR REPLACE TRIGGER employee_aiu
AFTER INSERT OR UPDATE ON employee
IS
DECLARE
CURSOR csr_supervisors
IS
SELECT DISTINCT
sup.column_value superssn
FROM TABLE(employees_trg.g_superssns) sup
ORDER BY sup.column_value;
CURSOR csr_constraint_violations
(p_superssn employee.superssn%TYPE)
IS
SELECT count(*) employees
FROM employees
WHERE pch.superssn = p_superssn
HAVING count(*) > 5;
r_constraint_violation csr_constraint_violations%ROWTYPE;
BEGIN
-- Check if for any inserted/updated employee there exists more than
-- 5 employees for the same supervisor. Serialise the constraint for each
-- superssn so concurrent transactions do not affect each other
FOR r_supervisor IN csr_supervisors LOOP
request_lock('TOO_MANY_EMPLOYEES_' || r_supervisor.superssn);
OPEN csr_constraint_violations(r_supervisor.superssn);
FETCH csr_constraint_violations INTO r_constraint_violation;
IF csr_constraint_violations%FOUND THEN
CLOSE csr_constraint_violations;
raise_application_error(-20001, 'Supervisor ' || r_supervisor.superssn || ' now has ' || r_constraint_violation.employees || ' employees');
ELSE
CLOSE csr_constraint_violations;
END IF;
END LOOP;
END;

Duplicate INSERT record procedure

I have problem with mu procedure which insert duplicate last record in table
example when I put INSERT..... 'AAA' I got to rows in table 'AAA' and 'AAA'
In place when I put DBMS()... in code I got tow records
I use trigger and sequence for column ID in HistoriaDismissDate but they are in good condition. I check if I dropped trigger and sequence and its the same situation
I also use viewDate but this view get mi ONE record not two
my code
CREATE OR REPLACE PROCEDURE ChangeDismissDate
IS
v_id VARCHAR2(11);
v_dateBhd DATE := TO_DATE('20491231','yyyymmdd');
v_dateDismiss DATE := TO_DATE('20491231','yyyymmdd');
v_login VARCHAR2(50);
last_id NUMBER :=0;
CURSOR cur IS
select EMP_NO, LOGIN, ODEJSCIE_BHD, ODEJSCIE_OLD FROM viewDate;
BEGIN
OPEN cur;
LOOP
FETCH cur INTO v_id,v_login,v_dateBhd,v_dateDismiss;
DBMS_OUTPUT.put_line(v_id || ' ' || v_login || ' ' || v_dateBhd || ' ' || v_dateDismiss);
UPDATE employee_tab SET DISMISS_DATE = v_dateBhd WHERE EMP_NO = v_id;
COMMIT;
INSERT INTO HistoriaDismissDate(CUSTOMER_ID,LOGIN, DATE_CHANGE, DATE_BHD, DATE_DISMISS)
VALUES(v_id,v_login, sysdate, v_dateBhd, v_dateDismiss);
COMMIT;
EXIT WHEN cur%NOTFOUND;
END LOOP;
CLOSE cur;
EXCEPTION
WHEN NO_DATA_FOUND
THEN
DBMS_OUTPUT.put_line (DBMS_UTILITY.format_error_stack);
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line (DBMS_UTILITY.format_error_stack);
END;
/
2 tips on your original code:
1) cursor is a very old programming technic on PL/SQL. Prefer to use FOR ... LOOP construction. It's cleaner and less error-prone! See how it works:
CREATE OR REPLACE PROCEDURE ChangeDismissDate IS
BEGIN
for cur in (select EMP_NO, LOGIN, ODEJSCIE_BHD, ODEJSCIE_OLD FROM viewDate) loop
DBMS_OUTPUT.put_line(cur.EMP_NO || ' ' || cur.login || ' ' || cur.ODEJSCIE_BHD || ' ' || cur.ODEJSCIE_OLD);
UPDATE employee_tab
SET DISMISS_DATE = cur.ODEJSCIE_BHD
WHERE EMP_NO = cur.EMP_NO;
INSERT INTO HistoriaDismissDate
( CUSTOMER_ID,LOGIN, DATE_CHANGE, DATE_BHD, DATE_DISMISS )
VALUES
( cur.EMP_NO, cur.LOGIN, sysdate, cur.ODEJSCIE_BHD, cur.ODEJSCIE_OLD, );
end loop;
end;
/
2) Never, I mean never put a commit inside your procedure. The commit should be done on the caller block or on your client-side app. When you put a commit inside your procedure, you miss the chance to rollback after running it and other procedures could not call it if they want to control the transaction flow.

Resources