Need to populate table with Foreign and Primary key - oracle

I need to create three tables from all_tab_col system table such that schema details are in one schema_detail table, table details are in table_detail table and column details are in col_table. These three tables are to be populated simultaneously through a stored procedure, with PK(generated using SEQUENCE) in schema_detail is FK in table_detail table and PK(generated using SEQUENCE) in table_detail is FK in col_detail table.

SQL is a set based language, so I would be tempted to solve your task with three set bases steps.
Some mock up tables (just add columns for the details you are interested in):
CREATE TABLE schema_detail (
schema_id NUMBER GENERATED ALWAYS AS IDENTITY NOT NULL,
schema_name VARCHAR2(128 BYTE) NOT NULL,
CONSTRAINT schema_detail_pk PRIMARY KEY (schema_id)
);
CREATE TABLE table_detail (
schema_id NUMBER,
table_id NUMBER GENERATED ALWAYS AS IDENTITY NOT NULL,
table_name VARCHAR2(128 BYTE) NOT NULL,
CONSTRAINT table_detail_pk PRIMARY KEY (table_id),
CONSTRAINT table_detail_fk FOREIGN KEY (schema_id)
REFERENCES schema_detail(schema_id)
ON DELETE CASCADE
);
CREATE INDEX table_detail_schema_idx ON table_detail(schema_id);
CREATE INDEX table_detail_name_idx ON table_detail(table_name);
CREATE TABLE col_detail (
table_id NUMBER,
col_id NUMBER GENERATED ALWAYS AS IDENTITY NOT NULL,
col_name VARCHAR2(128 BYTE) NOT NULL,
CONSTRAINT col_detail_pk PRIMARY KEY (col_id),
CONSTRAINT col_detail_fk FOREIGN KEY (table_id)
REFERENCES table_detail(table_id)
ON DELETE CASCADE
);
CREATE INDEX col_detail ON col_detail(table_id);
I'd fill the table schema_detail first. PK is generated automatically:
INSERT INTO schema_detail(schema_name)
SELECT DISTINCT c.owner FROM all_tab_columns c ORDER BY owner;
SCHEMA_ID SCHEMA_NAME
1 APPQOSSYS
2 AUDSYS
3 CTXSYS
...
Next, I'd fill the tables. The schema_id needs to be looked up the the schema_detail table. Again, we let the PKs be generated automatically:
INSERT INTO table_detail(schema_id, table_name)
SELECT DISTINCT s.schema_id, c.table_name
FROM all_tab_columns c
JOIN schema_detail s ON c.owner = s.schema_name
ORDER BY table_name;
SCHEMA_ID TABLE_ID TABLE_NAME
1 8403 WLM_CLASSIFIER_PLAN
1 8404 WLM_FEATURE_USAGE
1 8405 WLM_METRICS_STREAM
...
And last, I'd fill the columns:
INSERT INTO col_detail(table_id, col_name)
SELECT DISTINCT t.table_id, c.column_name
FROM all_tab_columns c
JOIN table_detail t ON c.table_name = t.table_name
JOIN schema_detail s ON c.owner = s.schema_name
ORDER BY s.schema_id, t.table_id, c.column_name;
Does this solve your question or do you need a PL/SQL procedure?

In case you insist on a PL/SQL stored procedure, I would code it along the lines of:
CREATE OR REPLACE PROCEDURE myproc IS
schema_id schema_detail.schema_id%type;
table_id table_detail.table_id%type;
FUNCTION lookup_insert_schema (p_schema_name VARCHAR2)
RETURN NUMBER
IS
sid schema_detail.schema_id%type;
BEGIN
BEGIN
SELECT schema_id INTO sid
FROM schema_detail
WHERE schema_name = p_schema_name;
EXCEPTION WHEN NO_DATA_FOUND THEN
INSERT INTO schema_detail (schema_name)
VALUES (p_schema_name)
RETURNING schema_id INTO sid;
END;
RETURN sid;
END lookup_insert_schema;
-- lookup p_table_name in table table_detail, if not found, insert it
FUNCTION lookup_insert_table (p_schema_id NUMBER, p_table_name VARCHAR)
RETURN NUMBER
IS
tid table_detail.table_id%type;
BEGIN
BEGIN
SELECT table_id INTO tid
FROM table_detail
WHERE schema_id = p_schema_id
AND table_name = p_table_name;
EXCEPTION WHEN NO_DATA_FOUND THEN
INSERT INTO table_detail (schema_id, table_name)
VALUES (p_schema_id, p_table_name)
RETURNING table_id INTO tid;
END;
RETURN tid;
END lookup_insert_table;
BEGIN
FOR r IN (SELECT * FROM all_tab_columns)
LOOP
schema_id := lookup_insert_schema(r.owner);
table_id := lookup_insert_table(schema_id, r.table_name);
INSERT INTO col_detail (table_id, col_name)
VALUES (table_id, r.column_name);
END LOOP;
END myproc;
/

Related

Trigger on UPDATE OF a particular column Oracle

CREATE TABLE regions (
region_id number NOT NULL,
region_name varchar2(25) NULL,
CONSTRAINT reg_id_pk PRIMARY KEY (region_id)
);
CREATE TABLE tbl_history
(
operation character varying(120) NOT NULL ,
table_name character varying(120) NOT NULL ,
column_name character varying(120) NOT NULL,
old_value character varying(120),
new_value character varying(120)
);
CREATE OR REPLACE trigger regions_audit
BEFORE INSERT OR DELETE OR UPDATE ON regions
FOR EACH ROW
ENABLE
DECLARE
BEGIN
for c in (SELECT table_name, column_name from user_tab_columns ) loop
if updating(c.column_name ) then
INSERT INTO tbl_history (operation,table_name, column_name, old_value, new_value)
VALUES('Update',c.table_name,c.column_name,:old.column_name,:new.column_name);
end if;
end loop;
END;
/
Trigger on UPDATE OF a Dynamic column?
result is:
operation table_name column_name old_value new_value_
UPDATE regions region_name Asia Europe
UPDATE regions region_id 5 4
That's in vain, you can't create "generic" trigger which would work on any table. In Oracle, triggers are anchored to their tables, so code which shows how to do it might be
SQL> create or replace trigger regions_audit
2 before insert or delete or update on regions
3 for each row
4 enable
5 declare
6 begin
7 if updating then
8 insert into tbl_history
9 (operation, table_name, column_name, old_value, new_value)
10 values
11 ('Update', 'regions', 'region_name', :old.region_name,:new.region_name);
12 end if;
13 end;
14 /
Trigger created.
SQL>
In other words: table and column name are static; table name is regions as that's the table that trigger is based on. Column name is region_name as you really shouldn't be modifying primary key column's value. Old and new values are then also related to region_name.
As of other operations (deleting, inserting), you'd do similarly.

how to validate employee age that it must me greater than 18 before inserting record(tiried using check constraint while creating table but not work)

--dept table
create table department(
dept_id number(5) ,
dept_name varchar2(100),
dept_city varchar2(100) ,
dept_country varchar2(100),
CONSTRAINT dept_pk PRIMARY KEY(dept_id)
);
insert into department( dept_id, dept_name, dept_city, dept_country )values(1,'hr','hyderabad','india');
insert into department( dept_id, dept_name, dept_city, dept_country )values(2,'marketing','banglore','india');
insert into department(dept_id, dept_name, dept_city, dept_country)values(3,'sales','dhaka','bangladesh');
create sequence s1
start with 1
increment by 1;
create table employee(
employee_id number(10) ,
employee_name varchar2(100) NOT NULL,
employee_age number(3) ,
employee_sal number(9,2),
dept_id number(5),
CONSTRAINT employee_pk PRIMARY KEY(employee_id),
constraint dept_fk foreign key(dept_id) references department(dept_id)
);
CREATE OR REPLACE TRIGGER trg_before_emp_insr
BEFORE INSERT
on employee_details
FOR EACH ROW
DECLARE
emp_age number;
BEGIN
IF (employee_age < 18) THEN
RAISE_APPLICATION_ERROR(-20000,'Employee age must be greater than or equal to 18.');
END IF;
END;
/
insert into employee(employee_id, employee_name, employee_age, employee_sal,dept_id )values(s1.nextval,'ravi',45,7333,1);
insert into employee(employee_id, employee_name, employee_age, employee_sal,dept_id )values(s1.nextval,'sai',74,4451,2);
insert into employee(employee_id, employee_name, employee_age, employee_sal,dept_id )values(s1.nextval,'chandu',35,9428,3);
insert into employee( employee_id,employee_name, employee_age, employee_sal,dept_id )values(s1.nextval,'raju',7,25422,2);
insert into employee( employee_id,employee_name, employee_age, employee_sal,dept_id )values(s1.nextval,'teja',36,7955,1);
select * from employee
You want to use the :NEW record to get the value from the row being inserted (and to use the EMPLOYEE table rather than EMPLOYEE_DETAILS):
CREATE OR REPLACE TRIGGER trg_before_emp_insr
BEFORE INSERT
on employee
FOR EACH ROW
BEGIN
IF (:NEW.employee_age < 18) THEN
RAISE_APPLICATION_ERROR(-20000,'Employee age must be greater than or equal to 18.');
END IF;
END;
/
db<>fiddle here
However, you should consider storing date of birth rather than age as tomorrow (or definitely next year) the age value will be outdated but storing the date of birth and calculating the age would not.
create table employee(
employee_id number(10) ,
employee_name varchar2(100) NOT NULL,
employee_dob DATE,
employee_sal number(9,2),
dept_id number(5),
CONSTRAINT employee_pk PRIMARY KEY(employee_id),
constraint dept_fk foreign key(dept_id) references department(dept_id)
);
CREATE OR REPLACE TRIGGER trg_before_emp_insr
BEFORE INSERT
on employee
FOR EACH ROW
BEGIN
IF :NEW.employee_dob > TRUNC(ADD_MONTHS(SYSDATE, -18*12)) THEN
RAISE_APPLICATION_ERROR(-20000,'Employee age must be greater than or equal to 18.');
END IF;
END;
/
db<>fiddle here

SQL Developer will not insert data

create or replace PROCEDURE ADD_TO_BLACKLIST(
P_EMPLOYEE_USERNAME IN VARCHAR2,
T_CURSOR OUT SYS_REFCURSOR
)
AS
BEGIN
DECLARE
E_COUNT PLS_INTEGER := 0;
BEGIN
SELECT COUNT(*) INTO E_COUNT FROM EXAMPLE_TABLE
WHERE UPPER(EMPLOYEE_USERNAME) LIKE UPPER(P_EMPLOYEE_USERNAME)||'%';
IF E_COUNT = 0 THEN
INSERT INTO EXAMPLE_TABLE
(employee_number, employee_username)
SELECT EMPLOYEE_NUMBER, EMAIL FROM EXAMPLE_VIEW
WHERE UPPER(EMAIL)=CONCAT(UPPER(P_EMPLOYEE_USERNAME), '#microsoft.com');
ELSE
UPDATE EXAMPLE_TABLE
SET (EMPLOYEE_NUMBER, EMPLOYEE_USERNAME) =
(SELECT EMPLOYEE_NUMBER, EMAIL FROM EXAMPLE_VIEW
WHERE UPPER(EMAIL) = CONCAT(UPPER(P_EMPLOYEE_USERNAME), '#microsoft.com'));
COMMIT;
END IF;
OPEN T_CURSOR For
SELECT * FROM EXAMPLE_VIEW
WHERE EMAIL LIKE CONCAT(UPPER(P_EMPLOYEE_USERNAME), '%');
END;
END ADD_TO_BLACKLIST;
This compiles, but when I try to test it with a valid P_EMPLOYEE_USERNAME (which I've confirmed to be in the EXAMPLE_VIEW), I do not see any data being inserted.
I am new to PLSQL and not sure how to figure out the value of E_COUNT
The Example_Table DDL is
CREATE TABLE "Example_Table"
( "EMPLOYEE_NUMBER" NUMBER NOT NULL ENABLE,
"EMPLOYEE_USERNAME" VARCHAR2(250 BYTE) NOT NULL ENABLE,
"ACCOUNT_STATUS" NUMBER DEFAULT 0,
"ACCOUNT_STATUS_LAST_UPDATE" TIMESTAMP (6) DEFAULT SYSDATE NOT NULL ENABLE,
CONSTRAINT "BOE_SAFEGAURD_PK" PRIMARY KEY ("EMPLOYEE_USERNAME"))
The issue is in below line,you are not converting the case after concatenation.please modify and try below,
WHERE UPPER(EMAIL) = UPPER(CONCAT(UPPER(P_EMPLOYEE_USERNAME), '#microsoft.com'));
EDIT : To prove the theory please find below the details.
I have tested this and it works,
DDL to create the tables:
CREATE TABLE Example_Table
( EMPLOYEE_NUMBER NUMBER NOT NULL ENABLE,
EMPLOYEE_USERNAME VARCHAR2(250 BYTE) NOT NULL ENABLE,
ACCOUNT_STATUS NUMBER DEFAULT 0,
ACCOUNT_STATUS_LAST_UPDATE TIMESTAMP (6) DEFAULT SYSDATE NOT NULL ENABLE,
CONSTRAINT BOE_SAFEGAURD_PK PRIMARY KEY (EMPLOYEE_USERNAME));
CREATE TABLE Example_view
( EMPLOYEE_NUMBER NUMBER NOT NULL ENABLE,
EMAIL VARCHAR2(250 BYTE) NOT NULL ENABLE,
ACCOUNT_STATUS NUMBER DEFAULT 0,
ACCOUNT_STATUS_LAST_UPDATE TIMESTAMP (6) DEFAULT SYSDATE NOT NULL ENABLE
);
DML to populate data to example_view that will be used for the test.
insert into example_view values(1,'Test#microsoft.com',1,sysdate);
Modified the procedure to add UPPER on the rightside of the join for both insert and update conditions and place the commit after end if.A good code should have only one commit and that should be at the end of execution before the exception block of main begin..end block.
create or replace PROCEDURE ADD_TO_BLACKLIST(
P_EMPLOYEE_USERNAME IN VARCHAR2,
T_CURSOR OUT SYS_REFCURSOR
)
AS
BEGIN
DECLARE E_COUNT PLS_INTEGER := 0;
BEGIN
SELECT COUNT(*) INTO E_COUNT FROM EXAMPLE_TABLE WHERE UPPER(EMPLOYEE_USERNAME) LIKE UPPER(P_EMPLOYEE_USERNAME)||'%';
IF E_COUNT = 0 THEN
INSERT INTO EXAMPLE_TABLE
(employee_number, employee_username)
SELECT EMPLOYEE_NUMBER, EMAIL FROM EXAMPLE_VIEW WHERE UPPER(EMAIL)=UPPER(CONCAT(UPPER(P_EMPLOYEE_USERNAME), '#microsoft.com'));
ELSE
UPDATE EXAMPLE_TABLE SET (EMPLOYEE_NUMBER, EMPLOYEE_USERNAME) = (SELECT EMPLOYEE_NUMBER, EMAIL FROM EXAMPLE_VIEW WHERE UPPER(EMAIL)=UPPER(CONCAT(UPPER(P_EMPLOYEE_USERNAME), '#microsoft.com')));
END IF;
COMMIT;
OPEN T_CURSOR For
SELECT * FROM EXAMPLE_VIEW WHERE EMAIL LIKE CONCAT(UPPER(P_EMPLOYEE_USERNAME), '%');
END;
END ADD_TO_BLACKLIST;
In an anonymous block invoked the procedure,
DECLARE
T_CURSOR SYS_REFCURSOR;
BEGIN
ADD_TO_BLACKLIST('test',T_CURSOR);
end;
Ran a query to check if records are inserted,
select * from example_table;
Output is below,
You just need a commit after IF-ELSE condition rather than inside it. I have updated your code along with some other minor updates -
create or replace PROCEDURE ADD_TO_BLACKLIST( P_EMPLOYEE_USERNAME IN VARCHAR2,
T_CURSOR OUT SYS_REFCURSOR
)
AS
E_COUNT PLS_INTEGER := 0;
BEGIN
SELECT COUNT(*)
INTO E_COUNT
FROM EXAMPLE_TABLE
WHERE UPPER(EMPLOYEE_USERNAME) LIKE UPPER(P_EMPLOYEE_USERNAME)||'%';
IF E_COUNT = 0 THEN
INSERT INTO EXAMPLE_TABLE
(employee_number, employee_username)
SELECT EMPLOYEE_NUMBER, EMAIL
FROM EXAMPLE_VIEW
WHERE UPPER(EMAIL) = CONCAT(UPPER(P_EMPLOYEE_USERNAME), '#microsoft.com');
ELSE
UPDATE EXAMPLE_TABLE
SET (EMPLOYEE_NUMBER, EMPLOYEE_USERNAME) = (SELECT EMPLOYEE_NUMBER, EMAIL
FROM EXAMPLE_VIEW
WHERE UPPER(EMAIL)=CONCAT(UPPER(P_EMPLOYEE_USERNAME), '#microsoft.com'));
END IF;
COMMIT;
OPEN T_CURSOR For
SELECT *
FROM EXAMPLE_VIEW
WHERE EMAIL LIKE CONCAT(UPPER(P_EMPLOYEE_USERNAME), '%');
END ADD_TO_BLACKLIST;

How to find currval in oracle 12c for identity columns

I create a table in oracle 12 with a column as identity. The problem is that I want to find the current value of identity column. How I can find this, please someone help me to solve this problem...
You could make use of the data dictionary views *_TAB_IDENTITY_COLS
Here is a working example.
create TABLE t ( ID INTEGER GENERATED BY DEFAULT AS IDENTITY, NAME VARCHAR2(10));
Table created.
INSERT INTO t(NAME) VALUES ( 'TESTER' );
1 row(s) inserted.
select SEQUENCE_NAME FROM user_tab_identity_cols WHERE TABLE_NAME ='T' ;
SEQUENCE_NAME
-----------
ISEQ$$_1727054
Now you could get the currval from this sequence.
select ISEQ$$_1727054.CURRVAL FROM DUAL;
CURRVAL
-------
1
LIVESQL DEMO - Free OTN account required.
Why do you want to know? If to insert a child row you can use the returning clause of the insert statement like this:
insert into master (...) values (...)
returning master_id into l_master_id;
insert into child (master_id, ...) values (l_master_id, ...);
As I've written in this blog post, this query produces all the identities' backing sequences' currval values of your schema:
with
function current_value(p_table_name varchar2) return number is
v_current number;
begin
for rec in (
select data_default
from user_tab_cols
where table_name = p_table_name
and data_default is not null
and identity_column = 'YES'
)
loop
execute immediate replace(
'select ' || rec.data_default || ' from dual',
'.nextval',
'.currval'
) into v_current;
return v_current;
end loop;
return null;
end;
select *
from (
select table_name, current_value(table_name) current_value
from user_tables
)
where current_value is not null
order by table_name;
/
The output could be something like this:
TABLE_NAME CURRENT_VALUE
--------------------------
T1 3
T2 1

Oracle: Dynamically set all NOT NULL columns in a Table to allow NULL

I have a table with 75+ columns in it.
Almost all of the columns have the NOT NULL constraint.
If do a giant alter table modify statement (with every column in there), I get an error saying something along the lines of "You can't set this field to NULL, because it already is NULL"
I have to do this for several tables, and so would prefer to have a dynamic solution.
Can I dynamically find all of the columns that are NOT NULL, and set them to NULL?
I've seen several similar questions like this, but can't find a solution for Oracle SQL.
Modify all columns in a table to 'not null' no matter what
Here is a test table, with two not null columns, and one null column:
create table zzz_mark_test_me (
cust_id varchar2(20) not null,
cust_name varchar2(20) null,
cust_phone varchar2(20) not null
);
table ZZZ_MARK_TEST_ME created.
desc zzz_mark_test_me
Name Null Type
---------- -------- ------------
CUST_ID NOT NULL VARCHAR2(20)
CUST_NAME VARCHAR2(20)
CUST_PHONE NOT NULL VARCHAR2(20)
Now invoke this SQL:
select 'alter table ' || table_name ||
' modify (' || column_name || ' null );'
from user_tab_columns
where table_name='ZZZ_MARK_TEST_ME' and nullable='N'
order by column_id;
Which yields this:
alter table ZZZ_MARK_TEST_ME modify (CUST_ID null );
alter table ZZZ_MARK_TEST_ME modify (CUST_PHONE null );
Copy/paste the output into SQL*Plus etc. and invoke:
alter table ZZZ_MARK_TEST_ME modify (CUST_ID null );
table ZZZ_MARK_TEST_ME altered.
alter table ZZZ_MARK_TEST_ME modify (CUST_PHONE null );
table ZZZ_MARK_TEST_ME altered.
And now, no more NOT NULL:
desc zzz_mark_test_me
Name Null Type
---------- ---- ------------
CUST_ID VARCHAR2(20)
CUST_NAME VARCHAR2(20)
CUST_PHONE VARCHAR2(20)
You can use this procedure. You can first comment out line containing "execute immediate" to see what it executes, before running.
First parameter is schema_name, second is table_name.
create or replace procedure proc_null(t_owner in varchar2, t_name in varchar2) as
v_exec_imm varchar2(1000);
begin
for o in (select owner, column_name from all_tab_cols where owner=t_owner and table_name=t_name and nullable = 'N')
loop
v_exec_imm := 'alter table '||t_owner||'.'||t_name||' modify ('||o.column_name||' null) ';
execute immediate v_exec_imm; -- comment this line if You want, modifies table
dbms_output.put_line( v_exec_imm );
end loop;
end proc_null;

Resources