How do I declare a variable and use it in a subsequent query - oracle

I'm trying to do something that's really simple in TSQL but I'm utterly stuck doing the same in PlSQL.
I want to do the equivalent of this:-
declare #Today Date = GetDate();
declare #FirstDate Date;
declare #LastDate Date;
with cteStartDates as
(
Select START_DATE
From TABLE1
Union All
Select START_DATE
From TABLE2
)
Select #FirstDate = MIN(START_DATE),
#LastDate = DateAdd(Day, 1, #Today)
From cteStartDates;
Basically, I'm trying to get the a start date and end date where start date is the date of a first record in one of two tables and end date is tomorrow. I then need to use #FirstDate and #LastDate as parameters in a whole bunch of subsequent queries.
I'm falling at the first hurdle in PLSQL. I can't even work out how to do the equivalent of this:-
declare #Today Date = GetDate();
I've tried reading around Oracle Variables but I'm not really understanding it. I don't understand the difference between DEFINE, DECLARE and VARIABLE and no matter which I try I can't seem to get it to work and keep getting problems I don't really understand.
For example (based on Declare but I've tried all the following with Define and Variable also), I've tried this as an experiment (assign a value to variable and then issue an otherwise valid query which doesn't even use the variable):-
Declare
v_Today Date;
Begin
Select sysdate into v_Today from dual;
Select ID From atable;
End;
That tells me I need an Into clause on the second select. I don't really understand why, I'm not trying to assign ID to anything, I just want to select it. I've seen some examples that sort of imply that an into will define the column names (I'm not sure I've understood that correctly though). OK, I tried this:-
Declare
v_Today Date;
Begin
Select sysdate into v_Today from dual;
Select ID into IDColumn From atable;
End;
That gives me a error saying identifier IDColumn must be declared so clearly the into can't simply name columns.
From examples I get the impression that perhaps the begin and end surround the bock in which variables are assigned values that can then be used later in the script. So I tried this:-
Declare
v_Today Date;
Begin
Select sysdate into v_Today from dual;
End;
Select v_Today from Dual;
That tells me that it encountered the keyword Select, so it seem I can't just simply follow up the declare begin and end block with a query.
Some example seem to show that you can assign a variable, execute then use the variable. So I tried executing the Declare/Begin/End Block on it's own - that gave me message saying it ran successfully. Then I tried executing the subsequent Select v_Today from Dual, v_Today's not recognised, so clearly I've lost the value of the variable by splitting up the executions.
I feel like this should be trivially easy but I'm clearly not getting it. Can someone point me in the right direction?
Edit> Ah, finally figured it out. I can use the variables within the Begin and end but I can't just issue a select in there.

PL/SQL block is enclosed with BEGIN/END keywords. Once you leave the block you end your PL/SQL context. And enter plain SQL context where PL/SQL variables are not known.
In simple words :-)
This is correct what you have learned:
you need to select values into variables
variables must be declared in DECLARE part of PL/SQL block
if you want to return a variable to the client - e.g. to have it displayed - you need to use Oracle's package dbms_output.
Like this:
Declare
v_Today Date;
Begin
Select sysdate into v_Today from dual;
dbms_output.put_line(v_Today);
End;
You will not see a thing until you issue before PL/SQL block:
SET SERVEROUTPUT ON
This blog post can help: https://blogs.oracle.com/connect/post/building-with-blocks
Steve published whole series of posts for PL/SQL beginners. This is just the first one.

The variables in the Declare section can be used in the Begin and End block

Related

Simple query with variables as parameters in SQL Developer

Ok I have seen a bunch of questions attempt to answer this but all the answers seem horribly complicated or don't actually answer the question.
Is there a way to simply do something like this:
DECLARE
v_some_variable VARCHAR2(10) := 'Find Me';
BEGIN
select column1, column2, column3
from someTable st
where st.columnTarget = v_some_variable
END;
And have this displayed in a grid?
This is so simple on SQL server and is maddeningly NOT simple in Oracle.
Every permutation or combination I try from various answers either does not work and gives an error, requires defining every column and datatype from the output prior to running the query or does not output to the grid and you have to roll your own text output. All of these are very poor solutions in my opinion. What am I missing? Oracle can't be this bad can it?
It is arguably possible to print query output from PL/SQL by returning a refcursor or printing it or something. Although like you, I don't find any of those solutions very easy or intuitive.
I do a lot of SQL development in Oracle, and generally - if you want to get a result grid back, don't use a PL/SQL block. Just use plain SQL statements.
select column1, column2, column3
from someTable st
where st.columnTarget = :v_some_variable; -- when you run this, it'll prompt for the value
If you don't like the prompt popup, you can also declare bind variables in SQL*Plus (the backend behind SQL Developer) like this:
var v_some_variable VARCHAR2(10)
exec :v_some_variable := 'Find Me';
select column1, column2, column3
from someTable st
where st.columnTarget = :v_some_variable;
If you run that as a script (F5), it won't prompt you for a value. Note that this still isn't using a PL/SQL block.
If you just want to use SQLPlus to write a query and execute it to see the results in a grid, then the simplest way to go is the following:
Start by defining the parameter:
define p = 1000;
Then you can use it:
select *
from table1 t1
join etc on etc.id = t1.etc_id
where etc.p = &p
You need to learn to do things the Oracle way, which is not the same as the SQL Server way. It's also incredibly simple in Oracle - if you know the way Oracle does it.
First we create the database table and add some data.
create table some_table (first varchar2(10), second varchar2(10));
insert into some_table values ('First', 'Find me');
Now for the PL/SQL.
declare
v_var varchar2(10);
v_var2 varchar2(10);
begin
v_var := 'Find me';
select first into v_var2 from some_table where second = v_var;
end;
/
Compare my PL/SQL with yours. Hint: Note that mine contains into
Refer to this db fiddle

How to use DB link in a variable in for loop

I have below query which I am trying to write in a procedure after begin clause. I dont want to use it as a cursor because of some dependency.
I want to make my db link dynamic instead of hardcoding it and for this reason i put my entire for loop in variable. If i take the variable out then my procedure is working fine. I dont want to change logic of my code while trying to make dblink dynamic.
But this part of loop is not working and throwing an error as
encounter the symbol end of the file when expecting one of the following:
PROCEDURE TMP_CHECK
IS
open CS for NESS_QUERY;
loop
fetch CS into REC;
exit when CS%notfound;
INSERT INTO TMP_Data(ID,NAME,ID_TST,CHK_DATE,VALUE,CHECK,SOURCE) VALUES
(IN_SEQ_NO,DB_NAME,DB_ID,REC.DAY_ID,REC.nb_ord,'ORDS','LEOSOFT');
COMMIT;
END LOOP;
CLOSE CS;
END LOOP;
END;
Dynamic SQL is hard because it turns compilation errors into runtime errors. It looks like your query has several compilation errors: duplicate table aliases, out-of-scope alias references, cross joins between the remote tables (unless that is deliberate, in which case yuck!). So the first thing to do is get the query running as straight SQL, only then make it dynamic.
Also don't include commented code in your template SQL. Things are already hard enough, why make them even harder by doing stuff like this?
ORDER BY
-- TE.market asc,
-- TE.entity asc,
TE.dayiid ASC)'
So, now we've got that out of the way let's look at the logic of what you're trying to do. We cannot drop dynamic segments of PL/SQL into a program. This just won't work ...
LQUERY='
FOR REC IN(
SELECT
... because you have not written a complete PL/SQL statement. But there is a way to do what you want: use a cursor variable. We can open a ref cursor for static and dynamic queries. Find out more.
The following is for illustrative purposes only: you haven't explained your business logic, so this is not necessarily the best way of doing things. But it should solve your immediate problem:
declare
....
l_order number;
l_dayiid number;
l_ety_id number;
rc sys_refcursor;
begin
...
FOR IIS_DB IN C_DB
LOOP
IN_DB_LINK:=LEO_DB.DATABASE_LINK;
IN_DAY:=LEO_DB.DAY_ID;
open rc for
'SELECT order,dayiid,ety_id
from ...
ORDER BY TE.dayiid ASC)';
loop
fetch rc into l_order, l_dayiid, l_ety_id;
exit when rc%notfound;
...
end loop;
close rc;
" PLS-00487: Invalid reference to variable 'REC'"
I think your problem is this:
fetch CS into REC;
You have defined REC as a string but clearly it should be a record type, which needs to match the projection of the query you're fetching. So you need to define something like this:
Type rec_t is record (
nb_ord number,
day_id number,
entity number
);
REC rec_t;
Now you can fetch a record into REC and reference its attributes.
Incidentally the nvl() you've written to supply NB_ORD is wrong. The first argument is the one you are testing for null: 500 will never be null so that's what you'll get for every row. You need to swap the parameters round.

Appropriate way to capture dates in variables?

Oracle SQL*Plus (and so SQL Developer) doesn't support declaring variables as date (Citation).
So I can select into a varchar, but it requires I convert to a string. Then it's not really a date anymore and I can't do date arithmetic on it.
What is the appropriate way to work with date variables? I'm using variables instead of declares at the start of a BEGIN/END because I need them available outside of the block to make selects easy to display.
clear screen;
variable someid number;
variable somedate varchar;
exec :someid := 1234;
Begin
select c.some_timestamp into :somedate from someschema.sometable c
where c.someid = :someid ;
end;
/
select :somedate from dual;
You'll need to use to_date or TO_TIMESTAMP if you want to do arithmetic.
Apparently it is SqlPlus limitation. Below is an example:
variable my_date varchar2(30)
exec :my_date := '12-04-2017';
select * from mytable where date_col = to_date(:my_date,'dd-mm-yyyy');
Take a look here Declare a variable of type DATE using var
This does more or less the same thing:
declare
somedate date;
Begin
select c.some_timestamp into somedate
from someschema.sometable c
where c.someid = &someid ;
dbms_output.put_line(somedate);
end;
/
Obviously you have the need to enable SERVEROUTPUT before running the script but you gain the ability to work with somedate as a date without needing to cast it from a string all the time.
Ultimately it comes down to the details of what you're trying to do. I find it's increasingly rare that I work interactively with SQL*Plus scripts these days: I use an IDE worksheet to develop them and then some shell program executes them autonomously. You say you are using SQL Developer, so you're probably on the same journey.

Executing data validation rules from a table using a procedure with runtime values

I have a table that is filled with many data validation queries. For example a row:
SELECT end_time - start_time
FROM mt_process_status
WHERE process_id = <PROCESS_ID> AND ref_date = <REF_DATE>
I have to execute all these SQL statements filling the values within '<>' with run time values and check if the performance of the process has not changed.
Can this be done with a stored procedure? I want to understand what the solution would look like. Any links to documentation of this sort of thing, anything to guide me in the right direction.
Somewhere there's a bunch of analysts in your organization telling each other, "We've done the difficult stuff, we've defined the queries. All the database has to do is execute them, how hard can that be?" Answer: very hard.
Let's take the query you posted:
select end_time - start_time from mt_process_status where process_id = <PROCESS_ID> AND ref_date = <REF_DATE>
It's easy enough to use replace(the_str, '<PROCESS_ID>', 1234) to substitute a value. But for ref_date that's presumably a date, so it needs to be replace(the_str, '<REF_DATE>', 'date ''2017-01-01'''). Starting to get icky, and that's just handling literals. It will be even ickier when the substitution values are passed as parameters.
Of course I've made an assumption that PROCESS_ID is numeric. Maybe it isn't. Who can tell? Is there a data dictionary where these details are defined?
It would be easier if the query was defined with dynamic SQL placeholders:
select end_time - start_time from mt_process_status where process_id = :PROCESS_ID AND ref_date = :REF_DATE
Then you could forget about the replace and simply run
execute immediate the_str
using 1234, date '2017-01-01'
into whatever;
But you still need to know how many placeholders there are, in what order they occur and what datatype they are. It may feel like this is soft-coded and configurable but there is still a really hard dependency between the query and the program which calls it.
Plus you have lost the ability to do impact analysis. What queries will be affected when you change mt_process_status? Who can tell?
CREATE OR REPLACE PROCEDURE MY_PROC(g_id)
AS
l_query varchar2(1000);
l_duration number;
BEGIN
-- Get the query from the table
select query into l_query from my_table where id=g_id;
-- l_query contains "select end_time - start_time from mt_process_status where process_id = <PROCESS_ID> and ref_date = <REF_DATE>"
-- Put value to replace the tags (take care of code injection...)
l_query := REPLACE(l_query,'<PROCESS_ID>', something);
l_query := REPLACE(l_query,'<REF_DATE>', something_else);
EXECUTE IMMEDIATE lquery RETURN INTO l_duration;
-- Do what you have to do...
-- If you do a function, then you can:
-- RETURN l_duration;
END;
/
Your question is not clear. This is the answer to your question. Use variables in a procedure.
regards.
I would put the reference data in another table and do it as a join

Struggling to write Oracle Sql Query using Substitution Variables

I'm trying to take some of our more complicated queries and make them as user friendly as possible. For various reasons, we can't/would prefer not to bake them into the schema. Thus, I'm trying to write a version of a query we have to run regularly to simplify entering some of the parameters. My current option is to find-replace the pertinent values each time I have to run it, which is less than ideal.
I'd like to leverage TOAD's support of substitution variables (where if one of these is present, it prompts the user to enter the intended values then runs the whole query).
I've attempted to set up a test query to familiarize myself with the process, but it's not behaving as expected.
DECLARE
&&v_value VARCHAR2 (200);
v_result VARCHAR2 (200);
BEGIN
select &v_value into v_result from dual;
dbms_output.PUT_LINE('result :'|| to_char(v_result));
END;
The result is
result :
Which is of course less than ideal. My expectation is for it to be
result : some_value
Where 'some_value' was the text I entered into the TOAD prompt window.
The reason I need this to work is so that we can treat this as a procedure and pass it the values it needs once and use it throughout the series of queries we have to run. I'm not very familiar or adept with building these kids of queries, so any help or guidance would be appreciated.
I was planning on running it via TOAD's SQL editor window.
The actual procedure I need to run is actually a series of updates and inserts; I used a select as a test case to ensure I understood how to use them.
It looks like you're having a problem with assigning a value to the parameter. I would use the following syntax as it both avoids doing an unnecessary context switch and allows you to do exception handling on the value passed.
DECLARE
v_value VARCHAR2(200);
v_result VARCHAR2(200);
BEGIN
v_value := '&input';
v_result := v_value;
dbms_output.put_line('result : ' || v_result);
END;
/
Given that you're running your series of select statements in Toad - presumably as a script - I fail to see why you need a procedure at all.
You could just use the parameter in your queries like so (making sure to run as a script):
set verify on;
select '&&v_val' value from dual;
select * from dual
where dummy = '&&v_val';
old: select '&&v_val' value from dual
new: select 'X' value from dual
VALUE
-----
X
1 row selected.
old: select * from dual
where dummy = '&&v_val'
new: select * from dual
where dummy = 'X'
DUMMY
-----
X
1 row selected.
(I deliberately set the verify to be on so that you could see the results of what happens when running the above two statements as a script; you might prefer to switch it off.)

Resources