Calling os_command.exec from an Oracle stored procedure - oracle

I have used os_command.exec to send commands to the Linux shell. I am using Oracle 12c.
Here is an example code which works fine:
select os_command.exec('/home/smucha/app/smucha/product/12.1.0/dbhome_1/bin/sqlldr userid=system/password control=/home/smucha/load_data.cmt')
from dual
I would like to run a similar command in a stored procedure. Is there any way to do this?
I have tried the following but it does not work. My procedure runs without errors but no records are loaded.
execute immediate 'select os_command.exec(''/home/smucha/app/smucha/product/12.1.0/dbhome_1/bin/sqlldr userid=system/acorp56k control=/home/smucha/IZ/load_data.cmt'') from dual';

Your query is never executed. From the documentation:
If dynamic_sql_statement is a SELECT statement, and you omit both into_clause and bulk_collect_into_clause, then execute_immediate_statement never executes.
Your execute immdiate doesn't have an into clause, so it's essentially ignored.
You don't need the query though, you can call the function directly:
procedure foo is
result pls_integer; -- or whatever type your function actually returns
begin
result := os_command.exec('/home/smucha/app/smucha/product/12.1.0/dbhome_1/bin/sqlldr userid=system/password control=/home/smucha/load_data.cmt');
-- do something with the result?
end foo;
As an aside, you might want to consider using an external table rather than a call out to SQL*Loader.

Related

dbms_output.put_line doesn't work inside a Cursor For loop of a stored procedure

I am having a bizarre problem that seems very specific to CURSOR FOR Loops inside of a stored procedure. For clarity, I am using Oracle within DBeaver and am attempting to loop over all of the columns in a table and print out the results of a select statement.
I don't have access to the exact code but this is functionally approximate:
CREATE OR REPLACE PROCEDURE column_null(table_name_in IN VARCHAR2)
AS
str_query VARCHAR2(1000);
temp_number NUMBER(10);
CURSOR col_cursor IS
SELECT * FROM user_tab_cols
WHERE table_name = table_name_in;
BEGIN
FOR c_id IN col_cursor
LOOP
str_query := 'select COUNT(*) FROM ' || table_name_in ||
' WHERE ' || c_id.column_name || ' IS NOT NULL';
EXECUTE IMMEDIATE str_query INTO temp_number;
DBMS_OUTPUT.PUT_LINE(temp_number);
END LOOP;
END;
Now, the bizarre part is that if I do this exact same code block outside of a stored function (minus an extra DECLARE keyword), it works as expected. Even if I try to just echo out 'Hello' within a loop it works as expected, but as soon as it becomes a stored procedure it stops working. I've been testing this for hours today, and am completely baffled; for reference, I have only recently become acquainted with PL/SQL so its mysteries escape me.
Furthermore, it seems specific to CURSOR FOR loops; if I replace the Cursor For loop with a generic numeric loop (i.e. FOR c_id IN 1 .. 10), a procedure will produce output just fine. And it isn't just DBMS_OUTPUT.PUT_LINE that's affected; pretty much everything that goes on inside the Cursor For loop is ignored in a stored procedure, including variable updates, even though they work fine otherwise in normal PL/SQL blocks.
To summarize: Works fine as a PL/SQL block, works fine in a numeric for loop, but for some reason the exact combination of stored procedure and cursor for loop causes no output to be produced; in fact from my testing it seems like nothing meaningful happens within the cursor for loop of a stored function.
Is this a DBeaver bug? A PL/SQL oddity? I'm posting here because I'm ignorant as to whether this is expected behavior due to how Procedures and/or Cursor For loops work, or if this is a bug of some kind.
What you have done is declaring a procedure. Now that you have declared it, you have to call it using a program like bellow. Most likely it will generate outputs.
Option 01
set serveroutput on;
Declare
v_table_name_in IN VARCHAR2(499);
Begin
v_table_name_in := 'your table name';
column_null(table_name_in => v_table_name_in);
end;
Option 02
Get a return parameter. ideally a table type as out parameter. and inside the above code, loop through it and print the value.
Option 03.
Log the outputs into a log table.
I found the error was solved by simply adding AUTHID current_user to the procedure; apparently the procedure didn't have permission to access the table it was trying to select. Strangely though, no error was produced when trying to run the procedure; it just didn't produce any output.

Dynamic Query Re-write or Evaluating the Query Before Executing Table Function

First, I want to make it clear that the question is not about the materialized views feature.
Suppose, I have a table function that returns a pre-defined set of columns.
When a function call is submitted as
SELECT col1, col2, col3
FROM TABLE(my_tfn(:p1))
WHERE col4 = 'X';
I can evaluate the parameter and choose what queries to execute.
I can either open one of the pre-defined cursors, or I can assemble my query dynamically.
What if instead of evaluating the parameter I want to evaluate the text of the requesting query?
For example, if my function returns 20 columns but the query is only requesting 4,
I can assign NULLs to remaining 16 clumns of the return type, and execute fewer joins.
Or I can push the filter down to my dynamic query.
Is there a way to make this happen?
More generally, is there a way to look at the requesting query before exuting the function?
There is no robust way to identify the SQL that called a PL/SQL object.
Below is a not-so-robust way to identify the calling SQL. I've used code like this before, but only in special circumstances where I knew that the PL/SQL would never run concurrently.
This seems like it should be so simple. The data dictionary tracks all sessions and running SQL. You can find the current session with sys_context('userenv', 'sid'), match that to GV$SESSION, and then get either SQL_ID and PREV_SQL_ID. But neither of those contain the calling SQL. There's even a CURRENT_SQL in SYS_CONTEXT, but it's only for fine-grained auditing.
Instead, the calling SQL must be found by a string search. Using a unique name for the PL/SQL object will help filter out unrelated statements. To prevent re-running for old statements, the SQL must be individually purged from the shared pool as soon as it is found. This could lead to race conditions so this approach will only work if it's never called concurrently.
--Create simple test type for function.
create or replace type clob_table is table of clob;
--Table function that returns the SQL that called it.
--This requires elevated privileges to run.
--To simplify the code, run this as SYS:
-- "grant execute on sys.dbms_shared_pool to your_user;"
--(If you don't want to do that, convert this to invoker's rights and use dynamic SQL.)
create or replace function my_tfn return clob_table is
v_my_type clob_table;
type string_table is table of varchar2(4000);
v_addresses string_table;
v_hash_values string_table;
begin
--Get calling SQL based on the SQL text.
select sql_fulltext, address, hash_value
bulk collect into v_my_type, v_addresses, v_hash_values
from gv$sql
--Make sure there is something unique in the query.
where sql_fulltext like '%my_tfn%'
--But don't include this query!
--(Normally creating a quine is a challenge, but in V$SQL it's more of
-- a challenge to avoid quines.)
and sql_fulltext not like '%quine%';
--Flush the SQL statements immediately, so they won't show up in next run.
for i in 1 .. v_addresses.count loop
sys.dbms_shared_pool.purge(v_addresses(i)||', '||v_hash_values(i), 'C');
end loop;
--Return the SQL statement(s).
return v_my_type;
end;
/
Now queries like these will return themselves, demonstrating that the PL/SQL code was reading the SQL that called it:
SELECT * FROM TABLE(my_tfn) where 1=1;
SELECT * FROM TABLE(my_tfn) where 2=2;
But even if you go through all this trouble - what are you going to do with the results? Parsing SQL is insanely difficult unless you can ensure that everyone always follows strict syntax rules.

How do I pass a cursor value when calling a stored procedure?

This is only my second time diagnosing a PL/SQL procedure. I need to test the code in the stored procedure, and I'm trying to call it in SQL Developer. I have run the error details report, and the code has no obvious bugs in it.
So now I am trying to run it through a test window so I can see if the output is correct. However I can't seem to get the right argument for the 3 parameter. Here are the parameters in the the procedure.
CREATE OR REPLACE PROCEDURE ADVANCE.WW_DEATHDATE_REPORT(begindate varchar2, enddatevarchar2, RC1 IN OUT du_refCUR_PKG.RC) AS
Here is the Code I am trying to use to call the procedure. What do I need to do to get it to run correct? I keep getting error messages saying I'm using a wrong value in the parameter.
BEGIN
ADVANCE.WW_DEATHDATE_REPORT('20100101','20150101',du_refcur_pkg);
END;
There are multiple ways to do this, one way is like the below,
DECLARE
du_refcur_pkg SYS_REFCURSOR;
BEGIN
OPEN du_refcur_pkg FOR SELECT ... ;
ADVANCE.WW_DEATHDATE_REPORT('20100101','20150101',du_refcur_pkg);
END;
Another way would be,
BEGIN
ADVANCE.WW_DEATHDATE_REPORT( '20100101','20150101', CURSOR (SELECT ... ) );
END;

Oracle: What does 'execute immediate' mean?

What is the significance of execute immediate in PL/SQL when used with DML and DDL statements? Does execute immediate implicitly commit to a database like DDL statements do ?
The following is an example I have encountered:
SELECT 'TRUNCATE TABLE BD_BIDS_APPR_DET' INTO V_SQL1 FROM DUAL;
EXECUTE IMMEDIATE V_SQL1;
SELECT 'TRUNCATE TABLE BD_BIDS_SYS_DET' INTO V_SQL1 FROM DUAL;
EXECUTE IMMEDIATE V_SQL1;
SELECT 'TRUNCATE TABLE BD_BIDS_EXT_DET' INTO V_SQL1 FROM DUAL;
EXECUTE IMMEDIATE V_SQL1;
It is for running native dynamic SQL.
For DML you'd use it when running statements that you don't have available at compile time, e.g. if the column list is based on a selection from the user.
In your case it's being used because DDL cannot be run as static SQL from within PL/SQL. Only certain query, DML and TCL commands are valid. Anything else has to be treated as dynamic.
I'd say it's rare to need to use DDL from a PL/SQL block. TRUNCATE might be reasonable; if you find anything creating or dropping objects on the fly then that might be more of a concern as it can suggest a suboptimal data model.
EXECUTE IMMEDIATE itself does not automatically commit; but if you execute DDL then that will behave the same as if you ran it outside PL/SQL, so it will commit in your case, yes.
Incidentally, I'm not sure why your code is using an intermediate variable to hold the statement; that's useful if you want to display what it's going to run maybe, but you don't seem to be doing that. What you have could be done as:
EXECUTE IMMEDIATE 'TRUNCATE TABLE BD_BIDS_EXT_DET';
i.e. without using V_SQL_1 at all.
It provides end-to-end support when executing a dynamic SQL statement or an anonymous PL/SQL block. It substitutes the unknown values, executes the statement, returns the data, and then releases the resources.
EXECUTE IMMEDIATE [dynamic SQL string statement without terminator]
[INTO {define_variable [, define_variable] ... | record}]
[USING [IN|OUT|IN OUT] bind_argument [, [IN|OUT|IN OUT] bind_arguments] ];
define_variable is a PL/SQL variable or record that captures the result set of the SELECT query
Bind arguments are the actual values or local variables which would replace the bind variables in the SQL string statement.

Call Oracle stored procedure with no arguments

I'm trying to call an Oracle stored procedure that accepts no input parameters. However, when running the procedure, I get an error back that states
PLS-00306: wrong number or types of arguments in call to 'MY_PROC'
To call the proc, I'm just entering the following text into TOra:
BEGIN
SCHEMA.MY_PROC();
END;
I've also tried (same error though)
EXEC SCHEMA.MY_PROC();
I'm familiar with MSSQL and I'm able to execute SP with no problem using SQL server, but I can't figure out how to do the same with Oracle. I can't view the actual code for the stored procedure, but from the limited documentation I have, it appears it accepts no input parameters and the return value is a ref cursor. I have a feeling that I need to pass in a ref cursor somehow, but everything I've tried in that regard has not worked.
I just want to view the results of the SP as if I had done a SELECT statement, that is, with the records populating the data grid in the results panel in the TOra interface.
It sounds like the procedure does have an OUT parameter (in Oracle, procedures do not return anything but can have OUT and IN OUT parameters, functions return something). So you would have to pass in a variable for that OUT parameter. Something like
DECLARE
l_results SYS_REFCURSOR;
BEGIN
schema.my_proc( l_results );
END;
should successfully call the procedure. But then you want your GUI to display the results from that cursor. That, unfortunately, gets a little more complicated because now you're talking about a GUI-specific issue.
I don't use TOra, so I don't know what you need to do in TOra to get the cursor to display. In SQL*Plus (or SQL Developer, Oracle's free GUI), you could do something like
create or replace procedure my_proc( p_rc OUT SYS_REFCURSOR )
as
begin
open p_rc
for select 1 col1
from dual;
end;
/
variable rc refcursor;
exec my_proc( :rc );
print rc;
This creates a stored procedure with an OUT parameter that is a cursor, declares a host variable that can be passed in, and then prints the results.

Resources