Problem with distributed query in procedure to populate data warehouse (Oracle) - 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

Related

RTTI in Oracle Triggers

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.

How can I create a table type with multiple columns and use it in a stored procedure?

I created a table type to use it as input in a stored procedure, so that the procedure can receive multiple customer IDs at once.
But now I also need to use other data related to the customers and I was wondering how I can use the type I created with multiple custom columns, and not only Customer ID.
This is how it is now:
CREATE TYPE T_CUSTOMERS IS TABLE OF NUMBER;
CREATE OR REPLACE PROCEDURE PR_SAMPLE (
CUSTOMERS_LIST IN T_CUSTOMERS,
C1 OUT SYS_REFCURSOR
)
IS
BEGIN
...
END;
You can create the OBJECT type and then create the table type containing the newly created objects.
CREATE TYPE T_CUSTOMERS_OBJ AS OBJECT (
CUSTOMER_ID NUMBER,
CUSTOMER_NAME VARCHAR2(300)
);
/
CREATE TYPE T_CUSTOMERS IS
TABLE OF T_CUSTOMERS_OBJ;
/
Then you can use the T_CUSTOMERS type in your procedure.

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 TYPE in Oracle PL/SQL as "mirror" of existing table

Assume I have a table in my database called
CREATE TABLE EMPLOYEES ( id number, lname varchar2(10))
I know I can create a user defined TYPE as follows
create type t_EMP authid definer as object ( id number, lname varchar2(30))
But is there a way to DYNAMICALLY create type without having to specify all column names ( so that next time the table changes, all I have to do is drop/recreate the TYPE)
You don't need to create type
You can just create variable using your table like this
variable_name table_name%rowtype;
Then you can use it as you want
variable_name.id := 5;
variable_name.lname := 'some name';

Create table-returning function in package, using %ROWTYPE and TABLE OF

I have some tables which I cannot change. For each table, I need to create a FUNCTION inside a PACKAGE that returns a temporary table with the same data/layout as the original table. Instead of copying all table column definitions manually, I'd like to use statements such as %ROWTYPE.
I want to tell Oracle: "this function returns a table with the same layout as the original table XY".
Please have a look at this example. This is the (legacy) table:
CREATE TABLE TEST.Emp (
ID RAW(16),
NAME VARCHAR2(10)
);
/
These are the package and type definitions:
CREATE OR REPLACE TYPE TEST.row_Emp AS OBJECT (
ID RAW(16),
NAME VARCHAR2(10)
);
/
CREATE OR REPLACE PACKAGE TEST.Emp_PKG AS
TYPE t_Emp IS TABLE OF TEST.row_Emp INDEX BY BINARY_INTEGER;
FUNCTION F_Emp_Select (
VersionId INT DEFAULT NULL
) RETURN t_Emp;
END;
/
And here is the package body:
CREATE OR REPLACE PACKAGE BODY TEST.Emp_PKG AS
FUNCTION F_Emp_Select (
VersionId INT DEFAULT NULL
) RETURN t_Emp
AS
VersionVar INT := VersionId;
v_ret t_Emp;
BEGIN
SELECT
CAST(
MULTISET(
SELECT ID, NAME FROM TEST.Emp
) AS t_Emp) -- <== this is line 15
INTO v_ret
FROM dual;
RETURN v_ret;
END;
END;
/
If I execute this in SQLPlus, I get the following error:
Errors for PACKAGE BODY TEST.EMP_PKG:
LINE/COL ERROR
-------- -----------------------------------------------------------------
11/9 PL/SQL: SQL Statement ignored
15/22 PL/SQL: ORA-00902: invalid datatype
What am I doing wrong?
EDIT: I need this for a more complex case. This function will be used in other functions and procedures. For example, I need to be able to do a join on the result of the function:
SELECT ... FROM ...
INNER JOIN TEST.Emp_PKG.F_Emp_Select(...) ON ...
So I don't need the whole result.
Sorry for the confusion, I'm coming from SQL Server, where I have done such things many times.
If you need the result as something you can treat as a table, you could use a pipeline function:
CREATE OR REPLACE TYPE row_Emp AS OBJECT (
ID RAW(16),
NAME VARCHAR2(10)
);
/
CREATE OR REPLACE TYPE tab_Emp AS TABLE OF row_Emp
/
CREATE OR REPLACE PACKAGE Emp_PKG AS
FUNCTION F_Emp_Select (
VersionId INT DEFAULT NULL
) RETURN tab_Emp PIPELINED;
END;
/
CREATE OR REPLACE PACKAGE BODY Emp_PKG AS
FUNCTION F_Emp_Select (
VersionId INT DEFAULT NULL
) RETURN tab_EMP PIPELINED
AS
BEGIN
FOR row IN (SELECT ID, NAME FROM Emp) LOOP
PIPE ROW (row_Emp(row.ID, row.NAME));
END LOOP;
RETURN;
END;
END;
/
Notice the object type and the table type have to be declared at schema level; the table type can't be a PL/SQL-seclared table (collection) type as that wouldn't be usable in plain SQL, even within other PL/SQL. Unfortunately %ROWTYPE is a PL/SQL construct, so you can't use that to define your schema-level table type.
You can then do:
SELECT * FROM TABLE(Emp_PKG.F_Emp_Select(<optional versionId>));
... or use it in a join as:
SELECT ... FROM ...
INNER JOIN TABLE(TEST.Emp_PKG.F_Emp_Select(...)) ON ...
SQL FIddle demo.
Assuming that your goal is to return a collection, not a temporary table, it's a bit less complicated than your example
CREATE OR REPLACE PACKAGE emp_pkg
AS
TYPE emp_typ IS TABLE OF emp%rowtype index by binary_integer;
FUNCTION get_emps
RETURN emp_typ;
END;
CREATE OR REPLACE PACKAGE BODY emp_pkg
AS
FUNCTION get_emps
RETURN emp_typ
IS
l_emps emp_typ;
BEGIN
SELECT *
BULK COLLECT INTO l_emps
FROM emp;
RETURN l_emps;
END;
END;
Now, architecturally, I would be very concerned about a solution that involved selecting all the data from a table into a PL/SQL collection. PL/SQL collections have to be stored entirely in the session's SGA which is relatively expensive server RAM. If you have thousands or tens of thousands of rows in your table, that can be a pretty substantial amount of space on the server particularly if there may be many different sessions all calling these procedures at roughly the same time. If your tables all have a couple hundred rows and only one session at a time will be using these functions, maybe this approach will be sufficient.

Resources