CTE inside Table-valued function in Oracle - oracle

I'm trying to write a Table-valued function in oracle, which contains a CTE.
I've been able to do it in SQL Server in this way:
ALTER FUNCTION [dbo].[region_parents]
(
#regionId INT
)
RETURNS TABLE
AS
RETURN
(
WITH cte AS
(
SELECT id, owner_region_id FROM regions WHERE id = #regionId
UNION ALL
SELECT r.id, r.owner_region_id
FROM cte INNER JOIN
regions r ON cte.owner_region_id = r.id
)
SELECT id
FROM cte
)
This is needed in order to call it in a cross apply:
SELECT *
FROM shops s
...
...
...
INNER JOIN locations l ON s.location_id = l.id
LEFT JOIN location_criteria lc ON lc.location_id = l.id
CROSS APPLY region_parents(l.region_id) r
In Oracle, I've tried to do it in this way, using User-Defined Datatypes:
CREATE OR REPLACE TYPE TABLE_RES_OBJ AS OBJECT (
id NUMBER
);
CREATE OR REPLACE TYPE TABLE_RES AS TABLE OF TABLE_RES_OBJ;
CREATE OR REPLACE FUNCTION region_parents (regionId IN INTEGER)
RETURN TABLE_RES
IS
cteresult TABLE_RES;
BEGIN
WITH cte(id, owner_region_id) AS
(
SELECT id AS id, owner_region_id AS owner_region_id FROM regions WHERE id = regionId
UNION ALL
SELECT r.id, r.owner_region_id
FROM cte INNER JOIN
regions r ON cte.owner_region_id = r.id
)
SELECT TABLE_RES(id)
BULK COLLECT INTO cteresult
FROM cte;
RETURN cteresult;
END;
/
The problem is that I obtain the following error:
PL/SQL: ORA-00932: inconsistent datatypes: expected UDT got NUMBER
I've also tried to achieve it in this way, without success.

Seems to be a simple distraction: "SELECT TABLE_RES(id)" should be "SELECT TABLE_RES_OBJ(id)".

It isn't anything to do with the CTE. You're trying to select a table object, not a simple object that is an element/row in that table. This:
SELECT TABLE_RES(id)
BULK COLLECT INTO cteresult
FROM cte;
should be:
SELECT TABLE_RES_OBJ(id)
BULK COLLECT INTO cteresult
FROM cte;
fiddle

Related

Avoid using same view multiple times inside oracle procedures

I am using a view named V_ENT_MSG in my oracle procedure "TEST PROC" as shown below. The same view is used in multiple places inside the procedure and the procedure execution time is very high. The view returns more than 5 Million records every time. How can I improve procedure execution time. [ Note that I cannot change the view V_ENT_MSG].
create or replace PROCEDURE "TESTPROC"
AS
BEGIN
INSERT
INTO OUTBOUND1
(
DELIVERY_EVENT_ID,
MESSAGE_ID
)
SELECT
V2.DELIVERY_EVENT_ID,PS.MESSAGE_ID
FROM V_ENT_MSG V2,
R_SOURCE PS
WHERE V2.primary_source = PS.SOURCE;
COMMIT;
INSERT
INTO OUTBOUND2
(
DELIVERY_EVENT_ID,
MESSAGE_BODY
)
SELECT
V2.DELIVERY_EVENT_ID,PS2.MESSAGE_BODY
FROM V_ENT_MSG V2,
R_SOURCE2 PS2
WHERE V2.primary_source = PS2.SOURCE;
COMMIT;
INSERT
INTO OUTBOUND3
(
DELIVERY_EVENT_ID,
SUBJECT
)
SELECT
V2.DELIVERY_EVENT_ID,PS3.SUBJECT
FROM V_ENT_MSG V2,
R_SOURCE3 PS3
WHERE V2.primary_source = PS3.SOURCE;
COMMIT;
END TESTPROC;
You may be able to rewrite this as a multitable insert. To do this, you need to:
(Outer) join the view to each table
insert all the result of this query
Use the when clause to control which rows go in which tables
Which looks something like:
insert all
when ps.source is not null then
into outbound1 ( delivery_event_id, message_id )
values ( delivery_event_id, message_id )
when ps2.source is not null then
into outbound2 ( delivery_event_id, message_body )
values ( delivery_event_id, message_body )
when ps3.source is not null then
into outbound3 ( delivery_event_id, subject )
values ( delivery_event_id, subject )
select v.delivery_event_id,ps3.subject
from v_ent_msg v
left join r_source ps
on v.primary_source = ps.source
left join r_source2 ps2
on v.primary_source = ps2.source
left join r_source3 ps3
on v.primary_source = ps3.source;
This assumes that each primary_source joins to at most one row in r_sourceX. If it's 1:M and each value of primary_source can join to a different number of rows in each of the other tables it gets trickier.
You'll need to assign a row_number for each source value in each r_sourceX table and only insert those rows where this is one.
Additionally whether you have opted for GTT as suggested by #Littlefoot or not ,you can still use this approach.
How about leveraging INSERT ALL with WITH clause to achieve the same ,
INSERT ALL
/* inserting to OUTBOUND1 based on table_name = R_SOURCE defined during union */
WHEN table_name = 'R_SOURCE' THEN
INTO outbound1(delivery_event_id, message_id)
VALUES(delivery_event_id, message_type)
/* inserting to OUTBOUND2 based on table_name = R_SOURCE2 defined during union */
WHEN table_name = 'R_SOURCE2' THEN
INTO outbound2(delivery_event_id, message_body)
VALUES (delivery_event_id, message_type)
/* inserting to OUTBOUND3 based on table_name = R_SOURC3 defined during union */
WHEN table_name = 'R_SOURCE3' THEN
INTO outbound3(delivery_event_id, subject)
VALUES (delivery_event_id, message_type)
WITH ent_msg
AS
(SELECT v2.delivery_event_id,v2.primary_source
FROM v_ent_msg v2),
src_data
AS
( SELECT v2.delivery_event_id delivery_event_id,ps.message_id message_type,'R_SOURCE' table_name
FROM r_source ps
JOIN ent_msg v2
ON v2.primary_source = ps.source
UNION ALL
SELECT v2.delivery_event_id,ps2.message_body,'R_SOURCE2' table_name
FROM r_source2 ps2
JOIN ent_msg v2
ON v2.primary_source = ps2.source
UNION ALL
SELECT v2.delivery_event_id,ps3.subject,'R_SOURCE3' table_name
FROM r_source3 ps3
JOIN ent_msg v2
ON v2.primary_source = ps3.source
)
SELECT delivery_event_id,message_type,table_name
FROM src_data
I couldn't validate SQL because of tables are missing.
There are other ways too in case of performance issue where we can go for BULK COLLECT with LIMIT. I am not aware of the data set you are dealing with.
Hope this guides you proceeding with your question.

T-SQL to PL/SQL - Use temporary variables and the "INTO" expression to search the total of a query in Oracle DB

In sql below I return the search between the tables and then return the total of this already paginated.
The need to return this total is to paginate
That query is using sql server.
Sql server:
SELECT
bu.Id as 'BarcoUsuarioId',
Barco_Id as 'BarcoId',
bu.Usuario_Id as 'UsuarioId',
barco.Nome as 'NomeBarco'
into #tmpBarcoUsuario
FROM BARCO_USUARIO AS bu
inner join BARCO as barco on barco.Id = bu.Barco_Id
where bu.Usuario_Id = #usuarioId
declare #totalEmbarcacoes as int = (select count(*) from #tmpBarcoUsuario);
select
BarcoUsuarioId,
BarcoId,
UsuarioId,
NomeBarco,
#totalEmbarcacoes as TotalEmbarcacoes from #tmpBarcoUsuario
order by BarcoId
OFFSET #pageSize *(#pageNumber - 1) ROWS
fetch next #pageSize ROWS ONLY";
However, when using the Oracle database, I having some difficulty.
As can be seen in sql below, I already made the pagination query in oracle DB:
Oracle:
SELECT *
FROM(
SELECT ROWNUM rnum, b.*
FROM (
SELECT
barcoUser.ID BarcoUsuarioId ,
barcoUser.BARCO_ID BarcoId ,
barcoUser.USARIO_ID UsuarioId ,
barco.NOME NomeBarco
FROM BARCO_USUARIO barcoUser INNER JOIN EMBARCACAO barco ON barco.ID = barcoUser.BARCO_ID
WHERE (barcoUser.USARIO_ID=:usuarioId)ORDER BY BarcoId DESC
)b
)WHERE RNUM between :PageSize * (:PageNumber - 1) AND (:PageSize * :PageNumber)";
but how would I do to return the total of all this already paginated, as was done in sql server?
You can use the analytic count(*) function
SELECT *
FROM(
SELECT ROWNUM rnum, b.*
FROM (
SELECT
barcoUser.ID BarcoUsuarioId ,
barcoUser.BARCO_ID BarcoId ,
barcoUser.USARIO_ID UsuarioId ,
barco.NOME NomeBarco ,
count(*) over () TotalCount
FROM BARCO_USUARIO barcoUser
INNER JOIN EMBARCACAO barco ON barco.ID = barcoUser.BARCO_ID
WHERE (barcoUser.USARIO_ID=:usuarioId)
ORDER BY BarcoId DESC
)b
)
WHERE RNUM between :PageSize * (:PageNumber - 1)
AND (:PageSize * :PageNumber)
Note that Oracle has supported the OFFSET FETCH syntax since 12c if you want to minimize the changes between database engines. The rownum approach, however, may be faster than the OFFSET FETCH approach.

Query taking long when i use user defined function with order by in oracle select

I have a function, which will get greatest of three dates from the table.
create or replace FUNCTION fn_max_date_val(
pi_user_id IN number)
RETURN DATE
IS
l_modified_dt DATE;
l_mod1_dt DATE;
l_mod2_dt DATE;
ret_user_id DATE;
BEGIN
SELECT MAX(last_modified_dt)
INTO l_modified_dt
FROM table1
WHERE id = pi_user_id;
-- this table contains a million records
SELECT nvl(MAX(last_modified_ts),sysdate-90)
INTO l_mod1_dt
FROM table2
WHERE table2_id=pi_user_id;
-- this table contains clob data, 800 000 records, the table 3 does not have user_id and has to fetched from table 2, as shown below
SELECT nvl(MAX(last_modified_dt),sysdate-90)
INTO l_mod2_dt
FROM table3
WHERE table2_id IN
(SELECT id FROM table2 WHERE table2_id=pi_user_id
);
execute immediate 'select greatest('''||l_modified_dt||''','''||l_mod1_dt||''','''||l_mod2_dt||''') from dual' into ret_user_id;
RETURN ret_user_id;
EXCEPTION
WHEN OTHERS THEN
return SYSDATE;
END;
this function works perfectly fine and executes within a second.
-- random user_id , just to test the functionality
SELECT fn_max_date_val(100) as max_date FROM DUAL
MAX_DATE
--------
27-02-14
For reference purpose i have used the table name as table1,table2 and table3 but my business case is similar to what i stated below.
I need to get the details of the table1 along with the highest modified date among the three tables.
I did something like this.
SELECT a.id,a.name,a.value,fn_max_date_val(id) as max_date
FROM table1 a where status_id ='Active';
The above query execute perfectly fine and got result in millisecods. But the problem came when i tried to use order by.
SELECT a.id,a.name,a.value,a.status_id,last_modified_dt,fn_max_date_val(id) as max_date
FROM table1 where status_id ='Active' a
order by status_id desc,last_modified_dt desc ;
-- It took almost 300 seconds to complete
I tried using index also all the values of the status_id and last_modified, but no luck. Can this be done in a right way?
How about if your query is like this?
select a.*, fn_max_date_val(id) as max_date
from
(SELECT a.id,a.name,a.value,a.status_id,last_modified_dt
FROM table1 where status_id ='Active' a
order by status_id desc,last_modified_dt desc) a;
What if you don't use the function and do something like this:
SELECT a.id,a.name,a.value,a.status_id,last_modified_dt x.max_date
FROM table1 a
(
select max(max_date) as max_date
from (
SELECT MAX(last_modified_dt) as max_date
FROM table1 t1
WHERE t1.id = a.id
union
SELECT nvl(MAX(last_modified_ts),sysdate-90) as max_date
FROM table2 t2
WHERE t2.table2_id=a.id
...
) y
) x
where a.status_id ='Active'
order by status_id desc,last_modified_dt desc;
Syntax might contain errors, but something like that + the third table in the derived table too.

With clause not working with union

My query result is a union of several queries. I am facing the below error when I use WITH clause within a union. Any ideas why?
select column1 from TABLE_A
union
with abcd as (select * from TABLE_B)
select column2 from TABLE_A A, abcd
where abcd.m_reference = A.m_reference
ORA-32034: unsupported use of WITH clause
32034. 00000 - "unsupported use of WITH clause"
*Cause: Inproper use of WITH clause because one of the following two reasons
1. nesting of WITH clause within WITH clause not supported yet
2. For a set query, WITH clause can't be specified for a branch.
3. WITH clause can't sepecified within parentheses.
*Action: correct query and retry
Encapsulate your WITH statement in a dummy select.
select column1 from TABLE_A
union
select * from (
with abcd as (select * from TABLE_B)
select column2 from TABLE_A A, abcd
where abcd.m_reference = A.m_reference
)
Just define the CTE first, before the actual UNION query. Then use it as you would a regular table:
with abcd as (select * from TABLE_B)
select column1 from TABLE_A
union
select column2
from TABLE_A A
inner join abcd
on abcd.m_reference = A.m_reference
You can use multiple CTE as follows:
with cte1 AS (...),
cte2 AS (...)
select * from ...
Encapsulating it is the way to go if you have multiple WITHs; for example I just had to do this monstrosity to quickly pull in data from ID numbers from an Excel sheet
select * from (
with childvendor as (
select vendornumber, name From vendor where vendornumber = '0000800727'
)
select
v.vendornumber as parentvendor,
v.name as parentname,
cv.vendornumber as childvendor,
cv.name as childname
From
vendor v, childvendor cv
where
v.vendornumber = '0000800004'
)
UNION ALL
select * from (
with childvendor as (
select vendornumber, name From vendor where vendornumber = '0000800042'
)
select
v.vendornumber as parentvendor,
v.name as parentname,
cv.vendornumber as childvendor,
cv.name as childname
From
vendor v, childvendor cv
where
v.vendornumber = '0000800035'
)
And so on

Using Row_Number in Linq where clause

how can I simulate the following sql query using linq. I just want to have a Row_Number column and use it's values in linq where clause.
With t As (
Select Row_Number() over ( Order by Id ) as 'RowId', * From Orders
)
Select * From t Where RowId between 1 and 10
I think what you're trying to do is just Skip/Take, for paging.
So basically:
var result = dataContext.Products.Skip(0).Take(10).ToList();
(Code is untested, written off the top of my head)

Resources