Compare and Merge both table and collection - oracle

I want to compare data from table and collection both. I am using merge to compare the data from the collection to the stored table, but it is giving error of missing expression and I have to update data only when it is not matched. What is the solution for this. As data copied from one collection to another collection in the below code and then comparing from the table. Please find the below code.
CREATE OR REPLACE PACKAGE BODY PKG_USERRECORD IS
CONST_SUCCESS CONSTANT CHAR(1) := 'S';
CONST_FAILURE CONSTANT CHAR(1) := 'F';
PROCEDURE PR_CREATE_USERRECORD_STRING(PC_USER_INPUT IN VARCHAR2,
PC_STATUS OUT VARCHAR2) IS
CURSOR CUR_USERSTRING IS
SELECT REGEXP_SUBSTR(USER_INPUT, '([^~]*)(~|$)', 1, 1, NULL, 1) USERID,
REGEXP_SUBSTR(USER_INPUT, '([^~]*)(~|$)', 1, 2, NULL, 1) USERNAME,
REGEXP_SUBSTR(USER_INPUT, '([^~]*)(~|$)', 1, 3, NULL, 1) DATE_OF_JOINING,
REGEXP_SUBSTR(USER_INPUT, '([^~]*)(~|$)', 1, 4, NULL, 1) CREATED_BY,
REGEXP_SUBSTR(USER_INPUT, '([^~]*)(~|$)', 1, 5, NULL, 1) CREATION_DATE,
REGEXP_SUBSTR(USER_INPUT, '([^~]*)(~|$)', 1, 6, NULL, 1) MODIFIED_BY,
REGEXP_SUBSTR(USER_INPUT, '([^~]*)(~|$)', 1, 7, NULL, 1) MODIFIED_DATE
FROM (
SELECT REGEXP_SUBSTR(PC_USER_INPUT, '[^^]+', 1, LEVEL) USER_INPUT
FROM DUAL
CONNECT BY LEVEL <= LENGTH(PC_USER_INPUT) -
LENGTH(REPLACE(PC_USER_INPUT, '^')) + 1
);
TYPE T_CUR_USERSTRING IS TABLE OF CUR_USERSTRING%ROWTYPE;
T_USERRECORD T_CUR_USERSTRING;
T_USERRECORD_INSERT T_CUR_USERSTRING := T_CUR_USERSTRING();
T_USERRECORD_UPDATE T_CUR_USERSTRING := T_CUR_USERSTRING();
PROCEDURE PR_CLEANUP IS
BEGIN
BEGIN
IF CUR_USERSTRING%ISOPEN THEN
CLOSE CUR_USERSTRING;
END IF;
T_USERRECORD.DELETE;
T_USERRECORD_INSERT.DELETE;
T_USERRECORD_UPDATE.DELETE;
EXCEPTION
WHEN OTHERS THEN
NULL;
END;
END;
BEGIN
OPEN CUR_USERSTRING;
LOOP
--T_USERRECORD.DELETE;
FETCH CUR_USERSTRING BULK COLLECT
INTO T_USERRECORD; --LIMIT 1;
IF T_USERRECORD.COUNT = 0 THEN
EXIT;
END IF;
FOR I IN T_USERRECORD.FIRST .. T_USERRECORD.LAST LOOP
IF T_USERRECORD(I).USERID IS NULL THEN
T_USERRECORD_INSERT.EXTEND;
T_USERRECORD_INSERT(T_USERRECORD_INSERT.COUNT) := T_USERRECORD(I);
ELSE
T_USERRECORD_UPDATE.EXTEND;
T_USERRECORD_UPDATE(T_USERRECORD_UPDATE.COUNT) := T_USERRECORD(I);
END IF;
END LOOP;
FORALL N IN T_USERRECORD_INSERT.FIRST .. T_USERRECORD_INSERT.LAST
-- INSERT INTO QM_USERDATA VALUES T_USERRECORD_INSERT (N);
INSERT INTO QM_USERDATA
(USERID,
USERNAME,
DATE_OF_JOINING,
CREATED_BY,
CREATION_DATE,
MODIFIED_BY,
MODIFIED_DATE)
VALUES
(SEQ_QM_USERDATA.NEXTVAL,
T_USERRECORD_INSERT(N).USERNAME,
T_USERRECORD_INSERT(N).DATE_OF_JOINING,
T_USERRECORD_INSERT(N).CREATED_BY,
T_USERRECORD_INSERT(N).CREATION_DATE,
T_USERRECORD_INSERT(N).MODIFIED_BY,
T_USERRECORD_INSERT(N).MODIFIED_DATE);
FORALL M IN T_USERRECORD_UPDATE.FIRST .. T_USERRECORD_UPDATE.LAST
MERGE INTO QM_USERDATA U
USING (select T_USERRECORD_UPDATE(M).USERID AS USERID,
T_USERRECORD_UPDATE(M).USERNAME AS USERNAME,
T_USERRECORD_UPDATE(M).DATE_OF_JOINING AS DATE_OF_JOINING,
T_USERRECORD_UPDATE(M).CREATED_BY AS CREATED_BY,
T_USERRECORD_UPDATE(M).CREATION_DATE AS CREATION_DATE,
T_USERRECORD_UPDATE(M).MODIFIED_BY AS MODIFIED_BY,
T_USERRECORD_UPDATE(M).MODIFIED_DATE AS MODIFIED_DATE
FROM DUAL) UPDATE_MERGE
ON (U.USERID = UPDATE_MERGE.USERID)
WHEN MATCHED THEN
UPDATE
SET U.USERNAME = UPDATE_MERGE.USERNAME,
U.DATE_OF_JOINING = UPDATE_MERGE.DATE_OF_JOINING,
U.CREATED_BY = UPDATE_MERGE.CREATED_BY,
U.CREATION_DATE = UPDATE_MERGE.CREATION_DATE,
U.MODIFIED_BY = UPDATE_MERGE.MODIFIED_BY,
U.MODIFIED_DATE = UPDATE_MERGE.MODIFIED_DATE;
END LOOP;
CLOSE CUR_USERSTRING;
-- COMMIT;
PR_CLEANUP;
PC_STATUS := CONST_SUCCESS;
EXCEPTION
WHEN OTHERS THEN
PR_CLEANUP;
PC_STATUS := CONST_FAILURE;
PR_ERROR_LOGGING(SQLCODE || ' ' || SUBSTR(SQLERRM, 0, 3000),
'PR_CREATE_USERRECORD_STRING');
END PR_CREATE_USERRECORD_STRING;
END PKG_USERRECORD;

Related

Trigger that changes value in another table /plsql

I've created two tables: Employees and Departments
CREATE TABLE EMP
( emp_id number(3) PRIMARY KEY,
dept_id Number(3) NOT NULL,
emp_name Varchar2(50) NOT NULL,
address Varchar2(100),
phone Varchar2(20) NOT NULL,
salary Number(8,2) NOT NULL,
CONSTRAINT fk_DEPT FOREIGN KEY (dept_id) REFERENCES DEPT(DEPT_ID));
CREATE TABLE DEPT
( dept_id number(3) PRIMARY KEY,
dept_name varchar2(50) NOT NULL,
emp_cnt Number(3) NOT NULL)
I need to create trigger that changes value in DEPT.emp_cnt after inserting or deleting data in EMP table.
Here is my attempt
create or replace trigger add_emp_to_the_dep
after insert or delete on EMP
for each row
begin
update DEPT
set emp_cnt = :new.emp_id
where DEPT.dept_id = :new.dept_id;
if INSERTING then
emp_cnt += 1;
else DELETING then
emp_cnt -= 1;
end if;
end;
Wrong syntax; there's no such thing as emp_cnt += 1; in Oracle's PL/SQL.
Try something like this instead:
create or replace trigger add_emp_to_the_dep
after insert or delete on emp
for each row
begin
if inserting then
update dept set
emp_cnt = emp_cnt + 1
where dept_id = :new.dept_id;
elsif deleting then
update dept set
emp_cnt = emp_cnt - 1
where dept_id = :old.dept_id;
end if;
end;
/
You can use a compound trigger to collate the changes and make the minimum number of updates:
CREATE TRIGGER add_emp_to_the_dep
FOR INSERT OR UPDATE OR DELETE ON emp
COMPOUND TRIGGER
TYPE ids_type IS TABLE OF EMP.DEPT_ID%TYPE;
TYPE cnt_type IS TABLE OF PLS_INTEGER;
TYPE idx_type IS TABLE OF PLS_INTEGER INDEX BY PLS_INTEGER;
ids ids_type := ids_type();
cnts cnt_type := cnt_type();
idxs idx_type := idx_type();
PROCEDURE modify_dept_cnt (
id EMP.DEPT_ID%TYPE,
cnt PLS_INTEGER
)
IS
BEGIN
IF id IS NULL THEN
RETURN;
END IF;
IF NOT idxs.EXISTS(id) THEN
ids.EXTEND;
cnts.EXTEND;
ids(ids.COUNT) := id;
cnts(cnts.COUNT) := cnt;
idxs(id) := ids.COUNT;
ELSE
cnts(idxs(id)) := cnts(idxs(id)) + cnt;
END IF;
END modify_dept_cnt;
AFTER EACH ROW
IS
BEGIN
modify_dept_cnt(:NEW.DEPT_ID, 1);
modify_dept_cnt(:OLD.DEPT_ID, -1);
END AFTER EACH ROW;
AFTER STATEMENT
IS
BEGIN
FORALL i IN 1 .. ids.count
UPDATE dept
SET emp_cnt = emp_cnt + cnts(i)
WHERE dept_id = ids(i);
END AFTER STATEMENT;
END;
/
Then, if you do:
INSERT INTO emp (emp_id, dept_id, emp_name, phone, salary)
SELECT 1, 1, 'Alice', '0', 100 FROM DUAL UNION ALL
SELECT 2, 1, 'Betty', '1', 100 FROM DUAL UNION ALL
SELECT 3, 2, 'Carol', '2', 100 FROM DUAL UNION ALL
SELECT 4, 1, 'Debra', '3', 100 FROM DUAL UNION ALL
SELECT 5, 3, 'Emily', '4', 100 FROM DUAL UNION ALL
SELECT 6, 3, 'Fiona', '5', 100 FROM DUAL;
It will collate all the changes and UPDATE the DEPT table only 3 times, as employees for 3 unique DEPT_ID are added, rather than performing 6 updates, one for each inserted row.
db<>fiddle here
create or replace trigger add_emp_to_the_dep
after insert or delete on emp
for each row
begin
if inserting then
update dept set
emp_cnt = emp_cnt + 1
where dept_id = :new.dept_id;
elsif deleting then
update dept set
emp_cnt = emp_cnt - 1
where dept_id = :old.dept_id;
end if;
end;

How To: create a dynamic SQL statement, using either CURSOR or REFCURSOR?

I'm new to Oracle SQL PL/SQL so please bear with me. I would like to be able to modify/update a data set (tblDATA_TEST) with particular data values from a another data set (tblMODIFY_TEST) by constructing on the fly dynamic SQL statements and executing them.
The example that I have constructed so far is based upon using a CURSOR (FOR/LOOP), but yesterday I came across an article that suggested a more appropriate solution would be to use an REFCURSOR and regerttably not having a great deal of experience with Oracle syntax I thought I'd better ask the experts, you guys, before I get lost.
The desired end result using the sample data sets would be to see the following constructed SQL statements being created and executed.
-- Example constucted SQL statments
-- UPDATE "tblDATA_TEST" SET "tb_excl_flg" = -1 WHERE "BR_CD" = '0123';
-- UPDATE "tblDATA_TEST" SET "is_hdgd_flg" = -1 WHERE "ORG_GRP_ID" = 654;
-- UPDATE "tblDATA_TEST" SET "data_dte" = TO_DATE('31.10.2020','DD.MM.YYYY') WHERE "data_dte" = TO_DATE('3.09.2020','DD.MM.YYYY');
-- UPDATE "tblDATA_TEST" SET "is_incl_flg" = -1 WHERE "data_dte" = TO_DATE('31.10.2020','DD.MM.YYYY');
-- UPDATE "tblDATA_TEST" SET "OUT_AMT" = 800.50 WHERE "ORG_ID" = 321;
-- UPDATE "tblDATA_TEST" SET "OUT_AMT" = 500.50 WHERE "ORG_DESCR" = 'CLIENT E';
-- UPDATE "tblDATA_TEST" SET "OUT_AMT" = 800.50 WHERE "CTRY_CD" = 'UK' AND "BR_CD" = '0654' AND "ORG_ID" = 888;
So here's the example code, consider that you have the following data set held within a table named 'tblDATA_TEST' with the following example inserted data records.
CREATE TABLE "tblDATA_TEST" ("data_dte" DATE, RGN_CD VARCHAR2(5), CTRY_CD VARCHAR2(5), BR_CD VARCHAR2(5), ORG_GRP_ID NUMBER, ORG_ID NUMBER, ORG_DESCR VARCHAR2(255), OUT_AMT FLOAT, "is_ovrdue_flg" NUMBER, "is_hdgd_flg" NUMBER, "tb_incl_flg" NUMBER, "tb_excl_flg" NUMBER);
INSERT INTO "tblDATA_TEST" VALUES (TO_DATE('30.09.2020','DD.MM.YYYY'), 'AMER', 'US', 0123, 987, 789, 'CLIENT A', 100.50, 0, 0, 0, 0 );
INSERT INTO "tblDATA_TEST" VALUES (TO_DATE('30.09.2020','DD.MM.YYYY'), 'AMER', 'US', 0123, 654, 456, 'CLIENT B', 200.50, 0, 0, 0, 0 );
INSERT INTO "tblDATA_TEST" VALUES (TO_DATE('30.09.2020','DD.MM.YYYY'), 'EMEA', 'UK', 0456, 321, 123, 'CLIENT C', 300.50, 0, 0, 0, 0 );
INSERT INTO "tblDATA_TEST" VALUES (TO_DATE('30.09.2020','DD.MM.YYYY'), 'EMEA', 'UK', 0456, 654, 654, 'CLIENT D', 400.50, 0, 0, 0, 0 );
INSERT INTO "tblDATA_TEST" VALUES (TO_DATE('30.09.2020','DD.MM.YYYY'), 'EMEA', 'UK', 0654, 321, 321, 'CLIENT E', 500.50, 0, 0, 0, 0 );
INSERT INTO "tblDATA_TEST" VALUES (TO_DATE('30.09.2020','DD.MM.YYYY'), 'EMEA', 'UK', 0654, 321, 888, 'CLIENT F', 600.50, 0, 0, 0, 0 );
and I would like to be able to update some of those data records within the tblDATA_TEST table, with the following modify data table tblMODIFY_TEST and inserted data records.
CREATE TABLE "tblMODIFY_TEST" ("data_dte" DATE, "mdfy_rank" NUMBER, "tbl_name" VARCHAR2(250), "fld_name" VARCHAR2(250), "fld_value" VARCHAR2(250), "reason_descr" VARCHAR2(250), "valid_dte" DATE, "criteriafld1" VARCHAR2(250 BYTE), "criteriavalue1" VARCHAR2(250), "criteriafld2" VARCHAR2(250), "criteriavalue2" VARCHAR2(250), "criteriafld3" VARCHAR2(250), "criteriavalue3" VARCHAR2(250), "criteriafld4" VARCHAR2(250), "criteriavalue4" VARCHAR2(250), "criteriafld5" VARCHAR2(250), "criteriavalue5" VARCHAR2(250));
INSERT INTO "tblMODIFY_TEST" VALUES (TO_DATE('31.10.2020','DD.MM.YYYY'), '1', 'tblDATA_TEST', 'tb_excl_flg', '-1', 'Test branch code (BR_CD), varchar data type', NULL ,'BR_CD', '0123', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL );
INSERT INTO "tblMODIFY_TEST" VALUES (TO_DATE('31.10.2020','DD.MM.YYYY'), '2', 'tblDATA_TEST', 'is_hdgd_flg', '-1', 'Test organisation group code (ORG_GRP_CD), number data type', NULL, 'ORG_GRP_CD', '654', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL );
INSERT INTO "tblMODIFY_TEST" VALUES (TO_DATE('31.10.2020','DD.MM.YYYY'), '3', 'tblDATA_TEST', 'data_dte', '31.10.2020', 'Test data date (date_dte), date data type', NULL, 'data_dte', '30.09.2020', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL );
INSERT INTO "tblMODIFY_TEST" VALUES (TO_DATE('31.10.2020','DD.MM.YYYY'), '4', 'tblDATA_TEST', 'is_incl_flg', '-1', 'Test data date (date_dte), date data type', NULL, 'data_dte', '31.10.2020', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL );
INSERT INTO "tblMODIFY_TEST" VALUES (TO_DATE('31.10.2020','DD.MM.YYYY'), '5', 'tblDATA_TEST', 'OUT_AMT', '800.50', 'Test outstanding amount (OUT_AMT), float data type', NULL, 'ORG_ID', '321', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL );
INSERT INTO "tblMODIFY_TEST" VALUES (TO_DATE('31.10.2020','DD.MM.YYYY'), '6', 'tblDATA_TEST', 'OUT_AMT', '800.50', 'Test validation date, this should not get processed (valid_dte)', TO_DATE('31.08.2020','DD.MM.YYYY'), 'ORG_ID', '321', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL );
INSERT INTO "tblMODIFY_TEST" VALUES (TO_DATE('31.10.2020','DD.MM.YYYY'), '7', 'tblDATA_TEST', 'OUT_AMT', '500.50', 'Test outstanding amount (OUT_AMT), float data type', NULL, 'ORG_DESCR', 'CLIENT E', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL );
INSERT INTO "tblMODIFY_TEST" VALUES (TO_DATE('31.10.2020','DD.MM.YYYY'), '8', 'tblDATA_TEST', 'OUT_AMT', '800.50', 'Test outstanding amount (OUT_AMT), float data type and multiple criteria', NULL, 'CTRY_CD', 'UK', 'BR_CD', '0654', 'ORG_ID', 888, NULL, NULL, NULL, NULL );
take note, since the intention of MODIFY dynamic query process is to be generic/universal i.e. to be able to update any field value in any table, the modify fields that are to be used within the construction of the SQL statement are all text fields (VARCHAR), that implies that when constructing the dynamic SQL statement the process is going to have to determine what is the field data type of the table/field that is to be updated, and furthermore to determine what are the field data types of the potential numerous 'criteria fields' that are to be used.
Hence I've put a function together "fncGET_FLD_TYP" that can determine the field data type by looking at the system table USER_TAB_COLUMNS, pass the table name and field name in order to get the field data type back.
CREATE or REPLACE FUNCTION "fncGET_FLD_TYP"("tblNAME" IN VARCHAR, "fldNAME" IN VARCHAR) RETURN VARCHAR AS "fldDATA_TYPE" VARCHAR(50);
BEGIN
SELECT DATA_TYPE INTO "fldDATA_TYPE" FROM USER_TAB_COLUMNS WHERE TABLE_NAME = "tblNAME" AND COLUMN_NAME = "fldNAME";
RETURN "fldDATA_TYPE";
END;
for simulation purposes I've created a fuction named fncMODIFY, but I suppose ideally this would all run as stored procedure.
CREATE or REPLACE FUNCTION "fncMODIFY"
RETURN VARCHAR DETERMINISTIC IS
sql_stmt VARCHAR2(4000);
fld_typ VARCHAR2(50);
z_row NUMBER :=0;
CURSOR c_modify IS SELECT * FROM "tblMODIFY_TEST" WHERE "valid_dte" >= "data_dte" OR "valid_dte" IS NULL ORDER BY "mdfy_rank";
BEGIN -- iterate through each of the MODIFY record set, constructing an UPDATE SQL statement on the fly and executing it, setting the table_name.field_name to the 'fld_value', (workout perhaps via a udf function the field data type & convert accordingly), based upon the criteria# fields and their values (via a function workout the field data type & convert accordingly)
FOR r_modify IN c_modify LOOP
sql_stmt := 'UPATE "' || r_modify."tbl_name" || '" SET "' || r_modify."fld_name" || '" = ';
FOR Z in (SELECT COLUMN_NAME FROM USER_TAB_COLUMNS WHERE TABLE_NAME = 'tblMODIFY_TEST') LOOP
z_row := z_row + 1;
CASE
WHEN Z.COLUMN_NAME = 'fld_name' THEN
SELECT "fncGET_FLD_TYP"(r_modify."tbl_name", r_modify."fld_name") INTO fld_typ FROM DUAL;
CASE
WHEN fld_typ = 'DATE' THEN
sql_stmt := sql_stmt || 'TO_DATE(<fld_value>, ''DD.MM.YYYY'')';
WHEN fld_typ IN('FLOAT', 'NUMBER') THEN
--sql_stmt := sql_stmt || 'TO_NUMBER(<fld_value>)';
sql_stmt := sql_stmt || '<fld_value>';
ELSE --'VARCHAR2'
sql_stmt := sql_stmt || ' ''<fld_value>''' || fld_typ ;
END CASE;
sql_stmt := REPLACE(sql_stmt, '<fld_value>', r_modify."fld_value");
WHEN SUBSTR(Z.COLUMN_NAME,1,11) = 'criteriafld' THEN -- AND r_modify.COLUMN_NAME IS NOT NULL THEN
IF z_row = 8 THEN
sql_stmt := sql_stmt || ' WHERE <criteriafld#> = ';
ELSIF z_row > 8 THEN
sql_stmt := sql_stmt || ' AND <criteriafld#> = ';
END IF;
SELECT "fncGET_FLD_TYP"(r_modify."tbl_name", Z.COLUMN_NAME) INTO fld_typ FROM DUAL;
CASE
WHEN fld_typ = 'DATE' THEN
sql_stmt := sql_stmt || 'TO_DATE(<criteriavalue#>, ''DD.MM.YYYY'')';
WHEN fld_typ IN ('FLOAT','NUMBER') THEN
--sql_stmt := sql_stmt || 'TO_NUMBER(<criteriavalue#>)';
sql_stmt := sql_stmt || '<criteriavalue#>';
ELSE -- 'VARCHAR2'
sql_stmt := sql_stmt || '<criteriavalue#>';
END CASE;
sql_stmt := sql_stmt || fld_typ;
--sql_stmt := REPLACE(sql_stmt, '<criteriafld#>', r_modify.criteriafldZ);
--sql_stmt := REPLACE(sql_stmt, '<criteriavalue#>', r_modify.criteriavalueZ);
ELSE -- DO NOTHING
sql_stmt := sql_stmt || '';
END CASE;
END LOOP;
RETURN sql_stmt || ';';
-- EXECUTE.IMMEDIATELY UPDATE tbl_name SET fld_name = fld_value WHERE criteriafld1 = criterivalue1 AND criteriafld# = criterivalue# etc etc
-- EXAMPLE MODIFY_TEST DATA
-- UPDATE "tblDATA_TEST" SET "tb_excl_flg" = -1 WHERE "BR_CD" = '0123';
-- UPDATE "tblDATA_TEST" SET "is_hdgd_flg" = -1 WHERE "ORG_GRP_ID" = 654;
-- UPDATE "tblDATA_TEST" SET "is_incl_flg" = -1 WHERE "data_dte" = TO_DATE('31.10.2020','DD.MM.YYYY');
-- UPDATE "tblDATA_TEST" SET "OUT_AMT" = 800.50 WHERE "ORG_ID" = 321;
-- UPDATE "tblDATA_TEST" SET "OUT_AMT" = 500.50 WHERE "ORG_DESCR" = 'CLIENT E';
-- UPDATE "tblDATA_TEST" SET "OUT_AMT" = 800.50 WHERE "CTRY_CD" = 'UK' AND "BR_CD" = '0654' AND "ORG_ID" = 888;
-- EXECUTE.IMMEDIATELY sql_stmt;
END LOOP;
END;
You can use the following SQL statements to see what is in each data table
SELECT * FROM "tblDATA_TEST";
SELECT * FROM "tblMODIFY_TEST"; -- Content of the tblMODIFY_TEST table
SELECT * FROM "tblMODIFY_TEST" WHERE "valid_dte" >= "data_dte" OR "valid_dte" IS NULL ORDER BY "mdfy_rank"; -- We only want to process modify updates that are still valid, i.e. where the validation date is in the future, or is NULL
and this SQL statement calls the function fncMODIFY which doesn't execute anything yet since I'm still struggling to get the correct syntax in order to be able to pass the fld_name/criteriafld# field values in order to be able to construct the WHERE criteria correctly.
SELECT "fncMODIFY" FROM DUAL; -- Call the fuction
hence the current output is:
UPATE "tblDATA_TEST" SET "tb_excl_flg" = -1 WHERE <criteriafld#> = <criteriavalue#> AND <criteriafld#> = <criteriavalue#> AND <criteriafld#> = <criteriavalue#> AND <criteriafld#> = <criteriavalue#> AND <criteriafld#> = <criteriavalue#>;
but the desire is to get the example SQL statements above and have them actually executed.
Any asistance/guidance would be very much appreciated and thank you in advance.
You didn't replace <criteriafield#> and value with actual field name and value, so it cannot look like you wish.
You didn't call for EXECUTE IMMEDIATE so it was not fired and nothing happens.
You can avoid PL/SQL at all (your function for datatype can also be avoided) and buid all you need via single SQL statement with join on all_tab_cols. Then feed this to PL/SQL's EXECUTE IMMEDIATE and update that tables. If you'll build it as PL/SQL block with BEGIN...END and do EXECUTE IMMEDIATE on it, you will not need to split it into multiple statements and can create reproducible text for logging purposes (the logged code block can be executed via SQLPlus and debugged).
Also it is worth to mention that your dynamic SQL does not handle SQL injection.
UPD: Added a code without cursors and other row-by-row processing stuff. Finally you'll get a PL/SQL block that you need to pass to EXECUTE IMMEDIATE and update your tables.
Some points to mention:
I didn't use quoted identifiers because they will make your life a nightmare in one day. So all the content of modification table tblMODIFY_TEST need an update to uppercase.
Don't play with regional formatting of dates, timestamps and numbers, use ISO format, because it is deterministic and can be read by anybody without guessing month position and group separator.
Implement some checks on values to avoid SQL injections.
Your data is a good example of dynamic SQL issues, because it already contains invalid column names.
with unp as (
select
/*Turn criteria to rows, because in one day you can need more than 5*/
mdfy_rank,
tbl_name,
fld_name,
fld_value,
criterianum,
criteriafield,
criteriavalue
from tblmodify_test
unpivot(
(criteriafield, criteriavalue) for criterianum in (
(criteriafld1, criteriavalue1) as 1,
(criteriafld2, criteriavalue2) as 2,
(criteriafld3, criteriavalue3) as 3,
(criteriafld4, criteriavalue4) as 4,
(criteriafld5, criteriavalue5) as 5
)
) p
where valid_dte >= data_dte or valid_dte is null
)
, prep as (
select
unp.tbl_name,
unp.mdfy_rank,
unp.fld_name,
/*Build formatted field value*/
case
when atc_val.data_type like '%CHAR%'
then '''' || fld_value || ''''
when atc_val.data_type = 'DATE'
then 'to_date(''' || fld_value || ''', ''dd.mm.yyyy'')'
when atc_val.data_type in ('NUMBER', 'FLOAT', 'BINARY_FLOAT', 'BINARY_DOUBLE')
then fld_value
end as fld_value,
atc_val.data_type as value_datatype,
/*Build where clause*/
listagg(criteriafield || ' = :wb' || criterianum, ' and ') within group (order by criterianum) as where_clause,
/*Build formatted bind variables to get rid of infinite sequences of quotes*/
listagg(
/*Add quotes*/
case
when atc_where.data_type like '%CHAR%'
then '''' || criteriavalue || ''''
when atc_where.data_type = 'DATE'
then 'to_date(''' || criteriavalue || ''', ''dd.mm.yyyy'')'
when atc_where.data_type in ('NUMBER', 'FLOAT', 'BINARY_FLOAT', 'BINARY_DOUBLE')
then criteriavalue
end
, ', ') within group (order by criterianum) as where_bind_vars
from unp
left join all_tab_cols atc_val
on upper(unp.tbl_name) = atc_val.table_name
and upper(unp.fld_name) = atc_val.column_name
/*Check validity of columns and tables*/
left join all_tab_cols atc_where
on upper(unp.tbl_name) = atc_where.table_name
and upper(unp.criteriafield) = atc_where.column_name
group by
tbl_name,
mdfy_rank,
fld_name,
fld_value,
atc_val.data_type
order by
tbl_name,
mdfy_rank
)
select
/*
tbl_name,
mdfy_rank,
fld_name,
fld_value,
*/
/*Build final PL/SQL block of updates*/
'BEGIN' || chr(10) || chr(9) ||
listagg(
'execute immediate ''update ' || upper(tbl_name)
|| ' set ' || fld_name || ' = :vb where '
|| where_clause || ''' using '
|| fld_value || ', ' || where_bind_vars || ';'
, chr(10) || chr(9)
) within group(order by mdfy_rank)
|| chr(10) || 'END;' as stmt
from prep

How to optimize my stored procedure

I would like to know if there are ways I could optimize n improve performance of this stored procedure. I'm fairly new at it & would appreciate your help
To summary , the stored procedure takes an array . Each array an entry of the form a:b:c . After extracting the required data, an entry is made in a table.
create or replace
PROCEDURE insert_sku_prom_assigments (
sku_assigment_array tp_string_array_table
)
IS
first_colon_pos NUMBER;
second_colon_pos NUMBER;
sku VARCHAR2 (40);
assign VARCHAR2 (20);
promotion VARCHAR2 (40);
l_count INTEGER;
BEGIN
FOR i IN sku_assigment_array.FIRST .. sku_assigment_array.LAST
LOOP
first_colon_pos := INSTR (sku_assigment_array (i), ':', 1, 1);
second_colon_pos := INSTR (sku_assigment_array (i), ':', 1, 2);
sku := SUBSTR (sku_assigment_array (i), 1, first_colon_pos - 1);
assign :=
SUBSTR (sku_assigment_array (i),
first_colon_pos + 1,
second_colon_pos - first_colon_pos - 1
);
promotion := SUBSTR (sku_assigment_array (i), second_colon_pos + 1);
IF sku IS NOT NULL AND assign IS NOT NULL AND promotion IS NOT NULL
THEN
SELECT COUNT (*)
INTO l_count
FROM mtep_sku_promotion_rel_unver
WHERE sku_id = sku
AND assignment = assign
AND promotion_id = promotion
AND ROWNUM = 1;
IF l_count < 1
THEN
INSERT INTO mtep_sku_promotion_rel_unver
(sku_id, assignment, promotion_id
)
VALUES (sku, assign, promotion
);
END IF;
END IF;
l_count := 0;
END LOOP;
END insert_sku_prom_assigments;
#jorge is right there probably isn't much performance improvement to be found. But you could consider these alternatives to selecting a count before inserting:
This:
INSERT INTO mtep_sku_promotion_rel_unver
(sku_id, assignment, promotion_id
)
SELECT sku, assign, promotion FROM DUAL
WHERE NOT EXISTS
( SELECT NULL
FROM mtep_sku_promotion_rel_unver
WHERE sku_id = sku
AND assignment = assign
AND promotion_id = promotion
);
Or, assuming those 3 columns are defined as a key:
BEGIN
INSERT INTO mtep_sku_promotion_rel_unver
(sku_id, assignment, promotion_id
)
VALUES (sku, assign, promotion
);
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN NULL;
END;
This is not performance-related, but I also like to use the Oracle function apex_util.string_to_table to break up delimited strings easily:
PROCEDURE ...
IS
v_tab apex_application_global.vc_arr2;
...
BEGIN
v_tab := apex_util.string_to_table (sku_assigment_array (i), ':');
if v_tab.count >= 3 then
sku := v_tab(1);
assign := v_tab(2);
promotion := v_tab(3);
end if;
Replace the row-by-row PL/SQL with a single SQL statement:
create or replace procedure insert_sku_prom_assigments
(
sku_assigment_array tp_string_array_table
) is
begin
--#4: Insert new SKU, ASSIGN, and PROMOTION.
insert into mtep_sku_promotion_rel_unver(sku_id, assignment, promotion_id)
select sku, assign, promotion
from
(
--#3: Get values.
select sku_string,
substr (sku_string, 1, first_colon_pos - 1) sku,
substr (sku_string, first_colon_pos + 1, second_colon_pos-first_colon_pos - 1) assign,
substr (sku_string, second_colon_pos + 1) promotion
from
(
--#2: Get positions.
select
sku_string,
instr(sku_string, ':', 1, 1) first_colon_pos,
instr(sku_string, ':', 1, 2) second_colon_pos
from
(
--#1: Convert collection to table.
select column_value sku_string
--Use this for debugging:
--from table(tp_string_array_table('asdf:qwer:1234'))
from table(sku_assigment_array)
) converted_collection
) positions
) sku_values
--Only non-null values:
where sku is not null and assign is not null and promotion is not null
--Row does not already exist:
and not exists
(
select 1/0
from mtep_sku_promotion_rel_unver
where sku_id = sku
and assignment = assign
and promotion_id = promotion
);
end insert_sku_prom_assigments;
/
Here are sample objects for testing:
create or replace type tp_string_array_table is table of varchar2(4000);
create table mtep_sku_promotion_rel_unver(
sku_id varchar2(100),
assignment varchar2(100),
promotion_id varchar2(100));
begin
insert_sku_prom_assigments(tp_string_array_table('asdf:qwer:1234'));
end;
/
SQL almost always outperforms PL/SQL. It's also easier to test and debug once you get used to the way the code flows.

split string into multiple rows with multiple columns

I have a string with user information. I have to write a function that takes string as input and inserts into some table.
Input string contains multiple rows with multiple columns like following:
inputString= "1,cassey,1222,12-12-12:2,timon,,02-02-12:3,john,3333,03-03-12"
what i want is to create insert from this...
How it can be achieved?
Solution in a single Query as following:
But I replaced the ',,' with 'NULL'
inputString= "1,cassey,1222,12-12-12:2,timon,NULL,02-02-12:3,john,3333,03-03-12"
SELECT REGEXP_SUBSTR (REGEXP_SUBSTR (inputString, '[^:]+', 1, LEVEL), '[^,]+', 1, 1) AS Col1,
REGEXP_SUBSTR (REGEXP_SUBSTR (inputString, '[^:]+', 1, LEVEL), '[^,]+', 1, 2) AS Col2,
REGEXP_SUBSTR (REGEXP_SUBSTR (inputString, '[^:]+', 1, LEVEL), '[^,]+', 1, 3) AS Col3,
REGEXP_SUBSTR (REGEXP_SUBSTR (inputString, '[^:]+', 1, LEVEL), '[^,]+', 1, 4) AS Col4
FROM DUAL
CONNECT BY LEVEL <= LENGTH (REGEXP_REPLACE (inputString, '[^:]+')) + 1;
Result:
Col1 Col2 Col3 Col4
------ ------ ------ ------
1 cassey 1222 12-12-12
2 timon NULL 02-02-12
3 john 3333 03-03-12
3 rows selected.
Package Spec:
CREATE OR REPLACE PACKAGE string_conversion AS
TYPE string_test_tab IS TABLE OF VARCHAR2(2000);
TAB string_test_tab;
PROCEDURE string_convert(v_input IN VARCHAR2, v_output OUT string_test_tab);
END string_conversion;
/
Package Body:
CREATE OR REPLACE PACKAGE BODY string_conversion AS
PROCEDURE string_convert(v_input IN VARCHAR2, v_output OUT string_test_tab) IS
v_index NUMBER := 1;
v_index_comma NUMBER := 1;
TAB2 string_test_tab;
v_input_str VARCHAR2(2000):= v_input||',';
BEGIN
v_output := string_test_tab();
LOOP
v_index_comma := INSTR (v_input_str, ',',v_index);
EXIT WHEN v_index_comma = 0;
v_output.extend();
v_output(v_output.count):= SUBSTR(v_input_str, v_index, v_index_comma - v_index);
dbms_output.put_line(v_output(v_output.count));
v_index := v_index_comma + 1;
END LOOP;
END string_convert;
END string_conversion;
/
Testing:
DECLARE
v_out1 string_conversion.string_test_tab;
BEGIN
string_conversion.string_convert('a,b,c,d,e',v_out1);
FOR j IN v_out1.FIRST .. v_out1.LAST
LOOP
dbms_output.put_line(v_out1(j));
END LOOP;
END;
/
OUTPUT:
a
b
c
d
e
DECLARE
v_string VARCHAR2(20) := 'a,b,c,d,e';
v_val VARCHAR2(2000);
BEGIN
dbms_output.put_line('v_string = '||v_string);
LOOP
v_val := SUBSTR(v_string,1, INSTR(v_string, ',', 1)-1);
IF INSTR(v_string, ',', 1) = 0 THEN
v_val := v_string;
END IF;
dbms_output.put_line(v_val);
EXIT WHEN INSTR(v_string, ',', 1) = 0;
v_string := SUBSTR(v_string,INSTR(v_string, ',', 1)+1);
END LOOP;
END;

Open tickets between 2 dates

I'm trying to find the open tickets, that are open between may 1st 2012 and may 30th 2012 for that day.
So, I should get: May 1st : 0, May 2nd 0, 3rd : 2
But what I'm getting currently is May 1st: 2, 2nd: 2, 3rd:2 then start to go up every 3 days
data :
CREATE TABLE bugs
(
BUG_ID NUMBER PRIMARY KEY,
REPORTED_DATE DATE NOT NULL,
DESCRIPTION VARCHAR2(20),
PRIORITY NUMBER(2),
ASSIGNED_TO VARCHAR2(10),
CLOSED_DATE DATE,
NOTE VARCHAR2(20)
);
INSERT INTO BUGS VALUES (1230, '25-APR-12', NULL, 3, 'Team 3', '28-APR-12', NULL);
INSERT INTO BUGS VALUES (1231, '29-APR-12', NULL, 1, 'Team 1', '29-APR-12', NULL);
INSERT INTO BUGS VALUES (1232, '03-MAY-12', NULL, 1, 'Team 1', '03-MAY-12', NULL);
INSERT INTO BUGS VALUES (1233, '03-MAY-12', NULL, 1, 'Team 3', '08-MAY-12', NULL);
INSERT INTO BUGS VALUES (1234, '04-MAY-12', NULL, 2, 'Team 5', '15-MAY-12', NULL);
INSERT INTO BUGS VALUES (1235, '04-MAY-12', NULL, 2, 'Team 1', NULL, NULL);
INSERT INTO BUGS VALUES (1236, '05-MAY-12', NULL, 1, 'Team 2', '06-MAY-12', NULL);
INSERT INTO BUGS VALUES (1237, '05-MAY-12', NULL, 3, 'Team 3', '10-MAY-12', NULL);
INSERT INTO BUGS VALUES (1238, '09-MAY-12', NULL, 4, 'Team 5', '16-MAY-12', NULL);
INSERT INTO BUGS VALUES (1239, '09-MAY-12', NULL, 5, 'Team 6', NULL, NULL);
INSERT INTO BUGS VALUES (1240, '12-MAY-12', NULL, 5, 'Team 2', '30-MAY-12', NULL);
INSERT INTO BUGS VALUES (1241, '12-MAY-12', NULL, 1, 'Team 1', '12-MAY-12', NULL);
INSERT INTO BUGS VALUES (1242, '13-MAY-12', NULL, 4, 'Team 4', '20-MAY-12', NULL);
INSERT INTO BUGS VALUES (1243, '15-MAY-12', NULL, 4, 'Team 3', '01-AUG-12', NULL);
INSERT INTO BUGS VALUES (1244, '15-MAY-12', NULL, 2, 'Team 4', '20-MAY-12', NULL);
INSERT INTO BUGS VALUES (1245, '20-MAY-12', NULL, 2, 'Team 4', NULL, NULL);
INSERT INTO BUGS VALUES (1246, '22-MAY-12', NULL, 2, 'Team 4', '23-MAY-12', NULL);
INSERT INTO BUGS VALUES (1247, '25-MAY-12', NULL, 2, 'Team 1', '29-MAY-12', NULL);
INSERT INTO BUGS VALUES (1248, '30-MAY-12', NULL, 1, 'Team 1', '01-JUN-12', NULL);
INSERT INTO BUGS VALUES (1249, '05-JUN-12', NULL, 1, 'Team 2', '07-JUN-12', NULL);
COMMIT;
My code so far:
DECLARE
v_date bugs.closed_date%type := '01-may-12';
v_end_date bugs.closed_date%type := '20-may-12';
v_openbugs NUMBER(10);
BEGIN
WHILE v_date <= v_end_date
LOOP
SELECT COUNT(bug_id)
INTO v_openbugs
FROM bugs
WHERE bugs.reported_date < v_date
AND v_date > bugs.closed_date;
DBMS_OUTPUT.PUT_LINE (v_date ||' '|| v_openbugs);
v_date := v_date + 1;
END LOOP;
END;
I need to do this in PL/SQL.
You can manage this just with SQL:
WITH DAYS AS
(SELECT
TO_DATE('06/01/2012', 'MM/DD/YYYY') - ROWNUM D
FROM
DUAL
WHERE ROWNUM < 32)
SELECT
DAYS.D,
SUM(CASE
WHEN B.REPORTED_DATE IS NULL THEN
0
ELSE
1
END)
FROM
DAYS
LEFT OUTER JOIN BUGS B
ON (DAYS.D BETWEEN B.REPORTED_DATE AND NVL(CLOSED_DATE, DAYS.D))
GROUP BY DAYS.D
ORDER BY DAYS.D ASC
If you are going to make this an automatic report then I would pass the dates as an argument and store the result in a cursor and have the calling procedure format the output as required.
This is the Answer I got
DECLARE
v_date bugs.closed_date%type := '01-may-12';
v_end_date bugs.closed_date%type := '31-may-12';
v_openbugs NUMBER(10);
v_maxbug number(10):=0;
BEGIN
DBMS_OUTPUT.PUT_LINE ('Date' ||' '|| 'Open bugs');
WHILE v_date <= v_end_date
LOOP
SELECT COUNT(bug_id)
INTO v_openbugs
FROM bugs
WHERE bugs.reported_date <= to_date(v_date)
AND nvl(bugs.closed_date,'31-may-12') >= to_date(v_date);
if v_maxbug < v_openbugs then v_maxbug:=v_openbugs; end if;
DBMS_OUTPUT.PUT_LINE (v_date ||' '|| v_openbugs);
v_date := v_date + 1;
END LOOP;
DBMS_OUTPUT.PUT_LINE ('===========================');
DBMS_OUTPUT.PUT_LINE ('the max number of open bugs is:'|| v_maxbug);
END;

Resources