PL/SQL: SELECT INTO using a variable in the FROM clause - sql

Code:
lc_tab1_col1 VARCHAR2(4000);
lc_tab1_col2 VARCHAR2(4000);
lc_tab2_col2 VARCHAR2(4000);
lc_tab2_col2 VARCHAR2(4000);
CURSOR my_cursor IS select col1, col2 from tab1;
[...]
OPEN my_cursor;
LOOP
FETCH my_cursor INTO lc_tab1_col1, lc_tab1_col2;
EXIT WHEN my_cursor%NOTFOUND;
SELECT lc_tab1_col2.col1, lc_tab1_col2.col2 INTO lc_tab2_col2, lc_tab2_col2 FROM lc_tab1_col2 WHERE lc_tab1_col2.col3 = lc_tab1_col1;
[...]
END LOOP;
CLOSE my_cursor;
Hey folks,
I am trying to get the above code working.
The issue I am having is that a SELECT INTO statement apparently does not support using a variable (in that case lc_tab1_col2) as the table name in the FROM clause of the statement.
When compiling the package an ORA-000942 is thrown (table or view does not exist), which tells me the variable is interpreted directly instead of being replaced and interpreted at runtime.
I can't think of a workaround on the fly, any ideas on how to fix this?
Some more background: lc_tab1_col2 contains the name of a table in the database whereas lc_tab1_col1 contains an ID.
This ID is present in all of the tables that can be contained in lc_tab1_col2 (hence the WHERE clause).
Apart from the ID there are two other columns (lc_tab1_col2.col1 and lc_tab1_col2.col2) that are present in all those tables, but that are not present in tab1. I need to select those two values to work with them inside the loop.
As there are many tables to consider, I need this SELECT INTO statement to be dynamic. It wouldn't be feasible to parse the tables one by one. Looking forward to anyone sharing a clever idea for overcoming this issue :) Thanks in advance!

I think, you exception really means that this table does not exist or you don't have privileges to SELECT it.
I've executed a below code and everything was ok. I have tried to compile it in a package and also I didn't have any compilation errors
DECLARE
user_tables varchar2(30) := 'TBLCOMPANIES';
BEGIN
SELECT table_name
INTO user_tables
FROM user_tables
WHERE user_tables.table_name = user_tables;
dbms_output.put_line(user_tables) ;
END;
/

Related

Saving result set in variable using a cursor

I have a problem using Oracle SQL to loop over a result set twice.
The problem
I have a cursor that gets me all foreign keys to a given table name. Using the result form this cursor, I loop through all the constraints and disable them. Then I perform a data import and then I need to loop over the same result set and enable them.
CURSOR c_fkeys_inn(tabellnavn IN VARCHAR2)
IS
SELECT table_name,constraint_name, status
FROM user_constraints
WHERE (r_constraint_name,r_owner) IN
(SELECT constraint_name, owner
FROM user_constraints
WHERE owner ='POP'
AND table_name=upper(tabellnavn)
)
AND STATUS = 'ENABLED';
What I would like to do
My brain jumps directly to a variable. I would like to perform the cursor just once, and then save the result from the cursor to a variable.
Is this possible or are there anything I do to save the result from the cursor and loop twice?
Please try this code. I have sightly modified your code to just display the constraint's table names. You can modify the end part of the plsql according to your requirement. Please comment if you have come across any mistakes or issues, thank you.
CREATE or replace PROCEDURE a_proc(name_table varchar)
AS
CURSOR c_fkeys_inn(tabellnavn IN VARCHAR2)
IS
SELECT table_name,constraint_name, status
FROM user_constraints
WHERE STATUS = 'ENABLED'
AND TABLE_NAME=tabellnavn;
names_t c_fkeys_inn%ROWTYPE;
TYPE c_fkeys IS TABLE OF names_t%TYPE;
fkeys c_fkeys;
BEGIN
OPEN c_fkeys_inn(name_table);
FETCH c_fkeys_inn BULK COLLECT INTO fkeys;
CLOSE c_fkeys_inn;
FOR indx IN 1..fkeys.COUNT LOOP
DBMS_OUTPUT.PUT_LINE(fkeys(indx).table_name);
END LOOP;
END a_proc;
To run the code please run a separate plsql block. Please find a simple and a sample plsql block given below.
begin
a_proc('SUPPLIER');
END;

Return values of select in function/procedure

I have a SQL sentence where I make a minus of two tables to search the differences. As I use frequently I would like to create a function or procedure to make these and get output by screen. Someone could explain me how is the best way to make these, could you put me some example?
If you frequently use the MINUS query, then it is better to create a view on the query. To fetch the resultset, you just need to select from the view.
For example,
CREATE OR REPLACE VIEW my_view AS
SELECT column_list FROM table1
MINUS
SELECT column_list FROM table2
And to fetch the result,
SELECT * FROM my_view;
Read the documentation for more details on CREATE VIEW
Maybe this is the what you're looking for, if you're using Oracle 11g Release 2:
create or replace procedure prnt_my_view(my_view in varchar2, separator in varchar2 default ',') is
type myrefcur is ref cursor;
type rowtext is table of varchar2(256);
rowdef varchar2(256);
rows_cv myrefcur;
text rowtext;
begin
select listagg(column_name,'||'''||separator||'''||') within group (order by column_id) into rowdef from user_tab_columns where lower(table_name) = lower(my_view);
open rows_cv for 'select '||rowdef||' from '||my_view;
fetch rows_cv bulk collect into text;
for i in text.first..text.last loop
dbms_output.put_line(text(i));
end loop;
close rows_cv;
exception when others then
dbms_output.put_line('something is wrong:'||sqlerrm);
end;
edit:
if you can't use listagg, check other solutions for example here: alternative to listagg in Oracle?

Using CURSOR to fetch multiple table names from another table

I've created one table "Meta_Data_Table_Names" where I inserted forty eight table names in the MetaTableName column. And there is another column to provide Row count with corresponding table name.
I wanted to fetch the table name from “Meta_Data_Table_Names” and execute SELECT Query sequentially through Loop for validation purpose.
Whenever, I execute from TOAD , It’s throwing an error:
Table or view does not exist.
Do we need to make a place holder for 'Meta_name' which can be scanned? Or any particular syntax to read the value during Query?
DECLARE
CURSOR c1 IS SELECT MetaTableName FROM Meta_Data_Table_Names;
CURSOR c2 IS SELECT ROW_COUNT FROM Meta_Data_Table_Names;
Meta_name Meta_Data_Table_Names.MetaTableName%TYPE;
Count_num Meta_Data_Table_Names.ROW_COUNT%TYPE;
BEGIN
OPEN c1;
OPEN c2;
FOR i IN 1..48 LOOP
FETCH c1 INTO Meta_name;
FETCH c2 INTO Count_num;
IF (Count_num > 2000)
THEN
SELECT * FROM Meta_Name X
MINUS
SELECT * from ASFNCWK07.Meta_Name#NCDV.US.ORACLE.COM Y
UNION ALL
SELECT * FROM ASFNCWK07.Meta_Name#NCDV.US.ORACLE.COM Y
MINUS
SELECT * FROM Meta_Name X;
ELSE
DBMS_OUTPUT.PUT_LINE ('No Validation is required');
END IF;
END LOOP;
END;
Oracle does not allow you to do queries with dynamic table names, i.e. if the table name is not known at compile time. Do do that, you need Dynamic SQL, which is a bit too broad to go into here.
There are a number of problems with your code.
Firstly, we cannot use variable names in normal SQL: for this we need dynamic SQL. For instance:
execute immediate 'select 1 from '|| Meta_Name || into n;
There are a lot of subtleties when working with dynamic SQL: the PL/SQL documentation devotes a whole chapter to it. Find out more.
Secondly, when executing SQL in PL/SQL, we need need to provide a target variable. This must match the projection of the executed query. When selecting a whole row the %ROWTYPE keyword is useful. Again the documentation covers this: find out more. Which leads to ...
Thirdly, because you're working with dynamic SQL and you don't know in advance which tables will be in scope, you can't easily declare target variables. This means you'll need to use ref cursors and/or Type 4 dynamic SQL techniques. Yikes! Read Adrian Billington's excellent blog article here.
Lastly (I think), the UNION ALL in your query doesn't allow you to identify which rows are missing from where. Perhaps that doesn't matter.

using comma separated values inside IN clause for NUMBER column

I have 2 procedures inside a package. I am calling one procedure to get a comma separated list of user ids.
I am storing the result in a VARCHAR variable. Now when I am using this comma separated list to put inside an IN clause in it is throwing "ORA-01722:INVALID NUMBER" exception.
This is how my variable looks like
l_userIds VARCHAR2(4000) := null;
This is where i am assigning the value
l_userIds := getUserIds(deptId); -- this returns a comma separated list
And my second query is like -
select * from users_Table where user_id in (l_userIds);
If I run this query I get INVALID NUMBER error.
Can someone help here.
Do you really need to return a comma-separated list? It would generally be much better to declare a collection type
CREATE TYPE num_table
AS TABLE OF NUMBER;
Declare a function that returns an instance of this collection
CREATE OR REPLACE FUNCTION get_nums
RETURN num_table
IS
l_nums num_table := num_table();
BEGIN
for i in 1 .. 10
loop
l_nums.extend;
l_nums(i) := i*2;
end loop;
END;
and then use that collection in your query
SELECT *
FROM users_table
WHERE user_id IN (SELECT * FROM TABLE( l_nums ));
It is possible to use dynamic SQL as well (which #Sebas demonstrates). The downside to that, however, is that every call to the procedure will generate a new SQL statement that needs to be parsed again before it is executed. It also puts pressure on the library cache which can cause Oracle to purge lots of other reusable SQL statements which can create lots of other performance problems.
You can search the list using like instead of in:
select *
from users_Table
where ','||l_userIds||',' like '%,'||cast(user_id as varchar2(255))||',%';
This has the virtue of simplicity (no additional functions or dynamic SQL). However, it does preclude the use of indexes on user_id. For a smallish table this shouldn't be a problem.
The problem is that oracle does not interprete the VARCHAR2 string you're passing as a sequence of numbers, it is just a string.
A solution is to make the whole query a string (VARCHAR2) and then execute it so the engine knows he has to translate the content:
DECLARE
TYPE T_UT IS TABLE OF users_Table%ROWTYPE;
aVar T_UT;
BEGIN
EXECUTE IMMEDIATE 'select * from users_Table where user_id in (' || l_userIds || ')' INTO aVar;
...
END;
A more complex but also elegant solution would be to split the string into a table TYPE and use it casted directly into the query. See what Tom thinks about it.
DO NOT USE THIS SOLUTION!
Firstly, I wanted to delete it, but I think, it might be informative for someone to see such a bad solution. Using dynamic SQL like this causes multiple execution plans creation - 1 execution plan per 1 set of data in IN clause, because there is no binding used and for the DB, every query is a different one (SGA gets filled with lots of very similar execution plans, every time the query is run with a different parameter, more memory is needlessly used in SGA).
Wanted to write another answer using Dynamic SQL more properly (with binding variables), but Justin Cave's answer is the best, anyway.
You might also wanna try REF CURSOR (haven't tried that exact code myself, might need some little tweaks):
DECLARE
deptId NUMBER := 2;
l_userIds VARCHAR2(2000) := getUserIds(deptId);
TYPE t_my_ref_cursor IS REF CURSOR;
c_cursor t_my_ref_cursor;
l_row users_Table%ROWTYPE;
l_query VARCHAR2(5000);
BEGIN
l_query := 'SELECT * FROM users_Table WHERE user_id IN ('|| l_userIds ||')';
OPEN c_cursor FOR l_query;
FETCH c_cursor INTO l_row;
WHILE c_cursor%FOUND
LOOP
-- do something with your row
FETCH c_cursor INTO l_row;
END LOOP;
END;
/

Possible to extend static SQL statements with dynamic parts?

I'd like to create a Oracle package where I have a procedure that executes some dynamic SQL. This is no problem if I'm doing it all dynamic with EXECUTE IMMEDIATE but it would be better if the static parts of the query could be coded static (to have compile time checking).
Example of fully dynamic query:
-- v_stmt is built dynamically.
v_stmt := 'SELECT count(*) FROM <here some joins> WHERE <here some conditions>';
EXECUTE IMMEDIATE v_stmt
USING v_param1, v_param2
RETURNING INTO v_count;
Example of what I tried to make the FROM-part static:
-- v_stmt is built dynamically.
v_stmt := 'SELECT count(*) FROM my_package.my_function(:param1, :param2) WHERE <here some conditions>';
EXECUTE IMMEDIATE v_stmt
USING v_param1, v_param2
RETURNING INTO v_count;
FUNCTION my_function(
i_param1 IN VARCHAR2,
i_param2 IN NUMBER
)
RETURN SYS_REFCURSOR
AS
v_cursor SYS_REFCURSOR;
BEGIN
-- Open a cursor for different queries depending on params.
IF i_param2 = 1 THEN
OPEN v_cursor FOR <some static query>;
ELSE
OPEN v_cursor FOR <some other static query>;
END IF;
RETURN v_cursor;
END;
This doesn't work because it's not possible to select from a SYS_REFCURSOR (at least that's what I found with Google).
Is there any way to reach this goal?
edit: As requested, here are some examples:
Static queries:
SELECT a.*, ca.CUS_ID FROM adresses a INNER JOIN customer_adresses ca ON (ca.adr_id = a.adr_id);
SELECT p.*, cp.CUS_ID FROM persons p INNER JOIN customer_persons cp ON (cp.per_id = p.per_id);
Then they are extended dynamically like the following examples:
-- Checks if there is an adress in the customer where the zip is null.
SELECT count(*) FROM <static adresses query> q WHERE q.cus_id = :param1 AND a.zip IS NULL;
-- Checks if there is at least one person in the customer.
SELECT count(*) FROM <static persons query> q WHERE q.cus_id = :param1;
Sorry, but why the need to do this? Seems you're over complicating things by introducing a function that will return different types of data/tables depending on the parameter list. Very confusing imo. Besides, you have to do the work somewhere, you're just trying to hide it in this function (inside if param1=this then x if param1=that then y...)
Besides, even if you did implement a cursor function (even pipelined), it would be a bad idea in this case because you'd be forcing Oracle into doing work that it wouldn't necessarily need to do (ignore all the context switching for now). To just get a count, you'd have Oracle grab each an every row result and then count. Many times Oracle can just do a fast full index scan to get the count (depending on the query of course). And often same query run multiple times will not need to do all the work each time if blocks are found in buffer cache. I'd challenge you to run the count multiple times using straight SQL vs using a function returning a cursor. You might be surprised. And to my knowledge (check me on this) the new 11g function result cache won't work on a pipelined functions or a function returning a ref cursor (along with other issues like invalidations due to relies on tables).
So, what I'm saying is why not just do: select count(1) into v_variable from ...;
If you want to hide and modularize, then just know what you're potentially losing.
You may want to open a query in function1 and then pipeline the results of it as a table to function2 which then will add a where clause to this "table"
In this case you'll want to rewrite your function1 as a pipelined table function
v_stmt := 'SELECT count(*) FROM table(my_package.my_function(:param1, :param2)) WHERE <here some conditions>';
EXECUTE IMMEDIATE v_stmt
USING v_param1, v_param2
RETURNING INTO v_count;
CREATE TYPE object_row_type AS OBJECT (
OWNER VARCHAR2(30),
OBJECT_TYPE VARCHAR2(18),
OBJECT_NAME VARCHAR2(30),
STATUS VARCHAR2(7)
);
CREATE TYPE object_table_type AS TABLE OF object_row_type;
FUNCTION my_function(
i_param1 IN VARCHAR2,
i_param2 IN NUMBER
)
RETURN object_table_type PIPELINED AS
BEGIN
You can have compile time checking of expressions with Oracle expression filter.
It's probably more complicated than the other solutions, but if you really need to verify your conditions it can be helpful.