Passing Oracle function as file input to sqlplus

Apologies if this is the incorrect forum.There is an issue in the function call I am facing while calling the same from a unix shell scripts.
Basically, I want the ref cursor to return values to a variable in sqlpus. The function call is currently saved in a ".txt" file
in a unix location. I want to read the function name from the file and then execute it.But when I am running the below I get error.
Can someone please advise how to achieve the desired results?

--- create tables

create table fn_test
(a number, 
b number, 
c number
);

insert into fn_test values(1,2,3)
insert into fn_test values(3,4,5);
commit;

-- create function

create or replace function test_fn
return sys_refcursor
as
p_ref_out sys_refcursor;
begin
open p_ref_out
for select *
from fn_test;
return p_ref_out;
end;

---- changes in Unix::

# create the input file
echo "insert into test_a values(5555,4646,353454);" > t_fn.txt
echo "commit;" >> t_fn.txt
echo test_f >> t_fn.txt

--- Create the main script

#!/bin/ksh
sqlplus -s user/pass@server << ! < t_fn.txt
set head off
set echo off
set define off
set feedback off
set serveroutput on
var l_refcursor refcursor
exec :l_refcursor := @t_fn.txt
print l_refcursor
!

I get Error:

PLS-00103: Encountered the symbol "INSERT" when expecting one of the following:

A couple of points first:

  • The table names in the database and the script "t_fn.txt" are not the same. ("fn_test" and "test_a")
  • "test_f" in the script "t_fn.txt" is only going to confuse sqlplus. What is it?

Regarding the main script:

Do you want to:
(a) execute "t_fn.txt" thereby inserting 1 record into table "fn_test" and then
(b) invoke function "test_fn" so that it fetches all 3 records (2 existing + 1 new)?

If you redirect the t_fn.txt script ("< t_fn.txt") to sqlplus, it will only run the script and disregard the commands inside the here-doc. You will have to execute the script within the here-doc.

Secondly, "l_refcursor" can be only be assigned the value of a compiled object. In this case, it is the function "test_fn". You cannot execute an external SQL script (anonymous PL/SQL block, "t_fn.txt" in this case) and assign it to "l_refcursor".

Considering all the points above, the following works in my system.

In Oracle:

SQL> 
SQL> create table fn_test (a number, b number, c number);

Table created.

SQL> insert into fn_test values(1,2,3);

1 row created.

SQL> insert into fn_test values(3,4,5);

1 row created.

SQL> commit;

Commit complete.

SQL> -- create function
SQL> create or replace function test_fn
  2  return sys_refcursor
  3  as
  4      p_ref_out sys_refcursor;
  5  begin
  6      open p_ref_out for select * from fn_test;
  7      return p_ref_out;
  8  end;
  9  /

Function created.

SQL> show errors
No errors.
SQL> 
SQL> --
SQL> select * from fn_test;

	 A	    B	       C
---------- ---------- ----------
	 1	    2	       3
	 3	    4	       5

2 rows selected.

SQL> 
SQL> 

In Linux:

$ 
$ cat -n t_fn.txt
     1	insert into fn_test values(5555,4646,353454);
     2	commit;
$ 
$ 
$ cat -n main_script.ksh
     1	#!/bin/ksh
     2	sqlplus -s user/password@database << !
     3	set head off
     4	set echo off
     5	set define off
     6	set feedback off
     7	set serveroutput on
     8	set timing off
     9	set pages 0
    10	@t_fn.txt
    11	var l_refcursor refcursor
    12	exec :l_refcursor := test_fn
    13	print l_refcursor
    14	!
    15	
$ 
$ . main_script.ksh
      5555	 4646	  353454
	 1	    2	       3
	 3	    4	       5
$ 
$ 

The sqlplus variable "timing" is set to off by default, but in my setup it is set to on in the sqlplus profile script. So I set it off in the testcase above.
I set the variable "pages" to 0 in order to remove the extra blank line at the top where the header would have been.

I actually do not encounter the "PLS-00103" error. Try the above and see if it works in your system. Otherwise, more investigation will be required.

2 Likes

Thanks for the advise. Yeahh..It seems I really cant run both the inserts and the function in the same file and expect my script to catch the ref cursor returned by the function. Actually I was trying to avoid the splitting of the single file and still get the results from the function.

Of course you can! :slight_smile:

In the following, the database part remains as it was earlier.
Sorry I don't have ksh at the moment. The shell script should not require any changes though.

$
$ cat -n insert_and_fetch.sql
     1  set head off
     2  set echo off
     3  set define off
     4  set feedback off
     5  set serveroutput on
     6  set timing off
     7  set pages 0
     8  insert into fn_test values(5555,4646,353454);
     9  commit;
    10  var l_refcursor refcursor
    11  exec :l_refcursor := test_fn
    12  print l_refcursor
    13
$
$ cat -n main_script.sh
     1  #!/usr/bin/bash
     2  sqlplus -s user/password@database << !
     3  @insert_and_fetch.sql
     4  !
     5
$
$ . main_script.sh
      5555       4646     353454
         1          2          3
         3          4          5
$
$

You can also do it without that here-document.

$
$ cat -n main_script_1.sh
     1  #!/usr/bin/bash
     2  exit | sqlplus -s user/password@database @insert_and_fetch.sql
     3
$
$
$ . main_script_1.sh
      5555       4646     353454
      5555       4646     353454
         1          2          3
         3          4          5
$
$

"exit" is a Bash shell built-in. If you don't have it in your shell, then the following should work as well.

$
$ cat -n main_script_2.sh
     1  #!/usr/bin/bash
     2  echo exit | sqlplus -s user/password@database @insert_and_fetch.sql
     3
$
$
$ . main_script_2.sh
      5555       4646     353454
      5555       4646     353454
      5555       4646     353454
         1          2          3
         3          4          5
$
$

Help me out, please: What does and how works that construct in your main_script_1.sh :

  exit | sqlplus ...  

?

In the shell script "main_script_1.sh", if you invoke this command:

 sqlplus -s user/password@database @insert_and_fetch.sql

then it opens a child shell, runs the "sqlplus" program in it, passing the SQL script "insert_and_fetch.sql" to it.
Thereafter, the control goes to the "SQL> " prompt of "sqlplus" until you manually type in an "exit" command.

The "exit <n>" builtin command causes the child shell to exit with status <n>. If <n> is not supplied, the exit status is that of the last command executed, which would be "sqlplus" in this case.

1 Like

Thanks.
But, why the pipe? And, can't you supply an exit in the sql-script?

Yes, I missed that one. Adding an exit at the end of the SQL script does exit the subshell.

$ 
$ cat -n test_script.sql
     1	set pages 0 feed off time off timing off
     2	select 'This is passed from the SQL script to the database.' as x from dual;
     3	exit
$ 
$ cat -n db_shell_script.sh
     1	#!/usr/bin/bash
     2	sqlplus -s user/password@db @test_script.sql
$ 
$ . db_shell_script.sh
This is passed from the SQL script to the database.
$ 
$ 

And I think it's a cleaner way of doing things as long as the SQL script is self-contained.
If additional data (variables, additional commands etc.) are to be passed from the parent shell however, then the pipe would be used. The following:

( <cmd1> ; <cmd2> ; <cmd3> ; ... ) | sqlplus user/pass@db @sql_script

is just an idiom that's been floating around for a while. Here, "<cmd1>" etc. are proper SQL statements or sqlplus commands that would run in the database, which the shell won't understand, so they need to be wrapped with an "echo".

My understanding is that sqlplus runs the "sql_script" first and then accepts and runs everything passed from the command chain.
Testing again:

$ 
$ # No exit in the SQL script
$ cat -n test_script.sql
     1	set pages 0 feed off time off timing off
     2	select 'This is passed from the SQL script to the database.' as x from dual;
$ 
$ # The following should be run by sqlplus after the SQL script
$ ( echo "select 'After SQL script' from dual;" ; echo "exit" )
select 'After SQL script' from dual;
exit
$ 
$ # Test
$ ( echo "select 'After SQL script' from dual;" ; echo "exit" ) | sqlplus -s user/password@db @test_script.sql
This is passed from the SQL script to the database.
After SQL script
$ 
$ 

The "exit" above is a sqlplus command, not the shell builtin. sqlplus runs it, exits as a consequence and then the subshell exits as well.
The "exit" builtin closes the subshell itself and the sqlplus gets closed as well.