I am trying to troubleshoot an issue with a stored procedure inside a package and need some guidance.
At some point inside the SP, a record, or set of records, are inserted into a temp table. I say possibly set of records because insertion happens inside a loop. When the loop exits a cursor is set using selection from this temp table and content of the temp table deleted.
Would this not mean that the cursor would now return empty dataset to the application calling it?
This is code; input is one or more Item IDs (I trimmed unnecessary code):
PROCEDURE USPGETOUTCOMEBYITEMCOI
(
IPSITEMIDS VARCHAR2,
OPDQUERIEDON OUT TIMESTAMP,
OPIERRORCODE OUT NUMBER,
CUR_OUT OUT GETDATACURSOR
)
IS
LVIERRORCODE NUMBER(38):=0;
LVSQUERY VARCHAR2(4000):='';
V_NEWITEM VARCHAR2(38);
V_NEWITEM2 VARCHAR2(4000);
V_TEMPITEMID VARCHAR2(38);
V_NEWITEMSLIST VARCHAR2(4000) := REPLACE(IPSITEMIDS, '''', '');
V_ORIGINDATE TIMESTAMP;
CURSOR cur IS
SELECT REGEXP_SUBSTR(V_NEWITEMSLIST, '[^,]+', 1, LEVEL) V_NEWITEM2 FROM DUAL CONNECT BY instr(V_NEWITEMSLIST, ',',1, LEVEL -1) > 0;
BEGIN
-- Loop thorugh each ITEM ID and determine outcome, add ITEM ID and OUTCOME to temp table
FOR rec IN cur LOOP
V_NEWITEM := rec.V_NEWITEM2;
....
INSERT INTO TEMPOUTCOME
(
ITEMID,
OUTCOME,
ORIGINDATE
)
VALUES
(
V_TEMPITEMID,
V_OUTCOME,
V_ORIGINDATE
);
COMMIT;
....
END LOOP;
LVSQUERY:='SELECT ITEMID, OUTCOME, ORIGINDATE FROM TEMPOUTCOME WHERE ITEMID IN (' || IPSITEMIDS || ')';
OPEN CUR_OUT FOR LVSQUERY;
OPDQUERIEDON:= SYSTIMESTAMP;
-- Delete from temp table all item IDs used in this session
DELETE FROM TEMPOUTCOME WHERE ITEMID IN (select REGEXP_SUBSTR(IPSITEMIDS, '\''(.*?)\''(?:\,)?', 1, LEVEL, NULL, 1) FROM dual CONNECT BY LEVEL <= REGEXP_COUNT(IPSITEMIDS, '''(?: +)?(\,)(?: +)?''', 1) + 1);
CREATE GLOBAL TEMPORARY TABLE today_sales(order_id NUMBER)
ON COMMIT delete ROWS
set serveroutput on
declare
cursor cur_temp is select
*
from
today_sales;
begin
insert into today_sales values ( 1 );
commit;
for i in cur_temp loop
dbms_output.put_line('There is data');
end loop;
end;
So I executed above two codes and it doesn't print anything. This means as soon as you commit, data will be deleted and cursor will return 0 records.
And if you execute the same above code (plsql code) by removing/commenting commit, this will print data, which means cursor is returning records.
So answer to your question: yes, cursor will return empty data set as soon as you commit.
Related
I have to copy data from one table to another with below two conditions
table names will be known at run time
records need to be copied one at a time so that modifications can be done in column values when required
I have created a procedure to to do this through dynamic query. Since the column list is not known already I am not able to declare a rowtype variable. I saw an example of DBMS_SQL where you can define the columns for select clause. Below is the format
DBMS_SQL.DEFINE_COLUMN(cursor_var,position,column_var);
Problem here is that in all the examples I found the column_var were already declared. However in my case I will get to know the no of columns that will be in cursor sql and their data type at run time. so I need to find a way to pass the data type of "column_var" as part of DBMS_SQL.DEFINE_COLUMN. Is there a way to do that? Is there a better way?
Below is just a sample code
CREATE OR REPLACE PROCEDURE pr_test (P_TABLE_NAME IN VARCHAR2)
IS
V_SQL VARCHAR2(500);
SRC_CUR INT;
DEST_CUR INT;
TYPE COL_DTL_TYPE IS RECORD
(
COLUMN_ID INT,
COLUMN_NAME VARCHAR2(250),
DATA_TYPE VARCHAR2(250),
DATA_LENGTH INT
);
COL_DTL_REC COL_DTL_TYPE;
TYPE TBL_COL_LIST_TYPE IS TABLE OF COL_DTL_TYPE;
TBL_COL_LIST TBL_COL_LIST_TYPE;
V_CNT INT := 0;
BEGIN
V_SQL := 'SELECT * FROM ' || P_TABLE_NAME;
SRC_CUR := DBMS_SQL.OPEN_CURSOR;
DBMS_SQL.PARSE(SRC_CUR,V_SQL,DBMS_SQL.NATIVE);
TBL_COL_LIST := TBL_COL_LIST_TYPE();
FOR COL_DTL_REC IN (
SELECT COLUMN_ID,COLUMN_NAME,DATA_TYPE,DATA_LENGTH
FROM ALL_TAB_COLUMNS WHERE TABLE_NAME =P_TABLE_NAME
)
LOOP
V_CNT := V_CNT + 1;
TBL_COL_LIST.EXTEND;
TBL_COL_LIST(V_CNT) := COL_DTL_REC;
-- Here is where I am stuck and not able to give column data type
DBMS_SQL.DEFINE_COLUMN(SRC_CUR,V_CNT,COL_DTL_REC.COLUMN_NAME COL_DTL_REC.DATA_TYPE , COL_DTL_REC.DATA_LENGTH)
END LOOP;
END;
copying to destination table will come later.
I'm not really sure how to approach this question.. I understand the basic syntax of writing a procedure. This is an exercise for a beginner database class (which seems to be at a level way above beginner)
Create a procedure to place a purchase order for a specified date based on data in the inventory report table.
Name the procedure placeorder [procedure name is important].
The procedure should take one parameter: inputDate (use the datatype of the PODate column in PURCHASEORDERS). The input date format accepted should be: 'DD-MON-YYYY', e.g., 01-JAN-2017.
For each raw material in the Inventory Report table (where: ReportDate matches inputDate), make a separate entry in the PURCHASEORDERS table for a next-day delivery order and a same-day delivery order (each raw material may generate up to 2 inserts).
Corresponding order type should be either next_day or same_day
Only make an entry (insertion) in PURCHASEORDERS if needed, i.e., if
an entry exists for a raw material and report date combination in the
Inventory Report table. If the Inventory Report for a day (e.g.,
30-NOV-2017) has a 0 value for the ordersameday attribute, it means
no same_day order is needed.
If no order is needed for the provided input date (i.e., no order at all across ALL raw materials), raise an application error with a message: “no order needed” (see Triggers and Procedures tutorial on D2L for an example of how to raise this error). You can use any suitable error-number. Your procedure should leave the “Price” (for the purchase order) empty (i.e., it can remain null). Assume it’ll be populated later.
Thanks!
Sorry this is what i've written so far.. #kara
CREATE OR REPLACE PROCEDURE placeorder (inputDate in DATE)
AS
new_inputDate PURCHASEORDERS.PODate%TYPE;
new_orderType PURCHASEORDERS.ORDERTYPE%TYPE;
c_orderSameDay INVENTORYREPORT.ORDERSAMEDAY%TYPE;
c_orderNextDay INVENTORYREPORT.ORDERNEXTDAY%TYPE;
CURSOR C1 IS
SELECT REPORTDATE INTO inputDate FROM dual;
SELECT ir.itemId, ir.ORDERSAMEDAY, ir.ORDERNEXTDAY FROM INVENTORYREPORT
WHERE
ir.REPORTDATE = inputDate;
BEGIN
OPEN C1;
WHILE C1%FOUND LOOP
FETCH C1 INTO new_inputDate, new_orderType, c_orderSameDay, c_orderNextDay;
IF c_orderSameDay > 0
THEN INSERT INTO PURCHASEORDERS (new_orderType) VALUES (orderSameDay);
ELSE <application error>;
END IF;
IF c_orderNextDay > 0
THEN INSERT INTO PURCHASEORDERS (new_orderType) VALUES (orderNextDay);
ELSE <application error>;
END IF;
END LOOP;
CLOSE C1;
END;
/
#kara I added to the if statements but still getting a couple errors when trying to compile the procedure. It this doing what it's supposed to be doing?
CREATE OR REPLACE PROCEDURE placeorder (inputDate in DATE)
AS
new_inputDate PURCHASEORDERS.PODate%TYPE;
new_orderType PURCHASEORDERS.ORDERTYPE%TYPE;
c_orderSameDay INVENTORYREPORT.ORDERSAMEDAY%TYPE;
c_orderNextDay INVENTORYREPORT.ORDERNEXTDAY%TYPE;
CURSOR C1 IS
SELECT REPORTDATE INTO inputDate FROM dual;
SELECT ir.itemId, ir.ORDERSAMEDAY, ir.ORDERNEXTDAY FROM INVENTORYREPORT
WHERE
ir.REPORTDATE = inputDate;
BEGIN
OPEN C1;
WHILE C1%FOUND LOOP
FETCH C1 INTO new_inputDate, new_orderType, c_orderSameDay, c_orderNextDay;
IF c_orderSameDay > 0
THEN INSERT INTO PURCHASEORDERS (new_orderType) VALUES (orderSameDay);
ELSE INSERT INTO PURCHASEORDERS (new_orderType) VALUES ('no order needed');
END IF;
IF c_orderNextDay > 0
THEN INSERT INTO PURCHASEORDERS (new_orderType) VALUES (orderNextDay);
ELSE INSERT INTO PURCHASEORDERS (new_orderType) VALUES ('no order needed');
END IF;
FETCH C1 INTO new_inputDate, new_orderType, c_orderSameDay, c_orderNextDay;
END LOOP;
CLOSE C1;
COMMIT;
END placeorder;
/
There are a bunch of examples how to create a procedure. Here a small one:
CREATE OR REPLACE PROCEDURE ProcName (paraName IN VARCHAR2)
AS
myStringVariable VARCHAR2 (4000);
myDateVariable DATE;
BEGIN
SELECT orderDateAsString
INTO myStringVariable
FROM orders
WHERE orderId = paraName;
myDateVariable := TO_DATE (myStringVariable, 'dd.mm.yyyy HH24:MI:SS'); -- '13.03.2018 23:59:59'
dbms_output.put_line('My date: ' || myStringVariable);
END;
And some example-code, to call the procedure:
begin
ProcName('1234');
end;
But you should look at your exercise first and think about you tasks. I think, this is what you got to do:
Create a Table.
Create Insert-Statements for the table
Create some PL/SQL-code which performs the insert with conditions.
move the PL/SQL-code to a procedure.
Call the procedure with a PL/SQL-block.
Explaination for you edits
You should build your code step-by-step if. You got multiple errors.
Check you Cursor. The Select-statement is not valid.
You wrote two selects.
You do not use new_inputdate. Why did you define it and what should it do?
Run you SELECT-statement alone without your code. if you like your data you can put it in an cursor.
Example with your code:
CREATE OR REPLACE PROCEDURE placeorder (inputDate IN DATE)
AS
-- new_inputDate PURCHASEORDERS.PODate%TYPE; --you don't use this one
new_orderType PURCHASEORDERS.ORDERTYPE%TYPE;
c_orderSameDay INVENTORYREPORT.ORDERSAMEDAY%TYPE;
c_orderNextDay INVENTORYREPORT.ORDERNEXTDAY%TYPE;
-- you mixed up your cursors. seems like you didn't try the select-statement alone..
CURSOR C2
IS
SELECT ir.itemId, ir.ORDERSAMEDAY, ir.ORDERNEXTDAY
FROM INVENTORYREPORT
WHERE ir.REPORTDATE = inputDate;
BEGIN
OPEN C2;
WHILE C2%FOUND
LOOP
FETCH C2 INTO new_orderType, c_orderSameDay, c_orderNextDay;
NULL; -- Do something
END LOOP;
CLOSE C2;
END;
/
Think about what you want to do in the loop. Build a small skript with the variables and think about what it should do:
Example-Skript of you Loop-content:
DECLARE
-- new_inputDate PURCHASEORDERS.PODate%TYPE; --you don't use this one
new_orderType NUMBER := 0;
c_orderSameDay NUMBER := 1;
c_orderNextDay NUMBER := 2;
BEGIN
-- this is what you're doing in you loop.
IF c_orderSameDay > 0 -- check if c_orderSameDay is greater than 0? In Oracle you use 'NULL' as empty value. Perhaps it should be 'c_orderSameDay IS NOT NULL'
THEN
INSERT INTO PURCHASEORDERS (new_orderType) -- you perform your insert.
VALUES (orderSameDay);
ELSE
RAISE; -- you raise an exception? this means c_orderSameDay has alsway to be set.
END IF;
IF c_orderNextDay > 0
THEN
INSERT INTO PURCHASEORDERS (new_orderType)
VALUES (orderNextDay);
ELSE
RAISE; -- you raise an exception? this means c_orderNextDay has alsway to be set.
END IF;
END;
I want to create a stored procedure where I can pass in variable to the WHERE clause below.
DROP TABLE fan0ia_mstr.Store_List;
CREATE TABLE fan0ia_mstr.Store_List AS(
SELECT
a11.ANA_Code,
a11.Premise_Name_Full,
a11.Store_Code,
a11.Estates_Segment,
a12.Post_Code
FROM Store_Dimension_Hierarchy a11
JOIN Location a12
ON a11.ANA_Code = a12.ANA_Code
WHERE a11.Area_Desc = 'VARIABLE' ) WITH DATA
PRIMARY INDEX (ANA_Code)
The VARIABLE will be a character string. I don't need to display the results, I just want the table to be created.
Also how do I trap any errors e.g. if the table doesn't exist for some reason I still want it to be created
thanks
As you don't have variable database/table/column names you simply need to wrap your existing code (slightly modified) into a Stored Procedure:
replace procedure myproc(IN variable varchar(100))
begin
BEGIN
-- simply try dropping the table and ignore the "table doesn't exist error"
DECLARE exit HANDLER FOR SQLEXCEPTION
BEGIN -- 3807 = table doesn't exist
IF SQLCODE <> 3807 THEN RESIGNAL; END IF;
END;
DROP TABLE fan0ia_mstr.Store_List;
END;
CREATE TABLE fan0ia_mstr.Store_List AS(
SELECT
a11.ANA_Code,
a11.Premise_Name_Full,
a11.Store_Code,
a11.Estates_Segment,
a12.Post_Code
FROM Store_Dimension_Hierarchy a11
JOIN Location a12
ON a11.ANA_Code = a12.ANA_Code
WHERE a11.Area_Desc = :variable ) WITH DATA
PRIMARY INDEX (ANA_Code);
end;
Of course a DELETE/INSERT or a Temporary table might be more efficient.
Edited... second option needs a execute immediate too...
CREATE PROCEDURE PROCEDURE1(
V_AREA_DESC IN VARCHAR2 )
AS
BEGIN
BEGIN
EXECUTE IMMEDIATE 'DROP TABLE fan0ia_mstr.Store_List';
EXCEPTION
WHEN OTHERS THEN
NULL;
END;
EXECUTE IMMEDIATE 'CREATE TABLE fan0ia_mstr.Store_List AS
(SELECT a11.ANA_Code,
a11.Premise_Name_Full,
a11.Store_Code,
a11.Estates_Segment,
a12.Post_Code
FROM Store_Dimension_Hierarchy a11
JOIN Location a12
ON a11.ANA_Code = a12.ANA_Code
WHERE a11.Area_Desc = ''' || v_area_desc || '''
) WITH DATA PRIMARY INDEX (ANA_Code)';
END PROCEDURE1;
but you can avoid drop / create with truncate / insert
CREATE PROCEDURE PROCEDURE1(
V_AREA_DESC IN VARCHAR2 )
AS
BEGIN
execute immediate 'truncate TABLE fan0ia_mstr.Store_List';
insert into fan0ia_mstr.Store_List (SELECT a11.ANA_Code,
a11.Premise_Name_Full,
a11.Store_Code,
a11.Estates_Segment,
a12.Post_Code
FROM Store_Dimension_Hierarchy a11
JOIN Location a12
ON a11.ANA_Code = a12.ANA_Code
WHERE a11.Area_Desc = v_area_desc
);
commit;
END PROCEDURE1;
I am using an oracle database. I am used to SQL server but not familiar with PL/SQL for the Oracle database.
How do I Set a variable that returns all the rows that contain the value of that variable: I am lost, I tried to understand, but it is not making sense to me. This si a recent attempt I made to to this.
DECLARE date1 varchar(40);
Begin
Select '''07/31/2013_09%''' into :date1 from dual;
End;
/
print date1
Begin
Select * from TABLE1 where start_time LIKE date1;
End;
/
I should get all the rows returned from this.
Thank you for your help.
This might help you get started:
create table table1 (
start_time varchar2(10),
foo number
);
insert into table1 values ('xyz', 1);
insert into table1 values ('wxy', 2);
insert into table1 values ('abc', 3);
create type table1_obj as object (
start_time varchar2(10),
foo number
);
/
create type table1_tab as table of table1_obj;
/
declare
v table1_tab;
begin
select table1_obj(start_time, foo) bulk collect into v
from table1 where start_time like '%x%';
end;
/
You have to create a parametrized cursor and pass that date as parameter to that cursor as below.
CREATE or REPLACE procedure proc1(p_date date)
as
CURSOR C1 (date1 date)is
SELECT * from TABLE1 where start_time LIKE date1;
BEGIN
FOR i in c1(p_dat)
LOOP
.......
END LOOP;
END;
It looks like you're missing the understanding of several basic building blocks:
You need a PL/SQL data structure where you'll save the data set queried from database table: PL/SQL Collections and Records. See especially nested tables and record variables.
You query the database with PL/SQL Static SQL. See especially SELECT INTO statements. In this case there is no need for dynamic SQL.
But of course it is bit harder to get a set of rows out of the database than only one row. Here the keywords are: SELECT INTO Statement with BULK COLLECT Clause. Note that depending on your query and table size bulk collection will potentially exhaust your server's memory (by loading millions of rows).
Here is an example that should give you a kickstart:
create table so26 (
day date,
event varchar(10)
);
insert all
into so26 values(trunc(sysdate - 1), 'foo1')
into so26 values(trunc(sysdate - 1), 'foo2')
into so26 values(trunc(sysdate - 1), 'foo3')
into so26 values(trunc(sysdate ), 'bar')
into so26 values(trunc(sysdate + 1), 'zoo')
select 1 from dual;
select * from so26;
declare
type event_list_t is table of so26%rowtype;
v_events event_list_t := event_list_t();
function get_events(p_day in date default sysdate) return event_list_t as
v_events event_list_t := event_list_t();
begin
select *
bulk collect into v_events
from so26
where day = trunc(p_day);
return v_events;
end;
begin
v_events := get_events(sysdate + 1);
if v_events.first is null then
dbms_output.put_line('no events on that day');
return;
end if;
for i in v_events.first .. v_events.last loop
dbms_output.put_line(i || ': event = ' || v_events(i).event);
end loop;
end;
/
Example output when get_events(sysdate - 1):
1: event = foo1
2: event = foo2
3: event = foo3
i'm creating a procedure to update/insert a table using merge statement(upsert).now i have a problem: using procedure parameters i have to do this upsert.
procedure xyz( a in table.a%type,b in table.b%type,....)
is
some local variables;
begin
merge into target_table
using source_table --instead of the source table, i have to use procedure parameters here
on (condition on primary key in the table)
when matched then
update the table
when not matched then
insert the table ;
end xyz;
so how to use procedure parameters instead of source table in merge statement?? or
suggest me a query to fetch the procedure parameters and use it as source table values.
help me please.
Thanks in advance.
I know that I'm eight years late to the party, but I think that I was trying to do something similar to what you were doing, but trying to Upsert based on parameters passed into a stored procedure that returns an empty string on success and an error on failure back to my VB Code. Below is all of my code along with comments explaining what I did, and why I did it. Let me know if this helps you or anyone else. This is my first time answering a post.
PROCEDURE UpsertTSJobData(ActivitySeq_in IN NUMBER,
Owner_in In VARCHAR2,
NumTrailers_in IN NUMBER,
ReleaseFormReceived_in IN NUMBER,
Response_out OUT VARCHAR2) AS
err_num NUMBER;
err_msg VARCHAR2(4000);
BEGIN
--This top line essentially does a "SELECT *" from the named table
--and looks for a match based on the "ON" statement below
MERGE INTO glob1app.GFS_TS_JOBDATA_TAB tsj
--This select statement is used for the INSERT when no match
--is found and the UPDATE when a match is found.
--It creates a "pseudo-table"
USING (
SELECT ActivitySeq_in AS ActSeq,
Owner_in As Owner,
NumTrailers_in As NumTrailers,
ReleaseFormReceived_in As ReleaseFormReceived
FROM DUAL) input
--This ON statement is what we're doing the match on to find
--matching records. This decides whether it will be an
--INSERT or an UPDATE
ON (tsj.Activity_seq = ActivitySeq_in)
WHEN MATCHED THEN
--Here we UPDATE based on the passed in input table
UPDATE
SET OWNER = input.owner,
NUMTRAILERS = input.NumTrailers,
RELEASEFORMRECEIVED = input.releaseformreceived
WHEN NOT MATCHED THEN
--Here we INSERT based on the passed in input table
INSERT (
ACTIVITY_SEQ,
OWNER,
NUMTRAILERS,
RELEASEFORMRECEIVED
)
VALUES (
input.actseq,
input.owner,
input.numtrailers,
input.releaseformreceived
);
Response_out := '';
EXCEPTION
WHEN OTHERS THEN
err_num := SQLCODE;
err_msg := SUBSTR(SQLERRM, 1, 3900);
Response_out := TO_CHAR (err_num) || ': ' || err_msg;
END;
Maby something like
DECLARE V_EXISTS NUMBER;
BEGIN SELECT COUNT(*) INTO V_EXISTS FROM TARGET_TABLE WHERE PK_ID = :ID;
IF V_EXISTS > 0 THEN
-- UPDATE
ELSE
-- INSERT
END IF;
END;
Also, you may try to use so-called tempotary table (select from DUAL)
CREATE TABLE TEST (N NUMBER(2), NAME VARCHAR2(20), ADRESS VARCHAR2(100));
INSERT INTO TEST VALUES(1, 'Name1', 'Adress1');
INSERT INTO TEST VALUES(2, 'Name2', 'Adress2');
INSERT INTO TEST VALUES(3, 'Name3', 'Adress3');
SELECT * FROM TEST;
-- test update
MERGE INTO TEST trg
USING (SELECT 1 AS N, 'NameUpdated' AS NAME,
'AdressUpdated' AS ADRESS FROM Dual ) src
ON ( src.N = trg.N )
WHEN MATCHED THEN
UPDATE
SET trg.NAME = src.NAME,
trg.ADRESS = src.ADRESS
WHEN NOT MATCHED THEN
INSERT VALUES (src.N, src.NAME, src.ADRESS);
SELECT * FROM TEST;
-- test insert
MERGE INTO TEST trg
USING (SELECT 34 AS N, 'NameInserted' AS NAME,
'AdressInserted' AS ADRESS FROM Dual ) src
ON ( src.N = trg.N )
WHEN MATCHED THEN
UPDATE
SET trg.NAME = src.NAME,
trg.ADRESS = src.ADRESS
WHEN NOT MATCHED THEN
INSERT VALUES (src.N, src.NAME, src.ADRESS);
SELECT * FROM TEST;
DROP TABLE TEST;
see here
Its very difficult to tell from you question exactly what you what, but I gather you want the table that you are merging into ( or on ) to be dynamic. In that case, what you should be using is the DBMS_SQL package to create dynamic SQL