How to display the value of a variable used in a PL/SQL block in the log file of a batch file? And Arrays? - variables

Basically I'm using a batch file to run a .sql file on Windows Task Scheduler. The batch file generates a log file that displays all the put_lines. I now want to also see the value assigned to the variable: v_chks_dm, but couldn't figure out a way to do it. Tried the get_line statement but failed... Does anyone know how to do it?
thanks!
This is what's in the batch file:
echo off
echo ****************************>>C:\output.log
sqlplus userid/password#csdpro #V:\CONDITION_TEST.sql>>C:\output.log
Here's the .sql file
declare
v_chks_dm number;
begin
Select /*+parallel (a,4)*/ count(distinct a.src_table) into v_chks_dm
from hcr_dm.hcr_dm_fact a;
dbms_output.put_line('v_chkt_dm value assigned');
-- dbms_output.get_line(v_chks_dm);
if.... then... else.... end if;
end;
One more question... what if the variable is an array? I have something like this, but got an error says ORA-06533: Subscript beyond count. The number of values in the array usually varies from 0 to 10, but could be more. Thanks!
declare
type v_chks_array is varray(10) of varchar2(50);
arrSRCs v_chks_array;
begin
arrSRCs :=v_chks_array();
arrSRCs.EXTEND(10);
Select /*+parallel (a,4)*/ distinct a.src_table BULK collect into arrSRCs
from hcr_dm.hcr_dm_fact a;
dbms_output.put_line(arrSRCs(10));
end;

dbms_output.put_line('v_chkt_dm value = ' || v_chkt_dm);
or, better
dbms_output.put_line('v_chkt_dm value = ' || to_char(v_chkt_dm, '<number format>'));
You can choose appropriate number formats in documentation.

Related

Toad oracle 10.5 [duplicate]

I have this anonymous PL/SQL block which calculates and prints a value return from a table.
DECLARE
U_ID NUMBER :=39;
RETAIL BINARY_FLOAT:=1;
FLAG NUMBER;
BEGIN
SELECT NVL(RETAIL_AMOUNT,1),UNIT_ID INTO RETAIL, FLAG FROM UNITS WHERE UNIT_ID=U_ID;
LOOP
SELECT NVL(MAX(UNIT_ID),U_ID) INTO FLAG FROM UNITS WHERE FATHER_ID=FLAG;
IF FLAG=U_ID THEN EXIT; END IF;
SELECT RETAIL* RETAIL_AMOUNT INTO RETAIL FROM UNITS WHERE UNIT_ID=FLAG;
EXIT WHEN FLAG=U_ID;
END LOOP;
DBMS_OUTPUT.PUT_LINE( RETAIL);
END;
This block work correctly, but I wanted to do the same thing using a PL/SQL Function
I wrote the function as follow:
CREATE OR REPLACE FUNCTION GET_UNIT_RETAIL(U_ID NUMBER)
RETURN NUMBER
IS
RETAIL BINARY_FLOAT:=1;
FLAG NUMBER;
BEGIN
SELECT NVL(RETAIL_AMOUNT,1),UNIT_ID
INTO RETAIL, FLAG
FROM UNITS
WHERE UNIT_ID=U_ID;
LOOP
SELECT NVL(MAX(UNIT_ID),U_ID)
INTO FLAG
FROM UNITS
WHERE FATHER_ID=FLAG;
IF FLAG=U_ID THEN
EXIT;
END IF;
SELECT RETAIL* RETAIL_AMOUNT
INTO RETAIL
FROM UNITS
WHERE UNIT_ID=FLAG;
EXIT WHEN FLAG=U_ID;
END LOOP;
RETURN NUMBER;
END;
/
When I try to execute the above code to save the function to the database, the environment (SQL*PLUS) hangs for a long time and at the end returns this error:
ERROR at line 1:
ORA-04021: timeout occurred while waiting to lock object
What is the problem ??? Please !
Sounds like ddl_lock problem
Take a look at
dba_ddl_locks to see who is "blocking" a create or replace.
Also try to create under different name - and see what happens.
The problem was because the Object GET_UNIT_RETAIL was busy by other environment
Here is the answer:
https://community.oracle.com/thread/2321256

How to execute function with parameter in PostgreSQL using anonymous code block in FireDAC?

My database (PostgreSQL 11) has a function that must be called to execute an action. The return is not important, as long as there's no error. This function has arguments that must be passed as parameters.
I cannot directly use TFDConnection.ExecSQL because I use parameters of type array that is not supported by the method (to my knowledge). So, I use TFDQuery.ExecSQL like this:
msql1: = 'DO $$ BEGIN PERFORM DoIt (:id,:items); END $$; ';
{... create FDQuery (fq), set connection, set SQL.Text to msql1}
fq.Params.ParamByName ('id'). AsInteger: = 1;
{$ REGION 'items'}
fq.ParamByName ('items'). DataType: = ftArray;
fq.ParamByName ('items'). ArrayType: = atTable; // Must be atTable, not atArray
if length (items)> 0 then
begin
fq.ParamByName ('items'). ArraySize: = length (items);
for it: = 0 to length (items) -1 do
fq.ParamByName ('items'). AsIntegers [it]: = items [it];
end;
fq.ExecSQL;
{$ ENDREGION}
When executing the above code, the error above message raises
"Parameter 'id' not found".
After some research that suggested using fq.Params.ParamByName I was also unsuccessful.
However, if you change the way the function is called to
select DoIt (:id,:items); and obviously replacing the execution with fq.Open works perfectly.
Is it possible to execute a PL / pgSQL block that contains parameters in the function called by this block using TFDConnection / TFDQuery?
PS: I'm using Delphi Rio 10.3.3
When you assign a value to TFDQuery.SQL, FireDAC does some pre-processing of the SQL based on various options. ResourceOptions.CreateParams option controls whether parameters should be processed. This is enabled by default.
The preprocessor recognizes string literals in your SQL and doesn't try to look for the parameters in them. You used dollar quoted string constant and that's why FireDAC doesn't recognize parameters in it. Even if you add parameter manually I think that FireDAC would not bind the value.
With that said, the proper way to execute stored procedures/function is to use TFDStoredProc. You just assign StoredProcName and call its Prepare method which will retrieve procedure's metadata (parameters) from database so you don't need to set ArrayType or DataType of the parameter.
In your code you set the DataType to ftArray which is wrong, because in case of array parameter it should be set to array's element type. Anyway, by setting fq.ParamByName ('items').AsIntegers you effectively set parameter's DataType to ftInteger. All you need to do is to set ArraySize
Here's what you should do instead:
procedure DoIt(Connection: TFDConnection; ID: Integer; const Items: TArray<Integer>);
var
StoredProc: TFDStoredProc;
ParamItems: TFDParam;
Index: Integer;
begin
StoredProc := TFDStoredProc.Create(nil);
try
StoredProc.Connection := Connection;
StoredProc.StoredProcName := 'DoIt';
StoredProc.Prepare;
StoredProc.Params.ParamByName('id').AsInteger := ID;
if Length(Items) > 0 then
begin
ParamItems := StoredProc.Params.ParamByName('items');
ParamItems.ArraySize := Length(Items);
for Index := Low(Items) to High(Items) do
ParamItems.AsIntegers[Index] := Items[Index];
end;
StoredProc.ExecProc;
finally
StoredProc.Free;
end;
end;
Alternatively you can use ExecFunc to get the result of stored function.

UTL_FILE.WRITE_ERROR when calling utl_file.put in a loop

I have below code in my PL/SQL procedure, which I called in API_XXX.put(it calls utl_file.put) in a while loop. And the l_xmldoc is CLOB from a function of getReportXML, which returns the xml clob.
the code I write to write xml into a file is like:
l_offset := 1;
WHILE (l_offset <= l_length)
LOOP
l_char := dbms_lob.substr(l_xmldoc,1,l_offset);
IF (l_char = to_char(10)) ---I also tried if (l_char=chr(10)) but it did not work
THEN
API_XXXX.new_line(API_XXX.output, 1);
ELSE
API_XXXX.put(fnd_API_XXX.output, l_char);
END IF;
l_offset := l_offset + 1;
END LOOP;
Please note that the API_XXX is the existing package which I am not able to modify, and this api calls fflush in the end of put.
API_XXX.put's part is like below("WHICH" is the first param):
elsif WHICH = API_XXX.OUTPUT then
temp_file := OUT_FNAME;
utl_file.put(F_OUT, BUFF);
utl_file.fflush(F_OUT);
API_XXX.new_line is like(LINES is the number of lines to write):
elsif WHICH = API_XXX.OUTPUT then
temp_file := OUT_FNAME;
utl_file.new_line(F_OUT, LINES);
utl_file.fflush(F_OUT);
I notice a that the put/new_line procedure in my customer's side will sometimes raise UTL_FILE.WRITE_ERROR for unknown reason(maybe due to the l_length is too large(up to 167465)) in the while loop from my customer.
I read Oracle PL/SQL UTL_FILE.PUT buffering
. And I found that this is the same cause, my l_xmldoc is really large and when I loop it, I found that it is without a new line terminator so the buffer is up to 32767 even though I fflush every time.
So, how should I convert the l_xmldoc into a varchar with new line terminator.
PS: I confirmed that my customer is using Oralce 11g
Post the Oracle Version you are using! Or we can just guess around...
Your fflush will not work as you expect - From the documentation:
FFLUSH physically writes pending data to the file identified by the file handle. Normally, data being written to a file is buffered. The FFLUSH procedure forces the buffered data to be written to the file. The data must be terminated with a newline character.
tbone is abolutely right the line TO_CHAR(10) is wrong! Just try SELECT TO_CHAR(10) FROM DUAL; you will get 10 which you then compare to a single character. A single character will never be '10' since 10 has two characters!
Your problem is most likely a buffer-overflow with too large XML-Files, but keep in mind, also other problems on the target system can lead to write_errors, which should be handled.
Solutions
Quick&Dirty: Since you don't seem to care about performance anyways you can just close the file every X byte and reopen it with A for append. So just add to the loop:
IF MOD( l_offset, 32000 ) = 0
THEN
UTL_FILE.FCLOSE( f_out );
UTL_FILE.FOPEN( out_fpath, out_fname, f_out, 'a', 32767 );
END IF;
Use the right tool for the right job: UTL_FILE is not suited for handling complex data. The only usecase for UTL_FILE are small newline-separated lines of text. For everything else you should write RAW bytes! (Which will also allow you porper control over ENCODING, which is currently just mini-vanilly-lucky-guess)
Write a Java-Stored-Procedure with NIO-Filechannels - fast, safe, nice... But be careful, your program might run 10 times as fast!
Just a guess, but instead of "to_char(10)" you might try chr(10) to determine/write a newline. Not sure if this will solve your problem, but sometimes very long lines (without newlines) can cause issues.
For example:
declare
l_clob clob;
l_char char;
begin
l_clob := 'Line 1' || chr(10) || 'Line 2' || chr(10);
for i in 1 .. DBMS_LOB.GETLENGTH(l_clob)
loop
l_char := dbms_lob.substr(l_clob, 1, i);
if (l_char = chr(10)) then
--if (l_char = to_char(10)) then
dbms_output.put_line('Found a newline at position ' || i);
end if;
end loop;
end;
Notice the difference between chr(10) and to_char(10). Easy enough to test if this solves your problem anyway.

Monitoring long-running PL/SQL block

I have a fairly time intensive PL/SQL block that builds fingerprints from molecular structures. I would like to print output to SQL*Plus console to provide feedback on how many structures have been processed. I can do this with dbms_output.put_line
However everytime that is called a new line is written. I want to overwrite the line.
For example, currently I have the below.
Structure x of y processed
Structure x of y processed
Structure x of y processed
Structure x of y processed
Eventually I fill up the buffer as I'm dealing with thousands of structure records.
Is there a method I can use that will just overwrite the last output line?
Using DBMS_OUTPUT means that SQL*Plus will display nothing until the entire PL/SQL block is complete and will then display all the data currently in the buffer. It is not, therefore, an appropriate way to provide an ongoing status.
On the other hand, Oracle does provide a package DBMS_APPLICATION_INFO that is specifically designed to help you monitor your running code. For example, you could do something like
CREATE PROCEDURE process_structures
AS
<<other variable declarations>>
rindex BINARY_INTEGER;
slno BINARY_INTEGER;
totalwork NUMBER := y; -- Total number of structures
worksofar NUMBER := 0; -- Number of structures processed
BEGIN
rindex := dbms_application_info.set_session_longops_nohint;
FOR i IN (<<select structures to process>>)
LOOP
worksofar := worksofar + 1;
dbms_application_info.set_session_longops(
rindex => rindex,
slno => slno,
op_name => 'Processing of Molecular Structures',
sofar => worksofar ,
totalwork => totalwork,
target_desc => 'Some description',
units => 'structures');
<<process your structure with your existing code>>
END LOOP;
END;
From a separate SQL*Plus session, you can then monitory progress by querying the V$SESSION_LONGOPS view
SELECT opname,
target_desc,
sofar,
totalwork,
units,
elapsed_seconds,
time_remaining
FROM v$session_longops
WHERE opname = 'Processing of Molecular Structures';
You may also send messages to a named pipe and have another process read the message from the pipe.
procedure sendmessage(p_pipename varchar2
,p_message varchar2) is
s number(15);
begin
begin
sys.dbms_pipe.pack_message(p_message);
exception
when others then
sys.dbms_pipe.reset_buffer;
end;
s := sys.dbms_pipe.send_message(p_pipename, 0);
if s = 1
then
sys.dbms_pipe.purge(p_pipename);
end if;
end;
function receivemessage(p_pipename varchar2
,p_timeout integer) return varchar2 is
n number(15);
chr varchar2(200);
begin
n := sys.dbms_pipe.receive_message(p_pipename, p_timeout);
if n = 1
then
return null;
end if;
sys.dbms_pipe.unpack_message(chr);
return(chr);
end;
I don't think you can. As far as I understood the dbms_output it just doesn't work that way.
I recommend you use put to echo a single dot and a newline every 1000 or so entries to see that something is happening and write into a table or sequence the current position so you can have a look if you want to know.

Cursor loops; How to perform something at start/end of loop 1 time only?

I've got the following in one of my Oracle procedures, I'm using it to generate XML
-- v_client_addons is set to '' to avoid null error
OPEN C_CLIENT_ADDONS;
LOOP
FETCH C_CLIENT_ADDONS INTO CLIENT_ADDONS;
EXIT WHEN C_CLIENT_ADDONS%NOTFOUND;
BEGIN
v_client_addons := v_client_addons || CLIENT_ADDONS.XML_DATA;
END;
END LOOP;
CLOSE C_CLIENT_ADDONS;
-- Do something later with v_client_addons
The loop should go through my cursor and pick out all of the XML values to display, such as :
<add-on name="some addon"/>
<add-on name="another addon"/>
What I would like to achieve is to have an XML start/end tag inside this loop, so I would have the following output
<addons>
<add-on name="some addon"/>
<add-on name="another addon"/>
</addons>
How can I do this without having the <addons> tag after every line? If there are no addons in the cursor (cursor is empty), then I would like to skip this part enitrely
check the length of v_client_addons. If it is greater than 0, you actually appened something. Then create you parent tag with its children, else just ignore it.
How about using SQL to generate the entire XML, instead of looping over a cursor?
SELECT XMLELEMENT("addons", XMLAGG(C.XML_DATA)) INTO v_client_addons
FROM CLIENT_ADDON_TABLE C;