sqlplus - using a bind variable in "IN" clause - oracle

I am setting a bind variable in a PL/SQL block, and I'm trying to use it in another query's IN expression. Something like this:
variable x varchar2(255)
declare
x varchar2(100);
begin
for r in (select id from other_table where abc in ('&val1','&val2','&val3') ) loop
x := x||''''||r.id||''',';
end loop;
--get rid of the trailing ','
x:= substr(x,1,length(x)-1);
select x into :bind_var from dual;
end;
/
print :bind_var;
select *
from some_table
where id in (:bind_var);
And I get an error (ORA-01722: Invalid number) on the query that tries to use the bind variable in the "IN" list.
The print statement yiels '123','345' which is what I expect.
Is it possible to use the bind variable like this or should I try a different approach?
(using Oracle 10g)
Clarification:
This is for a reconcilliation sort of thing. I want to run
select *
from some_table
where id in (select id from other_table where abc in ('&val1','&val2','&val3'))
before the main part of the script (not pictured here) deletes a whole bunch of records. I want to run it again afterwards to verify that records in some_table have NOT been deleted. However, the data in other_table DOES get deleted by this process so I can't just refer to the data in other_table because there's nothing there. I need a way to preserve the other_table.id values so that I can verify the parent records afterwards.

I would store the other_table.id's in a PL/SQL table and reference that table in the query afterwards:
type t_id_table is table OF other_table.id%type index by binary_integer;
v_table t_id_table;
-- fill the table
select id
bulk collect into v_table
from other_table
where abc in ('&val1','&val2','&val3');
-- then at a later stage...
select *
from some_table st
, table(cast(v_table AS t_id_table)) idt
where st.id = idt.id;

You can't use comma-separated values in one bind variable.
You could say:
select * from some_table where id in (:bind_var1, :bind_var2)
though
You're better off using something like:
select * from some_table where id in ("select blah blah blah...");

I would use a global temporary table for this purpose
create global temporary table gtt_ids( id number ) ;
then
...
for r in (select id from other_table where ... ) loop
insert into gtt_ids(id) values (r.id) ;
end loop;
...
and at the end
select *
from some_table
where id in (select id from gtt_ids);

changed the loop to use listagg (sadly this will only work in 11gr2).
but for the variable in list, I used a regular expression to accomplish the goal (but pre 10g you can use substr to do the same) this is lifted from the asktom question linked.
variable bind_var varchar2(255)
variable dataSeperationChar varchar2(255)
declare
x varchar2(100);
begin
select listagg(id,',') within group(order by id) idList
into x
from(select level id
from dual
connect by level < 100 )
where id in (&val1,&val2,&val3) ;
select x into :bind_var from dual;
:dataSeperationChar := ',';
end;
/
print :bind_var;
/
select *
from (
select level id2
from dual
connect by level < 100
)
where id2 in(
select -- transform the comma seperated string into a result set
regexp_substr(:dataSeperationChar||:bind_var||','
, '[^'||:dataSeperationChar||']+'
,1
,level) as parsed_value
from dual
connect by level <= length(regexp_replace(:bind_var, '([^'||:dataSeperationChar||'])', '')) + 1
)
;
/*
values of 1,5, and 25
BIND_VAR
------
1,5,25
ID2
----------------------
1
5
25
*/
EDIT
Oops just noticed that you did mark 10g, the only thing to do is NOT to use the listagg that I did at the start

Ok, I have a kind of ugly solution that also uses substitution variables...
col idList NEW_VALUE v_id_list /* This is NEW! */
variable x varchar2(255)
declare
x varchar2(100);
begin
for r in (select id from other_table where abc in ('&val1','&val2','&val3') ) loop
x := x||''''||r.id||''',';
end loop;
--get rid of the trailing ','
x:= substr(x,1,length(x)-1);
select x into :bind_var from dual;
end;
/
print :bind_var;
select :x idList from dual; /* This is NEW! */
select *
from some_table
where id in (&idList); /* This is CHANGED! */
It works, but I'll accept an answer from someone else if it's more elegant.

Related

Variables in PLSQL

I want to get the value defined in my Procedure as mentioned below.
declare
city varchar2(50) := 'XYZ';
TYPE table_type is table of table_name%rowtype;
var table_type;
begin
select * bulk collect into var from table_name;
DBMS_OUTPUT.PUT_LINE(var(1).field); -- Output is City
I want the output of
DBMS_OUTPUT.PUT_LINE(var(1).field); -- Output is XYZ
How can I achieve this?
First of all, your code does not work. Second, if you are going to recover a variable, you don't need bulk collect
Example
create table mytest ( c1 varchar2(50) ;
insert into mytest values ( 'XYZ' );
commit;
Then
SQL> set serveroutput on
declare
vcity mytest.c1%type := 'XYZ';
var mytest.c1%type;
begin
select c1 into var from mytest where c1=vcity;
DBMS_OUTPUT.PUT_LINE('vcity is '||vcity||' ');
DBMS_OUTPUT.PUT_LINE('var is '||var||' ');
end;
/SQL> 2 3 4 5 6 7 8 9
vcity is XYZ
var is XYZ
PL/SQL procedure successfully completed.
SQL>
vcity is just a variable defined with the type of the column c1, but
assigned to a constant, in this case 'XYZ'
var is the variable which I use to recover the value of c1 in the
table, whatever that value is.
You don't need bulk collect here at all.
Assuming you have several cities to list, you need to define a collection to house them once selected, then a variable of that collection. So for example:
--setup
create table table_name ( id integer
, city varchar2(10)
);
insert into table_name(id, city)
select level, 'City #' || trunc(dbms_random.value( 10, 75 ))
from dual connect by level <= 10;
-- process
declare
type city_list is table of table_name%rowtype;
var city_list;
begin
select *
bulk collect
into var
from table_name;
for r in 1 .. var.count
loop
dbms_output.put_line(var(r).city); -- Output is City
end loop;
end;
Let's keep remain mystery about why you want like this,
If I understood you , to achieve what you want you have to specify columns instead of '*' and replace the variable city with column field.
Dbfiddle link for your reference (unable to provide via link through mobile device)
https://dbfiddle.uk/?rdbms=oracle_18&fiddle=20f1a1fff11d4acda7acc701ad120d32
DECLARE
city varchar2(50) := 'XYZ';
TYPE table1_type is table of table1%rowtype;
var table1_type;
BEGIN
select city,field2,field3,field4
bulk collect into var
from table1;
DBMS_OUTPUT.PUT_LINE(var(1).field);
END;
/

PLS 00357 Error- Table, View or Sequence "txt.col1" not allowed in the context

I have created one Stored Procedure. In that Stored Proc I want if the value of col1 & col2 match with employee then insert the unique record of the employee. If not found then match the value of col1, col2 & col3 with employee match then insert the value. If also not found while match all these column then insert the record by using another column.
Also one more thing that i want find list of values like emp_id by passing the another column value and if a single record can not match then make emp_id as NULL.
create or replace procedure sp_ex
AS
empID_in varchar2(10);
fname_in varchar2(20);
lname_in varchar2(30);
---------
type record is ref cursor return txt%rowtype; --Staging table
v_rc record;
rc rc%rowtype;
begin
open v_rc for select * from txt;
loop
fetch v_rc into rc;
exit when v_rc%notfound;
loop
select col1 from tbl1
Where EXISTS (select col1 from tbl1 where tbl1.col1 = rc.col1);
IF txt.col1 = rc.col1 AND txt.col2 = rc.col2 THEN
insert into main_table select distinct * from txt where txt.col2 = rc.col2;
ELSIF txt.col1 = rc.col1 AND txt.col2 = rc.col2 AND txt.col3 = rc.col3 THEN
insert into main_table select distinct * from txt where txt.col2 = rc.col2;
ELSE
insert into main_table select * from txt where txt.col4 = rc.col4;
end if;
end loop;
close v_rc;
end sp_ex;
I found an error while compile this Store Procedure PLS-00357: Table,View Or Sequence reference not allowed in this context. How to resolve this issue and how to insert value from staging to main table while using CASE or IF ELSIF statement. Could you please help me so that i can compile the Stored Proc.
Since I don't have your database to work with it's difficult to be 100% certain, but to my eye the line which reads
rc rc%rowtype;
should say
rc txt%rowtype;
You've defined the cursor v_rc as returning txt%rowtype, and your SQL statement used with this cursor is select * from txt, but that data type is at odds with the definition of rc. Thus, it appears you need to change rc as shown.
It also looks like the LOOP statement which comes immediately after exit when v_rc%notfound; should be removed, as there's nothing after that which would terminate that loop.
In addition, you have many references to columns in the txt table, e.g. IF txt.col1 = rc.col1. You can't refer to values in a table in this manner. I'm not quite sure what you're trying to do here so I can't really suggest anything.
Also, the statement
select col1 from tbl1
Where EXISTS (select col1 from tbl1 where tbl1.col1 = rc.col1);
is selecting a column from the database, but isn't putting it anywhere. This should be either a singleton SELECT (SELECT..INTO) or a cursor.
One more thing: you can't use distinct *. You need to use a column list with distinct.
Perhaps the following would be close to what you're trying to do:
create or replace procedure sp_ex
AS
begin
FOR rc IN (SELECT * FROM TXT)
LOOP
FOR t1 IN (SELECT *
FROM TBL1
WHERE TBL1.COL1 = rc.COL1)
LOOP
IF t1.COL1 = rc.COL1 AND
t1.COL2 = rc.COL2
THEN
insert into main_table
select *
from txt
where txt.col2 = rc.col2;
ELSIF t1.col1 = rc.col1 AND
t1.col2 = rc.col2 AND
t1.col3 = rc.col3
THEN
insert into main_table
select *
from txt
where txt.col2 = rc.col2;
ELSE
insert into main_table
select *
from txt
where txt.col4 = rc.col4;
END IF;
END LOOP; -- t1
END LOOP; -- rc
end sp_ex;
Best of luck.

Oracle: Use a cursor to check whether a record in one table exists in another

As a POC for my non-technical team I need to come up with several ways to do the same thing which is to check whether a record in one table exists in another in order to see which is the most efficient. I've come up with two other ways that I am positive will be more efficient than a cursor, but I still need to show the time it takes to do this in a cursor. I can't figure out the syntax however.
I have two tables:
Table 1 has two fields I need and that I am fetching into the variables in the cursor:
Field = ID
Field = Account number
Table 2 has one field I need:
Field = Account Number (No ID available)
I need the ID from table 1 and the count of transactions where the account number from table 1 is not in table 2. Any suggestions?
Given that sample data:
SQL> SELECT * FROM T1;
ID
--
1
2
3
SQL> SELECT * FROM T2;
ID
--
2
3
4
The proper way to perform an anti-join T1 ▷ T2 is by writing:
SQL> SELECT ID FROM T1 WHERE ID NOT IN (SELECT ID FROM T2);
ID
--
1
Now, if you want to do it using cursor to show how slow that can be, you might want to use two nested loops like in the following example:
DECLARE
CURSOR c1 IS (SELECT ID FROM T1);
r1 c1%ROWTYPE;
CURSOR c2 IS (SELECT ID FROM T2);
r2 c2%ROWTYPE;
BEGIN
FOR r1 IN c1
LOOP
FOR r2 IN c2
LOOP
IF (r1.ID = r2.ID)
THEN
-- continue to the next iteration of the outer loop
-- as we have a match
GOTO continue;
END IF;
END LOOP;
-- we can only reach that point if there was no match
DBMS_OUTPUT.PUT_LINE(TO_CHAR(r1.ID));
<<continue>>
NULL;
END LOOP;
END;
I'm not sure if I follow you, but a simple cursor might look like this:
DECLARE
v_id NUMBER;
v_acct NUMBER;
BEGIN
FOR r1 IN ( SELECT ID, ACCT_NBR
FROM table1 T1
WHERE NOT EXISTS ( SELECT 1 FROM table2
where ID = T1.ID )
) LOOP
v_id := r1.id;
v_acct := r1.acct_nbr;
-- do something
END LOOP;
end;

Oracle: update non-unique field

I need to update a non-unique field. I have a table tbl:
create table tbl (A number(5));
Values in tbl: 1, 2, 2, 2 .. 2.
I need to replace all 2 with new non-unique values
New values: 1, 100, 101, 102, 103 ..
I wrote:
DECLARE
sql_stmt VARCHAR2(500);
cursor curs is
select A from tbl group by A having count(*)>1;
l_row curs%ROWTYPE;
i number(5);
new_mail VARCHAR2(20);
BEGIN
i:=100;
open curs;
loop
fetch curs into l_row;
exit when curs%notfound;
SQL_STMT := 'update tbl set a='||i||' where a='||l_row.A;
i:=i+1;
EXECUTE IMMEDIATE sql_stmt;
end loop;
close curs;
END;
/
But I got:
A
----------
1
100
...
100
What can be wrong? Why doesn't the loop work?
what about
update tbl
set a = 100 + rownum
where a in (
select a
from tbl
group by a
having count(*) > 1 )
the subquery finds duplicated A fields and the update gives them the unique identifier starting from 100. (you got other problems here like , what if id 100, 101.... already exists ).
first rule of PLSQL says that what ever you can do with SQL always do with SQL. writing straight up for loop cause allot of context switches between the sql and pl/sql engine. even if oracle automatically converts this to a bulk statement (10g<) it will still be faster with pure SQL.
Your cursor gets one row per unique value of A:
select A from tbl group by A having count(*)>1;
You need to get all the distinct rows that match those values. One way is to do this:
select a, r from (
select a, rowid as r, count(*) over (partition by a) as c
from tbl
) where c > 1;
... and then use the rowid values to do the update. I'm not sure why you're using dynamic SQL as it is not at all necessary, and you can simplify (IMO) the loop:
declare
i number(5);
begin
i:=100;
for l_row in (
select a, r from (
select a, rowid as r, count(*) over (partition by a) as c
from tbl
) where c > 1) loop
update tbl set a=i where rowid = l_row.r;
i:=i+1;
end loop;
end;
/
I've kept this as PL/SQL to show what was wrong with what you were attempting, but #haki is quite correct, you should (and can) do this in plain SQL if at all possible. Even if you need it to be PL/SQL because you're doing other work in the loop (as the new_mail field might suggest) then you might be able to still do a single update within the procedure, rather than one update per iteration around the loop.

What is the simplest way to define a local variable in Oracle?

In the SQL Server, I can define local variables like this.
declare #id number := 1000
select * from tbl_A where id = #id;
select * from tbl_B where id = #id;
It is very convenient.
I tried to do same thing in PL/SQL but it doesn't work.
DECLARE id number;
select 1000 into id from dual;
Do you know how to do something similar? The simplest method is my objective.
If you want to define a local variable in PL/SQL, you need a complete PL/SQL block
DECLARE
id NUMBER;
BEGIN
SELECT 1000
INTO id
FROM dual;
END;
or just
DECLARE
id NUMBER := 1000;
BEGIN
<<do something that uses the local variable>>
END;
If you want to declare a variable in SQL*Plus
SQL> variable id number
SQL> begin
select 1000 into :id from dual;
end;
/
SQL> print id
ID
----------
1000
SQL> SELECT * FROM tbl_a WHERE id = :id
An alternative to DECLARE Block is to use a WITH Clause:
WITH my_params AS (
SELECT 123 AS min_id FROM DUAL
)
SELECT *
FROM some_table
WHERE id > (SELECT min_id FROM my_params)
It is more portable as many vendors support the WITH clause and you can change seamless from parameter to dynamic value. For example:
WITH my_params AS (
SELECT min(id) AS min_id FROM some_id_table
)
SELECT *
FROM some_table
WHERE id > (SELECT min_id FROM my_params)
Solution for Oracle SQL
DEF x = foo
SELECT '&x' FROM dual;
The result will be : foo
NB: The variable will keep the value even after execution. To clear variable run UNDEFINE x.
General syntax to declare variable in PL/SQL is
var_nm datatype [NOT NULL := var_value ];
var_nn is the name of the variable.
datatype is a valid PL/SQL datatype.
NOT NULL is an optional specification on the variable which this variable cannot be assigned null value.
var_value or DEFAULT value is also an optional specification, where you can initialize a variable with some specific value.
Each variable declaration is a separate statement and must be terminated by a semicolon.
We can assign value to variables in one of the following two ways -
direct assignment (Eg. var_nm:= var_value;)
Using select from (Eg. SELECT col_nm INTO var_nm FROM tbl_nm [WHERE clause];)
In you case as Justin Cave has already mentioned it can be
DECLARE
id number;
BEGIN
SELECT 1000 into id from dual;
dbms_output.put_line('id : '|| id );
END;
/
OR
DECLARE
id number := 1000;
BEGIN
dbms_output.put_line('id : '|| id );
END;
/
NOTE: '/' i.e Back slash after END keyword indicates to execute the above PL/SQL Block.
(Just stumbled across this thread.) Beginning with SQL*Plus 12.2, you can declare and assign a value at the same time:
SQL> var id number = 1000
SQL> select * from tbl_A where id = :id;
Oracle calls it input binding.
If you must do it in PL/SQL, the answer was given by others.

Resources