inconsistent output between sql developer and stored procedure - oracle

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.

Related

HOW TO FILL A TYPE OR COLLECTION WITH NUMBERS FROM 10M TO 30M IN PLSQL ORACLE

Hello stackoverflow friends!
Please help, i have to assign a value since 100 to 300 in an cursor or a type or a table (i don't know which of them are better for this project). First i want to fill the type or cursor with those values (10M to 30M) and then choose randomly one of them but just once, i mean, it can't pick 102 twice, for example. There are millions of entries and i wouldn't want to affect the performance of the database. I tried do it with a cursor and n+1 but its so slow...
Thanks my Oracle's friends for your help and suggestions.
I believe n + 1 is slow; if you do it that way, it certainly takes time to insert 20 million rows one by one. Another option is to use row generator.
I have 21XE on laptop (nothing special; Intel i5, 8GB RAM) which isn't enough memory to do it in one go:
SQL> create table test (id number);
Table created.
SQL> insert into test
2 select 10e6 + level - 1
3 from dual
4 connect by level <= 20e6;
insert into test_20mil
*
ERROR at line 1:
ORA-30009: Not enough memory for CONNECT BY operation
SQL>
If you ask whether it really works?, the answer is yes - see for a small sample:
SQL> select level
2 from dual
3 connect by level <= 5;
LEVEL
----------
1
2
3
4
5
SQL>
Therefore, I used a loop to do it 20 times, each of them inserting 1 million rows at a time into a table (not row-by-row). Why not collection? Memory issues!
SQL> create table test (id number primary key, cb_picked number(1));
Table created.
SQL> set serveroutput on
SQL> set timing on
SQL> declare
2 l_mil number := 10e6;
3 begin
4 for i in 1 .. 20 loop
5 insert into test (id)
6 select l_mil + level - 1
7 from dual
8 connect by level <= 1e6;
9 dbms_output.put_Line('inserted ' || sql%rowcount);
10 l_mil := l_mil + 1e6;
11 end loop;
12 end;
13 /
inserted 1000000
inserted 1000000
inserted 1000000
inserted 1000000
inserted 1000000
inserted 1000000
inserted 1000000
inserted 1000000
inserted 1000000
inserted 1000000
inserted 1000000
inserted 1000000
inserted 1000000
inserted 1000000
inserted 1000000
inserted 1000000
inserted 1000000
inserted 1000000
inserted 1000000
inserted 1000000
PL/SQL procedure successfully completed.
Elapsed: 00:01:25.77
As you can see, it took slightly less than minute and a half.
SQL> set timing off
SQL> select count(*) from test;
COUNT(*)
----------------
20,000,000
SQL> select min(id), max(id) from test;
MIN(ID) MAX(ID)
---------------- ----------------
10,000,000 29,999,999
SQL>
That was inserting; what about fetching randomly picked rows? Use dbms_random.value function. To avoid selecting already picked values twice, update table's cb_picked column. To do that, create an autonomous transaction function (why? So that you could perform DML - update - and return value).
SQL> create or replace function f_rnd
2 return number
3 is
4 pragma autonomous_transaction;
5 retval number;
6 begin
7 select id
8 into retval
9 from test
10 where cb_picked is null
11 and id = round(dbms_random.value(10e6, 20e6));
12 update test set cb_picked = 1
13 where id = retval;
14 commit;
15 return retval;
16 end;
17 /
Function created.
Let's try it:
SQL> select f_rnd from dual;
F_RND
----------
19191411
SQL> select f_rnd from dual;
F_RND
----------
16411522
SQL> select * from test where cb_picked = 1;
ID CB_PICKED
---------- ----------
16411522 1
19191411 1
SQL>
That's it, I guess.

Get String, Split By delimiter and inset to table Procedure in Oracle [duplicate]

This question already has answers here:
How to convert comma separated values to rows in oracle?
(6 answers)
Closed 1 year ago.
I have Names table , z_names_seq sequence for AutoGenerate IDs (in names table) and Trigger z_names_on_insert that uses z_names_seq for Generate ID.
--Create Names Table
CREATE TABLE z_names (ID number,
NAME VARCHAR2(200))
--Sequence For Names Table
CREATE SEQUENCE z_names_seq
MINVALUE 1
START WITH 1
INCREMENT BY 1;
--Trigger For ID In Names Table
create or replace TRIGGER z_names_on_insert
BEFORE INSERT ON z_names
FOR EACH ROW
BEGIN
SELECT z_names_seq.nextval
INTO :new.ID
FROM dual;
END;
AND Whats The Question:
Write the procedure that gets string value, separate it by delimiter and insert into z_names table (ID comes from trigger and NAME comes from input string split by comma ( , )).
Example Of Input and output:
input : john, jim, jack
output:
ID
NAME
1
john
2
jim
3
jack
Split the string into rows. Here's one option which shows how to do that. INSERT doesn't contain the ID column as it is populated by a trigger.
SQL> create or replace procedure p_ins (par_string in varchar2) is
2 begin
3 insert into z_names (name)
4 select trim(regexp_substr(par_string, '[^,]+', 1, level))
5 from dual
6 connect by level <= regexp_count(par_string, ',') + 1;
7 end;
8 /
Procedure created.
SQL> exec p_ins('john,jim,jack');
PL/SQL procedure successfully completed.
SQL> select * From z_names;
ID NAME
---------- --------------------
1 john
2 jim
3 jack
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

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