reuse same "select bulk collect into" statement multiple times? - oracle

How can be reused the same "select bulk collect into" statement multiple times? So define once at the beginning and reuse shorter form later on? Well the statement is almost the same, every time the filter is different like this:
...where filter_condition = variable(i)

I don't think so.
Though, a simple option might be to create a view that utilizes the select statement you currently use, e.g.
SQL> create or replace view v_bulk as
2 select d.deptno, d.dname, e.ename, e.job, e.sal
3 from emp e join dept d on d.deptno = e.deptno;
View created.
Then, in PL/SQL procedure, reuse the view:
SQL> declare
2 type tr is record
3 (deptno number, dname varchar2(20), ename varchar2(20), job varchar2(20), sal number);
4 type tt is table of tr;
5 l_tab tt;
6 begin
7 select * bulk collect into l_tab from v_bulk --> this is always the same
8 where job = 'CLERK'; --> WHERE clause is changing
9
10 select * bulk collect into l_tab from v_bulk --> this is always the same
11 where sal > 2000; --> WHERE clause is changing
12 end;
13 /
PL/SQL procedure successfully completed.
SQL>

You can reuse the SELECT part of the query with dynamic SQL. Dynamic SQL may be a little slower and a little riskier (in terms of SQL injection possibilities) than the view-based solution offered by Littlefoot, but this approach doesn't require creating extra objects.
declare
v_numbers sys.odcivarchar2list;
v_sql clob := 'select 1 from dba_objects where 1=1';
begin
execute immediate v_sql||' and owner = ''SYSTEM''' bulk collect into v_numbers;
execute immediate v_sql||' and owner = ''DBSNMP''' bulk collect into v_numbers;
end;
/

Related

Update second column

I have a table "TEST_TABLE" with two columns TABLE_NAME and RECORD_COUNT.
enter image description here
We want to update the column RECORD_COUNT by taking the total records in table specified in TABLE_NAME.
You could do this with dynamic SQL but why be complicated?
Create a view
Create view my_tables as
Select
'table_1' as "table name",
count(*) as "rows"
From table_1
Add the following for each table
Union all
Select 'table_2',count(*) From table_2
You can then use the view like a table:
Select * from my_tables;
OK, but - why wouldn't you use information Oracle already provides for you? You should regularly gather statistics anyway, so:
SQL> execute dbms_stats.gather_schema_stats(ownname => 'SCOTT', estimate_percent => null);
PL/SQL procedure successfully completed.
and then fetch num_rows from user_tables:
SQL> select table_name, num_rows from user_tables where rownum <= 10;
TABLE_NAME NUM_ROWS
-------------------- ----------
EMP 14
DEPT 4
BONUS 0
SALGRADE 5
DUMMY 1
TBL_ERROR 1
AUDIT_TAB 2
SOURCE_DET 3
EXAMPLE 1
FLIGHT 0
10 rows selected.
SQL>
It can't be much worse than your attempt (due to possible frequent changes to tables' contents; inserts and deletes) because you'd collect your own data periodically (how often?) anyway.
If it has to be your way, then you'd use dynamic SQL, looping through all tables in your schema and actually count number of rows:
SQL> create table test_table
2 (table_name varchar2(30),
3 num_rows number);
Table created.
SQL> create or replace procedure p_test as
2 l_cnt number;
3 begin
4 execute immediate 'truncate table test_table';
5 for cur_R in (select table_name from user_tables) loop
6 execute immediate 'select count(*) from ' ||cur_R.table_name into l_Cnt;
7 insert into test_table (table_name, num_rows) values (cur_r.table_name, l_cnt);
8 end loop;
9 end;
10 /
Procedure created.
Running the procedure and viewing the result:
SQL> exec p_test;
PL/SQL procedure successfully completed.
SQL> select * From test_Table where rownum <= 10;
TABLE_NAME NUM_ROWS
-------------------- ----------
EMP 14
DEPT 4
BONUS 0
SALGRADE 5
DUMMY 1
TBL_ERROR 1
AUDIT_TAB 2
SOURCE_DET 3
EXAMPLE 1
FLIGHT 0
10 rows selected.
SQL>
Note that performance will suffer as number of tables and number of rows stored in them grows.
If I were you, I'd go for the first option.

Getting the SQL ID's of each statement executed in a stored procedure

I have the following Stored Procedure:
create or replace procedure insert_employee_to_dept (f_name IN varchar2, l_name IN varchar2, dept IN varchar2, tier IN char)
is
dept_count number;
t1_count number;
begin
INSERT INTO employees (id, first_name, last_name, department, tier)
VALUES (employee_sequence.NEXTVAL,
f_name,
l_name,
dept,
tier);
select count(*) into dept_count from employees where department = dept;
update dept_info set emp_count = dept_count where id = dept;
select count(*) into t1_count from employees where tier = 1;
update company set tier_one_count = t1_count where name = 'MyCompany';
end;
I enable SQL ID feedback using below statement
set feedback on sql_id;
Finally I run my Stored Procedure as such:
call insert_employee_to_dept('John', 'Doe', 'Finance', '5')
The output of the above command contains the SQL id of the PL/SQL block but this block does not have an execution block which can be queried using DBMS_XPLAN.DISPLAY_CURSOR
Is there a way to get all the SQL ID's that were executed when the stored procedure was called?
Following this answer, I enabled tracing and ran the procedure which generated the trace file on the server. Unfortunately in my use case I do not have access the file system of the database server
You may use all_statements system view to get all the statements executed in PL/SQL unit and their SQL_ID's.
create table t (
id int,
val int
)
/
create or replace procedure test_proc(
p_id in int
)
as
begin
insert into t(id, val) values(p_id, 100);
update t set val = 10;
delete from t where id = p_id;
end;
/
begin
test_proc(1);
end;
/
select
owner
, object_type
, object_name
, type
, line
, sql_id
, text
from all_statements
/
OWNER
OBJECT_TYPE
OBJECT_NAME
TYPE
LINE
SQL_ID
TEXT
DEMO
PROCEDURE
TEST_PROC
DELETE
8
85cd3d95cya5x
DELETE FROM T WHERE ID = :B1
DEMO
PROCEDURE
TEST_PROC
UPDATE
7
d6au94gzv1ydh
UPDATE T SET VAL = 10
DEMO
PROCEDURE
TEST_PROC
INSERT
6
bm74chwppp8w4
INSERT INTO T(ID, VAL) VALUES(:B1 , 100)
If you doing that for testing purpose, you can use dbms_application_info to set module and action, and then use them to select from v$sql/v$sqlarea/v$active_session_history/etc:
SQL> declare
2 n number;
3 begin
4 dbms_application_info.set_module('test_module','test_action');
5 select/*+test1*/ count(*) into n from dual;
6 select/*+test2*/ count(*) into n from dual;
7 dbms_application_info.set_module('','');
8 end;
9 /
PL/SQL procedure successfully completed.
SQL> select sql_id,substr(sql_text,1,50) sqltext50
2 from v$sqlarea
3 where module='test_module'
4 and action='test_action';
SQL_ID SQLTEXT50
------------- ------------------------------------
bavxnddfrxju6 SELECT/*+test2*/ COUNT(*) FROM DUAL
7d8853usybzdg SELECT/*+test1*/ COUNT(*) FROM DUAL
Btw, you can use module/action/client_id also for dbms_monitor:
Performing Application Tracing
DBMS_MONITOR
Or sometimes you can even use v$open_cursor (depends on many factors like open_cursor parameter, etc):
SQL> declare
2 n number;
3 begin
4 dbms_application_info.set_module('test_module','test_action');
5 select/*+test10*/ count(*) into n from dual;
6 select/*+test11*/ count(*) into n from dual;
7 dbms_application_info.set_module('','');
8 end;
9 /
PL/SQL procedure successfully completed.
SQL> select sql_id,substr(sql_text,1,50) sqltext50
2 from v$open_cursor
3 where sid=userenv('sid')
4 and user_name=user
5 and last_sql_active_time is not null
6 order by last_sql_active_time desc
7 fetch first 5 rows only;
SQL_ID SQLTEXT50
------------- ---------------------------------------------------
3669hp0tndbgu declare n number; begin dbms_application_inf
9szagfb62bhgs SELECT/*+test10*/ COUNT(*) FROM DUAL
9h8pabdtrb1wm select sql_id,substr(sql_text,1,50) sqltext50 from
1901dfp1ktg5d SELECT/*+test11*/ COUNT(*) FROM DUAL
bavxnddfrxju6 SELECT/*+test2*/ COUNT(*) FROM DUAL

Avoiding "select into" error in PL/SQL when filtering a table with a variable

In the following minimal PL/SQL query example, I can't get the query to work, in order to filter records from a table based on a certain date, which is stored in a variable.
The goal obviously is to end up with different query results, by changing the date variable to some other moment, different from sysdate.
This query does succeed in printing the calculation date variable's content, but the select query afterwards fails with ORA-06550: line 6, column 3 (so on 'select *' below) : PLS-00428: an INTO clause is expected in this SELECT statement.
DECLARE
v_calculation_date date := sysdate;
BEGIN
dbms_output.put_line(v_calculation_date);
select *
from t
WHERE to_number(to_char(trunc(t.some_date), 'YYYYMM'))
BETWEEN to_number(to_char(add_months(v_calculation_date,-24), 'YYYYMM'))
AND to_number(to_char(add_months(v_calculation_date,-1), 'YYYYMM'));
END;
OK, with a SELECT INTO then... This again prints the calculation date variable's content just fine, but the select query afterwards again fails, with the same error:
DECLARE
v_calculation_date date;
BEGIN
select sysdate into v_calculation_date from dual; -- dual is a dummy table with only one row and one column, just what we need here.
dbms_output.put_line(v_calculation_date);
select *
from t
WHERE to_number(to_char(trunc(t.some_date), 'YYYYMM'))
BETWEEN to_number(to_char(add_months(v_calculation_date,-24), 'YYYYMM'))
AND to_number(to_char(add_months(v_calculation_date,-1), 'YYYYMM'));
END;
I already have a SELECT INTO, why is the normal SELECT, which follows afterwards, then refused?
For the sake of completeness: this is a minimal query example for easy reproduction of the problem. My actual query is more complex, I eventually want something like this to work:
DECLARE
v_calculation_date date;
BEGIN
DROP TABLE t1;
CREATE TABLE t1 as (
WITH intermediary1 AS (
-- The actual query from above, as a subquery
SELECT *
FROM t
WHERE to_number(to_char(trunc(t.some_date), 'YYYYMM'))
BETWEEN to_number(to_char(add_months(v_calculation_date,-24), 'YYYYMM'))
AND to_number(to_char(add_months(v_calculation_date,-1), 'YYYYMM'));
)
)
/* other drops, creates, inserts ... */
END
What is wrong in my query?
Execution info: the SQL dialect is PL/SQL, the IDE is PL/SQL Developer version 13.0, the database server runs Oracle Database 12c Enterprise Edition Release 12.2.
In PL/SQL, every SELECT (that isn't part of a cursor) must have an INTO clause. Therefore, it's not enough that one of your SELECTs has it.
As of your final code (the one that contains CREATE TABLE): that won't work. DDL can't be executed from PL/SQL, unless you use dynamic SQL (execute immediate). Therefore, all your "other drops, creates, ..." will also have to be dynamic.
Note that we usually do not create objects dynamically; create table at SQL level. Then, if you want to populate it with different data, do so (either from SQL or PL/SQL). First delete "old" data - you can use delete or truncate (but it is DDL so - as you already know by now - it requires execute immediate), and then re-populate the table.
Dynamic SQL is difficult to debug. Don't use it if you don't have to.
If you're concerned about your own data so that other users wouldn't interfere, consider creating a global temporary table (or private; depending on database version you use).
[EDIT, based on your comments]
I'm afraid you're misinterpreting reality. DDL can't be executed in PL/SQL unless it is dynamic SQL. Your "final" code is an anonymous PL/SQL block (I'm talking about the last code you posted, the one that begins with the DECLARE, contains DROP and CREATE table t1) so it can not execute those statements as you posted.
Here's a demo. I don't have your tables so I'll mimic what you're doing, using Scott's sample DEPT table instead on my 11gXE database.
Does the WITH factoring clause work? Yes.
SQL> with intermediary1 as (
2 -- The actual query from above, as a subquery
3 select *
4 from dept
5 where 1 = 1
6 )
7 select * from intermediary1;
DEPTNO DNAME LOC
---------- -------------- -------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
Does it work with the CREATE TABLE? Not on my 11gXE:
SQL> create table t1 as (
2 with intermediary1 as (
3 -- The actual query from above, as a subquery
4 select *
5 from dept
6 where 1 = 1
7 )
8 select * from intermediary1
9 );
)
*
ERROR at line 9:
ORA-32034: unsupported use of WITH clause
I tried it on apex.oracle.com which runs 18cEE - doesn't work either.
Maybe your database version supports it, can't tell as I don't have any higher database version to try it on.
But OK, doesn't matter, that's easily fixed by moving a CTE into a subquery:
SQL> create table t1 as
2 select *
3 from (-- The actual query from above, as a subquery
4 select *
5 from dept
6 where 1 = 1
7 );
Table created.
So, everything is OK with the query itself, it won't cause my PL/SQL block to fail because of it.
OK, so - let's try it, the way you say you use frequently.
SQL> declare
2 v_calculation_date date;
3 begin
4 drop table t1;
5
6 create table t1 as
7 select *
8 from (-- The actual query from above, as a subquery
9 select *
10 from dept
11 where 1 = 1
12 );
13 /* other drops, creates, inserts ... */
14 end;
15 /
drop table t1;
*
ERROR at line 4:
ORA-06550: line 4, column 3:
PLS-00103: Encountered the symbol "DROP" when expecting one of the following:
( begin case declare exit for goto if loop mod null pragma
raise return select update while with <an identifier>
<a double-quoted delimited-identifier> <a bind variable> <<
continue close current delete fetch lock insert open rollback
savepoint set sql execute commit forall merge pipe purge
SQL>
Whooops! There's an error on DROP. Let's remove it and leave CREATE TABLE:
SQL> declare
2 v_calculation_date date;
3 begin
4 -- drop table t1;
5
6 create table t1 as
7 select *
8 from (-- The actual query from above, as a subquery
9 select *
10 from dept
11 where 1 = 1
12 );
13 /* other drops, creates, inserts ... */
14 end;
15 /
create table t1 as
*
ERROR at line 6:
ORA-06550: line 6, column 3:
PLS-00103: Encountered the symbol "CREATE" when expecting one of the following:
( begin case declare exit for goto if loop mod null pragma
raise return select update while with <an identifier>
<a double-quoted delimited-identifier> <a bind variable> <<
continue close current delete fetch lock insert open rollback
savepoint set sql execute commit forall merge pipe purge
SQL>
Whoops #2! Error on CREATE TABLE. Looks like your way doesn't really work. Let's try it my way, using dynamic SQL:
SQL> declare
2 v_calculation_date date;
3 begin
4 execute immediate 'drop table t1';
5
6 execute immediate (
7 'create table t1 as
8 select *
9 from (-- The actual query from above, as a subquery
10 select *
11 from dept
12 where 1 = 1
13 )'
14 );
15 /* other drops, creates, inserts ... */
16 end;
17 /
PL/SQL procedure successfully completed.
SQL> select * from t1;
DEPTNO DNAME LOC
---------- -------------- -------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
SQL>
Now it works.
I'd be more than happy if you demonstrate the way you're doing it without dynamic SQL. Please, post your own SQL*Plus session (just like I did). Or, if you use some GUI, post a screenshot, no problem.
You do not need a PL/SQL block for your query and can simplify it to:
SELECT *
FROM t
WHERE ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -24) <= some_date
AND some_date < TRUNC(SYSDATE, 'MM');
If you want it in a PL/SQL block then you want to use SELECT ... INTO ... if the query is going to return a single row:
DECLARE
v_calculation_date DATE := SYSDATE;
v_col1 T.COL1%TYPE;
v_col2 T.COL2%TYPE;
v_some_date T.SOME_DATE%TYPE;
BEGIN
SELECT col1, col2, some_date
INTO v_col1, v_col2, v_some_date
FROM t
WHERE ADD_MONTHS(TRUNC(v_calculation_date, 'MM'), -24) <= some_date
AND some_date < TRUNC(v_calculation_date, 'MM');
-- Do something with the variables.
DBMS_OUTPUT.PUT_LINE( v_col1 || ', ' || v_col2 || ', ' || v_some_date );
END;
/
If you can return multiple rows then use SELECT ... BULK COLLECT INTO ... or use a cursor:
DECLARE
v_calculation_date DATE := SYSDATE;
BEGIN
FOR cur IN (
SELECT *
FROM t
WHERE ADD_MONTHS(TRUNC(v_calculation_date, 'MM'), -24) <= some_date
AND some_date < TRUNC(v_calculation_date, 'MM')
)
LOOP
-- Do something with the cursor.
DBMS_OUTPUT.PUT_LINE( cur.col1 || ', ' || cur.col2 || ', ' || cur.some_date );
END LOOP;
END;
/
db<>fiddle here
Again, for your more complicated query, you do not need PL/SQL (and cannot have DDL statements in a PL/SQL context):
DROP TABLE t1;
CREATE TABLE t1 as
SELECT *
FROM t
WHERE ADD_MONTHS(TRUNC(SYSDATE, 'MM'), -24) <= some_date
AND some_date < TRUNC(SYSDATE, 'MM');
Or, if you want to use PL/SQL then do the DDL statements in the SQL context and then switch to PL/SQL for the DML statements:
DROP TABLE t1;
CREATE TABLE t1 AS
SELECT * FROM t WHERE 1 = 0;
-- or
-- TRUNCATE TABLE t1;
DECLARE
v_calculation_date DATE := SYSDATE;
BEGIN
INSERT INTO t1
SELECT *
FROM t
WHERE ADD_MONTHS(TRUNC(v_calculation_date, 'MM'), -24) <= some_date
AND some_date < TRUNC(v_calculation_date, 'MM');
END;
/

Single quote at then end is missing in create view in procedure

I have a stored procedure where a string parameter need to be passed to a create a view, I am facing difficulty to enclose the string in single quotes
EXECUTE IMMEDIATE
'CREATE VIEW view_Exec_Data as
Select * from Employees
where exec_id='''' ||To_NChar(EID)||''''; --EID is input parameter and value will be 9DE4D0106D1F390EE0
Above query is generated as
EXECUTE IMMEDIATE
'CREATE VIEW view_Exec_Data as
Select * from Employees
where exec_id='9DE4D0106D1F390EE0;
Single quote at the end is missing, not sure where I am doing wrong.
We lack some info; for example, I wonder why you used TO_NCHAR ... do you really need it?
Here's an example which presumes that employees table looks like this:
SQL> create table employees (exec_id varchar2(30), name varchar2(30));
Table created.
SQL> insert into employees values ('9DE4D0106D1F390EE0', 'Littlefoot');
1 row created.
SQL> select * From employees;
EXEC_ID NAME
------------------------------ ------------------------------
9DE4D0106D1F390EE0 Littlefoot
A procedure which creates a view. I'd suggest NOT to do that. Do you really really want to have zillion views, one per each EXEC_ID someone uses as a parameter? What's the purpose of doing that? Why don't you simply
select * from employees where exec_id = :par_eid;
Anyway, here you go: in order to make that many single quotes simpler, I used q-quoting mechanism.
SQL> create or replace procedure p_crv (par_eid in employees.exec_id%type)
2 is
3 l_str varchar2(200);
4 begin
5 l_str := q'[CREATE or replace VIEW view_Exec_Data as
6 Select * from Employees
7 where exec_id= to_nchar(']' || par_eid || q'[')]';
8
9 -- when using dynamic SQL, **ALWAYS** check whether command is properly written
10 dbms_output.put_line(l_str);
11
12 -- if it looks OK, then execute it
13 execute immediate l_str;
14 end;
15 /
Procedure created.
SQL> set serveroutput on
SQL> exec p_crv('9DE4D0106D1F390EE0');
CREATE or replace VIEW view_Exec_Data as
Select * from Employees
where exec_id= to_nchar('9DE4D0106D1F390EE0')
PL/SQL procedure successfully completed.
SQL> select * From view_exec_data;
EXEC_ID NAME
------------------------------ ------------------------------
9DE4D0106D1F390EE0 Littlefoot
SQL>
If you don't need to_nchar, it gets somewhat simpler:
SQL> create or replace procedure p_crv (par_eid in employees.exec_id%type)
2 is
3 l_str varchar2(200);
4 begin
5 l_str := q'[CREATE or replace VIEW view_Exec_Data as
6 Select * from Employees
7 where exec_id= ']' || par_eid || q'[']';
8
9 -- when using dynamic SQL, **ALWAYS** check whether command is properly written
10 dbms_output.put_line(l_str);
11
12 -- if it looks OK, then execute it
13 execute immediate l_str;
14 end;
15 /
Procedure created.
SQL> exec p_crv('9DE4D0106D1F390EE0');
CREATE or replace VIEW view_Exec_Data as
Select * from Employees
where exec_id= '9DE4D0106D1F390EE0'
PL/SQL procedure successfully completed.
SQL> select * From view_exec_data;
EXEC_ID NAME
------------------------------ ------------------------------
9DE4D0106D1F390EE0 Littlefoot
SQL>
You can use:
EXECUTE IMMEDIATE
'CREATE VIEW view_Exec_Data as
Select * from Employees
where exec_id=To_NChar(''' || EID ||''')';
-- ^ 3 (') ^ 3 + 1 (')
Update
Working for me. See this:
SQL> set serverout on
SQL> declare
2 EID varchar2(100) := '9DE4D0106D1F390EE0';
3 begin
4 DBMS_OUTPUT.PUT_LINE('CREATE VIEW view_Exec_Data as
5 Select * from Employees
6 where exec_id=To_NChar(''' || EID ||''')');
7 end;
8 /
CREATE VIEW view_Exec_Data as
Select * from Employees
where
exec_id=To_NChar('9DE4D0106D1F390EE0')
PL/SQL procedure successfully completed.
SQL>

Passing DataTable to Oracle Procedure

I want to pass DataTable to Oracle procedure which creates a Temporary table.
Even My procedure needs changes to accept datatable as parameter.
I want datatable in place of existing_table.
Below is the Procedure:
CREATE OR REPLACE procedure temptable
is
begin
EXECUTE IMMEDIATE'CREATE GLOBAL TEMPORARY TABLE TEMP_TABLE
ON COMMIT PRESERVE ROWS
AS
select * from existing_table';
End;
How do I work through this. Please help!
Here's an example of how you might do that:
SQL> create or replace procedure temptable (par_table_name in varchar2)
2 is
3 l_cnt number;
4 l_str varchar2(200);
5 begin
6 -- if TEMP_TABLE already exists, drop it
7 select count(*)
8 into l_cnt
9 from user_tables
10 where table_name = 'TEMP_TABLE';
11
12 if l_cnt > 0 then
13 execute immediate 'drop table temp_table';
14 end if;
15
16 -- create a new TEMP_TABLE
17 l_str := 'create global temporary table temp_table on commit preserve rows ' ||
18 'as select * from ' || par_table_name;
19 execute immediate (l_str);
20 end;
21 /
Procedure created.
SQL> exec temptable('dept');
PL/SQL procedure successfully completed.
SQL> select * from temp_table;
DEPTNO DNAME LOC
---------- -------------------- --------------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
SQL>
However, in Oracle, you don't really create/drop tables dynamically - you create them once and use them many times. For a global temporary table, you'd create it once and populate as necessary in different sessions.
There might be requirements as yours (i.e. to use the same table name for different data sources), so - if that's your case, see if the above code helps.

Resources