PL/SQL DB Deployment Script - oracle

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.

Related

Creating Oracle PL/SQL Stored procedure

I'm trying to convert the SQL Query to Oracle PL/SQL stored procedure.
Here is the query:
select * from table1 where DATE = " + s1 + " and TYPE='" + ty + "' and NAME='"+nm+"' Order by TYPE DEsc;
Here is the Stored Procedure:
CREATE PROCEDURE procedure1
(
s1 IN DATE,
ty IN CHAR DEFAULT 2,
nm IN VARCHAR2 DEFAULT 64
)
IS
d table1.DATE%TYPE;
C table1.TYPE%TYPE;
S table1.NAME%TYPE;
CURSOR tb IS select DATE,TYPE,NAME INTO d,c,s from table1;
BEGIN
FOR i IN tb
LOOP
DBMS_OUTPUT.PUT_LINE('DATE' ||i.DATE);
DBMS_OUTPUT.PUT_LINE('TYPE' ||i.TYPE);
DBMS_OUTPUT.PUT_LINE('NAME' ||i.NAME);
END LOOP;
END procedure1;
I do not see any output after Executing Stored procedure. I'm not sure if I have created the stored procedure correctly.
"I do not see any output after Executing Stored procedure"
Your "output" is DBMS_OUTPUT which is for displaying text to a screen. However, by default it writes the text to a buffer, and we need to enable the output to see the contents of the buffer.
How to do this varies depending on which client you're using. In SQL*Plus it's
SQL> set serveroutput on
In an IDE like TOAD, PLSQL Developer or Oracle SQL Developer there's a separate DBMS_OUTPUT tab: click on the tab and enable output (there's a button) - or set Preferences to always have it on.
DBMS_OUTPUT is rarely a useful means for returning data in an actual application. The normal approach is to use a Ref Cursor, which maps to JDBC and ODBC ResultSet classes. Something like this:
CREATE OR REPLACE PROCEDURE procedure1
(
s1 IN DATE,
ty IN CHAR DEFAULT 2,
nm IN VARCHAR2 DEFAULT 64,
rc out sys_refcursor
)
IS
BEGIN
open rc for
select * from table1
where d = s1
and c = ty
and s = nm;
END procedure1;
/
Incidentally, your parameters are defined with string datatypes but the defaults are numeric values. Please don't get into bad habits. Strong datatyping is a key defence against data corruption and broken code, so always use the correct data type.
try this;
CREATE PROCEDURE PROCEDURE1 (
S1 IN DATE,
TY IN CHAR DEFAULT 2,
NM IN VARCHAR2 DEFAULT 64
)
IS
BEGIN
FOR I IN (SELECT DATE, TYPE, NAME FROM TABLE1)--Easier way to use cursor
LOOP
DBMS_OUTPUT.PUT_LINE ('DATE' || I.DATE);
DBMS_OUTPUT.PUT_LINE ('TYPE' || I.TYPE);
DBMS_OUTPUT.PUT_LINE ('NAME' || I.NAME);
END LOOP;
END PROCEDURE1;
by executing this you only created the procedure and stored it in db, you need to call it and turn on system output to see the output. like this:
set serveroutput on;
begin
PROCEDURE1(null, null, null);
end;
What environment are using to compile your code? You should certainly be seeing some immediate feedback.
Note that in most environments, though, you need to do a little more than you did before.
The final ";" in your code is part of PL/SQL. It does not trigger execution of your DDL. Generally you should do this:
CREATE OR REPLACE PROCEDURE myproc
IS
BEGIN
...
END myproc;
/
And that "/" will submit your statement for execution.

PLSQL Passing Variables to External .SQL Scripts

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

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
.
.

Oracle Bind variable - query requested

I would like to create a bind parameter based results for my Oracle SQL Query.
like for example.
Select * from emp where ename = :xyz;
it should ask me for the xyz value at runtime.
what should i do?
it keeps me givig error - Bind variable not declared...
please help...
Thanks.
If you run your statement as a script (F5) then you'll get this in the 'script output' pane:
Bind Variable "xyz" is NOT DECLARED
If you run it as a statement (control-enter) instead then you're prompted for the bind value, and the result appears in the 'query result' pane.
If you do need to run as a script you can use a substitution variable in your query as AlexisSTDM showed, or you can keep it as a bind variable but declare it with the variable command and assign a value to it from a substitution variable - which means your actual query doesn't need to be hard-parsed for each new value:
variable xyz varchar2(1)
exec :xyz := '&abc';
select * from dual where dummy = :xyz;
When run as a script you'll be prompted for the substitution variable abc, and that value is then assigned to the bind variable xyz, which is used by the query. With a slightly more generic query and supplying the value X when prompted, the 'script output' pane shows:
old:exec :xyz := '&abc'
new:exec :xyz := 'X'
anonymous block completed
DUMMY
-----
X
You can add set verify off to hide the old/new lines, and can set feedback off before the exec to hide the anonymous block completed line, optionally turning it back on afterwards if you want feedback from your real queries. You can set a fixed value with exec if you prefer, but as you asked for prompts that isn't really relevant in this case; and if you have multiple bind values in the script, you can set them all from an anonymous PL/SQL block instead of using the exec shortcut multiple times.
If you are using bind variable in procedures, you can use the bind variable in the following manner
Create your query as dynamic means stored in local variable
for eg.
create procedure PROC ( output_result out SYS_REFCURSOR)
as
l_query varchar2(1000) := Null;
begin
l_query := 'Select * from emp where ename = :xyz';
OPEN output_result
FOR l_query using xyz;
End PROC ;
I think you should use the escape character, in my case is &.
it would be
SELECT * from emp where ename = &xyz;
Thanks
Regards

How do you pass an argument to a PL/SQL block within a sql file called using START in sqlplus?

I have a bash script running several sql files via sqlplus:
sqlplus $connectioninfo << end
start file1.sql
start file2.sql
start file3.sql $variable
quit
end
file3 has some PL/SQL:
BEGIN
DBMS_OUTPUT.PUT_LINE(&1);
END;
/
But it just prints the literal "&1" instead of the value of $variable. I have also tried the following in file3:
DEFINE var_a = &1;
BEGIN
DBMS_OUTPUT.PUT_LINE(var_a);
END;
/
and also the following:
DECLARE
var_b VARCHAR2(64) := &1;
BEGIN
DBMS_OUTPUT.PUT_LINE(var_b);
END;
/
and finally:
DEFINE var_a = &1;
DECLARE
var_b VARCHAR2(64) := var_a;
BEGIN
DBMS_OUTPUT.PUT_LINE(var_b);
END;
/
However, I am getting various errors or just the literal value '&1' for all of these.
Try adding SET DEFINE ON to the start of your script file3.sql.
If SET DEFINE is ON, SQL*Plus will replace &... substitution parameters with their values. If SET DEFINE is OFF (which it seems to be for you), SQL*Plus won't do this.

Resources