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;
Related
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;
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;
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
I have a PL/SQL function as follows:
create FUNCTION Test
return number
IS cnpParmId NUMBER;
good VARCHAR(1) := 'F';
exist VARCHAR(1) := 'F';
begin
good := 'F';
exist := 'F';
loop
select schema1.parm_id_seq.nextval into cnpParmId from dual;
begin
select 'T' into good from dual where cnpParmId not in (select PARM_ID from schema1.mytable1);
exception when NO_DATA_FOUND then good := 'F';
end;
exit when good = 'T';
end loop;
return cnpParmId;
end;
Now using this function am trying to insert values into tables:
declare
var1 number := Test;
BEGIN
INSERT INTO schema1.tbl
(PARM_ID, PARM_NAM, PARM_VAL_TXT, PARM_USAGE_TYPE_CD, PARM_USAGE_VAL_TXT, CRTE_TS, CRTE_USER_ID, LST_UPDT_TS, LST_UPDT_USER_ID, SITE_CD)
SELECT var1, 'VEL_INT_TYPE_MI', 'Minutes', 'RTS', NULL, SYSDATE, 'ilogdmgr', SYSDATE, 'ilogdmgr', NULL from dual;
INSERT INTO schema1.tbl
(PARM_ID, PARM_NAM, PARM_VAL_TXT, PARM_USAGE_TYPE_CD, PARM_USAGE_VAL_TXT,
CRTE_TS, CRTE_USER_ID, LST_UPDT_TS, LST_UPDT_USER_ID, SITE_CD)
select var1, 'Hours', 48, 'RTS', NULL, SYSDATE, 'ilogdmgr', SYSDATE, 'ilogdmgr', NULL from dual;
INSERT INTO schema1.tbl
(PARM_ID, PARM_NAM, PARM_VAL_TXT, PARM_USAGE_TYPE_CD, PARM_USAGE_VAL_TXT,
CRTE_TS, CRTE_USER_ID, LST_UPDT_TS, LST_UPDT_USER_ID, SITE_CD)
select var1, 'Days', 45, 'RTS', NULL, SYSDATE, 'ilogdmgr', SYSDATE, 'ilogdmgr', NULL from dual;
end;
The first insert takes the value that is returned from the function. I want the value to be refreshed for the second insert. I am expecting that the second insert will go to the function and returns another value. But thats now happening here. The same value is taken and the query fails with unique constraint index.
I will try select test(),... instead of select var1
I am trying to take some statistics against the Emp table, the create table & the rows inserted are given below. I am trying to develop a store procedure which will get all the columns for a particular table from oracle ALL_TAB_COLUMNS & I will generate the statistics.
The PL/SQL block of code given below is compiling but not returning any records when I run it. Can anyone please let me know where I might be getting wrong -
`is "distinct_cnt := 'SELECT COUNT(DISTINCT (' || table_rec.COLUMN_NAME || ')) FROM' || table_rec.TABLE_NAME;"`
a correct way of assigning the result to a variable.
create table emp( empno number(4,0),
ename varchar2(10),
job varchar2(9),
mgr number(4,0),
hiredate date,
sal number(7,2),
comm number(7,2),
deptno number(2,0)
)
insert into emp values( 7839, 'KING', 'PRESIDENT', null, to_date('17-11-1981','dd-mm-yyyy'), 5000, null, 10);
insert into emp values( 7698, 'BLAKE', 'MANAGER', 7839, to_date('1-5-1981','dd-mm-yyyy'), 2850, null, 30);
insert into emp values( 7782, 'CLARK', 'MANAGER', 7839, to_date('9-6-1981','dd-mm-yyyy'), 2450, null, 10);
insert into emp values( 7566, 'JONES', 'MANAGER', 7839, to_date('2-4-1981','dd-mm-yyyy'), 2975, null, 20);
insert into emp values( 7788, 'SCOTT', 'ANALYST', 7566, to_date('13-JUL-87','dd-mm-rr') - 85, 3000, null, 20);
insert into emp values( 7902, 'FORD', 'ANALYST', 7566, to_date('3-12-1981','dd-mm-yyyy'), 3000, null, 20);
insert into emp values( 7369, 'SMITH', 'CLERK', 7902, to_date('17-12-1980','dd-mm-yyyy'), 800, null, 20);
insert into emp values( 7499, 'ALLEN', 'SALESMAN', 7698, to_date('20-2-1981','dd-mm-yyyy'), 1600, 300, 30);
insert into emp values( 7521, 'WARD', 'SALESMAN', 7698, to_date('22-2-1981','dd-mm-yyyy'), 1250, 500, 30);
insert into emp values( 7654, 'MARTIN', 'SALESMAN', 7698, to_date('28-9-1981','dd-mm-yyyy'), 1250, 1400, 30);
insert into emp values( 7844, 'TURNER', 'SALESMAN', 7698, to_date('8-9-1981','dd-mm-yyyy'), 1500, 0, 30);
insert into emp values( 7876, 'ADAMS', 'CLERK', 7788, to_date('13-JUL-87', 'dd-mm-rr') - 51, 1100, null, 20);
insert into emp values( 7900, 'JAMES', 'CLERK', 7698, to_date('3-12-1981','dd-mm-yyyy'), 950, null, 30);
insert into emp values( 7934, 'MILLER', 'CLERK', 7782, to_date('23-1-1982','dd-mm-yyyy'), 1300, null, 10);
Commit;
create or replace
procedure p_profiling (V_tablename IN varchar2)
IS
cursor c1 is
select TABLE_NAME,
COLUMN_NAME
from
ALL_TAB_COLUMNS
where TABLE_NAME='V_tablename';
REC_CNT NUMBER;
distinct_cnt NUMBER;
is_valid NUMBER;
not_null NUMBER;
BEGIN
FOR table_rec in c1
LOOP
REC_CNT := 'SELECT COUNT(*) FROM' || table_rec.TABLE_NAME;
distinct_cnt := 'SELECT COUNT(DISTINCT (' || table_rec.COLUMN_NAME || ')) FROM' || table_rec.TABLE_NAME;
is_valid := 'SELECT COUNT(*) FROM '||table_rec.TABLE_NAME ||'WHERE'|| table_rec.COLUMN_NAME ||' IS NOT NULL
AND LENGTH('||table_rec.COLUMN_NAME||') = LENGTH(LTRIM(RTRIM('||table_rec.COLUMN_NAME||')))';
not_null := 'SELECT COUNT(*) FROM'|| table_rec.TABLE_NAME ||'WHERE '|| table_rec.COLUMN_NAME ||'IS NOT NULL';
DBMS_OUTPUT.PUT_LINE ('REC_CNT:'||REC_CNT||' '||'distinct_cnt:'||distinct_cnt||' '||'is_valid:'||is_valid ||' '||'TABLE_NAME'||table_rec.TABLE_NAME||' '||'COLUMN_NAME'||table_rec.COLUMN_NAME);
END LOOP;
END;
For executing the SQL statements created dynamically, you need to use EXECUTE IMMEDIATE:
create or replace
procedure p_profiling (V_tablename IN varchar2)
IS
cursor c1 is
select TABLE_NAME,
COLUMN_NAME
from
ALL_TAB_COLUMNS
where TABLE_NAME='V_tablename';
REC_CNT NUMBER;
distinct_cnt NUMBER;
is_valid NUMBER;
not_null NUMBER;
BEGIN
FOR table_rec in c1
LOOP
IF c1%ROWCOUNT = 1 THEN
EXECUTE IMMEDIATE 'SELECT COUNT(*) FROM' ||
table_rec.TABLE_NAME INTO REC_CNT;
END IF;
EXECUTE IMMEDIATE 'SELECT COUNT(DISTINCT (' ||
table_rec.COLUMN_NAME || ')) FROM' ||
table_rec.TABLE_NAME INTO distinct_cnt;
EXECUTE IMMEDIATE 'SELECT COUNT(*) FROM '||
table_rec.TABLE_NAME ||'WHERE'|| table_rec.COLUMN_NAME ||'
IS NOT NULL
AND LENGTH('||table_rec.COLUMN_NAME||') =
LENGTH(LTRIM(RTRIM ('||table_rec.COLUMN_NAME||')))' INTO is_valid;
EXECUTE IMMEDIATE 'SELECT COUNT(*) FROM'||
table_rec.TABLE_NAME ||'WHERE '|| table_rec.COLUMN_NAME
||'IS NOT NULL' INTO not_null;
DBMS_OUTPUT.PUT_LINE('REC_CNT:'||REC_CNT||' '||'distinct_cnt:'||
distinct_cnt||' '
||'is_valid:'||is_valid ||' '||'TABLE_NAME'||table_rec.TABLE_NAME||' '
||'COLUMN_NAME'||table_rec.COLUMN_NAME);
END LOOP;
END;
You need to use EXECUTE IMMEDIATE in order to execute these dynamics queries an also to obtain the data you are looking for.
Review this post: dynamic SELECT INTO clause in PL/SQL
Hope this help.