How to update 2 tables using triggers - oracle

I have 2 tables,
lv_data,
It has the following fields,
emp_name tot_days
guru 18
leave_data
it has the following fields,
emp_name From_date to_date no_of_days remaining_days
guru 02/05/2012 03/05/2012 2
In second table if the data is inserted, the no_of_days will be automatically calculated (from to_date - From_date)+1
Here I need to write the trigger to update the remaining_days column,
In first table for all emp_name, tot_days is 18 days, so in second table whenever the record is inserted, the remaining_days should be calculated like this
remaining_days := tot_days - no_of_days
And this(calculated) value should be updated in tot_days column in first table(lv_data),
Sample Example:
emp_name tot_days
guru 18
leave_data
emp_name From_date to_date no_of_days remaining_days
guru 02/05/2012 03/05/2012 2 16
Now the first table should be updated like,
emp_name tot_days
guru 16
So I need to update 2 tables. Can someone help me to update these 2 tables through trigger?

Have an before insert trigger on the table which will set the record before inserting it
Few things to note:
Your data model doesn't have a unique identifier for a row
I don't think your "no_of_days" calculation is right.
CREATE OR replace TRIGGER leave_data_before_insert
BEFORE INSERT ON LEAVE_DATA
FOR EACH ROW
DECLARE
CURSOR c_lv_data(
p_emp_id IN lv_data.id%TYPE) IS
SELECT tot_days
FROM lv_data
WHERE id = p_emp_id;
v_tot_days NUMBER;
BEGIN
OPEN c_lv_data(:new.id);
FETCH c_lv_data INTO v_tot_days;
:new.no_of_days := ( :new.from_date - :new.TO_DATE ) + 1;
:new.remaining_days := v_tot_days - :new.no_of_days;
UPDATE lv_data
SET tot_days = :new.remaining_days
WHERE id = :new.id;
CLOSE c_lv_data;
END;
DDL used to test:
CREATE TABLE lv_data
(
id NUMBER,
emp_name VARCHAR2(240),
tot_days NUMBER
);
CREATE TABLE leave_data
(
id NUMBER,
emp_name VARCHAR2(240),
from_date DATE,
to_date DATE,
no_of_days NUMBER,
remaining_days NUMBER
);
DML used to test:
INSERT INTO lv_data
VALUES (1,
'sathya',
18);
INSERT INTO LEAVE_DATA
VALUES ('1',
'sathya',
SYSDATE,
SYSDATE + 2,
NULL,
NULL);

Related

Need not to insert duplicate records into the main table instead want to update the existing record based on e_id

CREATE TABLE new_details_staging
(
e_id NUMBER(10),
e_name VARCHAR2(30),
portal_desc VARCHAR2(50),
risk_dec VARCHAR2(50),
CONSTRAINT pk_new_details_staging PRIMARY KEY (e_id)
);
INSERT INTO new_details_staging
VALUES (11, 'A', 'AA', 'High');
INSERT INTO new_details_staging
VALUES (22, 'B', 'BB', 'Low');
CREATE TABLE lookup_ref
(
ref_id NUMBER(10),
ref_typ VARCHAR2(30),
ref_typ_desc VARCHAR2(20),
CONSTRAINT pk_lookup_ref PRIMARY KEY (ref_id)
);
INSERT INTO lookup_ref
VALUES (181, 'portal', 'AA');
INSERT INTO lookup_ref
VALUES (182, 'portal', 'BB');
INSERT INTO lookup_ref
VALUES (183, 'risk', 'High');
INSERT INTO lookup_ref
VALUES (184, 'risk', 'Low');
CREATE TABLE new_details_main
(
e_id NUMBER(10),
e_name VARCHAR2(30),
portal NUMBER(20),
risk NUMBER(20),
CONSTRAINT pk_new_details_main PRIMARY KEY (e_id)
);
COMMIT;
Stored procedure
I want to insert records into the main table from the staging through this stored procedure.
create or replace procedure sp_main(ov_err_msg OUT varchar2)
is
begin
INSERT INTO new_details_main (e_id, e_name, portal, risk)
SELECT n.e_id
, n.e_name
, (
SELECT lp.ref_id
FROM lookup_ref lp -- lp is for Lookup-Portal
WHERE lp.ref_typ = 'portal'
AND lp.ref_typ_desc = n.portal_desc
),
(
SELECT lr.ref_id
FROM lookup_ref lr -- lr is for Lookup-Risk
WHERE lr.ref_typ = 'risk'
AND lr.ref_typ_desc = n.risk_dec
)
FROM new_details_staging n
;
end;
This procedure is giving me the exact results which are required but if data is added into the staging table for the same e_id then it should update the record in the main table and not insert the new record. Example,
If I truncate the staging table and inserted a new row in which risk was Low but this time it got updated to High
INSERT INTO new_details_staging
VALUES (22, 'B', 'BB', 'High');
So, in my main table, there should be 2 rows only but for e_id 22 it should have been updated to 183. How can I achieve this using the stored procedure?
I'd say you need MERGE.
Here's an example.
Procedure (I removed OUT parameter for simplicity; you didn't use it anyway). Also, I rewrote it so that it doesn't use subqueries for portal and risk values, but joined the same (lookup_ref) table twice (maybe you'll need to use outer join; data you have now suggest you don't, but - have that on mind):
SQL> CREATE OR REPLACE PROCEDURE sp_main
2 IS
3 BEGIN
4 MERGE INTO new_details_main m
5 USING (SELECT n.e_id,
6 n.e_name,
7 lp.ref_id portal,
8 lr.ref_id risk
9 FROM new_details_staging n
10 JOIN lookup_ref lp
11 ON lp.ref_typ_desc = n.portal_desc
12 AND lp.ref_typ = 'portal'
13 JOIN lookup_ref lr
14 ON lr.ref_typ_desc = n.risk_dec
15 AND lr.ref_typ = 'risk') x
16 ON (m.e_id = x.e_id)
17 WHEN MATCHED
18 THEN
19 UPDATE SET m.e_name = x.e_name, m.portal = x.portal, m.risk = x.risk
20 WHEN NOT MATCHED
21 THEN
22 INSERT (e_id,
23 e_name,
24 portal,
25 risk)
26 VALUES (x.e_id,
27 x.e_name,
28 x.portal,
29 x.risk);
30 END;
31 /
Procedure created.
Testing: run the procedure with "initial" set of data (the one you posted):
SQL> EXEC sp_main;
PL/SQL procedure successfully completed.
Result is then:
SQL> SELECT * FROM new_details_main;
E_ID E_NAME PORTAL RISK
---------- ---------- ---------- ----------
11 A 181 183
22 B 182 184
You said you'll then remove rows from the staging table and insert a new row:
SQL> DELETE FROM new_details_staging;
2 rows deleted.
SQL> INSERT INTO new_details_staging
2 VALUES (22, 'B', 'BB', 'High');
1 row created.
Let's run the procedure again an check new result:
SQL> EXEC sp_main;
PL/SQL procedure successfully completed.
SQL> SELECT * FROM new_details_main;
E_ID E_NAME PORTAL RISK
---------- ---------- ---------- ----------
11 A 181 183
22 B 182 183 --> RISK is modified to 183
SQL>

Insert statement with blank values without defining all columns

I have a need to insert 100+ rows of data into a table that has 25 text columns.
I only want to insert some data into those columns and the rest be represented by a white space.
(Note: Text fields on PeopleSoft tables are defined as NOT NULLABLE, with a single white space character used to indicate no data instead of null.)
Is there a way to write an insert statement that does not define all the columns along with the blank space. As an example:
INSERT INTO CUST.RECORD(BUSINESS_UNIT, PROJECT_ID, EFF_STATUS, TMPL, DESCR) VALUES('TOO1','PROJ1','A','USA00','USA00 CONTRACT');
For every other column in CUST.RECORD I'd like to insert ' ' without defining the column or the space in the insert.
One way is to set a Default value in table definition like this:
CREATE TABLE CUST.RECORD(
id NUMBER DEFAULT detail_seq.NEXTVAL,
master_id varchar2(10) DEFAULT ' ',
description VARCHAR2(30)
);
Edit: for your table you can use :
alter table CUST.RECORD modify( col2 varchar2(10) default ' ' );
You do not have to supply a value for a specific column IF either condition is true:
The column is defined as nullable. That is, it was NOT defined with the 'not null' clause.
or
The column is defined with a default value
SQL> create table my_test (my_id number not null,
2 fname varchar2(10), -- nullable
3 dob date default sysdate -- default value
4 )
5 ;
Table created.
SQL> --
SQL> -- only supplying value for my_id
SQL> insert into my_test(my_id) values (1);
1 row created.
SQL> --
SQL> -- and see the results
SQL> select *
2 from my_test;
MY_ID FNAME DOB
1 12-MAR-21
1 row selected.
SQL> --
SQL> select my_id,
2 nvl(fname,'NULL'),
3 dob
4 from my_test;
MY_ID NVL(FNAME, DOB
1 NULL 12-MAR-21
1 row selected.

How do i CHECK if a year is between 2000 and the current year?

I need to create a table for automobiles.
Each automobile has a year of manufacture. The year of manufacture must be between 2000 and the current year.
year_manufacture INTEGER CONSTRAINT nn_automobiles_year_manufacture NOT NULL
CONSTRAINT ck_automobiles_year_manufacture
CHECK ((year_manufacture>= 2000)AND(year_manufacture<= "current year")),
See if such a workaround helps.
Everything you've been already told stands. As you can't directly check the year against SYSDATE, create another column - THIS_YEAR - which won't be used anywhere in your code. It is automatically populated on each insert and gets current year.
Constraint (which can't be inline) then compares YEAR_MANUFACTURE with THIS_YEAR.
SQL> create table cars
2 (id number constraint pk_cars primary key,
3 name varchar2(20) not null,
4 this_year number(4) default extract (year from sysdate),
5 year_manufacture number(4),
6 --
7 constraint ch_year_manufacture check (year_manufacture between 2000 and this_year)
8 );
Table created.
SQL>
Testing:
SQL> -- OK - after 2000, before 2019 (which is the current year)
SQL> insert into cars (id, name, year_manufacture) values (1, 'BMW', 2005);
1 row created.
SQL> -- Wrong - before 2000
SQL> insert into cars (id, name, year_manufacture) values (2, 'Mercedes', 1998);
insert into cars (id, name, year_manufacture) values (2, 'Mercedes', 1998)
*
ERROR at line 1:
ORA-02290: check constraint (SCOTT.CH_YEAR_MANUFACTURE) violated
SQL> -- Wrong - after 2019 (which is the current year)
SQL> insert into cars (id, name, year_manufacture) values (3, 'Cooper', 2020);
insert into cars (id, name, year_manufacture) values (3, 'Cooper', 2020)
*
ERROR at line 1:
ORA-02290: check constraint (SCOTT.CH_YEAR_MANUFACTURE) violated
SQL> -- OK - current year
SQL> insert into cars (id, name, year_manufacture) values (4, 'Opel', 2019);
1 row created.
SQL>
SQL> select * from cars;
ID NAME THIS_YEAR YEAR_MANUFACTURE
---------- -------------------- ---------- ----------------
1 BMW 2019 2005
4 Opel 2019 2019
SQL>
That is not possible, according to the Restrictions on Check Constraints :
Conditions of check constraints cannot contain the following constructs:
[...]
Calls to the functions that are not deterministic (CURRENT_DATE, CURRENT_TIMESTAMP, DBTIMEZONE, LOCALTIMESTAMP, SESSIONTIMEZONE, SYSDATE, SYSTIMESTAMP, UID, USER, and USERENV)
It's not possible to create this with constraint. You could make a hack with virtual column but actually your requirement does not make much sense.
A value which is invalid today will be valid in five Months. However, a constraint must be valid (or invalid) at any time.
All you could do is to create a trigger which checks the year of manufacture at the time of insert or update. Would be like this one:
CREATE OR REPLACE TRIGGER check_manufacture_date
BEFORE UPDATE OR INSERT ON automobiles
FOR EACH ROW
BEGIN
IF :NEW.year_manufacture NOT BETWEEN 2000 AND EXTRACT(YEAR FROM SYSDATE) THEN
raise_application_error(20001, 'Manufacture must be between 2000 and current year');
END IF;
END;
You can not create such a CHECK constraint containig SYSDATE, but use a DML(Insert) trigger to check the condition ( considering year_manufacture is of type date ) :
Create or Replace Trigger trg_chk_man_date
Before Insert On tab -- replace with your table's name
For Each Row
Begin
if to_char(:new.year_manufacture,'YYYY') < 2000
or to_char(:new.year_manufacture,'YYYY') > to_char(sysdate,'YYYY') then
Raise_Application_Error(-20001,'The Wrong Manufacture Year !');
end if;
End;
If year_manufacture is numeric type column then just consider :new.year_manufacture < 2000 and extract(year from sysdate) < :new.year_manufacture as already #Wernfried Domscheit pointed out.

Number data type column is not behaving as number

I have a table in oracle which has column recid which is of number Datar type. The table is partition table and it has partition index on it.
When I query the partition like
select * from table partition (abc)
I am able to see value for rec id =50. But when I query
select * from table partition(abc) where rec id =50,
It doesn’t give any record .
If I do type casting as
select * from table partition(abc) where cast (recid as number ) =50
I am getting records.
Please let me know what might be the issue .?
The issue exist only for one partition and rest of the partition working normal.
If it's not behaving as a number, then it's not stored as a number.
Run a DESCRIBE (DESC) on your table in either SQL Developer, SQLcl, or SQL*Plus. It will show how the REC_ID column is defined.
If it's stored as a VARCHAR2, you wil get an error on your WHERE CLAUSE predicate for REC_ID, if not every REC_ID could be treated as also a number:
ORA-01722: invalid number
Like so:
SQL> DESC employees
Name Null? Type
EMPLOYEE_ID NOT NULL NUMBER(6)
FIRST_NAME VARCHAR2(20)
LAST_NAME NOT NULL VARCHAR2(25)
EMAIL NOT NULL VARCHAR2(25)
PHONE_NUMBER VARCHAR2(20)
HIRE_DATE NOT NULL DATE
JOB_ID NOT NULL VARCHAR2(10)
SALARY NUMBER(8,2)
COMMISSION_PCT NUMBER(2,2)
MANAGER_ID NUMBER(6)
DEPARTMENT_ID NUMBER(4)
SQL>
SQL> SELECT * FROM employees WHERE first_name = 50;
Error starting at line : 4 in command -
SELECT * FROM employees WHERE first_name = 50
Error report -
ORA-01722: invalid number
SQL> select * from emps_copy_num where first_name = 50;
EMPLOYEE_ID FIRST_NAME LAST_NAME EMAIL PHONE_NUMBER HIRE_DATE JOB_ID SALARY COMMISSION_PCT MANAGER_ID DEPARTMENT_ID
100 50 King SKING 515.123.4567 17-JUN-87 AD_PRES 24000 90
The first query fails - because not every value in that column can be simplicity cast as a number by the database.
The second query works, because I created a copy of the table where all of the first_name strings were values that COULD be cast as a number.
You probably have spaces in there somewhere, eg
SQL> create table t ( should_have_been_numeric varchar2(30));
Table created.
SQL>
SQL> insert into t values ('50 ');
1 row created.
SQL> insert into t values (' 50 ');
1 row created.
SQL> insert into t values (' 50');
1 row created.
SQL>
SQL> select * from t where should_have_been_numeric = '50';
no rows selected
SQL> select * from t where cast(should_have_been_numeric as number) = 50;
SHOULD_HAVE_BEEN_NUMERIC
------------------------------
50
50
50
3 rows selected.
but as already mentioned, if you are treating strings as numbers, then there is problems ahead in terms of spurious errors, not to mention potential performance issues because the optimizer also doesn't know that these are really numbers.

Oracle: how to drop a subpartition of a specific partition

I am using an oracle 11 table with interval partitioning and list subpartitioning like this (simplified):
CREATE TABLE LOG
(
ID NUMBER(15, 0) NOT NULL PRIMARY KEY
, MSG_TIME DATE NOT NULL
, MSG_NR VARCHAR2(16 BYTE)
) PARTITION BY RANGE (MSG_TIME) INTERVAL (NUMTOYMINTERVAL (1,'MONTH'))
SUBPARTITION BY LIST (MSG_NR)
SUBPARTITION TEMPLATE (
SUBPARTITION login VALUES ('FOO')
, SUBPARTITION others VALUES (DEFAULT)
)
(PARTITION oldvalues VALUES LESS THAN (TO_DATE('01-01-2010','DD-MM-YYYY')));
How do I drop a specific subpartitition for a specific month without knowing the (system generated) name of the subpartition? There is a syntax "alter table ... drop subpartition for (subpartition_key_value , ...)" but I don't see a way to specify the month for which I am deleting the subpartition. The partition administration guide does not give any examples, either. 8-}
You can use the metadata tables to get the specific subpartition name:
SQL> insert into log values (1, sysdate, 'FOO');
1 row(s) inserted.
SQL> SELECT p.partition_name, s.subpartition_name, p.high_value, s.high_value
2 FROM user_tab_partitions p
3 JOIN
4 user_tab_subpartitions s
5 ON s.table_name = p.table_name
6 AND s.partition_name = p.partition_name
7 AND p.table_name = 'LOG';
PARTITION_NAME SUBPARTITION_NAME HIGH_VALUE HIGH_VALUE
--------------- ------------------ ------------ ----------
OLDVALUES OLDVALUES_OTHERS 2010-01-01 DEFAULT
OLDVALUES OLDVALUES_LOGIN 2010-01-01 'FOO'
SYS_P469754 SYS_SUBP469753 2012-10-01 DEFAULT
SYS_P469754 SYS_SUBP469752 2012-10-01 'FOO'
SQL> alter table log drop subpartition SYS_SUBP469752;
Table altered.
If you want to drop a partition dynamically, it can be tricky to find it with the ALL_TAB_SUBPARTITIONS view because the HIGH_VALUE column may not be simple to query. In that case you could use DBMS_ROWID to find the subpartition object_id of a given row:
SQL> insert into log values (4, sysdate, 'FOO');
1 row(s) inserted.
SQL> DECLARE
2 l_rowid_in ROWID;
3 l_rowid_type NUMBER;
4 l_object_number NUMBER;
5 l_relative_fno NUMBER;
6 l_block_number NUMBER;
7 l_row_number NUMBER;
8 BEGIN
9 SELECT rowid INTO l_rowid_in FROM log WHERE id = 4;
10 dbms_rowid.rowid_info(rowid_in =>l_rowid_in ,
11 rowid_type =>l_rowid_type ,
12 object_number =>l_object_number,
13 relative_fno =>l_relative_fno ,
14 block_number =>l_block_number ,
15 row_number =>l_row_number );
16 dbms_output.put_line('object_number ='||l_object_number);
17 END;
18 /
object_number =15838049
SQL> select object_name, subobject_name, object_type
2 from all_objects where object_id = '15838049';
OBJECT_NAME SUBOBJECT_NAME OBJECT_TYPE
--------------- --------------- ------------------
LOG SYS_SUBP469757 TABLE SUBPARTITION
As it turns out, the "subpartition for" syntax does indeed work, though that seems to be a secret Oracle does not want to tell you about. :-)
ALTER TABLE TB_LOG_MESSAGE DROP SUBPARTITION FOR
(TO_DATE('01.02.2010','DD.MM.YYYY'), 'FOO')
This deletes the subpartition that would contain MSG_TIME 2010/02/01 and MSG_NR FOO. (It is not necessary that there is an actual row with this exact MSG_TIME and MSG_NR. It throws an error if there is no such subpartition, though.)
Thanks for the post - it was very useful for me.
One observation though on the above script to identify the partition and delete it:
The object_id returned by dbms_rowid.rowid_info is not the object_id of the all_objects table. It is actually the data_object_id. It is observed that usually these ids match. However, after truncating the partitioned table several times, these ids diverged in my database. Hence it might be reasonable to instead use the data_object_id to find out the name of the partition:
select object_name, subobject_name, object_type
from all_objects where data_object_id = '15838049';
From the table description of ALL_OBJECTS:
OBJECT_ID Object number of the object
DATA_OBJECT_ID Object number of the segment which contains the object
http://docs.oracle.com/cd/B19306_01/appdev.102/b14258/d_rowid.htm
In the sample code provided in the above link, DBMS_ROWID.ROWID_OBJECT(row_id) is used instead to derive the same information that is given by dbms_rowid.rowid_info. However, the documentation around this sample mentions that it is a data object number from the ROWID.
Examples
This example returns the ROWID for a row in the EMP table, extracts
the data object number from the ROWID, using the ROWID_OBJECT function
in the DBMS_ROWID package, then displays the object number:
DECLARE object_no INTEGER; row_id ROWID; ... BEGIN
SELECT ROWID INTO row_id FROM emp
WHERE empno = 7499; object_no := DBMS_ROWID.ROWID_OBJECT(row_id); DBMS_OUTPUT.PUT_LINE('The obj. # is
'|| object_no); ...

Resources