Join nested table and normal table to fetch the result - oracle

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.

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}

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

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

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

Insert all records bar those in a banned list

I have before insert trigger on table1. If some data (ID) is not allowed an application error is raised.
But, when I use, for example, insert into table1 select id from table2 where id in (1,2,3) And if only ID '3' is not allowed, the others ID's (1 and 2) are not inserted as well.
How can I overcome this? The trigger code is similar to:
CREATE OR REPLACE TRIGGER t1_before_insert BEFORE INSERT
ON table1
FOR EACH ROW
DECLARE
xx number(20);
BEGIN
select id into xx from blocked_id where id=:new.id;
if :new.id=xx then raise_application_error(-20001, '--');
end if;
END;
Okay, two points. Firstly, you're risking a NO_DATA_FOUND exception with your SELECT INTO ..., raising this exception will kill your entire insert. Secondly you're raising an exception, which will stop your entire insert.
You need to ignore those IDs that are in your blocked table rather than raise an exception. To follow your original idea one method would be to utilise the NO_DATA_FOUND exception to only insert if nothing is found. I create a view on your table and define an INSTEAD OF trigger on this.
I would not use this method though (see below)
If we set-up a test-environment:
SQL> create table tmp_test ( id number );
Table created.
SQL> create table tmp_blocked ( id number );
Table created.
SQL> insert into tmp_blocked values (3);
1 row created.
Then you can use the following:
SQL> create or replace view v_tmp_test as select * from tmp_test;
View created.
SQL> create or replace trigger tr_test
2 instead of insert on v_tmp_test
3 for each row
4
5 declare
6
7 l_id tmp_test.id%type;
8
9 begin
10
11 select id into l_id
12 from tmp_blocked
13 where id = :new.id;
14
15 exception when no_data_found then
16 insert into tmp_test values (:new.id);
17 end;
18 /
Trigger created.
SQL> show error
No errors.
SQL> insert into v_tmp_test
2 select level
3 from dual
4 connect by level <= 3;
3 rows created.
SQL> select * from tmp_test;
ID
----------
1
2
As I said, I would not use triggers; a more efficient way of doing it would be to use MERGE. Using the same set-up as above.
SQL> merge into tmp_test o
2 using ( select a.id
3 from ( select level as id
4 from dual
5 connect by level <= 3 ) a
6 left outer join tmp_blocked b
7 on a.id = b.id
8 where b.id is null
9 ) n
10 on ( o.id = n.id )
11 when not matched then
12 insert values (n.id);
2 rows merged.
SQL>
SQL> select * from tmp_test;
ID
----------
1
2
An even easier alternative would be to just use a MINUS
insert into tmp_test
select level
from dual
connect by level <= 3
minus
select id
from tmp_banned
I really don't like the use of a trigger and an error in this way -- data integrity is not really what triggers are for. This seems to me to be a part of the application that should be included in the application code, perhaps in a procedure that acts as an API for inserts into the table.

PL/SQL procedures same row

Table before solve:
ID NAME DATA
1 zhang 9
1 zhang 12
2 wang 1
2 wang 2
/this is the table before solved/
Table after solve:
ID NAME DATA
1 DIY 13
2 DIY 3
/this is what I want to get result/
There is the procedure:
update A a
set a.date=(select max(f_get(f.id,f.date,g.date))
from A f,A g
where f.date!=g.date
and f.id=a.id);
--function f_get()
create or replace function f_get
(id in varchar2,date in varchar,date2 in varchar )
return varchar is
Result varchar
date3 varchar(4);
begin
select nvl(date,date2) into date3
from dual;
Result:=date3;
delete from A a
where a.ID=id
and a.date=date2;--there is error
return(Result);
end f_get;
Your question does its best to hide itself, but this is the point:
"--there is error "
The error you get is (presumably) ORA-14551: cannot perform a DML operation inside a query, which you are getting because you are calling a FUNCTION which includes a DELETE command from a SELECT statement.
Oracle's transactional model doesn't allow queries to change the state of the database. Instead of a FUNCTION you need to write a procedure.
Although, if you want to remove duplicate rows, a straight SQL solution will suffice. Something like
delete from a
where (id, date) not in
( select id, max(date) from a
group by id)
/
You really should pay attention how to write questions. It would help us to help you. This is my guess what you are looking for. Unfortunately I don't have 9i available, but hope this helps !
create table so7t (
id number,
name varchar2(10),
data number -- date is a reserved word and can't be used as identifier
);
-- 1001
insert into so7t values (1, 'zhang', 9);
-- 1100
insert into so7t values (1, 'zhang', 12);
-- 0001
insert into so7t values (2, 'wang', 1);
-- 0010
insert into so7t values (2, 'wang', 2);
select * from so7t;
/* from http://www.dbsnaps.com/oracle/bitwise-operators-in-oracle/ */
create or replace function bitor (x number, y number)
return number
is
begin
return (x+y)-bitand(x,y);
end;
/
show errors
create or replace procedure solve (
p_id in number
) as
type ids_t is table of number;
v_ids ids_t;
v_result number := 0;
begin
select data bulk collect into v_ids from so7t where id = p_id;
for i in v_ids.first .. v_ids.last loop
v_result := bitor(v_result, v_ids(i));
end loop;
delete from so7t where id = p_id;
insert into so7t values (p_id, 'DIY', v_result);
end;
/
begin
solve(1);
commit;
solve(2);
commit;
end;
/
Table before solve:
ID NAME DATA
---------- ---------- ----------
1 zhang 9
1 zhang 12
2 wang 1
2 wang 2
Table after solve:
ID NAME DATA
---------- ---------- ----------
1 DIY 13
2 DIY 3

Resources