PLSQL Passing Variables to External .SQL Scripts - oracle

I have 2 scripts, Script1.sql & Script2.sql. Script1.sql retrieves some data from a table in the database which I want to then pass to Script2.sql to use.
Script1.sql is as below:
SET SERVEROUTPUT ON;
DECLARE
FundRecord Test_Table%ROWTYPE;
CURSOR Fund_Cursor IS SELECT Code, YOURNAME FROM Test_Table;
BEGIN
OPEN Fund_Cursor;
LOOP
FETCH Fund_Cursor INTO FundRecord;
EXIT WHEN Fund_Cursor%NOTFOUND;
DBMS_OUTPUT.PUT_LINE('Script1: ' || FundRecord.Code);
END LOOP;
CLOSE Fund_Cursor;
END;
/
#C:\Temp\Script2.sql FundRecord.Code;
And Script2.sql is as below:
BEGIN
DBMS_OUTPUT.PUT_LINE('Script 2:' || ' ' || '&1');
END;
/
The output from Script1.sql and Script2.sql is as follows:
Script1: ABDCE
Script2: FundRecord.Code
Why is the output of Script2 FundRecord.Code and not 'ABCDE' as I would expect?
How do I pass this in to ensure that Script2 is getting 'ABCDE' as the parameter?
Thanks

The record FundRrecord only exists within the PL/SQL block. You need to declare a separate variable that you can use outside the block:
set autoprint on serverout on
var somevalue varchar2(20)
col somevalue new_value somevalue
begin
for r in (
select dummy from dual
)
loop
dbms_output.put_line('Script 1: ' || r.dummy);
:somevalue := r.dummy;
end loop;
end;
/
#C:\Temp\Script2.sql &somevalue
The column ... new_value ... syntax is provided in SQL*Plus for page headers and footers in reports, but it is also very useful in scripts as it sets a define variable from the (last) result of a query. set autoprint on tells SQL*Plus to print the values of any bind variables (the ones with a leading :) after each PL/SQL block, and helpfully it does this by generating a query, allowing us to set up a column ... new_value and capture the result in a substitution variable.
Edit: regarding SQL Developer compatibility, I'll try some things out when I get a chance, but you might try adding something along the lines of
select :somevalue as somevalue from dual;
after the PL/SQL block, in case the column ... new_value construction works the same as in SQL*Plus but autoprint does not.

You can try as
#Script2.sql param1
and in Script2 SQL file, refer the parameter as
&1
Update 1
Here is my test case which works fine.
SELECT SYSDATE FROM &1;
This SQL statement is saved as Test.sql and it is invoked from SQLPLUS as
#D:\Test.sql dual
where dual is the parameter which been passed to Test.sql file
Result is displayed in the below screenshot

Related

PL/SQL DB Deployment Script

I'm trying to write a deployment script to run with SQL*Plus in a CI/CD pipeline but I can't find my way around what seems to be a very basic issue.
Here's a shortened version of the script release.sql:
DECLARE
vnum NUMBER;
BEGIN
SELECT COUNT(tname) INTO vnum FROM tab WHERE tname = 'DA_VERSION';
IF vnum = 0 THEN -- run create scripts
#ddl/da_001.sql
#ddl/da_002.sql
#dml/version.sql -- set initial version
END IF;
END;
da_001.sql looks like this:
CREATE TABLE TABLE_NAME
(
COLUMN1 NUMBER NOT NULL
, CONSTRAINT TABLE_NAME_PK PRIMARY KEY
(
COLUMN1
)
ENABLE
);
When I run
sqlplus.exe connection_string #release.sql
I get
CREATE TABLE DA_PRODUCTS
*
ERROR at line 6:
ORA-06550: line 6, column 1:
PLS-00103: Encountered the symbol "CREATE" when expecting one of the following:...
So it doesn't like the CREATE statement at the beginning of da_001.sql but I don't know why. What am I missing here?
Create an installation file for your scripts, ie
install.sql
===========
#ddl/da_001.sql
#ddl/da_002.sql
#dml/version.sql -- set initial version
and then selectively call it via a wrapper in SQL Plus
set feedback off
set pages 0
spool /tmp/runme.sql
select
case when COUNT(tname) = 0 then '##install.sql' else 'pro Skipped install.sql' end
FROM tab WHERE tname = 'DA_VERSION';
spool off
#/tmp/runme.sql
As others have said there is a strict separation between what SQL*PLus understands/is capable of and what is within the SQL and PLSQL languages.
Script handling is one of these distinct areas, which means you cannot executing a SQL script within a PLSQL block.
Also, SQL*Plus does not have any understanding of PLSQL logic constructs.
However, looking at your requirement there might be a way. The answer from #"Connor McDonald" should work. Here is my attempt using a more PLSQL based approach.
This approach uses SQLPLus variables, which can be referenced and amended in both PLSQL and SQLPlus.
First you need a 'No Op' script since when using the SQL*PLus '#' you must specify a valid script name:
noop.sql:
PROMPT No Op
Now your controller script:
-- Declare your variables
VAR script1 VARCHAR2(256)
VAR script2 VARCHAR2(256)
VAR script3 VARCHAR2(256)
DECLARE
vnum NUMBER;
BEGIN
:script1 := 'noop.sql';
:script2 := 'noop.sql';
:script3 := 'noop.sql';
SELECT COUNT(tname) INTO vnum FROM tab WHERE tname = 'DA_VERSION';
IF vnum = 0 THEN -- run create scripts
-- Set variables
:script1 := 'ddl/da_001.sql';
:script2 := 'ddl/da_002.sql';
:script3 := 'dml/version.sql'; -- set initial version
END IF;
END;
/
-- Make variables referencable as SQLPLus defines
COLUMN s1 NEW_VALUE s1
COLUMN s2 NEW_VALUE s2
COLUMN s3 NEW_VALUE s3
SELECT :script1 s1, :script2 s2, :script3 s3
FROM dual;
-- RUN !!
# &&s1
# &&s2
# &&s3
The 3 scriptn variables can be used in PLSQL.
To use as SQL_PLus substition variable (&) we use the COL ... NEW_VALUE command to map a SELECT list column alias to a substituion variable. So we will effectively map scriptn to subs variabl;e sn.
After the PLSQL blocks completes the scriptn variables will have teh value 'noop.sql' or the name of teh script to run.
Then at the end, reference the subs variables in the '#' commands.
Anything with 'noop.sql' will execute a blank script.
I haven't used this myself but you might try something like this (simplified demo):
declare
vnum number := 0;
begin
--select count(tname) into vnum from tab where tname = 'DA_VERSION';
if vnum = 0 then -- include create script
execute immediate q'[
#callthis.sql
]';
end if;
end;
To work within execute immediate, the called script must contain a single statement and no semicolon.

My long time SQL*Plus loop doesn't print DBMS_OUTPUT.PUT_LINE output during execution

I know that in order to print something on sqlplus like below:
begin
dbms_output.put_line('Hello!');
end;
/
I need to call
set serveroutput on;
before that.
I also know that is not needed, but I can also call
DBMS_OUTPUT.enable;
before, just in case. This is working for me.
But what if I want to keep printing the progress of a long loop? It seems impossible to me. I've tried everything to print some progress on the loop below but just doesn't work. Is there some way of doing that? I even tried to spool to a file and didn't work.
Note 1: I can't truncate or partition this table as the DBA doesn't want to help me with that, so I have to use this nasty loop...
Note 2: I've noticed that once the loop is done, the whole output is printed. Looks like oracle is buffering the output and printing everything at the end. I'm not sure how to avoid that and print on every loop iteration.
set serveroutput on;
declare
e number;
i number;
nCount number;
f number;
begin
DBMS_OUTPUT.enable;
dbms_output.put_line('Hello!');
select count(*) into e from my_big_table where upd_dt < to_date(sysdate-64);
f :=trunc(e/10000)+1;
for i in 1..f
loop
delete from my_big_table where upd_dt < to_date(sysdate-64) and rownum<=10000;
commit;
DBMS_OUTPUT.PUT_LINE('Progress: ' || to_char(i) || ' out of ' || to_char(f));
end loop;
end;
Thank you for any answer.
There are 2 standard ways for such things:
set module and action in your session DBMS_APPLICATION_INFO.SET_MODULE:
SQL> exec DBMS_APPLICATION_INFO.SET_MODULE('my_long_process', '1 from 100');
PL/SQL procedure successfully completed.
SQL> select action from v$session where module='my_long_process';
ACTION
----------------------------------------------------------------
1 from 100
set session_longops:
DBMS_APPLICATION_INFO.SET_SESSION_LONGOPS
I'd recommend it in your case since that is exactly designed for long operations.
Example on Oracle-Base.
----
PS: dbms_output,put_line saves all output in a collection (nested table) variable of dbms_output package, so you can't get it from another session and client can't get it during user call (execution). In addition to set serveroutput on you can also get the output using dbms_output.get_lines: http://orasql.org/2017/12/10/sqlplus-tips-8-dbms_output-without-serveroutput-on/
Btw, in case if you need to filter or analyze output from dbms_output, sometimes it's convenient to get output in a query, so you can use filter strings in where clause or aggregate them: https://gist.github.com/xtender/aa12b537d3884f4ba82eb37db1c93c25
DBMS_OUTPUT will only ever be displayed after the PL/SQL code has terminated and control has returned to the calling program.
Output is, as you found, buffered. When your PL/SQL code finishes, then the calling program (e.g. SQL*Plus) can go and fetch that output.
Insert into another table, maybe call it "MYOUTPUT".
Create the table:
create table myoutput (lineno number, outline varchar2(80));
Add this after your delete:
insert into MYOUTPUT values (i,'Progress: ' || to_char(i) || ' out of ' || to_char(f));
Then select from MYOUTPUT periodically to see progress.
select outline from myoutput order by lineno;
Bobby
You can use UTL_FILE to write output to an external file, as in:
DECLARE
fh UTL_FILE.FILE_TYPE;
nRow_count NUMBER := 0;
BEGIN
fh := UTL_FILE.FOPEN('DIRECTORY_NAME', 'some_file.txt', 'w');
FOR aRow IN (SELECT *
FROM SOME_TABLE)
LOOP
nRow_count := nRow_count + 1;
IF nRow_count MOD 1000 = 0 THEN
UTL_FILE.PUT_LINE(fh, 'Processing row ' || nRow_count);
UTL_FILE.FFLUSH(fh);
END IF;
-- Do something useful with the data in aRow
END LOOP; -- aRow
UTL_FILE.FCLOSE_ALL; -- Close all open file handles, including
-- the ones I've forgotten about...
END;

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

PL/SQL: Passing an 'array' of strings as argument to sql

Using plsql, I want to run my test.sql multiple times, each time will pass in a different argument to test.sql and also spool that result to a different file.
The file name may not have a relation with the argument being passed.
I'm hoping I can define two 'arrays'; one for the filename to spool to, and the other for the argument value.
declare
my_types sys.dbms_debug_vc2coll := sys.dbms_debug_vc2coll('typeA', 'typeB', 'typeC');
my_filenames sys.dbms_debug_vc2coll := sys.dbms_debug_vc2coll('fileNameForA', 'fileNameForB', 'fileNameForC');
begin
for r in my_types.first..my_types.last
loop
--dbms_output.put_line(my_types(r));
--dbms_output.put_line(my_filenames(r));
spool my_filenames(r)
#test.sql my_types(r);
spool off
end loop;
end;
/
For the spool, it says that it encountered symbol "MY_FILENAMES" when it expected := . < # % ;.
Also, it looks like test.sql is taking the argument I put in literally, so 'my_types(r)' instead of the expected 'typeA'. If there is a completely different and easier way of doing this in plsql then let me hear it. Thanks.
To get this to work (clunky, ugly), you have have to use the PLSQL to generate a sql scripts with calls to th esql script(s) you are trying to test. e.g.
set serveroutput on
spool run_it.sql
declare
my_types sys.dbms_debug_vc2coll := sys.dbms_debug_vc2coll('typeA', 'typeB', 'typeC');
my_filenames sys.dbms_debug_vc2coll := sys.dbms_debug_vc2coll('fileNameForA', 'fileNameForB', 'fileNameForC');
begin
for r in my_types.first..my_types.last
loop
dbms_output.put_line('spool ' || my_filenames(r) );
dbms_output.put_line('#test.sql ' || my_types(r) );
dbms_out.put_line ('spool off' );
end loop;
end;
/
spool off
#run_it.sql
Not tested/compiled. but I hope you get the idea.
With the above run_it.sql should look like:
spool fileNameForA
#test.sql typeA
spool off
spool fileNameForB
#test.sql typeB
spool off
.
.

Print text in Oracle SQL Developer SQL Worksheet window

I am using Oracle SQL (in SQLDeveloper, using the SQL Worksheet). I would like to print a statement before my select, such as
PRINT 'Querying Table1';
SELECT * from Table1;
What do I use to Print / show text output? It's not Print, because that gives me the error: Bind Variable Table1 is NOT DECLARED. DBMS_OUTPUT.PUT_LINE is an unknown command. (Obviously, I'm an inexperienced SQLDeveloper and Oracle user. There must be some synonym for Print, but I'm having trouble finding help on it without knowing what it is.)
for simple comments:
set serveroutput on format wrapped;
begin
DBMS_OUTPUT.put_line('simple comment');
end;
/
-- do something
begin
DBMS_OUTPUT.put_line('second simple comment');
end;
/
you should get:
anonymous block completed
simple comment
anonymous block completed
second simple comment
if you want to print out the results of variables, here's another example:
set serveroutput on format wrapped;
declare
a_comment VARCHAR2(200) :='first comment';
begin
DBMS_OUTPUT.put_line(a_comment);
end;
/
-- do something
declare
a_comment VARCHAR2(200) :='comment';
begin
DBMS_OUTPUT.put_line(a_comment || 2);
end;
your output should be:
anonymous block completed
first comment
anonymous block completed
comment2
PROMPT text to print
Note: must use
Run as Script (F5)
not
Run Statement (Ctl + Enter)
The main answer left out a step for new installs where one has to open up the dbms output window.
Then the script I used:
dbms_output.put_line('Start');
Another script:
set serveroutput on format wrapped;
begin
DBMS_OUTPUT.put_line('jabberwocky');
end;
You could set echo to on:
set echo on
REM Querying table
select * from dual;
In SQLDeveloper, hit F5 to run as a script.
You could put your text in a select statement such as...
SELECT 'Querying Table1' FROM dual;
For me, I could only get it to work with
set serveroutput on format word_wrapped;
The wraped and WRAPPED just threw errors: SQLPLUS command failed - not enough arguments
If I ommit begin - end it is error. So for me this is working (nothing else needed):
set serveroutput on;
begin
DBMS_OUTPUT.PUT_LINE('testing');
end;
If you don't want all of your SQL statements to be echoed, but you only want to see the easily identifiable results of your script, do it this way:
set echo on
REM MyFirstTable
set echo off
delete from MyFirstTable;
set echo on
REM MySecondTable
set echo off
delete from MySecondTable;
The output from the above example will look something like this:
-REM MyFirstTable
13 rows deleted.
-REM MySecondTable
27 rows deleted.

Resources