Convert T-SQL INNER JOIN on a Function to PL-SQL - oracle

I have a T-SQL view that does this:
LEFT JOIN fnc_AssetAttributeByType(3) tmp
ON
A.AssetID = tmp.AssetID;
Is there a straight forward way I can translate this (I'm writing a tool to do it programmatically) without creating a package or modifying the header of the function that would void other calls to it in other DML statements?
Main questions:
1. Can I even write a PL function that returns a table?
2. If not, what should I do?

You can create a function that returns a table type, and work on it in SQL by using it inside a table() clause. The user running the SQL needs execute privilege on the function.
SQL> create or replace type tp_asset as object (assetId number, asset_name varchar2(100), asset_val number)
2 /
Type created
SQL> create or replace type tp_tab_asset is table of tp_asset;
2 /
Type created
SQL> create or replace function fnc_AssetAttributeByType(p_num in number) return tp_tab_asset pipelined as
2 v_tp_asset tp_asset;
3 begin
4 for i in 1 .. 3
5 loop
6 v_tp_asset := tp_asset(i, 'ABC', i * 3);
7 pipe row (v_tp_asset);
8 end loop;
9 return;
10 end;
11 /
Function created
SQL> select *
2 from table(fnc_AssetAttributeByType(3)) a
3 left join table(fnc_AssetAttributeByType(3)) b on b.assetid = a.assetid;
ASSETID ASSET_NAME ASSET_VAL ASSETID ASSET_NAME ASSET_VAL
---------- --------------- ---------- ---------- --------------- ----------
1 ABC 3 1 ABC 3
2 ABC 6 2 ABC 6
3 ABC 9 3 ABC 9

Related

PLSQL record or collection into json object

I need to convert a Pl/SQl record or collection into json object using json_object(*) function.Is it possible or is there any other way?
As long as they are database types (not solely PLSQL types) you could do it with a table function, eg
SQL> create or replace
2 type the_row as object (
3 x int,
4 y int );
5 /
Type created.
SQL>
SQL> create or replace
2 type the_row_list as table
3 of the_row
4 /
Type created.
SQL>
SQL> -- interestingly, direct SELECT fails
SQL>
SQL> select json_object(*)
2 from table(the_row_list(the_row(1,1),the_row(2,2)));
select json_object(*)
*
ERROR at line 1:
ORA-40579: star expansion is not allowed
SQL> -- but a WITH is ok
SQL> with t as
2 (
3 select *
4 from table(the_row_list(the_row(1,1),the_row(2,2)))
5 )
6 select json_object(*)
7 from t;
JSON_OBJECT(*)
-------------------------------------------------------------------
{"X":1,"Y":1}
{"X":2,"Y":2}

declaring pl sql variable using query then using in subsequent query

I’ve read some on declaring and using variables in PL/SQL. and I’ve only done so in other versions (TSQL) or code languages.
Below is what I’m trying to do, which is
to declare a variable
assign a value to that variable via query
use the subsequent result from #2
in another query.
I’ve tried other methods listed on the Internet but nothing I do seems to work when calling the variable in the final query.
The declaration and into statements work but the last select query does not.
declare
Degr_term varchar2(20);
begin
select max(z.term)
INTO degr_term
from dwh.rpt_ersd_vw d
join dwh.dim_ers_term_vw z
on d.DIM_DEGR_ERS_TERM_SKEY = z.dim_ers_term_skey;
select * from ir_wip.stem_enr s where s.min_deg_term = degr_term;
end;
Just like you selected INTO from the 1st select statement, you have to do it in the 2nd as well. It means that you'll have to declare a variable which will hold its contents.
Presuming - maybe not correctly - that 2nd select returns only one row - you could
SQL> declare
2 degr_term varchar2(20);
3 l_stem stem_enr%rowtype; --> this
4 begin
5 select max(z.term)
6 into degr_term
7 from rpt_ersd_vw d
8 join dim_ers_term_vw z
9 on d.DIM_DEGR_ERS_TERM_SKEY = z.dim_ers_term_skey;
10
11 select *
12 into l_stem --> this
13 from stem_enr s
14 where s.min_deg_term = degr_term;
15 end;
16 /
PL/SQL procedure successfully completed.
SQL>
(I removed schema names as I don't have them and didn't feel like creating ones.)
If the 2nd query returns more than a single row, then you'd e.g.
SQL> declare
2 degr_term varchar2(20);
3 type l_stem_typ is table of stem_enr%rowtype;
4 l_stem_tab l_stem_typ;
5 begin
6 select max(z.term)
7 into degr_term
8 from rpt_ersd_vw d
9 join dim_ers_term_vw z
10 on d.DIM_DEGR_ERS_TERM_SKEY = z.dim_ers_term_skey;
11
12 select *
13 bulk collect
14 into l_stem_tab
15 from stem_enr s
16 where s.min_deg_term = degr_term;
17 end;
18 /
PL/SQL procedure successfully completed.
SQL>
[EDIT: How to output the result? Using a loop]
Presume that table looks like this (I don't know really, you never posted any sample data):
SQL> select * from stem_enr;
MIN_DEG_TERM NAME SURN
------------ ------ ----
1 Little Foot
1 Big Foot
Then you'd (simplified example; I didn't feel like creating other tables as well):
SQL> declare
2 type l_stem_typ is table of stem_enr%rowtype;
3 l_stem_tab l_stem_typ;
4 begin
5 select *
6 bulk collect
7 into l_stem_tab
8 from stem_enr s
9 where s.min_deg_term = 1;
10
11 for i in 1 .. l_stem_tab.count loop
12 dbms_output.put_line(l_stem_tab(i).name ||' '|| l_stem_tab(i).surname);
13 end loop;
14 end;
15 /
Little Foot
Big Foot
PL/SQL procedure successfully completed.
SQL>

Create Fucntion returning Table in pl/sql

TEMP table:
Node
Curr_Cnt
Prev_Cnt
Diff
First
20
40
20
Second
30
70
40
CREATE OR REPLACE FUNCTION NEW_FUNCTION
RETURNS table
IS
c_rec TEMP%ROWTYPE;
TYPE c_tab IS TABLE OF c_rec%TYPE INDEX BY PLS_INTEGER;
l_c_tab c_tab;
BEGIN
SELECT * INTO c_tab FROM
--**The below with clause starting from with returns the same table structure as above temp table**
( WITH batch_id_dtl AS
(SELECT a.*,
rownum rnum
FROM
(SELECT MIN(creation_date) min_cr,
MAX(creation_date) max_cr,
batch_id
FROM oalterr.q_audit_results
GROUP BY batch_id
ORDER BY 1 DESC
)a
WHERE BATCH_ID <= 251940
),
curr_cnt AS
......rest of the code......
);
RETURN C_TAB;
END NEW_FUNCTION;
The above function returns the following error:
expression 'C_TAB' is inappropriate as the left hand side of an assignment statement.
Can anyone please tell me what type should I add in return part and what am I doing wrong in the execution part between begin and end.
For sample table (the one you posted)
SQL> select * from temp;
NODE CURR_CNT PREV_CNT DIFF
------ ---------- ---------- ----------
First 20 40 20
Second 30 70 40
create type at SQL level so that function recognizes it:
SQL> create or replace type t_row as object
2 (node varchar2(10),
3 curr_cnt number,
4 prev_cnt number,
5 diff number);
6 /
Type created.
SQL> create or replace type t_tab as table of t_row;
2 /
Type created.
Function returns a type; for example, number, varchar2 or - in your case - t_tab:
SQL> create or replace function new_function
2 return t_tab
3 is
4 l_tab t_tab;
5 begin
6 select t_row(node, curr_cnt, prev_cnt, diff)
7 bulk collect
8 into l_tab
9 from temp;
10 return l_tab;
11 end new_function;
12 /
Function created.
So far so good. Now, call it. One way is straightforward, just like selecting sysdate (which is also a function):
SQL> select new_function from dual;
NEW_FUNCTION(NODE, CURR_CNT, PREV_CNT, DIFF)
--------------------------------------------------------------------------------
T_TAB(T_ROW('First', 20, 40, 20), T_ROW('Second', 30, 70, 40))
but that looks kind of ugly. Better way is
SQL> select * from table(new_function);
NODE CURR_CNT PREV_CNT DIFF
---------- ---------- ---------- ----------
First 20 40 20
Second 30 70 40
SQL>
The easiest way is to create the function with the PIPELINED clause and then, in body, using the native PL/SQL approach, in a loop iterate trough your select as cursor and return each row with PIPE ROW.
Aditionaly, it would be better to declare the return type and the table function in a package and to avoid generic return types.
Ofc. you then use such a function as
select * from table(package.function())
See also: https://docs.oracle.com/cd/B19306_01/appdev.102/b14289/dcitblfns.htm

inconsistent output between sql developer and stored procedure

When I execute the below query it will return C507,
but when I use it as sub query in one of my stored procedure it only returns C.
SELECT *
FROM ( SELECT MAX (REGEXP_SUBSTR ('C507|C507',
'[^|]+',
1,
LEVEL))
serial
FROM DUAL
CONNECT BY LEVEL <=
LENGTH ('C507|C507')
- LENGTH (REPLACE ('C507|C507', '|', ''))
+ 1);
I couldn't see any problem( tell us if your way is different ) :
SQL> Create or Replace Procedure Get_Serial( o_value out varchar2 ) Is
2 Begin
3 Select *
4 Into o_value
5 From (Select Max(Regexp_Substr('C507|C507', '[^|]+', 1, Level)) serial
6 From Dual
7 Connect By Level <= Length('C507|C507') -
8 Length(Replace('C507|C507', '|', '')) + 1);
9 End;
10 /
Procedure created
SQL> var serial varchar2;
SQL> exec Get_Serial(:serial);
PL/SQL procedure successfully completed
serial
---------
C507
It is a little bit difficult to guess what you really are doing (as you posted different queries with different input data, unformatted within the comment) so - have a look a this example.
I've modified your last query so that it returns DEP_ID and MAX piece of the NAME column.
SQL> CREATE TABLE dept
2 (
3 dep_id NUMBER,
4 name VARCHAR2 (50)
5 );
Table created.
SQL> INSERT INTO dept VALUES (1, 'mirko|mirko');
1 row created.
SQL> INSERT INTO dept VALUES (2, 'angelo|angelo');
1 row created.
SQL> INSERT INTO dept VALUES (3, 'angelo|mirko');
1 row created.
SQL> SELECT * FROM dept;
DEP_ID NAME
---------- --------------------------------------------------
1 mirko|mirko
2 angelo|angelo
3 angelo|mirko
SQL>
SQL> SELECT d.dep_id,
2 MAX (REGEXP_SUBSTR (d.name,
3 '[^|]+',
4 1,
5 LEVEL))
6 serial
7 FROM dept d
8 WHERE dep_id = &par_dep_id
9 CONNECT BY LEVEL <= LENGTH (d.name) - LENGTH (REPLACE (d.name, '|', '')) + 1
10 GROUP BY d.dep_id;
Enter value for par_dep_id: 1
DEP_ID SERIAL
---------- --------------------------------------------------
1 mirko
SQL> /
Enter value for par_dep_id: 2
DEP_ID SERIAL
---------- --------------------------------------------------
2 angelo
SQL> /
Enter value for par_dep_id: 3
DEP_ID SERIAL
---------- --------------------------------------------------
3 mirko
SQL>
Saying that you have problems with a stored procedure: you should post (by editing your initial message) its code. Is it really a procedure, or a function? Query returns 2 values - does that procedure have 2 OUT parameters? If it is a function, what does it return (what datatype)?
From my point of view, query is OK as is, but - you wouldn't be posting a question here if there's no problem, but it is unclear which problem you have.
Once again: I suggest you post copy/paste of your own SQL*Plus session so that we could see what you did and how Oracle responded. Without it, we're just blindly guessing.
I created a function for this query and call it on my SP instead of using it as a sub query on my stored procedure.

Join nested table and normal table to fetch the result

I am having a normal table temp and a nested table temp_nt
Temp
-------------
ID Status
-------------
1 open
2 close
3 open
4 open
5 close
---------------
Suppose my nested table is having list of ID, X
Lets say the data in nested table is like
temp_nt(1).ID=1 temp_nt(1).X='ANC'
temp_nt(2).ID=2 temp_nt(2).X='pqr'
temp_nt(3).ID=3 temp_nt(3).X='ANCF'
temp_nt(4).ID=4 temp_nt(4).X='ANCF'
Can it be possible to join both to get the data like below,
Status COUNT
-----------------------
open 3
close 1
-----------------------
Since ID=5 is not present in the nested table, therefore it is excluded from the count
It would help to define exactly what objects you're working with...
You have a table with 5 rows of data
SQL> create table foo(
2 id number,
3 status varchar2(10)
4 );
Table created.
SQL> insert into foo values( 1, 'open' );
1 row created.
SQL> insert into foo values( 2, 'close' );
1 row created.
SQL> insert into foo values( 3, 'open' );
1 row created.
SQL> insert into foo values( 4, 'open' );
1 row created.
SQL> insert into foo values( 5, 'close' );
1 row created.
But then how is your nested table defined? Is it defined in SQL or PL/SQL? Are you using the object from SQL or PL/SQL?
If you have defined the nested table in SQL
SQL> create type foo_obj is object (
2 id number,
3 status varchar2(10)
4 );
5 /
Type created.
SQL> create type foo_nt
2 as table of foo_obj;
3 /
Type created.
And you are using the nested table in PL/sQL, you can use the TABLE operator
SQL> ed
Wrote file afiedt.buf
1 declare
2 l_foos foo_nt := new foo_nt();
3 begin
4 l_foos.extend(4);
5 l_foos(1) := new foo_obj( 1, 'ANC' );
6 l_foos(2) := new foo_obj( 2, 'pqr' );
7 l_foos(3) := new foo_obj( 3, 'ANCF' );
8 l_foos(4) := new foo_obj( 4, 'ANCF' );
9 for x in (select t.status, count(*) cnt
10 from foo t,
11 table( l_foos ) l
12 where t.id = l.id
13 group by t.status)
14 loop
15 dbms_output.put_line( x.status || ' ' || x.cnt );
16 end loop;
17* end;
SQL> /
close 1
open 3
PL/SQL procedure successfully completed.
Is that what you're looking for? Or do you have a different setup?
If you are defining a local collection in PL/SQL, you won't be able to use that collection in a SQL statement since the SQL engine isn't able to access any information about the collection type. If you want to use the collection in SQL, it would make much more sense to define the collection in SQL.

Resources