PL/SQL procedures same row - oracle

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

Related

How do I modify the values of a collection inside a table?

Basically, given a table with the below structure, I want to add a city at the end of a list of cities so that it'll be visited last in a trip with a number/id input by keyboard.
CREATE TYPE type_cities IS VARRAY(101) of varchar2(12);
CREATE TABLE trip(
trip NUMBER(4),
name VARCHAR2(20),
cities type_cities,
status varchar2(12)
);
declare
nrTrip number(4) := &nr;
name_city varchar2(12) := &namecity;
number_last number(4);
begin
number_last = trip(nrTrip).cities.count();
trip(nrTrip).cities.extend();
select name_city into trip(nrTrip).cities(number_last+1);
end;
I don't know the syntax neccessary to do this (and I'd ask why it doesn't work if that's alright, it didn't work with
trip(nrTrip).cities(number_last+1) := name_city
either)
Here's one option: fetch the row first, update it (i.e. add a new city into the array), update the table.
Type and table:
SQL> CREATE TYPE type_cities IS VARRAY(101) of varchar2(12);
2 /
Type created.
SQL> CREATE TABLE trip(
2 trip NUMBER(4),
3 name VARCHAR2(20),
4 cities type_cities,
5 status varchar2(12)
6 );
Table created.
Initial record (otherwise, there's nothing to update):
SQL> insert into trip (trip, name, cities, status)
2 values (1, 'Test', type_cities('London'), 'OK');
1 row created.
Procedure:
SQL> declare
2 nrTrip number(4) := &nr;
3 l_row trip%rowtype;
4 name_city varchar2(12) := '&namecity';
5 number_last number(4);
6 begin
7 select *
8 into l_row
9 from trip
10 where trip = nrTrip;
11
12 number_last := l_row.cities.count;
13
14 l_row.cities.extend;
15 l_row.cities(number_last + 1) := name_city;
16
17 update trip set cities = l_row.cities
18 where trip = nrTrip;
19 end;
20 /
Enter value for nr: 1
Enter value for namecity: Zagreb
PL/SQL procedure successfully completed.
Result:
SQL> select * from trip;
TRIP NAME CITIES STATUS
---------- -------------------- ----------------------------------- ------------
1 Test TYPE_CITIES('London', 'Zagreb') OK
--------
SQL> here it is

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.

how to look up another field in Oracle Function

Table 1
ID
----------
1
2
3
4
5
Table 2
ID Desc
------------------------------
A1 Apple
A2 Pear
A3 Orange
I am trying to create a Function in Oracle, so that it add the prefix 'A' in Table 1, and after that I want to look up in Table 2 to get the DESC returned. It has to be a function.
Thank you!!!
You may use the following for creation of such a function :
Create or Replace Function Get_Fruit( i_id table2.description%type )
Return table2.description%type Is
o_desc table2.description%type;
Begin
for c in ( select description from table2 where id = 'A'||to_char(i_id) )
loop
o_desc := c.description;
end loop;
return o_desc;
End;
where
no need to include exception handling, because of using cursor
instead of select into clause.
using table_name.col_name%type for declaration of data types for
arguments or variables makes the related data type of the columns
dynamic. i.e. those would be able to depend on the data type of the
related columns.
the reserved keywords such as desc can not be used as column names
of tables, unless they're expressed in double quotes ("desc")
To call that function, the following might be preferred :
SQL> set serveroutput on
SQL> declare
2 i_id pls_integer := 1;
3 o_fruit varchar2(55);
4 begin
5 o_fruit := get_fruit( i_id );
6 dbms_output.put_line( o_fruit );
7 end;
8 /
Apple
PL/SQL procedure successfully completed
I am not sure with your question- Are you trying to achieve something like this:-
CREATE OR REPLACE FUNCTION Replace_Value
(
input_ID IN VARCHAR2
) RETURN VARCHAR2
AS
v_ID varchar(2);
BEGIN
begin
SELECT distinct a.ID into v_id from Table 2 a where a.ID in (select 'A'||b.id from table1 b where b.id=input_ID);
exception
when others then
dbms_output.put_line(sqlcode);
end;
RETURN v_id;
END Replace_Value;
Are you trying for something like this?
CREATE OR replace FUNCTION replace_value (table_name IN VARCHAR2,
input_id IN INTEGER)
RETURN VARCHAR2
AS
v_desc VARCHAR(20);
BEGIN
SELECT descr
INTO v_desc
FROM table2
WHERE id = 'A' || input_id
AND ROWNUM = 1; -- only needed if there are multiple rows for each id.
RETURN v_desc;
END replace_value;
You may also add an exception handling for NO_DATA_FOUND or INVALID_NUMBER

How to change below procedure to (update & insert) instead of (delete &insert) in Oracle

I want to update the record if already exists based on where condition or else insert. I have written query with delete and insert I am facing difficulty into converting into update and insert.
PS: I have tried using SQL%ROWCOUNT but I think i missed somewhere in syntax.
Any help would be appreciated.
Below is the proc i am using
Create Or Replace Procedure sal_proc(Empid Varchar2,Fmdt Date,bp Number)
As
Begin
--delete
delete from Emp_Sal
Where Empid = Empid
And Fmdt = Fmdt;
--insert
Insert Into Emp_Sal(empid,fmdt,Basicpay) Values (empid,fmdt,Bp);
End;
You do not need a procedure; you can use MERGE to update or insert a row at the same time:
merge into Emp_Sal e
using (
/* your values to insert/update */
select 2 as Empid, 'c' as Fmdt, 100 as Bp from dual
) x
on ( e.Empid = x.Empid And e.Fmdt = x.Fmdt)
when matched
then /* if a record exists, update */
update set Basicpay = Bp
when not matched
then /* it the record does not exist, insert */
insert values (x.empid, x.fmdt, x.Bp)
Of course you can use this inside a procedure to handle your input parameters or do whatever you may need to do in your procedure:
Create Or Replace Procedure sal_proc(Empid Varchar2,Fmdt Date,bp Number)
As
Begin
merge into Emp_Sal e
using (
/* your values to insert/update */
select Empid as Empid, Fmdt as Fmdt, bp as Bp from dual
) x
on ( e.Empid = x.Empid And e.Fmdt = x.Fmdt)
when matched
then /* if a record exists, update */
update set Basicpay = Bp
when not matched
then /* it the record does not exist, insert */
insert (empid,fmdt,Basicpay) values (x.empid, x.fmdt, x.Bp);
End;
An hint: use parameter names different fron column names to avoid confusion; a good practice could be use parameter names like p_XXX; just an example of how dangerous this can be:
SQL> create or replace procedure checkPar(n in number) is
2 c number;
3 begin
4 select count(1)
5 into c
6 from checkTab
7 where n = n;
8 --
9 dbms_output.put_line(c);
10 end;
11 /
Procedure created.
SQL> select * from checkTab;
N
----------
1
2
3
SQL> exec checkPar(1);
3
PL/SQL procedure successfully completed.
SQL> exec checkPar(999);
3
PL/SQL procedure successfully completed.
Not very efficient way but should work if huge data is not present in tables
CREATE OR REPLACE PROCEDURE sal_proc (E_Empid VARCHAR2, F_Fmdt DATE, b_bp NUMBER)
AS
var number;
BEGIN
Begin
--Checking if record exists
select 1
into var
from Emp_Sal
Where Empid = E_Empid
And Fmdt = F_Fmdt;
exception
when no_data_found then
var:= 0;
End;
if var <> 1 then
--insert
INSERT INTO Emp_Sal (empid, fmdt, Basicpay)
VALUES (e_empid, f_fmdt, b_Bp);
else
update Emp_Sal
set col ....<>;
end if;
END;

Resources