RTTI in Oracle Triggers - oracle

I have this dummy types :
create or replace type Service_TY as object(
code INTEGER,
visit_analysis char(1)
)FINAL;
/
create or replace type Employee_TY as object(
dummy varchar(30)
)NOT FINAL;
/
create or replace type Doctor_TY UNDER Employee_TY(
ID INTEGER
)FINAL;
/
create or replace type Assistant_TY UNDER Employee_TY(
ID INTEGER
)FINAL;
/
create or replace type Habilitation_TY as object(
employee ref Employee_TY,
service ref Service_TY
)FINAL;
/
And these dummy tables:
CREATE TABLE Service of Service_TY(
code primary key,
visit_analysis not null check (visit_analysis in ('v', 'a'))
);
/
CREATE TABLE Doctor of Doctor_TY(
ID primary key
);
/
CREATE TABLE Assistant of Assistant_TY(
ID primary key
);
/
CREATE TABLE Habilitation of Habilitation_TY;
/
I want to create a trigger that, when a new tuple is inserted in Habilitation, should check that, if the employee is an assistant (and not a doctor), the visit_analysis attribute is equal to 'a' to know if it is a legal tuple.
I don't know how to check the type of the Employee (if it is a doctor or an assistant).
I would do something like that:
create or replace
TRIGGER CHECK_HABILITATION
BEFORE INSERT ON HABILITATION
FOR EACH ROW
DECLARE
BEGIN
IF (:NEW.EMPLOYEE is of ASSISTANT_TY)
THEN
IF :NEW.SERVICE.visit_analysis = 'v'
THEN
raise_application_error(-10000, 'invalid tuple');
END IF;
END;
But it's not working.
How should I check that type?
The error I get is:
Error(14,4): PLS-00103: Encountered the symbol ";" when expecting one of the following: if

Try to put it into a variable, the following one should work.
create or replace
TRIGGER CHECK_HABILITATION
BEFORE INSERT ON HABILITATION
FOR EACH ROW
DECLARE
emp employee_TY;
ser service_TY;
BEGIN
select deref(:new.employee) into emp from dual;
if (emp is of (assistant_ty)) then
select deref(:new.service) into ser from dual;
if ser.visit_analysis = 'v' then
raise_application_error('-20001', 'invalid tuple');
end if;
end if;
END;
/

According to the documentation for the IS OF condition, you need to wrap the type in parentheses, like:
IF (:NEW.EMPLOYEE is of (ASSISTANT_TY) )
per https://docs.oracle.com/cd/B28359_01/server.111/b28286/conditions014.htm#SQLRF52157.
I'm not really familiar with using object types so there may be some other issue that I'm not seeing.

Related

Problem with distributed query in procedure to populate data warehouse (Oracle)

I have a problem with a procedure that I will use to populate a table in the data warehouse.
I'll try to exemplify.
I have three types defined as follows:
create or replace type room_t as object(
ID INTEGER,
n_seats INTEGER,
cinema ref cinema_t
) not instantiable not final;
/
create or replace type film_screening_t as object (
screen_date TIMESTAMP,
room ref room_t,
);
/
create or replace type ticket_t as object(
film ref film_screening_t,
purchase_date DATE,
price FLOAT,
n_ticket INTEGER
);
/
And the associated tables:
create table rooms of room_t(
ID primary key
);
/
create table film_screenings of film_screening_t(
room NOT NULL
);
create table tickets of ticket_t(
n_ticket primary key,
film NOT NULL
)
/
I also created a database link in another database that I called:
op_db_link
When I use this database link to get the id of the rooms like in the query below, everything is ok:
select deref(deref(film).room).id from tickets#op_db_link;
but when I use it in a procedure, I obtain only null values. The procedure is:
create or replace
PROCEDURE prova_procedure AS
room_id integer;
cursor c is
select deref(deref(film).room).id from tickets#op_db_link;
BEGIN
open c;
loop
fetch c into room_id;
dbms_output.put_line(user_id);
exit when c%notfound;
end loop;
end;
How can I solve this problem? I need to solve this problem in order to create an ETL procedure to populate the data warehouse

Define a record type in PL/SQL block that references a collection of itself

How to define a record type in PL/SQL anonymous block that contains a property that is a collection of itself? Look at the following example:
DECLARE
type t_item is record (
name varchar2(64),
children t_items -- referencing t_items type
);
type t_items is table of t_item; -- referencing t_item type
BEGIN
-- script code
END
PL/SQL has no type hoisting so Oracle engine raises an exception:
PLS-00498: illegal use of a type before its declaration
How to define a record t_item that contains a table of t_item in its property children?
You can use objects defined in the SQL Scope using inheritance:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TYPE abstract_item IS OBJECT (
name VARCHAR2(64)
) NOT FINAL NOT INSTANTIABLE
/
CREATE TYPE t_items IS TABLE OF abstract_item
/
CREATE TYPE t_item UNDER abstract_item (
children t_items
) INSTANTIABLE
/
Query 1:
SELECT t_item(
'1',
t_items(
t_item( '1.1', t_items() ),
t_item(
'1.2',
t_items(
t_item( '1.2.1', null )
)
),
t_item( '1.3', null )
)
)
FROM DUAL
Results: (SQLFiddle doesn't display it nicely - but it runs without errors)
| T_ITEM('1',T_ITEMS(T_ITEM('1.1',T_ITEMS()),T_ITEM('1.2',T_ITEMS(T_ITEM('1.2.1',NULL))),T_ITEM('1.3',NULL))) |
|-------------------------------------------------------------------------------------------------------------|
| oracle.sql.STRUCT#2a094aab |
You could use a similar declaration in PL/SQL:
DECLARE
items t_item;
BEGIN
items = t_item( 'Item Name', t_items( /* ... */ ) );
END;
/
An example with reference to objects:
create or replace type item; -- forward declaration
/
create or replace type l_item_ref is table of ref item;
/
create or replace type item is object( a number, list l_item_ref)
/
CREATE TABLE t_item OF item nested table list store as ref_items
/
declare
v_list l_item_ref;
begin
insert into t_item values(1,null);
insert into t_item values(2,null);
insert into t_item values(3,null);
select ref(p) bulk collect into v_list from t_item p;
insert into t_item values(123,v_list);
commit;
end;
select p.a,p.list from t_item p;
Below is an example of create a custom record type and then create table type using the custom record type.
In your spec file you can define the custom type as below.
TYPE STUDENT IS RECORD
("ID" NUMBER,
"NAME" VARCHAR2(100)
);
TYPE STUDENT_TABLE IS TABLE OF STUDENT;
Now in your package body you can use the custom table which you have defined as below.
students STUDENT_TABLE; (Declaration of variable of type student_table)
As this does not work as a table you can not do direct insert on this. And one more thing to remember as in table if you insert record one after another it basically makes a new entry but in case of custom record type will replace the old one.
**SELECT
123 AS ID,
abc AS NAME
BULK COLLECT INTO STUDENT_TABLE FROM DUAL;** --(This is how we insert)
Consider if we execute same statement with different values again it will override the previous one as explained above.
So if you have a requirement to append records then you can follow the below approach.
students STUDENT_TABLE;
studentsTemp STUDENT_TABLE; (Declare a temp also to store the previous value)
**SELECT
123 AS ID,
abc AS NAME
BULK COLLECT INTO STUDENT_TABLE FROM DUAL;
studentsTemp := STUDENT_TABLE;
SELECT
789 AS ID,
xyz AS NAME
BULK COLLECT INTO STUDENT_TABLE FROM DUAL;
studentsTemp := students multiset union all studentsTemp;** --(This is how you can append)
Below is the way to return it through cursor.
OPEN CursorStudent FOR
SELECT ID,
NAME,
FROM TABLE(studentsTemp); (Will return all records.)
Advantage of using this is you can avoid temporary tables and less scripts to maintain when moving to higher environments.

create a collection with 2 columns and insert all the rows at once PL/SQL

I have reference data which I want to use in a PL/SQL package.
This is a collection with two columns:
type table_info IS RECORD (
table_name VARCHAR2(50),
join_column VARCHAR2(50)
);
type config_tables_type is table of table_info; -- list of the config tables
I would like to add several rows at once to this collection, I tried this:
config_tables config_tables_type := config_tables_type (table_info('Commands','object_id'),
table_info('Contact_notificationcommands','command_object_id'),
table_info('Contactgroup_members','contact_object_id'),
table_info('Contactgroups','contact_object_id'),
table_info('Contactnotificationmethods','command_object_id'),
table_info('customvariables','object_id'),
table_info('Host_contactgroups','host_id'),
table_info('Host_contacts','host_id'),
table_info('Hostescalation_contactgroups','contactgroup_object_id'),
table_info('Hostescalation_contacts','contact_object_id'),
table_info('Host_parenthosts','parent_host_object_id'),
table_info('Hostdependencies','host_object_id'),
table_info('Hostdependencies','dependent_host_object_id'),
table_info('Hostescalations','host_object_id'),
table_info('Hostgroup_members','host_object_id'),
table_info('Hostgroups','hostgroup_object_id'),
table_info('Hosts','host_object_id'),
table_info('Service_contactgroups','contactgroup_object_id'),
table_info('Service_contacts','contact_object_id'),
table_info('Servicedependencies','service_object_id'),
table_info('Serviceescalation_contactgroups','contactgroup_object_id'),
table_info('Serviceescalation_contacts','contact_object_id'),
table_info('Serviceescalations','service_object_id'),
table_info('Servicegroup_members','service_object_id'),
table_info('Servicegroups','servicegroup_object_id'),
table_info('Services','service_object_id'),
table_info('Timeperiods','timeperiod_object_id')
);
But I have the following compilation error:
PLS-00222: no function with name 'TABLE_INFO' exists in this scope.
This type is declared in the package description and the initialisation of the config_tables collection is done in the package body.
Thanks
The below "hack" should do the trick!
declare
type table_info IS RECORD (
table_name VARCHAR2(50),
join_column VARCHAR2(50)
);
type config_tables_type is table of table_info;
config_tables config_tables_type;
function table_info_constructor(table_name VARCHAR2, join_column VARCHAR2) return table_info
is
t_i table_info;
begin
t_i.table_name := table_name;
t_i.join_column := join_column;
return(t_i);
end;
begin
config_tables := config_tables_type(table_info_constructor('Commands','object_id'),
table_info_constructor('Contact_notificationcommands','command_object_id'),
table_info_constructor('Contactgroup_members','contact_object_id'),
table_info_constructor('Contactgroups','contact_object_id'),
table_info_constructor('Contactnotificationmethods','command_object_id'),
table_info_constructor('customvariables','object_id'),
table_info_constructor('Host_contactgroups','host_id'),
table_info_constructor('Host_contacts','host_id'),
table_info_constructor('Hostescalation_contactgroups','contactgroup_object_id'),
table_info_constructor('Hostescalation_contacts','contact_object_id'),
table_info_constructor('Host_parenthosts','parent_host_object_id'),
table_info_constructor('Hostdependencies','host_object_id'),
table_info_constructor('Hostdependencies','dependent_host_object_id'),
table_info_constructor('Hostescalations','host_object_id'),
table_info_constructor('Hostgroup_members','host_object_id'),
table_info_constructor('Hostgroups','hostgroup_object_id'),
table_info_constructor('Hosts','host_object_id'),
table_info_constructor('Service_contactgroups','contactgroup_object_id'),
table_info_constructor('Service_contacts','contact_object_id'),
table_info_constructor('Servicedependencies','service_object_id'),
table_info_constructor('Serviceescalation_contactgroups','contactgroup_object_id'),
table_info_constructor('Serviceescalation_contacts','contact_object_id'),
table_info_constructor('Serviceescalations','service_object_id'),
table_info_constructor('Servicegroup_members','service_object_id'),
table_info_constructor('Servicegroups','servicegroup_object_id'),
table_info_constructor('Services','service_object_id'),
table_info_constructor('Timeperiods','timeperiod_object_id')
);
end;
Let me know how it works out for you.
For any further clarifications don't hesitate to ask me.
Ted.
There is an another alternative where you can basically create a schema level object i.e OBJECT type and TABLE Type and then call it in the plsql block as shown below. Hope this helps too.
--Create object type
CREATE OR REPLACE TYPE table_info
IS
OBJECT
(
table_name VARCHAR2(50),
join_column VARCHAR2(50))
/
--Create table type on Object type
CREATE OR REPLACE TYPE config_tables_type
IS
TABLE OF table_info
/
--PLSQL block of code
DECLARE
config_tables config_tables_type;
BEGIN
config_tables config_tables_type := config_tables_type (table_info('Commands','object_id'),
table_info('Contact_notificationcommands','command_object_id'),
table_info('Contactgroup_members','contact_object_id'),
table_info('Contactgroups','contact_object_id'),
table_info('Contactnotificationmethods','command_object_id'),
table_info('customvariables','object_id'),
table_info('Host_contactgroups','host_id'),
table_info('Host_contacts','host_id'),
table_info('Hostescalation_contactgroups','contactgroup_object_id'),
table_info('Hostescalation_contacts','contact_object_id'),
table_info('Host_parenthosts','parent_host_object_id'),
table_info('Hostdependencies','host_object_id'),
table_info('Hostdependencies','dependent_host_object_id'),
table_info('Hostescalations','host_object_id'),
table_info('Hostgroup_members','host_object_id'),
table_info('Hostgroups','hostgroup_object_id'),
table_info('Hosts','host_object_id'),
table_info('Service_contactgroups','contactgroup_object_id'),
table_info('Service_contacts','contact_object_id'),
table_info('Servicedependencies','service_object_id'),
table_info('Serviceescalation_contactgroups','contactgroup_object_id'),
table_info('Serviceescalation_contacts','contact_object_id'),
table_info('Serviceescalations','service_object_id'),
table_info('Servicegroup_members','service_object_id'),
table_info('Servicegroups','servicegroup_object_id'),
table_info('Services','service_object_id'),
table_info('Timeperiods','timeperiod_object_id')
);
END;
/

How to use trigger between two tables in Oracle SQL?

Imagine this two tables
CREATE TABLE A
(
idA smallint primary key,
idP smallint ,
...
);
CREATE TABLE P
(
idP smallint primary key,
Type char , (ex:A, B, C)
...
);
I would like to behaviour like if in table A doesnt have the same type as in table P, is not for example a worker
CREATE OR REPLACE TRIGGER VERIFYTYPE
BEFORE INSERT OR UPDATE ON A
FOR EACH ROW
BEGIN
IF((SELECT tp.Type
FROM P tp
WHERE tp.idP=:new.idP)!='W')
THEN RAISE_APPLICATION_ERROR(-20001, 'That is not a worker');
END IF;
END;
I believe somethin is wrong
IN PL/SQL you need to select into a variable. But gmiley is correct, a foreign key or virtual column with an index is a better solution.
CREATE OR REPLACE TRIGGER VERIFYTYPE
BEFORE INSERT OR UPDATE ON A
FOR EACH ROW
v_type CHAR(3);
BEGIN
SELECT tp.Type
INTO v_type
FROM P tp
WHERE tp.idP=:new.idP;
IF v_type != 'W'
THEN
RAISE_APPLICATION_ERROR(-20001, 'That is not a worker');
END IF;
END;
You have to:
rename the column 'type' as this is a keyword and you'll get a lot of troubles when using it
add variable x to the declare section
use SELECT col INTO x construct
use x in the IF construct

Object not exists error while using TABLE expression

Here is my code: Quite Straight forward..
create or replace package types
as
type rec is record
(
employee_id NUMBER,
fname varchar2(20)
);
type tab_rec is table of rec;
type tab_numbers is table of number;
type tab_chars is table of varchar2(10);
end types;
/
create or replace
function get_employees_rec
(
O_error_msg IN OUT varchar2,
L_access_tab OUT types.tab_chars
)
return boolean
as
--o_access_tab types.tab_chars;
cursor c_rec is
select first_name from employees;
begin
open c_rec;
fetch c_rec bulk collect into L_access_tab;
close c_rec;
return true;
exception
when others then
O_error_msg:=substr(sqlerrm,1,100);
return false;
end;
/
declare
O_error_msg varchar2(100);
L_access types.tab_chars;
begin
if get_employees_rec(O_error_msg,L_access)=FALSE then
dbms_output.put_line('Got you');
end if;
for rec in(select * from employees e,TABLE(L_access) f where value(f)=e.first_name)
loop
dbms_output.put_line(rec.first_name);
end loop;
end;
/
However I am getting the error :
ORA-21700: object does not exist or is marked for delete
ORA-06512: at line 9
21700. 00000 - "object does not exist or is marked for delete"
*Cause: User attempted to perform an inappropriate operation to
an object that is non-existent or marked for delete.
Operations such as pinning, deleting and updating cannot be
applied to an object that is non-existent or marked for delete.
*Action: User needs to re-initialize the reference to reference an
existent object or the user needs to unmark the object.
What is the reason behind this error?
You can't access those types from an external package like that, rather create them as database objects:
create or replace type rec is object (
employee_id NUMBER,
fname varchar2(20));
create or replace type tab_rec is table of rec;
etc.

Resources