ORACLE PL/SQL Variable Function Scope - Need Explanation - sql

I just tripped over an answer to a problem I was having with a PL/SQL variable not being recognized by a function and I was hoping someone could explain to me why my solution worked and what is happening "underneath the hood".
Background
As part of an optimization project, I am trying to collect metrics on individual SQL scripts within a Stored Procedure. The Stored Proc that I am dissecting has an In-type date parameter that I need to define in order to run each individual SQL Script:
CREATE OR REPLACE myStoredProc (DATE_IN DATE, ERROR_OUT OUT VARCHAR2)
IS
BEGIN
--Truncate Temp Tables
--6 Individual SQL Scripts
EXCEPTION
--Error Handling
END;
To run each script individually, I decided to just drop each SQL statement into a PL/SQL block and feed the DATE_IN parameter in as a variable:
DECLARE
DATE_IN DATE := TO_DATE('16-JUL-2014','DD-MON-RR');
BEGIN
--Place individual script here
END;
The Problem
This approach worked fine for a couple of the queries that referred to this DATE_IN variable but one query with a reference to an outside function which takes DATE_IN as a parameter started throwing an ORA-00904 error:
DECLARE
DATE_IN DATE := TO_DATE('16-JUL-2014','DD-MON-RR');
BEGIN
insert into temp_table
SELECT table1.field1,
table1.field2,
table2.fieldA,
MyFunction(table1.field1, DATE_IN) --This was the problem line
FROM
table1,
table2
WHERE EXISTS (inner query)
AND table1.keys = table2.keys
AND table2.date <= DATE_IN
END;
Solution
At the advice of another Developer, I was able to get around this error by adding a colon (:) in front of the DATE_IN variable that I was passing into the function so that the problem line read MyFunction(table1.field1, :DATE_IN). As soon as I did that, my error disappeared and I was able to run the query without issue.
I was happy for the result but the other Developer wasn't able to explain why it was needed, only that it was necessary to call any functions or other stored procs from a PL/SQL statement. I assume this has something to do with scope but I would like to get a better explanation as to why this colon was necessary for the function to see the variable.
Questions
I've tried to do a little research looking over Oracle documentation on parameters, variables, binding/declaring and constants but my research has only given me more questions:
After reading up on variables, I now question if that is the correct term for what I have been using (since I didn't actually use the VARIABLE command and I'm passing in a date - which is not an allowable data type). If my DATE_IN DATE := statement is not a variable, then what is it?
Why were the rest of my references to DATE_IN recognized by the compiler but passing the value to the function was out of scope?
What exactly is the colon (:) doing here? Is this turning that into a bind variable?
Thanks in advance. I appreciate any guidance you can provide!
----------------------------------EDIT--------------------------------------
I was asked to provide additional information. My Db version is 11G, 11.2.0.2.0. The query that I was able to reproduce this error is below.
DECLARE
EXTRACT_DT_IN DATE := TO_DATE('16-JUL-2014','DD-MON-RR');
BEGIN
--This begins the pre-optimized query that I'm testing
insert into AELI_COV_TMP_2_OPT
SELECT /*+ ordered use_nl(CM MAMT) INDEX (CM CSMB_CSMB2_UK) INDEX (MAMT (MBAM_CSMB_FK_I) */
CM.CASE_MBR_KEY
,CM.pyrl_no
,MAMT.AMT
,MAMT.FREQ_CD
,MAMT.HOURS
,aeli$cov_pdtodt(CM.CASE_MBR_KEY, EXTRACT_DT_IN)
FROM
CASE_MEMBERS CM
,MEMBER_AMOUNTS MAMT
WHERE EXISTS (select /*+ INDEX(SDEF SLRY_BCAT_FK_I) */
'x'
from SALARY_DEF SDEF
where SDEF.CASE_KEY = CM.CASE_KEY
AND SDEF.TYP_CD = '04'
AND SDEF.SLRY_KEY = MAMT.SLRY_KEY)
AND CM.CASE_MBR_KEY = MAMT.CASE_MBR_KEY
AND MAMT.STAT_CD = '00'
AND (MAMT.xpir_dt is null or MAMT.xpir_dt > EXTRACT_DT_IN)
AND MAMT.eff_dt <= EXTRACT_DT_IN;
--This ends the pre-optimized query that I'm testing
END;
Here is the error I'm encountering when trying to run an Explain Plan on this statement. I am able to get past this error if I remove reference to line 13 or I add a colon (:) to the EXTRACT_DT_IN on that line.
----------------------EDIT 2-------------------
Here is the function signature of aeli$.cov_pdtodt. (I've replaced the owner for security reasons).
CREATE OR REPLACE function __owner__.aeli$cov_pdtodt
(CASE_MBR_KEY_IN IN NUMBER, EXTRACT_EFF_DT_IN DATE)
RETURN DATE IS
PDTODT DATE;

Your anonymous block is fine as it is, as long as you execute the whole block. If you try to execute just the insert, or its select, as a standalone command then it will indeed fail with ORA-00904.
That isn't quite a scope problem, it's a context problem. You're trying to refer to a PL/SQL variable in an SQL context, and that is never going to work.
In a PL/SQL context this would work:
declare
some_var dual.dummy%type := 'X';
begin
insert into some_table
select dummy from dual where dummy = some_var;
end;
/
... because the insert has access to the PL/SQL some_var.
In an SQL context this will error:
select * from dual where dummy = some_var;
... because it's looking for a column called SOME_VAR, and there isn't one.
If you do this instead:
select * from dual where dummy = :some_var;
... the some_var is now a client-managed bind variable. If you execute that you'll either be prompted for the bind value, or given a not-all-variables-bound error, or bind-variable-not-declared, or similar, depending on your client.
If you only do an explain plan of that though, e.g. with
set auto trace traceonly explain
select * from dual where dummy = :some_var;
... then the bind variable doesn't necessarily have to be populated for the plan to be calculated. Some clients may still complain and want a bind value, but the parser would be OK with it - enough to produce a plan anyway. Though not able to take advantage of bind variable peeking or histograms etc.
For example, SQL Developer happily produces a plan for your original sample query if both references are turned into bind variables, just the insert ... part of the block is selected, and you press Explain Plan (F10).

I'm not sure what you read, but you're mixed up on a few things here.
Your DATE_IN is a variable. You don't need to type 'VARIABLE' anywhere to declare a variable, all you need is the name of the variable and the datatype.
All of the below are legitimate variables in PL/SQL (although poorly named).
variable_1 NUMBER;
variable_2 VARCHAR2(100);
variable_3 DATE;
It's hard to tell what you're doing in your code without seeing it all. Do you have two DATE_IN variables declared within the same block? Is DATE_IN the name of a column in your table?
If you have a column named DATE_IN in table1 or table2, that's likely your problem. Oracle doesn't know if you want to use your variable or your column, and it will always default to the column name. Your function would be expecting a DATE and receiving a column, hence the error.

Related

SQL injection with function call

Following query gets executed in my program, where 'a' is the parameter value which I am taking as input & passing it in query.
select * from emp where name LIKE LOWER('%a%')
Can anybody tell me whether I can do SQL injection attack on above query or is it safe?
I have seen SQL injection with where clause & like operator but can we do it with function call as well. What can I pass as the instead of 'a' for SQL injection.
I am using PL/SQL editor & Oracle DB.
Risk of SQL-injection appears when your application interacts with environment (other program or user) and assembles SQL query from parts using string concatenation. For example, you can write PL/SQL procedure:
create or replace procedure myproc(a varchar2) is
sql_str varchar2(4000);
sql_result number;
begin
execute immediate 'select count(*) from mytable where mycolumn = ' || a into sql_result;
end;
This procedure is vulnerable. You can pass there a string ''abc''' or 1 = 1 or anything like this and it distorts result (or made something worse).
Or you can write it like this:
create or replace procedure myproc(a varchar2) is
sql_str varchar2(4000);
sql_result number;
begin
execute immediate 'select count(*) from mytable where mycolumn = :A' using a into sql_result;
end;
And this procedure is not vulnerable.
Also you can write
create or replace procedure myproc(a varchar2) is
sql_str varchar2(4000);
sql_result number;
begin
select count(*)
into sql_result
from mytable
where mycolumn = a;
end;
Here is no problems at all, it is the most secure way (it is "static SQL"), but sometimes we need dynamic SQL (like in first two examples).
Why first way is bad and second is good? It is because SQL engine compiles queries almost like other compilers compile their code, like C++ for example. SQL engine compiles query as a "program" and defines possible "variables" in this "program". "Variable" in second procedure is the parameter :A. If query contains "variables", engine asks their values (USING clause) and passes them into compiled query. In first case engine gets concatenated string:
select count(*) from mytable where mycolumn = 'abc' or 1 = 1
considers it like a whole "program" and executes it "as is". In second case engine gets string
select count(*) from mytable where mycolumn = :A
compiles it, defines 1 "variable" A and asks it value, and then passes it to a "program", and that "program" just searches value 'abc' or 1 = 1 in column mycolumn. This works not only with dynamic SQL in PL/SQL code. It works the same way in any language, and all popular frameworks (for java, c#, delphi, etc.) and all popular DBMS provide instruments for safe work as in second example.
Of course, it was simplified example, sometimes consequences could be much more worse.
a can be
1') or 1 = 1 or LIKE LOWER('1
You should make sure the values passed as a are clean maybe by stripping the slashes in a.
You can exploit SQL Injection almost anywhere.
Checkout my white paper, Google "SQL Injection Anywhere" that demonstrates injections in weird places - including function calls.

Trouble using Oracle SQL variable in where clause

I'm trying to write a script to help assist with the numerous "What's happened to this actionlist" type calls, but i'm coming up against a brick wall doing some of the most simplest operations.
I'm trying to declare a variable and use it in the where clause of a query. I've trimmed everything irrelevant out of the query, in an attempt to get this core functionality to work.
The id is usually an 18 digit number, but does occasionally contain alpha-numerics, hence why it is a varchar. The column I'm trying to compare it to is an 18 byte varchar field.
declare
id_to_check VARCHAR(18);
begin
id_to_check := '549576015500000109';
select
txn_timestamp, exception_type
from cut.event
where irn_id = id_to_check;
end;
Every time it throws an error: 'an INTO clause is expected in this select statement'. I understand how INTO's work, i.e. if I wanted to assign the result of a select to the variable, but I don't understand how that would apply in this instance as I'm not assigning the result of the query to a variable?
The other frustrating thing is I'm actually following documentation on docs.oracle.com.
http://docs.oracle.com/cd/B19306_01/appdev.102/b14261/overview.htm#LNPLS001.
I've also looked at various google results, but I can't figure out how to do a comparison with the variable without first selecting something into it, but as you can see, I can't select into because I only need it for comparison reasons?
Kind Regards,
Ian
Raphaƫl Althaus's answer applies if you are doing something in PL/SQL, but I'm not sure that's actually what you want. It sounds like you want a plain SQL query, but you want to be able to pass it a value? Assuming you're running this in SQL*Plus or SQL Developer you have a few ways to declare and use a fixed value. (Similar methods will be available in other clients; PL/SQL developer seems to like both of these, at least in the command window, but not sure about Toad or anything else).
1) Using a substitution variable:
define id_to_check = 549576015500000109
select txn_timestamp, exception_type
from cut.event
where irn_id = '&id_to_check';
2) Using a bind variable:
variable id_to_check varchar2(18)
exec :id_to_check := '549576015500000109';
select txn_timestamp, exception_type
from cut.event
where irn_id = :id_to_check;
The exec is shorthand for a small anonymous PL/SQL block, but apart from step of assigning the value to the variable, it's plain SQL, so the query doesn't need to be wrapped in a begin ... end, and you don't have to worry about selecting into variables.
In either case you can have the ID value passed as an argument, or ask for it as part of the script, when the script it run. For example, you could write a query.sql script:
define id_to_check = &1
select txn_timestamp, exception_type
from cut.event
where irn_id = '&id_to_check';
... and run it with:
sqlplus -l -s <user>/<password> #query 549576015500000109
... or as:
accept id_to_check prompt 'Enter the ID to check: '
select txn_timestamp, exception_type
from cut.event
where irn_id = '&id_to_check';
... and have the user prompted to enter the ID at run-time.
The question is not "Do you want to put the result into a variable".
In the "body" of an Oracle procedure, you just can't use a SELECT without an INTO clause.
(In fact you can have, but in a cursor).
so
declare
id_to_check VARCHAR(18);
event_timestamp cut.event.txn_timestamp%TYPE;
event_exception cut.event.exception_type%TYPE;
begin
id_to_check := '549576015500000109';
select
txn_timestamp, exception_type
INTO event_timestamp, event_exception
from cut.event
where irn_id = id_to_check;
end;
Caution : if no result is found, this will raise a NO_DATA_FOUND exception.
You can manage it with
begin
id_to_check := '549576015500000109';
BEGIN
select
txn_timestamp, exception_type
INTO event_timestamp, event_exception
from cut.event
where irn_id = id_to_check;
EXCEPTION WHEN NO_DATA_FOUND THEN
--...something
END
end
Your real problem is NOT about using variable (you got that right), but about returning cursors from PL/SQL blocks.
You seem to believe that a bare select at the end of a procedure should somehow make it return the results or at least display them. While some databases work like that - Oracle does not. If you want any data to leave a block of any kind (like function or procedure), you have to pass them explicitly. Usually this is done by using an in parameter of a cursor ref type.
As you can see, the select without into would not make sense in Oracle. Even if you managed to make it work and select the data, you would not be able to access the results.

Viewing query results with a parameters in Oracle

I need to run big queries (that was a part of SP) and look at their results (just trying to find a bug in a big SP with many unions. I want to break it into parts and run them separately).
How can I do that if this SP have few parameters? I don't want to replace them in code, it would be great just to add declare in a header with a hardcode for this parameter.
I've tried something like this:
DECLARE
p_asOfDate DATE := '22-Feb-2011';
BEGIN
SELECT * from myTable where dateInTable < p_asOfDate;
END
But it says that I should use INTO keyword. How can I view this results in my IDE? (I'm using Aqua data studio)
I need to do that very often, so will be very happy if will find a simple solution
You are using an anonymous block of pl/sql code.
In pl/sql procedures you need to specify a target variable for the result.
So you first need to define a variable to hold the result in the declare section
and then insert the result data into it.
DECLARE
p_asOfDate DATE := '22-Feb-2011';
p_result myTable%ROWTYPE;
BEGIN
select * into p_result from myTable where dateInTable < p_asOfDate;
END
That said you will probaply get more than one row returned, so I would use
a cursor to get the rows separately.
DECLARE
CURSOR c_cursor (asOfDate IN DATE) is
select * from myTable where dateInTable < asOfDate;
p_asOfDate DATE := '22-Feb-2011';
p_result myTable%ROWTYPE;
BEGIN
OPEN c_cursor(p_asOfDate);
loop
FETCH c_cursor into p_result;
exit when c_cursor%NOTFOUND;
/* do something with the result row here */
end loop;
CLOSE c_cursor;
END
To output the results you can use something like this for example:
dbms_output.put_line('some text' || p_result.someColumn);
Alternatively you can execute the query on an sql command-line (like sqlplus)
and get the result as a table immediately.
I hope I understood your question correctly...
update
Here is a different way to inject your test data:
Use your tools sql execution environemnt to submit your sql statement directly without a pl/sql block.
Use a "&" in front of the variable part to trigger a prompt for the variable.
select * from myTable where dateInTable < &p_asOfDate;
The Result should be displayed in a formatted way by your tool this way.
I do not know about Aqua, but some tools have functions to define those parameters outside the sql code.

Oracle: select into variable being used in where clause

Can I change the value of a variable by using a select into with the variable's original value as part of the where clause in the select statement?
EI would the following code work as expected:
declare
v_id number;
v_table number; --set elsewhere in code to either 1 or 2
begin
select id into v_id from table_1 where name = 'John Smith';
if(v_table = 2) then
select id into v_id from table_2 where fk_id = v_id;
end if;
end;
Should work. Have you tried it? Any issues?
After parsing your select statements should have bind variables where your v_id is. The substitution is made when the statement is actually executed.
Edit:
Unless you're sticking constants into your queries, Oracle will always parse them into statements with bind variables - it enables the DBMS to reuse the same basic query with multiple values without reparsing the statement - a huge performance gain. The whole idea of a bind variable is runtime substitution of values into a parsed query. Think of it this way: in order to process a query, all of the values need to be known. You send them to the engine, Oracle does it's work, and returns a result. It's a serial process with no way for the output value to step on the input one.

PL/SQL Query with Variables

I have a fairly complex query that will be referencing a single date as a start or stop date multiple times throughout. I'm going to have to run this query for 3 different fiscal years, and don't want to have to hunt down the date 17 times in order to change it throughout my query.
Is there a way to set a variable at the beginning of my query and reference it throughout? I'm not looking to write a whole function, just reference a variable throughout my query.
Yes, depends how you want to do it.
You could use an anonymous procedure IE:
BEGIN
v_date DATE := TO_DATE(your_date, your_date_mask);
[your query referencing v_date where ever you need];
END;
Or if you run the query in SQLPlus, you use & to note variables (IE: &your_date), and will be prompted for the value when you run the script.
As OMG Ponies says, inside PL/SQL you can always refer to any PL/SQL variable (including parameters) right in the SQL as long as it's static SQL. Outside PL/SQL, or if your SQL is dynamic (because native dynamic SQL doesn't support reusable named parameters at least as of 10g) you can use the following trick. Add the following before the WHERE clause in your query:
CROSS JOIN (SELECT :dateparam Mydate FROM dual) Dateview
And everywhere you want to refer to that value in your main query, call it Dateview.Mydate Then when you execute the query, you need only pass in the one bind parameter.
You're not really saying how you reference this so I'll just show from SQL*Plus point of view.
Two ways
Have it prompt you for the value. Since you use the same variable many times you'll want to use the && operator.
SQL> SELECT &&var, &&var FROM Dual;
Enter value for var: 'PUMPKIN'
old 1: SELECT &&var, &&var FROM Dual
new 1: SELECT 'PUMPKIN', 'PUMPKIN' FROM Dual
'PUMPKI 'PUMPKI
------- -------
PUMPKIN PUMPKIN
Alternatively you could set it before you ran your SQL.
SQL> VARIABLE new_var VARCHAR2(20);
SQL> EXECUTE :new_var := 'PUMPKIN PIE';
PL/SQL procedure successfully completed.
SQL> SELECT :new_var, :new_var FROM DUAL;
:NEW_VAR :NEW_VAR
-------------------------------- --------------------------------
PUMPKIN PIE PUMPKIN PIE
If you use Toad with second mouse button -> Execute as Script, so not prompt you for values:
var myVar varchar2(20);
exec :req := 'x';
delete from MYTable where Field =
:myVar;