I have a question about stored procedures in Oracle.
Below is the stored procedure and tables as it stands:
create table STORES
(
ID number,
NAME varchar2(100),
CITY varchar2(100),
EXPIRES DATE
)
insert into stores values(1, 'Store 1', 'City 1', sysdate);
insert into stores values(2, 'Store 2', 'City 1', sysdate);
insert into stores values(3, 'Store 3', 'City 2', sysdate);
create table CLOSED
(
ID number,
NAME varchar2(100),
CITY varchar2(100)
)
create or replace PROCEDURE
pr_TestProc(subQuery IN VARCHAR2)
IS
begin
insert into CLOSED (ID, NAME, CITY)
select ID, NAME, CITY
from STORES
where ID in (1, 2, 3);
end;
What I'd like to do is replace the "in" values with the subQuery passed in as a parameter.
So if I run the procedure like:
execute pr_TestProc('select ID from STORES where EXPIRES <= sysdate');
The query passed in should be executed as a subquery being run inside the procedure.
Something like:
insert into CLOSED (ID, NAME, CITY) select ID, NAME, CITY
from STORES
where ID in (execute(subQuery));
Obviously this doesn't work, but what would be the best way to achieve this, or is it even possible?
Thanks,
Brian
You can use dynamic SQL
create or replace PROCEDURE pr_TestProc(subQuery IN VARCHAR2)
IS
l_sql_stmt varchar2(1000);
begin
l_sql_stmt := 'insert into CLOSED (ID, NAME, CITY) ' ||
' select ID, NAME, CITY ' ||
' from STORES ' ||
' where id in (' || subquery || ')';
dbms_output.put_line( l_sql_stmt );
EXECUTE IMMEDIATE l_sql_stmt;
end;
SQL> execute pr_TestProc('select ID from STORES where EXPIRES <= sysdate');
PL/SQL procedure successfully completed.
SQL> column name format a20
SQL> column city format a20
SQL> select * from closed;
ID NAME CITY
---------- -------------------- --------------------
1 Store 1 City 1
2 Store 2 City 1
3 Store 3 City 2
If you are calling this procedure rarely in a system that is relatively idle, that will probably work acceptably well. If you are calling it frequently with different subqueries, however, you are going to generate a ton of non-sharable SQL statements. That will force Oracle to do a lot of hard parsing. It will also flood your shared pool with non-sharable SQL statements, likely forcing out plans that you want to be cached, forcing more hard parsing when those SQL statements are then executed again. And if you do it fast enough, you're likely to end up getting errors (or causing other processes to get errors) that Oracle couldn't allocate enough memory in the shared pool for a particular query. Plus, dynamic SQL is harder to write, harder to debug, vulnerable to SQL injection attacks, etc. so it generally makes the system harder to deal with.
A more elegant solution would be to pass in a collection rather than a subquery would be to pass in a collection
SQL> create type id_coll
2 as table of number;
3 /
Type created.
SQL> ed
Wrote file afiedt.buf
1 create or replace PROCEDURE pr_TestProc( p_ids IN id_coll)
2 is
3 begin
4 insert into CLOSED (ID, NAME, CITY)
5 select ID, NAME, CITY
6 from STORES
7 where ID in (select column_value
8 from table( p_ids ) );
9* end;
SQL> /
Procedure created.
SQL> ed
Wrote file afiedt.buf
1 declare
2 l_ids id_coll;
3 begin
4 select id
5 bulk collect into l_ids
6 from stores
7 where expires <= sysdate;
8 pr_TestProc( l_ids );
9* end;
SQL> /
PL/SQL procedure successfully completed.
SQL> select * from closed;
ID NAME CITY
---------- -------------------- --------------------
1 Store 1 City 1
2 Store 2 City 1
3 Store 3 City 2
Maybe you dont need to pass the query into the stored procedure. Just call the stored procedure with the query as a parameter.
create or replace PROCEDURE
pr_TestProc(listOfIds_IN IN integer)
IS
begin
ids integer := listOfIds_IN;
insert into CLOSED (ID, NAME, CITY)
select ID, NAME, CITY from STORES where ID in (ids );
end;
Call the stored procedure like this:
pr_TestProc(SELECT id FROM Table WHERE condition)
Related
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 1 year ago.
Improve this question
I Have Two Tables
first :
CREATE TABLE z_names (ID number,
NAME VARCHAR2(200))
Second:
CREATE TABLE Z_FNAME
("FAMILY" VARCHAR2(200 BYTE),
"ID" NUMBER,
"NAME" VARCHAR2(200 BYTE),
"NAME_ID" NUMBER
)
How Write Procedure for Insert Into Second Table By String ,With This Conditions :
1- Example Of input String : nam1;fam1,nam2;fam2 => nam1 Insert Into NAME Column And fam1 Into FAMILY
2-ID Generated By Trigger I Have Writed Before
3-NAME_ID Comes From FIRST table
With a table you previously created (I remember that question so I reused it, as well as the sequence), you'd split input string into rows and fetch names, somehow; I chose regular expressions, presuming that names consist of only one word.
Table with names (inserted previously):
SQL> select * From z_names;
ID NAME
---------- --------------------
1 john
2 jim
3 jack
Procedure expects that strings you're passing as parameters contain name which is already inserted into the z_names table.
SQL> create or replace procedure p_test (par_string in varchar2) is
2 begin
3 insert into z_fname (family, id, name, name_id)
4 with temp as
5 (select regexp_substr(par_string, '[^,]+', 1, level) nf
6 from dual
7 connect by level <= regexp_count(par_string, ',') + 1
8 )
9 select regexp_substr(t.nf, '\w+', 1, 2) family,
10 z_names_seq.nextval id,
11 regexp_substr(t.nf, '\w+', 1, 1) name,
12 n.id
13 from temp t join z_names n on n.name = regexp_substr(t.nf, '\w+', 1, 1);
14 end;
15 /
Procedure created.
Testing:
SQL> exec p_test('john;Little,jack;Foot,jim;Bigfoot');
PL/SQL procedure successfully completed.
SQL> select * from z_fname;
FAMILY ID NAME NAME_ID
---------- ---------- -------------------- ----------
Little 10 john 1
Bigfoot 11 jim 2
Foot 12 jack 3
SQL>
However, from my point of view, that's somewhat awkward approach. I don't know what tables you use in this exercise represent, but I'd expect them to be related to each other (via referential integrity constraint). It also means that z_fname most probably isn't normalized - you would store only the foreign key constraint value which points to the master table (z_names), not store both name AND id.
It's just a template and you should scheck it and modify as you want (that's all what i can do with all data than you give me):
CREATE OR REPLACE PROCEDURE some_name
(name_in IN varchar2, fam_in IN varchar2)
IS
max_id_names number; --variable for max id from z_names and new name
max_id_fam number; --variable for max id from Z_FNAME
BEGIN
--1. Find max id from "ID Generated By Trigger I Have Writed Before" and SET IT TO max_id_names
INSERT INTO z_names
(
max_id_names,
NAME
)
VALUES
(
:max_id_names, --you will find it
name_in -- IN param
);
--2. Find max id from "ID Generated By Trigger I Have Writed Before" and SET IT TO max_id_fam
INSERT INTO Z_FNAME
(
FAMILY,
ID,
NAME,
NAME_ID
)
VALUES
(
fam_in, -- IN param
:max_id_fam, -- you will find it
name_in, --IN param
:max_id_names --you already have it because you inserted the names
);
END;
/
I have a table with many columns, and what I would like to do is duplicate all of the rows in the table, but also update one of the columns to a new value.
For example lets say I have the table below. I want to add to my table a duplicate of each row, except instead of BASIC access, it will have 'ADVANCED':
Before:
NAME, GENDER, ACCESS
----------------------
STEVE, MALE, BASIC
MOLLY, FEMALE, BASIC
After
NAME, GENDER, ACCESS
----------------------
STEVE, MALE, BASIC
MOLLY, FEMALE, BASIC
STEVE, MALE, ADVANCED
MOLLY, FEMALE, ADVANCED
Is there a way to do this without specifying all columns? I have 60 columns in the table, and the structure can change (meaning columns may be added, removed, renamed, etc).
Is it possible in Oracle SQL to automate this?
Just use insert . . . select:
insert into t (name, gender, access)
select name, gender, 'ADVANCED'
from t;
You need to list all the columns. You can shorten the manual process by using a query to generate the list. If you had to do this a lot and always knew you were leaving out access and access is the last column, you could use a view:
create view v_t as
select . . . -- all but access
from t;
insert into t ( . . . )
select v.*, 'ADVANCED'
from v_t;
Or you could use dynamic SQL to generate the statement.
However, I don't recommend any of those. Instead I would be concerned about a data model where you are regularly adding and modifying the columns in a table. That sounds dangerous.
Without specifying all the columns? With some help of a "temporary" table, here's how:
Your current table:
SQL> create table test
2 (name varchar2(10),
3 gender varchar2(20),
4 caccess varchar2(20));
Table created.
SQL> insert into test
2 select 'steve', 'male', 'basic' from dual union all
3 select 'molly', 'female', 'basic' from dual;
2 rows created.
Create a "temporary" table as a copy of the "original" table
update column you want to modify
copy the whole "temporary" table to the "original"
drop the "temporary" table
SQL> create table test_temp as select * From test;
Table created.
SQL> update test_temp set caccess = 'advanced';
2 rows updated.
SQL> insert into test select * From test_temp;
2 rows created.
SQL> drop table test_Temp;
Table dropped.
SQL> select * From test;
NAME GENDER CACCESS
---------- -------------------- --------------------
steve male basic
molly female basic
steve male advanced
molly female advanced
SQL>
Apparently, that works, but - what if the original table is huge? It takes a lot of space, and its copy takes approximately twice as much. Why are you doing that, anyway?
Try below method with anonymous block to avoid listing columns in insert statements
CREATE TABLE ACCESS_CHN
(NAAME VARCHAR2(100),
GENDER VARCHAR2(20),
ACCCESS VARCHAR2(30))
INSERT into ACCESS_CHN values('STEVE','MALE','BASIC');
INSERT into ACCESS_CHN values('MOLLY','FEMALE','BASIC');
COMMIT;
DECLARE
column_list varchar2(2000):=NULL;
plsql_block VARCHAR2(1000);
BEGIN
select LISTAGG(column_name,',') within group (order by column_id)
into column_list
from user_tab_columns
where table_name='ACCESS_CHN';
plsql_block := 'CREATE TABLE ACCESS_CHN_BKP as select '|| column_list || ' from ACCESS_CHN';
EXECUTE IMMEDIATE plsql_block;
plsql_block := 'UPDATE ACCESS_CHN_BKP set ACCCESS=''ADVANCED'' ';
EXECUTE IMMEDIATE plsql_block;
COMMIT;
plsql_block := 'CREATE TABLE ACCESS_CHN_FINAL as select * from ACCESS_CHN
union all
select * from ACCESS_CHN_BKP';
EXECUTE IMMEDIATE plsql_block;
END;
--To rerun drop tables ACCESS_CHN_BKP and ACCESS_CHN_FINAL
We generate tables dynamically Eg. Table T_1, T_2, T_3, etc & we can get that table names from another table by following query.
SELECT CONCAT('T_', T_ID) AS T_NAME FROM T_NAMES WHERE T_KEY = 'ABC';
Now I want to get records from this retrieved table name. What can I do ?
I'm doing like following but that's not working :
SELECT * FROM (SELECT CONCAT('T_', T_ID) AS T_NAME FROM T_NAMES WHERE T_KEY = 'ABC')
FYI : I'm hitting two individual queries as of now though I want to eliminate one and I can not follow cursor/procedure approach due to some limitations.
A procedure which utilizes refcursor seems to be the most appropriate to me. Here's an example:
SQL> -- creating test case (your T_NAMES table and T_1 which looks like Scott's DEPT)
SQL> create table t_names (t_id number, t_key varchar2(3));
Table created.
SQL> insert into t_names values (1, 'ABC');
1 row created.
SQL> create table t_1 as select * from dept;
Table created.
SQL> -- a procedure; accepts KEY and returns refcursor
SQL> create or replace procedure p_test
2 (par_key in varchar2, par_out out sys_refcursor)
3 as
4 l_t_name varchar2(30);
5 begin
6 select 'T_' || t_id
7 into l_t_name
8 from t_names
9 where t_key = par_key;
10
11 open par_out for 'select * from ' || l_t_name;
12 end;
13 /
Procedure created.
OK, let's test it:
SQL> var l_out refcursor
SQL> exec p_test('ABC', :l_out)
PL/SQL procedure successfully completed.
SQL> print l_out
DEPTNO DNAME LOC
---------- -------------- -------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
SQL>
I could propose to you Dynamic SQL.
First of all, you need to create a cursor. The cursor will iterate by the dynamic tables. Then you could use dynamic SQL to create a query and then execute it.
So example:
https://livesql.oracle.com/apex/livesql/file/content_C81136WLRFYZF8ION6Q57GWE1.html - detailed cursor example.
https://docs.oracle.com/cd/B28359_01/appdev.111/b28370/dynamic.htm#i13057 - dynamic SQL in Oracle
I am a newbie to Oracle and this is my first post for Oracle queries.
Below is the existing query which inserts 1 row for each SP call.
I want to make change in the SP which would accept input as array where SAP system would would send the Array to Stored Procedure.
As you observe in SP, the value of ID is incremented each time with the each update. The SP will take this input of Phone and Text and insert the value of ID in sequence wise.The ID is not passed in the input.
CREATE OR REPLACE PROCEDURE DetailsTable
(
Phoneno IN NUMBER,
Text IN VARCHAR2
)
aS
BEGIN
INSERT INTO PERSON.DETAILS(
ID,
PHONENO,
TEXT,
COUNTRY,
LANG,
--PRIORITY,
SENDER)
VALUES (
DETAILS_seq.nextval ,
p_phoneno,
p_text ,
'RSA',
'EN',
'Nest-Payroll');
commit;
END DetailsTable;
Please guide.
SQL> CREATE OR REPLACE TYPE arraytype AS VARRAY(1000) OF VARCHAR2(100);
2 /
Type created
SQL> CREATE OR REPLACE PROCEDURE test_array (in_array arraytype) IS
2 BEGIN
3 FOR i IN 1..in_array.count LOOP
4 DBMS_OUTPUT.PUT_LINE(in_array(i));
5 END LOOP;
6 END;
7 /
Procedure created
SQL> DECLARE
2 var_array arraytype;
3 BEGIN
4 var_array := arraytype();
5 var_array.EXTEND(10);
6 var_array(1) := '1st sentence in the array';
7 var_array(2) := '2nd sentence in the array';
8 test_array(var_array);
9 END;
10 /
1st sentence in the array
2nd sentence in the array
We can use a Type in SQL but it needs to be declared as a SQL Type:
create or replace type person_t as object
(phoneno number
, text varchar2(100)
);
/
create or replace type person_nt as table of person_t
/
Use it like this:
CREATE OR REPLACE PROCEDURE DetailsTable
(
p_array in person_nt
)
aS
BEGIN
INSERT INTO PERSON.DETAILS(
ID,
PHONENO,
TEXT,
COUNTRY,
LANG,
--PRIORITY,
SENDER)
select DETAILS_seq.nextval ,
t.phoneno,
t.text ,
'RSA',
'EN',
'Nest-Payroll'
from table (p_array)t;
commit;
END DetailsTable;
/
I have a procedure which takes table type input parameter. Now I have to use this parameter in IN-clause of SELECT query.
CREATE TYPE ids IS TABLE OF NUMBER;
CREATE PROCEDURE (emp_ids IN ids) IS
CURSOR IS (SELECT * FROM EMPLOYEES WHERE EMP_ID IN (SELECT * FROM TABLE(emp_ids)); .....
But I found that this code is not going to work because local collection types cannot be used in an SQL statement.
Is there any alternate way to achieve using table type parameter in a SELECT statement?
According to what you have posted, you are declaring collection as schema object, not the local type. This means that you shouldn't have any problems of using it. Here is an example:
-- collection type as schema object
SQL> create or replace type ids is table of number;
2 /
Type created
SQL> create or replace procedure proc1 (emp_ids IN ids)
2 IS
3 cursor c is (
4 select first_name
5 from employees
6 where employee_id in (select column_value
7 from table(emp_ids)
8 )
9 );
10 begin
11 for i in c
12 loop
13 dbms_output.put_line(i.first_name);
14 end loop;
15 end;
16 /
Procedure created
SQL> exec proc1(ids(101, 103, 200));
Neena
Alexander
Jennifer
PL/SQL procedure successfully completed