Can Sqlplus read the contents of a file into a variable? - oracle

I've been tinkering with sqlplus for awhile now, and I've managed to get sqlplus to read the contents of a file into a substitution variable as such:
exit | sqlplus -s login/pass#db #script.sql "`cat file.txt`"
This mostly works as my script requires... even newlines in the file are preserved. However, I was only using a sample file which was 50 or 60 bytes in size. The actual files I'll end up using will be at least a few kilobytes. So it was at this point I decided to check the max size of a substitution variable: 240 characters.
Is there a way within my sqlplus script to read a file's contents into a bind variable? The Oracle documentation seems to hint at this with the GET command, saying that typically you'll use this just to load a sql/sqlplus script.
http://docs.oracle.com/cd/B10501_01/server.920/a90842/ch13.htm#1009882
file_name[.ext] Represents the file you wish to load (typically a script).

Yes, there's a tricky way to do it.
Put something into props.txt and run the script:
DECLARE
-- the ## expression must be in separate line as follows
file_contents VARCHAR2(32767) := '
##props.txt
';
BEGIN
dbms_output.put_line('===');
dbms_output.put_line(file_contents);
dbms_output.put_line('===');
END;
/
Note that the file props.txt can not contain an "#" or you'll get nested
SQL*PLUS calls

No. Load would only store the file contents in Sql*Plus's own sql buffer. You can then run, edit and list the buffer.
A substitution variable is not the right place to load a file into. Use a bind variable of type clob for that and load the file contents using utl_file. But of course the file has to be located on the server in this case.
edit: if the data has to be located on the client, your option would be to load the clob using a pl/sql block and several calls to dbms_lob.writeappend
Your file would have to look like this (cannot test it ATM):
var l clob;
begin
dbms_lob.createtemporary(l);
dbms_lob.writeappend(l, 'abcdef...');
dbms_lob.writeappend(l, 'ijkl...');
end;
/

Please consider using literal quoted string - this allows you to have quotes in the linked file:
DECLARE
-- the ## expression must be in separate line as follows
file_contents VARCHAR2(32767) := q'[
##props.txt
]';
BEGIN
dbms_output.put_line('===');
dbms_output.put_line(file_contents);
dbms_output.put_line('===');
END;
/

Related

How to execute a oracle procedure having out parameter inside a unix script

Can any one please tell me how to execute a simple oracle stored procedure having out parameter inside a shell script.That means it should return a value into the unix environment.
I assume you want to start a script using SQLPLUS. This answer explains how to assign the value of an out parameter to a bind variable in SQLPLUS.
Call stored procedure from sqlplus
You can exit sqlplus with this value and use that value in the calling script.
exit x
But this usually is restricted to numerical values in a limited range.
There are a number of ways, but the one I tend to use is illustrated below.
The sqlplus script the_sql_script.sql
var ret varchar2(2000)
exec the_procedure ( the_out_param => :ret );
set pages 0 head off lines 200 trimspool on
spool sqlplus.return
select 'RETURN_VALUE=' || :ret FROM dual;
spool off
quit
In shell:
sqlplus / # the_sql_script.sql
. ./sqlplus.return
echo $RETURN_VALUE

how to write a batch file, which runs a PL/SQL block, to tell me many records are inserted/deleted/etc. in a log file?

I have created two batch files to run two separate .sql file in Windows Task Scheduler. The batch file for both looks like this:
sqlplus userid/password#database #C:\XXX.sql>>C:\output.log
echo commit; | userid/password#database
The first .sql file (SQL1) is a PL/SQL block like this:
SET SERVEROUT ON
DECLARE
....
BEGIN
IF ...
....
ELSE
#D:\DM_FIX.sql;
END IF
END
The DM_FIX.sql file is to insert a bunch of records into a table, and it starts with the INSERT command.
The second.sql file is not a block file. It's doing a bunch of DDL/DML comand. the file looks like below:
Truncate Table YYY
Reuse Storage;
Commit;
Insert into Table YYY
Select ... from
Commit;
Delete from Table YYY
where ...
Commit;
When I run the second .sql file, I get an output that indicates "The table has been truncated; #### records are inserted; #### records are deleted..."
But when I run the first, although the PL/SQL procedure is executed successfully, I don't get a line saying how many records are inserted, and I'm trying to figure out a way to do it.
Does any one know what could be the trick?
Thanks!
Revised Answer
As #Alex Poole helpfully pointed out, you can use the # nomenclature in a PL/SQL block from SQL*Plus, as that would load the second file's commands into the block that you're calling it from.
The reason that you're not getting any output is that, as far as SQL*Plus is concerned it's all one command: the SQLPlus block. In order to get output to your log for those commands, you'll need to create it yourself, using DBMS_OUTPUT. You would need to include a line like the one below after each command.
DBMS_OUTPUT.PUT_LINE(SQL%ROWCOUNT || ' rows inserted');
However, you should note that doing this will cause your script to throw errors if it's ever called from outside of a PL/SQL block. My original solution would avoid this restriction, as it eliminates the need to use DBMS_OUTPUT.
Original Answer
I find it difficult to believe that the PL/SQL in the first file is executing successfully. This is because you're mixing SQL*Plus commands with PL/SQL code. They are separate systems - it's like trying to use shell commands natively in a programming language. You should be getting PLS-00103: Encountered the symbol "#" when expecting... from the first file.
SQL*Plus doesn't have conditionals, so, in order to keep the PL/SQL and SQLPlus commands seperate, you'd need to fake it somewhat. I'd suggest putting the file name into a substitution variable, then using that to run a file:
VARIABLE v_my_file_bind varchar2(100)
DECLARE
...
BEGIN
IF ...
...
:v_my_file_bind := 'D:\EMPTY_FILE.sql';
ELSE
:v_my_file_bind := 'D:\DM_FIX.sql';
END IF;
END;
/
COLUMN v_my_file_column new_value my_file_substitution noprint
SELECT :v_my_file_bind v_my_file_column from dual;
#&&my_file_substitution
To show count of modified rows in PL/SQL, use SQL%ROWCOUNT:
begin
insert into my_table ...
select ...
from ...;
dbms_output.put_line('Rows inserted: ' || SQL%ROWCOUNT);
commit;
end;
/
Remember, that SQL%ROWCOUNT variable will reset to 0 after commit.
Documentation: http://docs.oracle.com/cd/B28359_01/appdev.111/b28370/sql_cursor.htm

Passing Contents of File as Parameter to Sql*Plus Command

I'm trying to write a sqlplus command that creates a table from a query that is stored in an .sql file.
The particular .sql file that contains the query would be supplied to my sqlplus command as a variable (&v_InputQuery).
I've tried something like this, but it doesn't work.
CREATE TABLE &v_OutputTable AS
(
< &v_InputQuery
)
;
I get an error saying that there's a missing SELECT keyword.
What I'd really like is for &v_InputQuery to be replaced not with the name of the file specified by the user, but with the actual contents of the file. Is there a way to do that?
Thank you very much.
Yes, you can do that. If your query is in a file called v_InputQuery.sql, you can do this:
CREATE TABLE &v_OutputTable AS (
#v_InputQuery.sql
) ;
It's important that the # is the first character on the line. SQL*Plus will read the file and put its contents at that location. So make sure you don't have any terminating characters in the file such as ; or /.
Unfortunately, You cannot create a SQL*Plus command, but instead create a shell script to do it!
Lets say my_script.sh is below
#you can always complete the user interaction at unix/dos
USER=your_user
PASS=your_pass
DB=your_db
OUTPUT_TABLE=$1;
QUERY_FILE=$2;
SELECT_QUERY=`cat $QUERY_FILE`;
sqlplus -S ${USER}/${PASS}#${DB} << !
SET SERVEROUTPUT ON;
VAR EXITCODE NUMBER;
BEGIN
EXECUTE IMMEDIATE ' CREATE TABLE $OUTPUT_TABLE AS $SELECT_QUERY ';
:EXITCODE := SQLCODE;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(SQLERRM);
:EXITCODE := SQLCODE;
END;
/
exit :EXITCODE
!
Executing the script as below(Depends on OS)
ksh my_script MY_OUTPUT_TABLE my_sql.sql;
Expanding a comment, #MaheswaranRavisankar's approach will work, but the dynamic SQL (i.e. execute immediate) isn't necessary, so the anonymous block isn't necessary either. It can be simplified somewhat to:
USER=your_user
PASS=your_pass
DB=your_db
OUTPUT_TABLE=$1;
QUERY_FILE=$2;
SELECT_QUERY=`cat $QUERY_FILE`;
sqlplus -S ${USER}/${PASS}#${DB} << !
WHENEVER SQLERROR EXIT FAILURE
CREATE TABLE $OUTPUT_TABLE AS $SELECT_QUERY
!
This also allows you to use a query which is already terminated by a ; or /, which the execute immediate version wouldn't like - you just need to decide whether your wrapper script needs one to match what your query files will contain.
Even the whenever ... line isn't vital, but the other answer tried to exit with the error code so I've mimicked that too somewhat. This will always exit with a generic failure status though (1 in Unix, not sure what Windows does). You can then test if it was successful with $? in the script if you want to.
You can exit with the actual SQL error instead of the generic value, by using whenever sqlerror exit sql.sqlcode instead. The problem with doing that is that most (all?) shells limit the return code to the range 0-255, so most errors will wrap and give something unhelpful anyway - a fairly-likely ORA-00955: name is already used by an existing object error would give a shell exit value of 187, for example. And it's possible the wrapped value would be zero, which would mask that an error occurred at all; an also-plausible ORA-01536: space quota exceeded for tablespace '%s' error would give a shell exit code of zero, which is unhelpful. Using exit failure would at least stop that.

Config SQL*Plus to return nothing but data

I need to write a simple shell function that returns a single field from an Oracle DB. Think of it as for example SELECT 'ABC' FROM dual; and ABC is what I am after.
Here is my function:
function getSomeOraVal
{
sqlplus $USER/$PASSWD#$ORADB<<!!
SET sqlprompt ''
SET sqlnumber off
SET verify off
SET pages 0
SET echo off
SET head on
SET feedback off
SET feed off
SET serveroutput on
SET escape '\'
VARIABLE v_someVal VARCHAR2(30);
BEGIN
SELECT 'ABC' INTO v_someVal FROM dual;
END;
/
SELECT :v_someVal FROM dual;
!!
}
However, I want to pipe the sqlplus output (data only -> 'ABC') into a shell variable, which the function then returns and can be called from other shell scripts. Unfortunately, sqlplus returns a whole lot of garbage, such as "Welcome to Oracle" on top and "Disconnected..." in the bottom.
How can I extract just the data from a SQL result set, or in this case a single value and pass it into a UNIX variable for further manipulation within the shell?
Thanks
There are a few different approaches in this askTom thread on returning values from SQL*Plus to a shell script.
One common approach is to select a constant token in addition to the value that you want to return (in Tom's example, that is the string "KEEP") and then use sed (or your favorite command-line parser) to extract the data you're actually interested in
#!/bin/ksh
x=`sqlplus / <<endl | grep KEEP | sed 's/KEEP//;s/[ ]//g'
select 'KEEP' , max(sal) from emp;
exit
endl`
echo the answer is $x
Other approaches, such as approaches that allow you to read multiple lines of output are also discussed in that thread.
If you don't want the header to be printed, you should be specifying
set head off
in your SQL*Plus script-- I'm not sure why you're explicitly setting the header on in the script if you don't want the header... Do you want to keep some part of the header?
Based on Justin's answer (which is great), when you only need to select one number (or token), I consider this a little shorter, yet more readable version:
x=`sqlplus -S / <<< "select 'KEEP' , max(sal) from emp;" | awk '/KEEP/{print $2}'`

call a oracle procedure from inside a sql block

If I have a .sql file with this :
begin
exec dbms_stats.gather_table_stats('schema',table');
end;
/
Should I need to use :
call 'exec dbms_stats.gather_table_stats('schema',table')';
I cannot test it out because of system problems so i am posting my query here.
As far as I can tell, both are flawed - exec is used in SQL/Plus without a begin/end block, and call is definitely not PL/SQL; you'd also probably have to escape the single quotes inside your call statement.
You can just use
begin
dbms_stats.gather_table_stats('ALERTS_OWNER','ASYNC_PROCESSING_REQUEST_T');
end;

Resources