What is the simplest way to define a local variable in Oracle? - 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.

Related

Reusing a variable in an Oracle SQL query

Very simple question, but one I can't seem to find an answer to.
I have a variable calculated at the start of an Oracle SQL script (in TOAD, if that makes a difference), and I hoped to reuse it later in an UPDATE statement.
Variable is declared and then set here:
DECLARE
v_months number;
v_check number;
BEGIN
v_check := '1'
SELECT (total_due/monthly_amount) INTO v_months FROM TABLE1 WHERE ...
and will return a numeric value of, say, 20.
and I want to use that figure here:
IF(v_check = 1)
update TABLE2 set paid = 'YES' where sequence between v_months and 48;
This doesn't seem to be possible as the variable is flagged up as an invalid identifier, but is there a way round this?
DECLARE the variable at the start of the PL/SQL block and then just re-use it in the same PL/SQL block and it works:
DECLARE
v_months PLS_INTEGER;
BEGIN
SELECT (total_due/monthly_amount) INTO v_months FROM TABLE1;
update TABLE2 set paid = 'YES' where sequence between v_months and 48;
END;
/
If you are trying to re-use it between different PL/SQL blocks then it will not work using a PL/SQL variable.
db<>fiddle here
If you want to use a variable in multiple statements then you can use bind variables:
VARIABLE v_months NUMBER
BEGIN
SELECT (total_due/monthly_amount) INTO :v_months FROM TABLE1;
END;
/
update TABLE2 set paid = 'YES' where sequence between :v_months and 48;

How to set Oracle bind variables when using SQLPlus?

How do I set Oracle bind variables when using SQLPlus?
Example:
SELECT orders.order_no FROM orders WHERE orders.order_date BETWEEN :v1 AND :v2
How do I set the dates of :v1 and :v2?
Notice the following:
VARIABLE is a SQLPlus command. You don't end it with a semicolon (;).
In the VARIABLE command, you do not precede the variable name with
colon (:).
Bind variable can't be of data type "date" - they are some sort of
character value.
For that reason, IN YOUR CODE you must use to_date() with the
proper format model, or some other mechanism, to convert string to
date. That is currently missing in your code. NEVER compare dates to
strings!
Brief demo below.
SQL> variable v1 varchar2(20)
SQL> exec :v1 := '2015/12/22';
PL/SQL procedure successfully completed.
SQL> select 1 as result from dual where to_date(:v1, 'yyyy/mm/dd') < sysdate;
RESULT
----------
1
In common
you may use define and use variable with &
define x = 12 ;
select &x from dual;
Or varable
variable x refcursor;
begin
open :x for select * from dual connect by level < 11;
end;
/
print x

How to use variables in an Oracle PL/SQL where clause

I can't seem to get variables to work in an Oracle PL/SQL where clause. I come from a Microsoft SQL Server background and there it was easy. For example, what would be all steps needed to do something similar to the following?
declare #var int set #var = 1
select * from SomeTable where SomeField = #var
This doesn't seem like it should be hard in PL/SQL, but evidently it is. :-/ I hear I need to use cursors and the like in PL/SQL?
Any help would be greatly appreciated. Thanks.
What do you want to do with the data that the SELECT returns? If you just want to see it you don't need PL/SQL at all, just do this in SQL Plus:
variable var number
exec :var := 1
select * from SomeTable where SomeField = :var;
Or in a tool like SQL Developer or Toad, just do this:
select * from SomeTable where SomeField = :var;
and it will prompt you to enter the value for :var.
The following code declares a variable var to use in the WHERE clause, and a variable result to put the result in then executes it inside a PL/SQL block.
DECLARE
var INT := 1;
result INT;
BEGIN
SELECT 123
INTO result
FROM DUAL
WHERE var = 1;
DBMS_OUTPUT.put_line (var);
DBMS_OUTPUT.put_line (result);
END;
The DBMS_OUTPUT.PUT_LINE calls make it produce this DBMS output:
1
123
declare
type t_rec is record
(
col1 number,
col2 myTable.col2%type
);
v_rec t_rec;
type t_tab is table of v_rec%type index by binary_integer;
v_tab t_tab;
begin
select col1, col2
bulk collect into v_tab
from myTable
where col3 = 'BLAH';
-- do something great with v_tab...
end;
Also know that if you try to select into (or bulk collect into) a variable and no rows are returned, you'll get a no_data_found exception, so you may want to handle that situation.
See more here on pl/sql collections. Above uses an associative array, but there are nested tables and varrays as well. Again, see the link.
Hope that helps.
I use it like this
select * from sec_mainmenu where serno = '&MENU_ID';
When you run it, pl/sql will prompt for MENU_ID value.

sqlplus - using a bind variable in "IN" clause

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.

How to get the last executed SQL statement and bind variable values in oracle

I have written the following query to get the last executed SQL statement in the oracle database for a particular session. The SQL text does not contain the actual value of the bind variables. How to get the bind variable values along with the SQL text.
SELECT * FROM v$SQLTEXT_WITH_NEWLINES WHERE address =
(SELECT prev_sql_addr FROM v$session WHERE audsid = userenv('SESSIONID'))
ORDER BY piece;
To get the bind variables you will have to use the code below, you dont need to use tracing.
SELECT * FROM v$sql_bind_capture WHERE sql_id='';
or
SELECT NAME,POSITION,DATATYPE_STRING,VALUE_STRING
FROM v$sql_bind_capture WHERE sql_id='';
http://shaharear.blogspot.com/2009/02/find-bind-variable-value.html
I don't think the bind variables values are stored by default. Not considering the potential security problems (seeing other sessions actual work), the amount of data to store would be massive.
If you want to see the values of the bind variables, you should activate the trace for that session. You would do this by executing the following command in that session:
alter session set events '10046 trace name context forever, level 12';
More information on AskTom: 10046 tracing
if you are in sqlplus you can execute
select * from table
( dbms_xplan.display_cursor (null,null, 'ADVANCED'));
or if you are looking for SQL executed by someone else just put in their the SQL_ID and child cursor #:
select * from table
( dbms_xplan.display_cursor ('sql_id',child_cursor#, 'ADVANCED'));
as in
select * from table
( dbms_xplan.display_cursor ('a18asdr99x',0, 'ADVANCED'));
This method shows the only shows peeked bind variables. The only dependable way is tracing with bind variables
dbms_monitor.session_trace_enable(session_id => 127,
serial_num => 29,
waits => FALSE,
binds => TRUE)
but of course that has to be done before the query gets executed
Run the below query which takes the sql_id as the input parameter and will give the output with replaced bind variable values.
set serveroutput on;
DECLARE
v_fulltext CLOB;
v_sql_id VARCHAR2 (100);
CURSOR c1( v_sql_id varchar2)
IS
SELECT decode(substr(NAME,1,4),':SYS',replace(name,':',':"')||'"' ,NAME ) NAME, POSITION, datatype_string,nvl(VALUE_STRING,'NULL') value_string
FROM v$sql_bind_capture
WHERE sql_id = v_sql_id;
BEGIN
v_sql_id:= '&sql_id';
SELECT sql_fulltext
INTO v_fulltext
FROM v$sql
WHERE sql_id =v_sql_id AND ROWNUM = 1;
FOR rec IN c1(v_sql_id)
LOOP
IF substr(rec.datatype_string,1,8) = 'VARCHAR2'
THEN
SELECT REPLACE (v_fulltext,
rec.NAME,
'''' || rec.value_string || ''''
)
INTO v_fulltext
FROM DUAL;
END IF;
IF rec.datatype_string = 'NUMBER'
THEN
SELECT REPLACE (v_fulltext, rec.NAME, rec.value_string)
INTO v_fulltext
FROM DUAL;
END IF;
END LOOP;
DBMS_OUTPUT.PUT_LINE(v_fulltext);
EXCEPTION
WHEN NO_DATA_FOUND
THEN DBMS_OUTPUT.PUT_LINE('NO SQL FOUND FOR THE SQL ID');
END;
/
Looking at BiPin's answer I modified it a bit to suit my needs.
I needed to figure out what parameters users were using when running a report in real time. Here's my solution which adds the childnumber to the query from v$sql_bind_capture.
declare
v_sql_id varchar(100);
v_fulltext clob;
v_childnumber number;
begin
v_sql_id := '&sql_id';
v_childnumber := '&childnumber';
SELECT LISTAGG(SQL_text, '') within group (order by piece)
INTO v_fulltext
FROM v$sqltext
WHERE sql_id =v_sql_id;
for I in (select name,VALUE_STRING from v$sql_bind_capture where sql_id = V_SQL_ID and child_number = V_CHILDNUMBER)LOOP
v_fulltext := regexp_replace(v_fulltext,i.name||' ',i.value_string);
end LOOP;
DBMS_OUTPUT.PUT_LINE(v_fulltext);
end;

Resources