How to handle procedure that returns 2 different results - oracle

I have a stored procedure made in Oracle 9g that returns a cursor with different columns depending on a parameter, its something like this:
CREATE OR REPLACE PROCEDURE ASCHEMA.SP_TWOCURSORS
(
aParam NUMBER,
P_RETURN OUT SYS_REFCURSOR
)
IS
BEGIN
IF aParam = 1 THEN
OPEN P_RETURN FOR
SELECT
a.column1, (number)
a.column2 (varchar2)
FROM
table1 a;
ELSE
OPEN P_RETURN FOR
SELECT
b.column1, (varchar2)
b.column2, (number)
b.column3 (number)
FROM
table1 b;
END IF;
END;
I have to consume this procedure in PowerBuilder and pass the returned data to a DataWindow1 or DataWindow2, depending on the returned cursor, these datawindows are filled in runtime by the execution of another procedures coming from other source. I can't modify the database objects (like split the sp in two), just the PowerBuilder code. My problem is how to handle this scenario in an elegant way. I have some ideas but don't know if it will work:
Create a DataWindow object that handles every column involved in both cursors returned from the sp, then copy each row to the expected DataWindow.
Create a DataStore and pass the sp with the Create method, then copy the rows in the expected DataWindow.
Execute the procedure dynamically, fetch every row and add each result into a new row of the expected DataWindow.
I haven't tried the first one because there are many columns and it will take a long time to do. The second looks good but I don't know how to handle a DataStore with no DataWindow object and don't know if this is possible (1). The third is my last option to solve this problem. I want to ask people before start implementing this solution because I'm new to PowerBuilder, and even if I won't work on it too long I want to do it in the right way.
Thanks for the help.
(1) I have found this article about using Custom DataStores but I don't know if I can use only 1 DataStore or I should use 2. Also, for the Oracle connection I don't use SQLCA but another transaction object, so I don't know how to do this.

Keep It Simple.
You know the details of the stored proc. If you are calling this sp from PB, you are knowing its aParam already before the call. Why not defining 2 datawindows, one for each version of the results ?
Each DW would have a retrieval argument (the one that is passed to the stored proc) and will get its result from the sp.
At runtime, depending on the retrieval argument, and before retrieving the values, assign the corresponding dataobject to the datawindow object that is on : either the DW that suits the aParam = 1 or the DW that suits the else part.

Related

What is the purpose of this package?

I am new to Oracle PL/SQL. I have found this package and it is called from a trigger. I just cannot figure exactly what this simple looking package code is doing.
It is called from a trigger as below:
IF INSERTING THEN
i := STATE_PKG_OVERRIDE_CN.AffectedRows.COUNT+1;
STATE_PKG_OVERRIDE_CN.AffectedRows(i).IDU := :new.IDU;
STATE_PKG_OVERRIDE_CN.AffectedRows(i).cn := :new.cn;
This is the package. Can somebody please explain the basics of what is it doing? does it return a value? change a value? what is AffectedRows RIDARRAY or EMPTY ?
create or replace PACKAGE STATE_PKG_OVERRIDE_CN
AS
TYPE rowid_cn IS RECORD
(
idu dirxml.USR.IDU%TYPE,
cn dirxml.USR.CN%TYPE
);
TYPE RIDARRAY IS TABLE OF rowid_cn INDEX BY BINARY_INTEGER;
AffectedRows RIDARRAY;
EMPTY RIDARRAY;
END;
I have googled EMPTY but found nothing, i believe it is creating a table of type record. The trigger is passing in a value of cn or IDU am i am familiar with these two values. But what is the package doing? or returning ? im confused.
Cheers
This is a bespoke package belonging to your organisation. (That's why Google wasn't helpful for you.) We cannot tell you for sure what it does or how it's used. But we can guess.
The package has no procedures or functions, it just defines array variables of a bespoke type, which can be used by other program units such as triggers. The trigger you posted assigns values to the array AffectedRows. Presumably this trigger fires FOR EACH ROW. Likely there is another trigger on the same table firing AFTER STATEMENT which reads that array and does some processing, then assigns AffectedRows := EMPTY to reset the array.
The purpose of this infrastructure is to pass state across trigger actions. A common reason for doing this is work around a mutating table exception. They are risky because state cannot be guaranteed; for instance if a insert fails before the AFTER STATEMENT trigger fires the AffectedRows array is not re-initialised, so subsequent processing will be incorrect (or will fail).
Since 11g Oracle provides compound triggers which remove the need for this sort of package. Find out more.
To investigate further, first you want to check USER_TRIGGERS to find other triggers on the table which owns the trigger you mentioned. If that doesn't help or you want to see whether other tables also use this package run this query:
select *
from user_dependencies
where referenced_type = 'PACKAGE'
and referenced_name = 'STATE_PKG_OVERRIDE_CN'
It begins with the package which
declares a type ROWID_CN as a record that contains two values: IDU and CN
syntax requires to create another type (RIDARRAY) which is based on previously declared ROWID_CN
affectedrows and empty are arrays whose type is RIDARRAY. Basically, you can imagine them as a table that has two columns, IDU and CN
Now, a trigger: piece of code you posted says that those 3 lines are executed when someone inserts a row into a table the trigger is based on. For example,
create or replace trigger trg_biu_emp
before insert or update on emp
for each row
declare
i number;
begin
if inserting then ...
Apparently, there's some code that is executed when updating, or even deleting rows.
Anyway:
i := ... line counts number of elements in affectedrows array (which is declared in the package) and adds 1 to that number. For example, if there were 3 elements, i would be 4.
...affectedrows(i).idu := :new.idu enters a new row into the array. Its ordinal number is i (4 in our example). As you're inserting a row into the table, trigger knows IDU column's :new value and puts it into the array. For example, if you used
insert into emp (idu, cn) values (100, 'A')
then affectedrows(4).idu = 100, while affectedrows(4).cn = 'A'
It is, probably, something similar with EMPTY array. Google can't return anything useful, it is just a custom-made array.

Problems inserting data into Oracle table with sequence column via SSIS

I am doing data insert into a table in Oracle which is having a sequence set to it in one of the columns say Id column. I would like to know how to do data loads into such tables.
I followed the below link -
It's possible to use OleDbConnections with the Script Component?
and tried to create a function to get the .nextval from the Oracle table but I am getting the following error -
Error while trying to retrieve text for error ORA-01019
I realized that manually setting the value via the package i.e. by using the Script task to enumerate the values but is not incrementing the sequence and that is causing the problem. How do we deal with it? Any links that can help me solve it?
I am using SSIS-2014 but I am not able to tag it as I don't due to paucity of reputation points.
I created a workaround to cater to this problem. I have created staging tables of the destination without the column that takes the Sequence Id. After the data gets inserted, I am then calling SQL statement to get the data into the main tables from staging table and using the .nextval function. Finally truncating/dropping the table depending on the need. It would still be interesting to know how this same thing can be handled via script rather having this workaround.
For instance something like below -
insert into table_main
select table_main_sequence_name.nextval
,*
from (
select *
from table_stg
)
ORA-01019 may be related to fact you have multiple Oracle clients installed. Please check ORACLE_HOME variable if it contains only one client.
One workaround I'm thinking about is creating two procedures for handling sequence. One to get value you start with:
create or replace function get_first from seq as return number
seqid number;
begin
select seq_name.nexval into seqid from dual;
return seqid;
end;
/
Then do your incrementation in script. And after that call second procedure to increment sequence:
create or replace procedure setseq(val number) as
begin
execute immediate 'ALTER SEQUENCE seq_name INCREMENT BY ' || val;
end;
/
This is not good approach but maybe it will solve your problem

Optional Data Input into Spotfire Text Area parameters got from a Oracle Function

I have created a Function from Oralce DB and used it as input to Spotfire report. The reason why i used a function instead of view is because the parameters had some complex logic operations and i was not able to get that done in a view.
Coming to the report i have built. Currently i am using 2 parameters in the function and i am taking both the values from Spotfire text area in Data on Demand mode. So i have set up two 'Input Field' for getting the data. Issue is that unless i enter values for both parameters i wont get the output. My requirement is that i need to add few more parameters for the report which i can but i need to set up the function and the settings in Spotfire such that if 5 parameters are there , if users enters one value for just one parameter report should run for that parameter. I know that parameters set up with complex logis are there but also i need to add additional ones and make sure it works properly. I dont know about proc in case a solution is achievable using that.
I cannot go back to use a view since that wont give me the desired result. Need any ideas or suggestions to implement it. Let me know in case i need to post additional information.
This is how i implemented in oracle :
create or replace function Function_test(p1 varchar2='',p2 varchar2='',p3 varchar2)
return SYS_REFCURSOR as
my_cursor SYS_REFCURSOR;
begin
open my_cursor for
select distinct
x.c1
x.c2
x.c3
from x
where x.c1=p3
and (p1='' or x.c2=p1)
and (p2='' or x.c3=p2);
return my_cursor;
end;
Using a STORED PROCEDURE or a TABLE VALUED FUNCTION will achieve similar results here and you can probably use either one. I understand you can't use a VIEW because VIEWS can't have parameters. I prefer PROCEDURES over VIEWS anyway while working with SPOTFIRE because if you need to add a column or parameter to the PROCEDURE, your INFORMATION LINK on the PROCEDURE will in inherit these changes, whereas using an INFORMATION LINK on a VIEW, the change will not cascade down and you'll need to recreate the INFORMATION LINK all together.
For your case there are a couple of things I would suggest. Since you are trying to make some of the parameters optional, you need to code your FUNCTION or PROCEDURE to accept this. For example, assume one parameter is #param1 and this is accepting input from {$propertyControl1}. To make this optional, you need to instruct users to leave the property control blank if they don't want it to be used to limit the results. Meanwhile, in your FUNCTION or PROCEDURE you need to default it's value to ''. Here is what it could look like using SQL Server, but it should be similar for ORACLE
CREATE FUNCTION dbo.MyFunction (#param1 VARCHAR(256) = '') --default value is blank. Remember don't use NULL since your property control can't be NULL
RETURNS
#returnTBL TABLE(
Column1 datetime,
Column2 int,
Column3 varchar(16))
AS
BEGIN
INSERT INTO #returnTBL (Column1, Column2, Column3)
SELECT
c.C1
c.C2
c.C3
FROM Table2
WHERE #param1 = '' OR c.C3 = #param1 --this returns all rows if the user passes in blank, and filters on it if they pass in a value
RETURN
END
Or, similarly, here is the same logic using a STORED PROCEDURE in SQL Server
CREATE PROCEDURE dbo.MyProcedure(#param1 VARCHAR(256) = '') --default value is blank. Remember don't use NULL since your property control can't be NULL
AS
SELECT
c.C1
c.C2
c.C3
FROM Table2
WHERE #param1 = '' OR c.C3 = #param1 --this returns all rows if the user passes in blank, and filters on it if they pass in a value
GO
Lastly, since you have multiple parameters, I wouldn't have the data on demand automatically refresh. I will have the users click the refresh button to retrieve the data. This will prevent the FUNCTION from being executed each time an individual parameter is change. If a user needs to change 3 of the 5 parameters, you really only want it executed once, versus three times.

Return data rows from a pl/sql block

I want to write pl/sql code which utilizes a Cursor and Bulk Collect to retrieve my data. My database has rows in the order of millions, and sometimes I have to query it to fetch nearly all records on client's request. I do the querying and subsequent processing in batches, so as to not congest the server and show incremental progress to the client. I have seen that digging down for later batches takes considerably more time, which is why I am trying to do it by way of cursor.
Here is what should be simple pl/sql around my main sql query:
declare
cursor device_row_cur
is
select /my_query_details/;
type l_device_rows is table of device_row_cur%rowtype;
out_entries l_device_rows := l_device_rows();
begin
open device_row_cur;
fetch device_row_cur
bulk collect into out_entries
limit 100;
close device_row_cur;
end;
I am doing batches of 100, and fetching them into out_entries. The problem is that this block compiles and executes just fine, but doesn't return the data rows it fetched. I would like it to return those rows just the way a select would. How can this be achieved? Any ideas?
An anonymous block can't return anything. You can assign values to a bind variable, including a collection type or ref cursor, inside the block. But the collection would have to be defined, as well as declared, outside the block. That is, it would have to be a type you can use in plain SQL, not something defined in PL/SQL. At the moment you're using a PL/SQL type that is defined within the block, and a variable that is declared within the block too - so it's out of scope to the client, and wouldn't be a valid type outside it either. (It also doesn't need to be initialised, but that's a minor issue).
Dpending on how it will really be consumed, one option is to use a ref cursor, and you can declare and display that through SQL*Plus or SQL Developer with the variable and print commands. For example:
variable rc sys_refcursor
begin
open :rc for ( select ... /* your cursor statement */ );
end;
/
print rc
You can do something similar from a client application, e.g. have a function returning a ref cursor or a procedure with an out parameter that is a ref cursor, and bind that from the application. Then iterate over the ref cursor as a result set. But the details depend on the language your application is using.
Another option is to have a pipelined function that returns a table type - again defined at SQL level (with create type) not in PL/SQL - which might consume fewer resources than a collection that's returned in one go.
But I'd have to question why you're doing this. You said "digging down for later batches takes considerably more time", which sounds like you're using a paging mechanism in your query, generating a row number and then picking out a range of 100 within that. If your client/application wants to get all the rows then it would be simpler to have a single query execution but fetch the result set in batches.
Unfortunately without any information about the application this is just speculation...
I studied this excellent paper on optimizing pagination:
http://www.inf.unideb.hu/~gabora/pagination/article/Gabor_Andras_pagination_article.pdf
I used technique 6 mainly. It describes how to limit query to fetch page x and onward. For added improvement, you can limit it further to fetch page x alone. If used right, it can bring a performance improvement by a factor of 1000.
Instead of returning custom table rows (which is very hard, if not impossible to interface with Java), I eneded up opening a sys_refcursor in my pl/sql which can be interfaced such as:
OracleCallableStatement stmt = (OracleCallableStatement) connection.prepareCall(sql);
stmt.registerOutParameter(someIndex, OracleTypes.CURSOR);
stmt.execute();
resultSet = stmt.getCursor(idx);

Return REF CURSOR to procedure generated data

I need to write a sproc which performs some INSERTs on a table, and compile a list of "statuses" for each row based on how well the INSERT went. Each row will be inserted within a loop, the loop iterates over a cursor that supplies some values for the INSERT statement. What I need to return is a resultset which looks like this:
FIELDS_FROM_ROW_BEING_INSERTED.., STATUS VARCHAR2
The STATUS is determined by how the INSERT went. For instance, if the INSERT caused a DUP_VAL_ON_INDEX exception indicating there was a duplicate row, I'd set the STATUS to "Dupe". If all went well, I'd set it to "SUCCESS" and proceed to the next row.
By the end of it all, I'd have a resultset of N rows, where N is the number of insert statements performed and each row contains some identifying info for the row being inserted, along with the "STATUS" of the insertion
Since there is no table in my DB to store the values I'd like to pass back to the user, I'm wondering how I can return the info back? Temporary table? Seems in Oracle temporary tables are "global", not sure I would want a global table, are there any temporary tables that get dropped after a session is done?
If you are using Oracle 10gR2 or later then you should check out DML error logging. This basically does what you want to achieve, that is, it allows us to execute all the DML in a batch process by recording any errors and pressing on with the statements.
The principle is that we create an ERROR LOG table for each table we need to work with, using a PL/SQL built-in package DBMS_ERRLOG. Find out more. There is a simple extension to the DML syntax to log messages to the error log table. See an example here. This approach doesn't create any more objects than your proposal, and has the merit of using some standard Oracle functionality.
When working with bulk processing (that is, when using the FORALL syntax) we can trap exceptions using the built-in SQL%BULK_EXCEPTIONS collection. Check it out. It is possible to combine Bulk Exceptions with DML Error Logging but that may create problems in 11g. Find out more.
"Global" in the case of temporary tables just means they are permanent, it's the data which is temporary.
I would define a record type that matches your cursor, plus the status field. Then define a table of that type.
TYPE t_record IS
(
field_1,
...
field_n,
status VARCHAR2(30)
);
TYPE t_table IS TABLE OF t_record;
FUNCTION insert_records
(
p_rows_to_insert IN SYS_REFCURSOR
)
RETURN t_table;
Even better would be to also define the inputs as a table type instead of a cursor.

Resources