I'm trying to build a data warehouse based on a star schema with 5 dimension tables and 1 facts table using two sets of data, MASTERDATA which holds 100 records and DATASTREAM which holds 10,000 records.
I am reading 100 records from DATASTREAM as an input into a cursor then reading the cursor record by record and then retrieving the relevant records from MASTERDATA on the index product_id as a index nested loop join. After this I am loading the new attributes from the transaction tuple inside the relevant dimension and fact tables.
However, I have a few errors. I'm just looking for help to understand why I am getting the errors I am getting. The errors at the moment are:
Error(98,6):PL/SQL:SQL Statement Ignored
Error(101,5):PL/SQL: ORA-00933: SQL command not properly ended
Error(105,8):PLS-00103:Encountered the symbol "LOOP" when expecting one of the following: if
Error(113):PLS-00103:Encountered the symbol "end-of-file" when expecting one of the following: ;
My code:
CREATE OR REPLACE PROCEDURE transactionINLJ AS TYPE t_cursor is ref cursor;
v_cursor t_cursor;
v_cursor_records DATASTREAM%rowtype;
record_100 varchar2(300);
rec number;
v_customer_id masterdata.customer_id%type;
v_customer_account_type masterdata.customer_account_type%type;
v_product_id masterdata.product_id%type;
v_product_name masterdata.product_name%type;
v_supplier_id masterdata.supplier_id%type;
v_supplier_name masterdata.supplier_name%type;
v_outlet_id masterdata.outlet_id%type;
v_outlet_name masterdata.outlet_name%type;
v_sale_price masterdata.sale_price%type;
t_customer_id int;
t_supplier_id int;
t_product_id int;
t_outlet_id int;
t_date_id int;
t_sales_fact int;
BEGIN
rec := 1;
WHILE (rec <= 10000)
LOOP
record_100 := 'SELECT * FROM datastream WHERE datastream_id between '|| TO_CHAR(rec) ||
' and ' || TO_CHAR(rec+99);
OPEN v_cursor FOR record_100;
LOOP
FETCH v_cursor INTO v_cursor_records;
EXIT WHEN v_cursor%notfound;
SELECT product_id, product_name, supplier_id, supplier_name, sale_price
INTO v_product_id, v_product_name, v_supplier_id, v_supplier_name, v_sale_price
FROM masterdata
WHERE product_id = v_cursor_records.product_id;
SELECT COUNT(0)
INTO t_product_id
FROM product_dim
WHERE product_id = v_cursor_records.product_id;
IF t_product_id = 0 THEN
INSERT INTO product_dim(product_id, product_name)
VALUES (v_cursor_records.product_id, v_cursor_records.product_name);
END IF;
SELECT COUNT(0)
INTO t_customer_id
FROM customer_dim
WHERE customer_id = v_cursor_records.customer_id;
IF t_customer_id = 0 THEN
INSERT INTO customer_dim(customer_id, customer_name,customer_account_type)
VALUES (v_cursor_records.customer_name, v_cursor_records.customer_account_type, v_cursor_records.customer_account_type);
END IF;
SELECT COUNT(0)
INTO t_supplier_id
FROM supplier_dim
WHERE supplier_id = v_cursor_records.supplier_id;
IF t_supplier_id = 0 THEN
INSERT INTO supplier_dim(supplier_id, supplier_name)
VALUES (v_cursor_records.supplier_id, v_cursor_records.supplier_name);
END IF;
SELECT COUNT(0)
INTO t_outlet_id
FROM outlet_dim
WHERE outlet_id = v_cursor_records.outlet_id;
IF t_outlet_id = 0 THEN
INSERT INTO outlet_dim(outlet_id, outlet_name)
VALUES (v_cursor_records.outlet_id, v_cursor_records.outlet_name);
END IF;
SELECT COUNT(0)
INTO t_date_id
FROM date_dim
WHERE d_date = v_cursor_records.d_date;
IF t_date_id = 0 THEN
INSERT INTO date_dim(d_date, d_year, d_quater, d_month, d_day)
VALUES (v_cursor_records.d_date
,EXTRACT(year FROM v_cursor_records.d_date), TO_CHAR(v_cursor_records.d_date,'Q')
,EXTRACT(month FROM v_cursor_records.d_date)
,EXTRACT(day FROM v_cursor_records.d_date));
END IF;
SELECT COUNT(0)
INTO t_sales_fact
FROM sales_fact
WHERE product_id = v_cursor_records.product_id
AND customer_id = v_csr_rec.customer_id
AND supplier_id = v_csr_rec.supplier_id
AND outlet_id = v_csr_rec.outlet_id
AND d_date = v_csr_rec.d_date
AND sale_price = v_csr_rec.sale_price
AND quantity_sold = v_csr_rec.quantity_sold;
IF t_sales_fact = 0 THEN
INSERT INTO sales_fact(customer_id,product_id,outlet_id,supplier_id,d_date,sale_price,total_sale,quantity_sold)
VALUES (v_cursor_records.customer_id, v_cursor_records.product_id, v_cursor_records.outlet_id,v_cursor_records.supplier_id,
v_cursor_records.d_date, v_cursor_records.sale_price, v_cursor_records.quantity_sold*sale_price, v_cursor_records.quantity_sold)
END IF;
COMMIT;
END LOOP;
CLOSE v_cursor;
COMMIT;
rec := rec+100;
END LOOP;
END;
It is an unfortunate truth that occasionally procedural processing is required. But almost all this can be done with just SQL and a tiny bit of PL/SQL extensions. In particular there is no need to "select count..." for any of your target tables, sql handles that quite easily on the INSERT statement itself. Further there is no need to loop through a cursor on a row-by-row (aka slow-by-slow) process, instead use BULK COLLECT and FORALL to handle the entire array (100 rows in this case) all with a single INSERT for each table. With it there is no need to for loop control counters, nor calculating the ID numbers to retrieve, nor the exact the number of rows (what would happen if your source table contained 10050 or 9950 rows instead of exactly 10000). As a side effect you gain considerable performance. The following shows that process:
create or replace procedure transactioninlj as
k_bulk_buffer_size constant integer := 100;
cursor v_cursor is
select d.customer_id
, d.outlet_id
, d.outlet_name
, d.customer_name
, d.customer_account_type
, d.d_date
, d.quantity_sold
, m.product_id
, m.product_name
, m.supplier_id
, m.supplier_name
, m.sale_price
from datastream d
join masterdata m on m.product_id = d.product_id
;
type t_cursor_records is table of v_cursor%rowtype;
v_cursor_records t_cursor_records;
begin
open v_cursor;
loop
fetch v_cursor
bulk collect
into v_cursor_records
limit k_bulk_buffer_size;
forall v_index in 1 .. v_cursor_records.count
insert into product_dim(product_id, product_name)
select v_cursor_records(v_index).product_id
, v_cursor_records(v_index).product_name
from dual
where not exists
( select null
from product_dim
where product_id = v_cursor_records(v_index).product_id
);
forall v_index in 1 .. v_cursor_records.count
insert into supplier_dim(supplier_id, supplier_name)
select v_cursor_records(v_index).supplier_id
, v_cursor_records(v_index).supplier_name
from dual
where not exists
( select null
from supplier_dim
where supplier_id = v_cursor_records(v_index).supplier_id
);
forall v_index in 1 .. v_cursor_records.count
insert into customer_dim(customer_id, customer_name,customer_account_type)
select v_cursor_records(v_index).customer_id
, v_cursor_records(v_index).customer_name
, v_cursor_records(v_index).customer_account_type
from dual
where not exists
( select null
from customer_dim
where customer_id = v_cursor_records(v_index).customer_id
);
forall v_index in 1 .. v_cursor_records.count
insert into outlet_dim(outlet_id, outlet_name)
select v_cursor_records(v_index).outlet_id
, v_cursor_records(v_index).outlet_name
from dual
where not exists
( select null
from outlet_dim
where outlet_id = v_cursor_records(v_index).outlet_id
);
forall v_index in 1 .. v_cursor_records.count
insert into date_dim(d_date, d_year, d_quater, d_month, d_day)
select v_cursor_records(v_index).d_date
, extract(year from v_cursor_records(v_index).d_date)
, to_char(v_cursor_records(v_index).d_date,'Q')
, extract(month from v_cursor_records(v_index).d_date)
, extract(day from v_cursor_records(v_index).d_date)
from dual
where not exists
( select null
from outlet_dim
where outlet_id = v_cursor_records(v_index).outlet_id
);
forall v_index in 1 .. v_cursor_records.count
insert into sales_fact( customer_id
, product_id
, outlet_id
, supplier_id
, d_date
, sale_price
, total_sale
, quantity_sold
)
select v_cursor_records(v_index).customer_id
, v_cursor_records(v_index).product_id
, v_cursor_records(v_index).outlet_id
, v_cursor_records(v_index).supplier_id
, v_cursor_records(v_index).d_date
, v_cursor_records(v_index).sale_price
, v_cursor_records(v_index).quantity_sold
* v_cursor_records(v_index).sale_price
, v_cursor_records(v_index).quantity_sold
from dual
where not exists
( select null
from sales_fact
where product_id = v_cursor_records(v_index).product_id
and customer_id = v_cursor_records(v_index).customer_id
and supplier_id = v_cursor_records(v_index).supplier_id
and outlet_id = v_cursor_records(v_index).outlet_id
and d_date = v_cursor_records(v_index).d_date
and sale_price = v_cursor_records(v_index).sale_price
and quantity_sold = v_cursor_records(v_index).quantity_sold
);
exit when v_cursor_records.count < k_bulk_buffer_size;
end loop;
close v_cursor;
commit;
end transactioninlj;
Note: The DDL for the source tables is not included in your post so I had to "invent" the definition for DATASTREAM. However, you only have 2 source inputs: DATASTREAM and MASTERDATA. Since you only select 5 columns from masterdata, every thing else must come from datastream.
Related
I am selecting same set of records twice, once to return IN REF_CURSOR and then selecting same records to build a CSV so that i can update all records in IN clause .... Can i some how change my procedure to SELECT only once instead of selecting same records twice
PROCEDURE LOADBATCH(
inBUCKET_SIZE IN NUMBER,
OUTCURSOR OUT REF_CURSOR )
AS
V_HANDLE VARCHAR2(2000);
V_LOCK_RESULT INTEGER;
IDs VARCHAR2(2000);
BEGIN
BEGIN
V_HANDLE := GET_LOCK_HANDLE('BATCH');
V_LOCK_RESULT := DBMS_LOCK.REQUEST(V_HANDLE, TIMEOUT => 1);
DBMS_OUTPUT.PUT_LINE(V_LOCK_RESULT);
IF V_LOCK_RESULT <> 1 THEN
OPEN OUTCURSOR FOR SELECT BATCH_ID,INSTRUCTION_ID,INSTRUCTION_DUMP,BATCH_MSG_TYPE,BATCH_AMOUNT,BATCH_CURRENCY,RECIEVED_DATETIME,MODIFIED_DATETIME,SETTLEMENT_DATE,BATCH_STATUS,FROM_MMBID,BATCH_DATE,MODIFICATION_DATETIME,PARENTBATCH_ID,INSTR_REASON FROM
( SELECT DISTINCT BI.*,
BM.*,
BM.AMOUNT AS BATCH_AMOUNT,
BM.CURRENCY AS BATCH_CURRENCY,
BI.PARENT_BATCH_ID AS PARENTBATCH_ID,
BI.REASON AS INSTR_REASON
FROM ACT.BATCH_INSTRUCTIONS BI
INNER JOIN ACT.BATCH_MESSAGES BM
ON BI.BATCH_ID =BM.ID
WHERE (BI.STAGE = 'NEW'
OR (BI.STAGE = 'PICKED'
AND (SYSDATE > (BI.LAST_PICKED_AT + interval '65' second))))
AND (BM.STAGE <> 'COMPLETED')
ORDER BY LAST_PICKED_AT ASC
) WHERE ROWNUM <=inBUCKET_SIZE ;
SELECT listagg(INSTRUCTION_ID, ',') WITHIN GROUP (
ORDER BY INSTRUCTION_ID) AS concatenation
INTO IDs
FROM
(SELECT DISTINCT *
FROM ACT.BATCH_INSTRUCTIONS BI
INNER JOIN ACT.BATCH_MESSAGES BM
ON BI.BATCH_ID =BM.ID
WHERE (BI.STAGE = 'NEW'
OR (BI.STAGE = 'PICKED'
AND (SYSDATE > (BI.LAST_PICKED_AT + interval '65' second)))
)
AND (BM.STAGE <> 'COMPLETED')
ORDER BY LAST_PICKED_AT ASC
)
WHERE ROWNUM <=inBUCKET_SIZE ;
DBMS_OUTPUT.PUT_LINE('IDs are:');
DBMS_OUTPUT.PUT_LINE(IDs);
IF( IDs IS NOT NULL) THEN
UPDATE ACT.BATCH_INSTRUCTIONS
SET LAST_PICKED_AT =sysdate,
STAGE = 'PICKED'
WHERE INSTRUCTION_ID IN
(SELECT INSTRUCTION_ID
FROM ACT.BATCH_INSTRUCTIONS
WHERE INSTRUCTION_ID IN
(SELECT regexp_substr(IDs,'[^,]+', 1, level)
FROM dual
CONNECT BY regexp_substr(IDs, '[^,]+', 1, LEVEL) IS NOT NULL
)
);
COMMIT;
END IF;
END IF;
EXCEPTION
WHEN NO_DATA_FOUND THEN
NULL;
END;
-- DBMS_OUTPUT.PUT_LINE('releasing lock:');
V_LOCK_RESULT := DBMS_LOCK.RELEASE(V_HANDLE);
END LOADBATCH;
Wont work with one select because "select into" does not accept dynamic SQL nor cursors.
I have a procedure that does the INSERT INTO and then the UPDATE of some fields (both in the same procedure), I'm using this answer from #Clive Number of rows affected by an UPDATE in PL/SQLto know the amount of data that has been updated to put in a log, but it brings me the total number of rows instead of just the records that have been updated.
Is that the right way to know?
What I need is to know how many rows were INSERTED from the INSERT STATEMENT and how many rows were UPDATED from the UPDATE STATEMENT.
My query:
CREATE OR REPLACE PROCEDURE OWNER.TABLE_NAME
AS
-- VARIABLE
v_qtd_regs number := 0;
v_code number;
v_errm VARCHAR2(500);
start_time pls_integer;
end_time pls_integer;
elapse_time number;
proc_name varchar2(100);
i NUMBER;
BEGIN
proc_name := 'PRDWBI_CGA_D_COLUMNS';
start_time := dbms_utility.get_time;
DS_FUNCESP.PRDSBI_GRAVA_LOG( 'I', 'DataWarehouse', proc_name, 'Início Carga' );
-- INSERT INTO TABLE:
INSERT INTO OWNER.TABLE_NAME
(COLUMN_ID, COLUMNS_NAME, COLUMN_NAME2)
(SELECT 1 AS COLUMN_ID, 'TEST' AS COLUMN_NAME, SYSDATE AS COLUMN_NAME2 FROM DUAL);
COMMIT;
-- UPDATE SOME COLUMNS I NEED
UPDATE OWNER.TABLE_NAME y
SET (y.COLUMNS_NAME, y.COLUMN_NAME2) =
(SELECT 'TEST2' AS COLUMN_NAME, SYSDATE AS COLUMN_NAME2 FROM DUAL x WHERE x.COLUMN_ID = y.COLUMN_ID)
WHERE EXISTS (SELECT 'TEST2' AS COLUMN_NAME, SYSDATE AS COLUMN_NAME2 FROM DUAL x WHERE x.COLUMN_ID = y.COLUMN_ID);
-- TO KNOW HOW MANY ROWS WERE UPDATED
i := SQL%rowcount;
COMMIT;
--dbms_output.Put_line(i);
SELECT COUNT(1) INTO v_qtd_regs FROM OWNER.TABLE_NAME where LinData >= TRUNC(SYSDATE);
end_time := dbms_utility.get_time;
elapse_time := ((end_time - start_time)/100);
v_errm := SUBSTR(SQLERRM, 1 , 500);
DS_FUNCESP.PRDSBI_GRAVA_LOG('T', 'DataWarehouse', proc_name, v_errm, v_qtd_regs, elapse_time );
COMMIT;
EXCEPTION
WHEN OTHERS THEN
v_code := SQLCODE;
v_errm := SUBSTR(SQLERRM, 1 , 500);
DS_FUNCESP.PRDSBI_GRAVA_LOG('E', 'Error', proc_name, v_errm);
END;
QUESTION EDITED TO SHOW A REAL EXAMPLE:
I created a table that takes data from "SYS.DBA_TAB_COLUMNS" just to use as an example, as shown below:
CREATE TABLE "DW_FUNCESP"."D_TEST"
(
"ID_COLUMN" NUMBER(10,0) GENERATED BY DEFAULT ON NULL AS IDENTITY MINVALUE 1 MAXVALUE 9999999999999999999999999999 INCREMENT BY 1
START WITH 1 CACHE 20 NOORDER NOCYCLE NOKEEP NOSCALE NOT NULL ENABLE,
"NM_OWNER" VARCHAR2(500 CHAR) NOT NULL ENABLE ,
"NM_TABLE" VARCHAR2(500 CHAR) NOT NULL ENABLE ,
"CD_COLUMN" NUMBER(20,0) NOT NULL ENABLE ,
"NM_COLUMN" VARCHAR2(500 CHAR) NOT NULL ENABLE ,
"DS_COLUMN" VARCHAR2(500 CHAR) NOT NULL ENABLE ,
"LINDATE" DATE DEFAULT SYSDATE NOT NULL ENABLE ,
"LINORIGIN" VARCHAR2(100 CHAR) NOT NULL ENABLE
)
Then I created a procedure to identify the inserted and updated records, as below:
CREATE OR REPLACE PROCEDURE DW_FUNCESP.PRDWBI_CGA_D_TEST
AS
-- variaveis de suporte as informações que deve gravar
rows_inserted integer;
rows_updated integer;
BEGIN
-- Insert Into table
INSERT INTO DW_Funcesp.D_TEST
(NM_OWNER, NM_TABLE, CD_COLUMN, NM_COLUMN, DS_COLUMN, LINDATE, LINORIGIN)
(SELECT
NVL(x.NM_OWNER ,'NOT FOUND') AS NM_OWNER ,
NVL(x.NM_TABLE ,'NOT FOUND') AS NM_TABLE ,
NVL(x.CD_COLUMN ,-1) AS CD_COLUMN ,
NVL(x.NM_COLUMN ,'NOT FOUND') AS NM_COLUMN ,
NVL(x.DS_COLUMN ,x.NM_COLUMN) AS DS_COLUMN ,
SYSDATE AS LINDATE ,
'SYS.DBA_TAB_COLUMNS' AS LINORIGIN
FROM
(
SELECT
d.OWNER AS NM_OWNER ,
d.TABLE_NAME AS NM_TABLE ,
d.COLUMN_ID AS CD_COLUMN,
d.COLUMN_NAME AS NM_COLUMN,
e.COMMENTS AS DS_COLUMN
FROM SYS.DBA_TAB_COLUMNS d
LEFT JOIN SYS.DBA_COL_COMMENTS e
ON e.OWNER = d.OWNER
AND e.TABLE_NAME = d.TABLE_NAME
AND e.COLUMN_NAME = d.COLUMN_NAME
WHERE d.OWNER = 'DW_FUNCESP'
) x
LEFT JOIN DW_FUNCESP.D_TEST y
ON y.NM_OWNER = x.NM_OWNER
AND y.NM_TABLE = x.NM_TABLE
AND y.NM_COLUMN = x.NM_COLUMN
WHERE y.ID_COLUMN IS NULL);
rows_inserted := sql%rowcount;
-- Update the table
UPDATE DW_FUNCESP.D_TEST z
SET (z.NM_COLUMN, z.DS_COLUMN, z.LINDATE) =
(SELECT
NVL(x.NM_COLUMN ,'NOT FOUND') AS NM_COLUMN ,
NVL(x.DS_COLUMN ,x.NM_COLUMN) AS DS_COLUMN ,
SYSDATE AS LINDATE
FROM
(
SELECT
d.OWNER AS NM_OWNER ,
d.TABLE_NAME AS NM_TABLE ,
d.COLUMN_ID AS CD_COLUMN,
d.COLUMN_NAME AS NM_COLUMN,
e.COMMENTS AS DS_COLUMN
FROM SYS.DBA_TAB_COLUMNS d
LEFT JOIN SYS.DBA_COL_COMMENTS e
ON e.OWNER = d.OWNER
AND e.TABLE_NAME = d.TABLE_NAME
AND e.COLUMN_NAME = d.COLUMN_NAME
WHERE d.OWNER = 'DW_FUNCESP'
) x
WHERE z.NM_OWNER = x.NM_OWNER
AND z.NM_TABLE = x.NM_TABLE
AND z.CD_COLUMN = x.CD_COLUMN)
WHERE EXISTS (SELECT
NVL(x.NM_COLUMN ,'NOT FOUND') AS NM_COLUMN ,
NVL(x.DS_COLUMN ,x.NM_COLUMN) AS DS_COLUMN ,
SYSDATE AS LINDATE
FROM
(
SELECT
d.OWNER AS NM_OWNER ,
d.TABLE_NAME AS NM_TABLE ,
d.COLUMN_ID AS CD_COLUMN,
d.COLUMN_NAME AS NM_COLUMN,
e.COMMENTS AS DS_COLUMN
FROM SYS.DBA_TAB_COLUMNS d
LEFT JOIN SYS.DBA_COL_COMMENTS e
ON e.OWNER = d.OWNER
AND e.TABLE_NAME = d.TABLE_NAME
AND e.COLUMN_NAME = d.COLUMN_NAME
WHERE d.OWNER = 'DW_FUNCESP'
) x
WHERE z.NM_OWNER = x.NM_OWNER
AND z.NM_TABLE = x.NM_TABLE
AND z.CD_COLUMN = x.CD_COLUMN);
rows_updated := sql%rowcount;
dbms_output.Put_line('inserted=>' || to_char(rows_inserted) || ', updated=>' || to_char(rows_updated));
COMMIT;
EXCEPTION
WHEN OTHERS THEN
RAISE;
END;
So my first insert output was:
inserted=>2821, updated=>2821
So I chose a data to be changed and it was updated, I made the following select to choose which data should be updated to bring in the DBMS output again:
SELECT * FROM DW_FUNCESP.D_TEST WHERE NM_TABLE = 'D_TEST';
I commented in a column as shown in the image, to bring in the update:
COMMENT ON COLUMN DW_FUNCESP.D_TEST.LINORIGIN IS 'The origin of the data';
I ran the procedure again, and the output was:
inserted=>0, updated=>2821
The result for that update:
Shouldn't you have brought just 1 updated data in the output, as only 1 updated? And not all the rows?
e.g.: inserted=>0, updated=>1
So my question remains, am I asking it correctly? Is it possible to obtain this result in the same procedure? Is it the update that is incorrect (despite having updated the data)?
You are not getting the rows inserted and rows updated. SQL%rowcount contains ONLY the number rows from the last select or DML statement. Since you set your variable only after the Update your only get the number of updates. If you want both then you need a separate variable for each.
Hint: There is no need to commit after each DML, actually that is ofter considered a very poor practice. You need to study as bit on transactions. The basic idea being that all operations complete successfully or none of them complete successfully. Look up ATOMIC and Atomicity.
So your revised procedure becomes:
create or replace procedure owner.table_name
as
-- VARIABLE
v_qtd_regs number := 0;
v_code number;
v_errm varchar2(500);
start_time pls_integer;
end_time pls_integer;
elapse_time number;
proc_name varchar2(100);
rows_inserted integer;
rows_updated integer;
begin
proc_name := 'PRDWBI_CGA_D_COLUMNS';
start_time := dbms_utility.get_time;
ds_funcesp.prdsbi_grava_log( 'I', 'DataWarehouse', proc_name, 'Início Carga' );
insert into owner.table_name
(column_id, columns_name, column_name2)
(select 1 as column_id, 'TEST' as column_name, sysdate as column_name2 from dual);
rows_inserted := sql%rowcount;
update owner.table_name y
set (y.columns_name, y.column_name2) =
(select 'TEST2' as column_name, sysdate as column_name2 from dual x where x.column_id = y.column_id)
where exists (select 'TEST2' as column_name, sysdate as column_name2 from dual x where x.column_id = y.column_id);
rows_updated := sql%rowcount;
dbms_output.Put_line('inserted=>' || to_char(rows_inserted) || ', updated=>' || tp_char(rows_updated));
select count(1) into v_qtd_regs from owner.table_name where lindata >= trunc(sysdate);
end_time := dbms_utility.get_time;
elapse_time := ((end_time - start_time)/100);
v_errm := substr(sqlerrm, 1 , 500);
ds_funcesp.prdsbi_grava_log('T', 'DataWarehouse', proc_name, v_errm, v_qtd_regs, elapse_time );
commit;
exception
when others then
v_code := sqlcode;
v_errm := substr(sqlerrm, 1 , 500);
ds_funcesp.prdsbi_grava_log('E', 'Error', proc_name, v_errm);
end;
Try to add the instruction i := SQL%rowcount; after each DML:
after INSERT to have the number of inserted rows
after UPDATE to have the number of updated rows
I would use ORA_SCN as the other answers suggest if you are interested what rows have been inserted or updated. But you want only to know how many, so I would leave the counting to Oracle (might be timeconsuming for larger number of rows).
Please have a look at the data dictionary view USER_TAB_MODIFICATIONS (or ALL_TAB_MODIFICATIONS if the table is in another schema than the procedure.
CREATE TABLE d (
id NUMBER GENERATED ALWAYS AS IDENTITY,
dt DATE DEFAULT SYSDATE,
foo VARCHAR2(128 BYTE)
);
Gathering the table statistics will reset the modifications view:
EXEC DBMS_STATS.GATHER_TABLE_STATS(NULL,'D');
Now after your INSERT, the modifications view will have the number of inserted rows:
INSERT INTO d(foo) SELECT object_name FROM all_objects;
67,141 rows inserted.
SELECT inserts, updates, deletes FROM user_tab_modifications WHERE table_name='D';
INSERTS UPDATES DELETES
67141 0 0
Likewise, after the UPDATE, the updated rows:
UPDATE d SET foo=lower(foo),dt=SYSDATE WHERE mod(id,10)=0;
6,714 rows updated.
SELECT inserts, updates, deletes FROM user_tab_modifications WHERE table_name='D';
INSERTS UPDATES DELETES
67141 6714 0
For clarity, I've used SQL instead of PL/SQL. You might have to grant some special privs to the schema containing the procedure. Add a comment with my name if you run into problems with that.
I have an in parameter (set = 0), to keep track of the count of entries I will be modifying. I am trying to merge data into a table called Table1, the records that have been updated in a different table (Table2) since the last time Table1 has been updated. The conditional statement will compare the Table1.LastUpdate column to the max(Modified_date) column of Table2 and only insert entries where the table1.last_update column is greater than the table2.max(modified_date) column. Then I will need to store this number and return it as an out parameter. What I have is follows:
create or replace procedure test_proc (rUpdated_Row_Count IN NUMBER, rUpdated_Row_Count_2 OUT NUMBER) is
CURSOR c1 is
select max(modified_date) as max_modified_date
from table1;
l_var c1%ROWTYPE;
-----------
CURSOR c2 is
select table2_id
, last_update
from table2;
k_var c2%ROWTYPE;
BEGIN
LOOP
Open c1;
Fetch c1 into l_var;
Open c2;
Fetch c2 into k_var;
EXIT WHEN c1%NOTFOUND;
IF k_var.last_update > l_var.max_modified_date THEN
Insert into table2(table2_id, last_update)
values(null, k_var.last_update);
commit;
rUpdated_Row_Count_2 := rUpdated_Row_Count + 1;
END IF;
END LOOP;
Close c1;
Close c2;
END test_proc;
Thanks in advance!
Modified my code (after doing further research):
create or replace procedure test_proc (rUpdated_Row_Count IN NUMBER, rUpdated_Row_Count_2 OUT NUMBER) is
CURSOR c1 is
select max(modified_date) as max_modified_date
from table1;
l_var c1%ROWTYPE;
-----------
CURSOR c2 is
select table2_id
, last_update
from table2;
k_var c2%ROWTYPE;
BEGIN
Open c1;
Open c2;
LOOP
Fetch c1 into l_var;
Fetch c2 into k_var;
EXIT WHEN c2%NOTFOUND;
IF k_var.last_update > l_var.max_modified_date THEN
Insert into table2(table2_id, last_update)
values(null, k_var.last_update);
commit;
rUpdated_Row_Count_2 := rUpdated_Row_Count + 1;
END IF;
END LOOP;
Close c1;
Close c2;
END test_proc;
Reproducable data / Code is below:
Create table1
(
table1_id number,
modified_date date
);
Create table2
(
table2_id number,
last_update date
);
insert into table1(table1_id, modified_date) values(1, sysdate);
insert into table1(table1_id, modified_date) values(2, sysdate);
insert into table1(table1_id, modified_date) values(3, sysdate -1);
insert into table2(table2_id, last_update) values(1, sysdate + 1);
insert into table2(table2_id, last_update) values(2, sysdate + 2);
Not quite sure what the "IN" parameter is for. Also not quite sure about the overall rationale. However, here's how I'd write a first version of your procedure:
create or replace procedure test_proc2 (
rUpdated_Row_Count IN NUMBER
, rUpdated_Row_Count_2 IN OUT NUMBER )
is
max_modified_date date ;
begin
select max( modified_date ) into max_modified_date from table1 ;
for rec_ in (
select table2_id, last_update
from table2
) loop
if rec_.last_update > max_modified_date then
insert into table2( table2_id, last_update )
values( null, rec_.last_update ) ;
rUpdated_Row_Count_2 := rUpdated_Row_Count_2 + 1 ;
end if ;
end loop;
end ;
/
Using your test tables (your DDL code should be: CREATE TABLE table1 ... by the way), we can use the following anonymous block for executing the procedure.
-- not sure what the "IN" parameter is used for
declare
rowcount_in number := 0 ; -- not needed
rowcount_out number := 0 ;
begin
test_proc2( rowcount_in, rowcount_out ) ;
dbms_output.put_line( 'updated rows: ' || rowcount_out ) ;
end;
/
updated rows: 2
After executing the anonymous block the tables contain ...
SQL> select * from table1 ;
TABLE1_ID MODIFIED_DATE
1 15-MAY-18
2 15-MAY-18
3 14-MAY-18
SQL> select * from table2 ;
TABLE2_ID LAST_UPDATE
1 16-MAY-18
2 17-MAY-18
NULL 16-MAY-18
NULL 17-MAY-18
Many people will tell you that you should use BULK operations (BULK COLLECT, FORALL etc) whenever possible. Does all that help you?
I have cursor it selects from TableA then Fetch Loop that inserts into TableB.
I want to check if the value already exists in the TableB.
If it exists then I want to skip the insert.
create or replace
PROCEDURE DAILY_RPT (
v_start IN DATE,
v_end IN DATE)
IS
ao_out_no out_pair.out_no%type;
cursor get is
SELECT ao_out_no from tableA;
BEGIN
open get;
LOOP
fetch get into ao_out_no;
EXIT WHEN get%NOTFOUND;
if (ao_out_no = (select out_no from TableA where out_no = ao_out_no) THEN
--DO NOTHING
else
INSERT INTO TABLEB(OUT_NO) VALUES (ao_out_no);
end if;
END LOOP;
close get;
END;
I used IF CONDITION however, I used variable into if condition & I am getting below.
PLS-00405: subquery not allowed in this context
if (ao_out_no = (select out_no from TableA where out_no = ao_out_no) THEN
You don't need cursor or PL/SQL at all:
INSERT INTO TABLEB(OUT_NO)
SELECT ao_out_no
FROM tableA ta
WHERE ... -- filtering rows
AND NOT EXISTS (SELECT * From TableB tb WHERE tb.OUT_NO = ta.ao_out_no);
Use the following :
for i in (
select out_no from TableA where out_no
)
loop
if i.out_no = ao_out_no
then
-- DO NOTHING
else
...
or
create a new variable named x, and then assign a value to it by
select out_no into x from TableA where out_no = ao_out_no;
and check returning value for x.
With corrected syntax, it would be something like this:
create or replace procedure daily_rpt
( v_start in date
, v_end in date )
as
begin
for r in (
select ao_out_no, 0 as exists_check
from tablea
)
loop
select count(*) into exists_check
from tablea
where out_no = r.ao_out_no
and rownum = 1;
if r.exists_check > 0 then
--DO NOTHING
else
insert into tableb (out_no) values (r.ao_out_no);
end if;
end loop;
end;
However, it's inefficient to query all of the rows and then do an additional lookup for each row to decide whether you want to use it, as SQL can do that kind of thing for you. So version 2 might be something like:
create or replace procedure daily_rpt
( v_start in date
, v_end in date )
as
begin
for r in (
select ao_out_no
from tablea
where not exists
( select count(*)
from tablea
where out_no = r.ao_out_no
and rownum = 1 )
)
loop
insert into tableb (out_no) values (r.ao_out_no);
end loop;
end;
at which point you might replace the whole loop with an insert ... where not exists (...) statement.
I have a Stored procedure in PLSQL which Inserts and Updates records on the basis of some condition.
Now here the issue is. While Inserting the record for the first time, it inserts records properly as required but
while updating it doesn't updates the record of the table.
Below is SP
PROCEDURE INSERT_INTO_VSAT_MST_DATA
(
P_SAPID IN NVARCHAR2,
P_CIRCLE IN NVARCHAR2,
P_CANDIDATEID IN NVARCHAR2,
P_SITEID IN NVARCHAR2,
P_PRIORITYID IN NVARCHAR2,
P_SITENAME IN NVARCHAR2,
P_LATITUDE IN NVARCHAR2,
P_LONGITUDE IN NVARCHAR2,
P_CONTACT_DETAILS IN CLOB,
P_SITETYPE IN NVARCHAR2,
P_SITE_PLOT_DIMENSION IN NUMBER,
P_TECHNOLOGY IN NVARCHAR2
)
AS
V_COUNT NUMBER:=0;
V_PANAROMICIMG_COUNT NUMBER:=0;
V_SATELLITEIMG_COUNT NUMBER:=0;
V_SITEPLOTIMG_COUNT NUMBER:=0;
V_VSAT_DETAIL_ID NUMBER:=0;
BEGIN
SELECT COUNT(VSAT_DETAIL_ID) INTO V_COUNT FROM TBL_VSAT_MST_DETAIL WHERE SAP_ID = P_SAPID AND CANDIDATE_ID = P_CANDIDATEID;
IF V_COUNT > 0 THEN
SELECT VSAT_DETAIL_ID INTO TBL_INSERT FROM TBL_VSAT_MST_DETAIL WHERE SAP_ID = P_SAPID AND CANDIDATE_ID = P_CANDIDATEID;
UPDATE TBL_VSAT_MST_DETAIL SET
CIRCLE = P_CIRCLE,
CONTACT_DETAILS = P_CONTACT_DETAILS,
SITE_TYPE = P_SITETYPE,
SITE_DETAILS_DIMENSION = P_SITE_PLOT_DIMENSION,
SITE_DETAILS_TECHNOLOGY = P_TECHNOLOGY
WHERE VSAT_DETAIL_ID = V_VSAT_DETAIL_ID
RETURNING VSAT_DETAIL_ID INTO TBL_INSERT;
ELSE
INSERT INTO TBL_VSAT_MST_DETAIL
(
SAP_ID,
CIRCLE,
CANDIDATE_ID,
SITE_ID,
PRIORITY,
SITE_NAME,
LATITUDE,
LONGITUDE,
CONTACT_DETAILS,
SITE_TYPE,
SITE_DETAILS_DIMENSION,
SITE_DETAILS_TECHNOLOGY
VALUES
(
P_SAPID,
P_CIRCLE,
P_CANDIDATEID,
P_SITEID,
P_PRIORITYID,
P_SITENAME,
P_LATITUDE,
P_LONGITUDE,
P_CONTACT_DETAILS,
P_SITETYPE,
P_SITE_PLOT_DIMENSION,
P_TECHNOLOGY
) RETURNING VSAT_DETAIL_ID INTO TBL_INSERT;
END IF;
IF TBL_INSERT > 0 THEN
BEGIN
SELECT COUNT(*) INTO V_PANAROMICIMG_COUNT FROM TBL_VSAT_IMAGE_DETAIL WHERE IMG_TYPE = 'Panaromic' AND IMG_ID = TBL_INSERT;
SELECT COUNT(*) INTO V_SATELLITEIMG_COUNT FROM TBL_VSAT_IMAGE_DETAIL WHERE IMG_TYPE = 'Satellite' AND IMG_ID = TBL_INSERT;
SELECT COUNT(*) INTO V_SITEPLOTIMG_COUNT FROM TBL_VSAT_IMAGE_DETAIL WHERE IMG_TYPE = 'SitePlot' AND IMG_ID = TBL_INSERT;
IF V_PANAROMICIMG_COUNT > 0 THEN
BEGIN
DELETE FROM TBL_VSAT_IMAGE_DETAIL WHERE IMG_TYPE = 'Panaromic' AND IMG_ID = TBL_INSERT;
END;
END IF;
IF V_SATELLITEIMG_COUNT > 0 THEN
BEGIN
DELETE FROM TBL_VSAT_IMAGE_DETAIL WHERE IMG_TYPE = 'Satellite' AND IMG_ID = TBL_INSERT;
END;
END IF;
IF V_SITEPLOTIMG_COUNT > 0 THEN
BEGIN
DELETE FROM TBL_VSAT_IMAGE_DETAIL WHERE IMG_TYPE = 'SitePlot' AND IMG_ID = TBL_INSERT;
END;
END IF;
FOR PMULTIFIELDS IN (SELECT REGEXP_SUBSTR(P_PANORAMIC_IMAGES,'[^,]+', 1, LEVEL) AS IMAGES FROM DUAL
CONNECT BY REGEXP_SUBSTR(P_PANORAMIC_IMAGES, '[^,]+', 1, LEVEL) IS NOT NULL
)
LOOP
INSERT INTO TBL_VSAT_IMAGE_DETAIL
(
IMG_ID,
IMG_NAME,
IMG_TYPE,
IMG_UPLOADED_DATE,
UPLOADED_BY
)
VALUES
(
TBL_INSERT,
PMULTIFIELDS.IMAGES,
'Panaromic',
SYSDATE,
P_CREATEDBY
);
END LOOP;
FOR PSATELLITEIMG IN (SELECT REGEXP_SUBSTR(P_SATELLITE_IMAGES,'[^,]+', 1, LEVEL) AS IMAGES FROM DUAL
CONNECT BY REGEXP_SUBSTR(P_SATELLITE_IMAGES, '[^,]+', 1, LEVEL) IS NOT NULL
)
LOOP
INSERT INTO TBL_VSAT_IMAGE_DETAIL
(
IMG_ID,
IMG_NAME,
IMG_TYPE,
IMG_UPLOADED_DATE,
UPLOADED_BY
)
VALUES
(
TBL_INSERT,
PSATELLITEIMG.IMAGES,
'Satellite',
SYSDATE,
P_CREATEDBY
);
END LOOP;
IF P_SITEPLOT_IMAGES IS NOT NULL THEN
BEGIN
INSERT INTO TBL_VSAT_IMAGE_DETAIL
(
IMG_ID,
IMG_NAME,
IMG_TYPE,
IMG_UPLOADED_DATE,
UPLOADED_BY
)
VALUES
(
TBL_INSERT,
P_SITEPLOT_IMAGES,
'SitePlot',
SYSDATE,
P_CREATEDBY
);
END;
END IF;
END;
END IF;
COMMIT;
EXCEPTION WHEN OTHERS THEN
ROLLBACK;
NOTE
While updating the record my TBL_INSERT returns as NULL
Expanding on what #user7294900 pointed you towards... in the declare section you have:
V_VSAT_DETAIL_ID NUMBER:=0;
then if v_count > 0 you do:
SELECT VSAT_DETAIL_ID INTO TBL_INSERT FROM TBL_VSAT_MST_DETAIL WHERE SAP_ID = P_SAPID AND CANDIDATE_ID = P_CANDIDATEID;
UPDATE TBL_VSAT_MST_DETAIL SET
CIRCLE = P_CIRCLE,
CONTACT_DETAILS = P_CONTACT_DETAILS,
SITE_TYPE = P_SITETYPE,
SITE_DETAILS_DIMENSION = P_SITE_PLOT_DIMENSION,
SITE_DETAILS_TECHNOLOGY = P_TECHNOLOGY
WHERE VSAT_DETAIL_ID = V_VSAT_DETAIL_ID
RETURNING VSAT_DETAIL_ID INTO TBL_INSERT;
The select is setting TBL_INSERT to the ID value from your table. But when you do the update your filter is using V_VSAT_DETAIL_ID, which is still set to its initial value of zero.
You probably meant to do:
SELECT VSAT_DETAIL_ID INTO V_VSAT_DETAIL_ID FROM TBL_VSAT_MST_DETAIL WHERE SAP_ID = P_SAPID AND CANDIDATE_ID = P_CANDIDATEID;
although you could still with your current select, and use that in the update too (making the returning into a bit redundant.
Be aware though that if v_count is not exactly 1, i.e. you have more than one row matching the P_SAPID and P_CANDIDATEID values, the select will get a too-many-rows exception. You won't see that because you are silently squashing any errors you get at run time.
It's usually not a good idea to commit or rollback inside a procedure anyway; it should be up to the caller to decide what to do, as this could be one of a series of statements and calls that you really want to treat as an atomic transaction. (You may be interested in savepoints.)
If you really, really want to rollback on exception within the procedure, you should at least re-raise the exception so the caller knows there was a problem:
EXCEPTION WHEN OTHERS THEN
ROLLBACK;
RAISE;
END INSERT_INTO_VSAT_MST_DATA;
but I would avoid when others if you can.
You could also combine a few steps by getting the ID at the start (again kind of assuming there is at most one matching row), and dropping the separate count and v_count variable:
SELECT MAX(VSAT_DETAIL_ID) INTO V_VSAT_DETAIL_ID
FROM TBL_VSAT_MST_DETAIL
WHERE SAP_ID = P_SAPID AND CANDIDATE_ID = P_CANDIDATEID;
IF V_VSAT_DETAIL_ID IS NOT NULL THEN
UPDATE TBL_VSAT_MST_DETAIL SET
CIRCLE = P_CIRCLE,
CONTACT_DETAILS = P_CONTACT_DETAILS,
SITE_TYPE = P_SITETYPE,
SITE_DETAILS_DIMENSION = P_SITE_PLOT_DIMENSION,
SITE_DETAILS_TECHNOLOGY = P_TECHNOLOGY
WHERE VSAT_DETAIL_ID = V_VSAT_DETAIL_ID
RETURNING VSAT_DETAIL_ID INTO TBL_INSERT;
ELSE
...
And I'm not sure why you're doing counts before your deletes later on, and it looks like all your tbl_insert references could/should be v_vast_detail_id - there doesn't seem an obvious reason to have two variables for that. Passing in a comma-delimited string that you then have to tokenize is also a bit painful - you should consider passing in a collection of values instead, if whatever calls this can manage that.
As also pointed out, you could use merge instead of the separate insert/update logic.
You don't assign value to V_VSAT_DETAIL_ID which is used in your update as a key.
You should use merge for this kind of operations