Merge in Oracle does not insert/update any rows - sql

Can you please suggest, what is wrong with this query? It is always extracting 0 records and not inserting the data.
I have checked the select query and it is returning the rows. But I am not sure what is wrong happening on the merge part that it does not insert/update the table.
ExtractType NUMBER(9);
RecordsExtracted NUMBER(9);
CurStatus NUMBER(9);
StartDate date;
ErrorMessage NVARCHAR2(1000);
LastExtrctTimestamp DATE;
BEGIN
StartDate := sysdate;
ExtractType := 79;
-- Fetching the Last Extract Time Stamp
Select max(ExtractTimestamp) INTO LastExtrctTimestamp from ExtractRecords where Status = 2 and ExtractRecords.ExtractType= ExtractType;
IF LastExtrctTimestamp IS NULL
THEN LastExtrctTimestamp := To_Date('01/01/1901', 'MM/dd/yyyy');
END IF;
MERGE INTO Table MCTH
USING (
SELECT
val1, val2, val3, .... val1
FROM
View_RPT
WHERE TransitionDate >= LastExtrctTimestamp
) Core
ON(MCTH.valId= Core.ValId)
WHEN MATCHED THEN
UPDATE SET
MCTH.val1= Core.val1,
MCTH.val2= Core.val2,
MCTH.val3= Core.val3,
.
.
MCTH.val4= Core.val4
WHEN NOT MATCHED THEN
INSERT (MCTH.val1,MCTH.val2,MCTH.val3,MCTH.val4,
...,MCTH.val5)
VALUES (Core.val1,Core.val2,Core.val3,Core.val4,
...,Core.val5);
RecordsExtracted := SQL%RowCount;
DBMS_OUTPUT.put_line('MCTH Records Merged:' || RecordsExtracted);
COMMIT;
END;

Roll your pl/sql logic into the merge statement, and you can test whether core is returning what you expect more easily:
merge into
margincalltransitionhistory mcth
using (
select
margincalltransitionhistoryid,
margincallid,
fromworkflowstatename,
toworkflowstatename,
transitiondate,
transitionbyname,
transitioncomment
from
margincalltranhistory_rpt
where
transitiondate >= (
select coalesce(max(extracttimestamp), date '1901-01-01')
from extractrecords
where status = 2 and
extracttype = 79)
) core
...
And for the love of God clean your code up -- I have no idea how you can work with that mess.

Related

Calculate values from column having expression

I have a table "test_calculate" this has a column "CONN_BY" having values
column can have more than 2 number to multiply and this table may contain millions of rows , I need to get the result of the calculation from "CONN_BY" to "MVP".
I have used xmlquery for the calculation and dynamic query but these are quite slow. Is there another way which is much faster .Please suggest.
You can try the dynamic query.
Create a function which returns the calculated value and use it in your insert or select queries.
CREATE OR REPLACE FUNCTION UFN_CALCULATE (CLM_VALUE VARCHAR2)
RETURN NUMBER IS
RES_VAL NUMBER;
BEGIN
EXECUTE IMMEDIATE 'select '||CLM_VALUE||' FROM DUAL' INTO RES_VAL;
RETURN RES_VAL;
END;
You can use that function like below.
SELECT UFN_CALCULATE('.0876543 * .09876') FROM DUAL;
SELECT UFN_CALCULATE(CONN_BY) FROM YOUR_TABLE;
One option is using select ... connect by level <= regexp_count(conn_by,'[^*]+')... query for the implicit cursor within a PL/SQL code block
SQL> set serveroutput on
SQL> declare
mvp owa.nc_arr; -- numeric array to initialize each multiplication to 1 for each id value
begin
dbms_output.put_line('ID MVP');
dbms_output.put_line('--------');
for c in
(
select id,
to_number( regexp_substr(conn_by,'[^*]+',1,level) ) as nr,
level as lvl , max( level ) over ( partition by id ) as mx_lvl
from test_calculate
connect by level <= regexp_count(conn_by,'[^*]+')
and prior sys_guid() is not null
and prior conn_by = conn_by
order by id, lvl
)
loop
if c.lvl = 1 then mvp(c.id) := 1; end if;
mvp(c.id) := c.nr * mvp(c.id);
if c.lvl = c.mx_lvl then
dbms_output.put_line(c.id||' '||mvp(c.id));
end if;
end loop;
end;
/
where test_calculate is assumed to have an identity column(id)
Demo

ORACLE Add new record if any field changes

I'm trying to write an Oracle procedure. I have a table and currently I'm using a merge statement. When a record is changed, it updates it, if it is new, it adds it.
However, we want to keep track of changed records. So I'm adding three fields: startdate, enddate, currentflag. I don't want to update the record if there are any changes, I want to add a new record instead. But I do want to add an enddate and change the flag on the old record.
So, if I have a table like this:
TableID
Field1
Field2
Field3
StartDate
EndDate
CurrentFlag
And it has data like this
TableID Field1 Field2 Field3 StartDate EndDate CurrentFlag
001 DataA Cow Brown 3-Oct-18 Y
001 DataA Cow White 1-Sep-18 3-Oct-18 N
002 DataB Horse Dapple 3-Oct-18 Y
I want to merge in some data
TableID Field1 Field2 Field3
001 NewData Cow Black
002 DataB Horse Dapple
005 Data3 Cat Black
So that the final table looks like this
TableID Field1 Field2 Field3 StartDate EndDate CurrentFlag
001 DataA Cow Brown 3-Oct-18 10-Oct-18 N
001 DataA Cow White 1-Sep-18 3-Oct-18 N
001 NewData Cow Black 10-Oct-18 Y
002 DataB Horse Dapple 3-Oct-18 Y
005 Data3 Cat Black 10-Oct-18 Y
My pseudocode is
for each record in source file
find current record in dest table (on ID and flag = Y)
if any other fields do not match (Field1, Field2, Field3)
then update current record, set enddate, current flag to n
and add new record with startdate = sysdate, current flag is Y
if no match found, then add new record with startdate = sysdate, current flag is Y
I'm not sure how to turn that pseudocode into Oracle SQL code. Can I use the same MERGE statement, but in the WHEN MATCHED add a check to see if any of the other fields are different?
I will be doing this for several tables, a few of which have a lot of records and many fields. So I need to figure out something that works and isn't as slow as molasses.
UPDATE
I have created a procedure as suggested, with some modifications, so it works:
CREATE OR REPLACE PROCEDURE TESTPROC AS
BEGIN
DECLARE
l_count NUMBER;
CURSOR TRN is
SELECT * from sourceTable;
BEGIN
FOR each_record IN TRN
LOOP
-- if a record found but fields differ ...
l_count := 0;
SELECT COUNT(*) INTO l_count
FROM destTable DIM
WHERE each_record.TableID = DIM.TableID
and (each_record.Field1 <> DIM.Field1
or each_record.Field2 <> DIM.Field2
or each_record.Field13 <> DIM.Field3)
AND DIM.CurrentFlag = 'Y';
-- ... then update existing current record, and add with new data
IF l_count > 0 THEN
UPDATE destTable DIM
SET EndDate = sysdate
,CurrentFlag = 'N'
WHERE each_record.TableID = DIM.TableID;
INSERT INTO destTable
(TableID
, Field1
, Field2
, Field3
, StartDate
, CurrentFlag)
VALUES (each_record.TableID
, each_record.Field1
, each_record.Field2
, each_record.Field3
, sysdate
, 'Y');
COMMIT;
END IF;
-- if no record found with this key...
l_count := 0;
SELECT COUNT(*) INTO l_count
FROM destTable DIM
WHERE each_record.TableID = DIM.TableID;
-- then add a new record
IF l_count = 0 THEN
INSERT INTO destTable
(TableID
, Field1
, Field2
, Field3
, StartDate
, CurrentFlag)
VALUES (each_record.TableID
, each_record.Field1
, each_record.Field2
, each_record.Field3
, sysdate
, 'Y');
END IF;
END LOOP;
COMMIT;
END;
END TESTPROC
And on my small table, it worked nicely. Now I'm trying it on one of my larger tables (800k records, but by no means the largest table), and I'm updating this question while it runs. It's been nearly an hour, and obviously that isn't acceptable. Once my program comes back, I'll add indices on the TableID, and TableID and CurrentFlag. If indices don't help, any suggestions for the slow as molasses aspect?
You can write a Simple procedure for the same:
DECLARE
l_count NUMBER;
CURSOR C1 is
-- YOUR DATA FROM SOURCE
BEGIN
for each_record in c1
l_count := 0;
SELECT COUNT(*) into l_count from destination_table where field1=
eachrecord.field1 and .... and flag = 'Y'; -- find current record in dest table (on ID and flag = Y)
-- if any other fields do not match (Field1, Field2, Field3)
IF L_COUNT > 0 THEN
update current record, set enddate, current flag to n
END IF;
INSERT new record with startdate = sysdate, current flag is Y
END;
Mod by OP: That led to the right direction. The following code will do the trick, providing there is also an index on TableID and (TableID, CurrentFlag).
CREATE OR REPLACE PROCEDURE TESTPROC AS
BEGIN
DECLARE
l_count NUMBER;
CURSOR TRN is
SELECT * from sourceTable;
BEGIN
FOR each_record IN TRN
LOOP
-- if a record found but fields differ ...
l_count := 0;
SELECT COUNT(*) INTO l_count
FROM destTable DIM
WHERE each_record.TableID = DIM.TableID
and (each_record.Field1 <> DIM.Field1
or each_record.Field2 <> DIM.Field2
or each_record.Field13 <> DIM.Field3)
AND DIM.CurrentFlag = 'Y';
-- ... then update existing current record, and add with new data
IF l_count > 0 THEN
UPDATE destTable DIM
SET EndDate = sysdate
,CurrentFlag = 'N'
WHERE each_record.TableID = DIM.TableID;
INSERT INTO destTable
(TableID
, Field1
, Field2
, Field3
, StartDate
, CurrentFlag)
VALUES (each_record.TableID
, each_record.Field1
, each_record.Field2
, each_record.Field3
, sysdate
, 'Y');
COMMIT;
END IF;
-- if no record found with this key...
l_count := 0;
SELECT COUNT(*) INTO l_count
FROM destTable DIM
WHERE each_record.TableID = DIM.TableID;
-- then add a new record
IF l_count = 0 THEN
INSERT INTO destTable
(TableID
, Field1
, Field2
, Field3
, StartDate
, CurrentFlag)
VALUES (each_record.TableID
, each_record.Field1
, each_record.Field2
, each_record.Field3
, sysdate
, 'Y');
END IF;
END LOOP;
COMMIT;
END;
END TESTPROC
Maybe you could use triggers to perform that.
CREATE OR REPLACE TRIGGER insTableID
BEFORE INSERT OR UPDATE
ON tableID
FOR EACH ROW
DECLARE
v_exists NUMBER := -1;
BEGIN
SELECT COUNT(1) INTO v_exists FROM tableID t where t.Field1 = :new.Field1 and ... ;
IF INSERTING THEN
IF v_exist > 0 THEN
null;--your DML update statement
ELSE
null;--your DML insert statement
END;
END IF;
IF UPDATING THEN
null;--your DML statement for update the old registry and a DML for insert the new registry.
END IF;
END;
In this way, you can update the registry related to the old values and insert a new row with the new values.
I hope that this helps you to solve your problem.

Oracle procedure insert into table from another table

I have this code from trigger and now i need to create procedure because i cant use trigger.
CREATE OR REPLACE TRIGGER LIVE_MATCHES_TO_MATCHES
instead of insert ON LIVE_MATCHES
for each row
declare
p_priority number:= 1;
p_sport number:=0;
begin
insert into matches(sub_list , priority , sport, created)
select :new.comp_name , p_priority, p_sport,sysdate
from dual
where not exists (
select 1 from matches
where sub_list = :new.comp_name);
end;
this is procedure :
CREATE OR REPLACE PROCEDURE LIVE_MATCHES_SOCCER_T IS
p_priority number := 1;
p_sport number:=0;
begin
INSERT INTO matches("sub_list","priority","sport","created")
SELECT LIVE_MATCHES.COMP_NAME,p_priority,p_sport, sysdate
FROM LIVE_MATCHES WHERE LIVE_MATCHES.COMP_NAME <> matches.SUB_LIST;
commit;
end;
but I m getting error that matches.sub_list is invalid identifier.
How will i create procedure that will insert into table only if sub_list is different from comp_name.. I will set up job that will call this procedure every 5 minutes..
You can use MERGE statement
CREATE OR REPLACE
PROCEDURE PR_INSRT_INTO_MATCHES
IS
P_PRIORITY NUMBER := 1;
P_SPORT NUMBER := 0;
BEGIN
MERGE INTO MATCHES M USING
(SELECT DISTINCT COMP_NAME AS COMP_NAME FROM LIVE_MATCHES
) LM ON (LM.COMP_NAME=M.SUB_LIST)
WHEN NOT MATCHED THEN
INSERT
(
M.SUB_LIST,
M.PRIORITY,
M.SPORT,
M.CREATED
)
VALUES
(
LM.COMP_NAME,
P_PRIORITY,
P_SPORT,
SYSDATE
)
COMMIT;
END;
I think you want:
INSERT INTO matches("sub_list","priority","sport","created")
SELECT lm.COMP_NAME, lm.p_priority, lm.p_sport, sysdate
FROM LIVE_MATCHES lm
WHERE NOT EXISTS (SELECT 1
FROM matches m
WHERE lm.COMP_NAME <> m.SUB_LIST
);
Unless your column names are lower-case (which can only be done in Oracle by using quotes when you create them - and which is not a particularly good idea in any case), then your stored procedure won't work, as you're quoting lower-case identifiers in it. Try this instead:
CREATE OR REPLACE PROCEDURE LIVE_MATCHES_SOCCER_T IS
p_priority number := 1;
p_sport number := 0;
begin
INSERT INTO matches
( sub_list, priority, sport, created )
SELECT LIVE_MATCHES.COMP_NAME, p_priority, p_sport, sysdate
FROM live_matches
WHERE NOT EXISTS ( SELECT 1 FROM matches WHERE LIVE_MATCHES.COMP_NAME = matches.SUB_LIST );
commit;
end;

how update result of big query in oracle?

Can I update the result of query easily?
Assume I have big query which returns salary column and I need update salaries based on this query results.
ID- is primary key for my table
Now I,m doing it like this:
STEP 1
select id from mytable ...... where something
STEP 2
update mytable set salary=1000 where id in (select id from mytable ...... where something)
Is there exists alternative to do that easily?
Try for update and current of. You said that you are looking for something like "updating data on grid"
create table my_table( id number, a varchar2(10), b varchar2(10));
insert into my_table select level, 'a', 'b' from dual connect by level <=10;
select * from my_table;
declare
rec my_table%rowtype;
cursor c_cursor is select * from my_table for update;
begin
open c_cursor;
loop
fetch c_cursor into rec;
exit when c_cursor%notfound;
if rec.id in (1,3,5) then
rec.a := rec.a||'x';
rec.b := rec.b||'+';
update my_table set row = rec where current of c_cursor;
else
delete from my_table where current of c_cursor;
end if;
end loop;
commit;
end;
select * from my_table;
Yes , you can directly update the result easily.
Here is example :
update
(
select salary from mytable ...... where something
) set salary=1000

procedure to check if dates of new row overlaps with existing dates in the table

I am trying to write a procedure to check if parameters given (dates) lie between any of the existing dates in the table. And if not insert new row.
CREATE OR REPLACE PROCEDURE test(date1 IN DATE, date2 IN DATE) AS
ddate1 DATE;
ddate2 DATE;
quer VARCHAR2(50);
BEGIN
SELECT fdate, tdate INTO ddate1, ddate2 FROM dataHolder;
IF (ddate1 < date1) AND (ddate2 > date2) THEN
quer := 'invalid';
ELSE
INSERT INTO dataHolder VALUES (date1, date2);
quer := 'success';
END IF;
DBMS_OUTPUT.PUT_LINE(quer);
END;
/
I have tried something like this but when executed I get this error:
ORA-01422: exact fetch returns more than requested number of rows
You are getting that error because your select statement returns more than one record. To simplify the process you could use merge statement and rewrite your procedure as follows:
CREATE OR REPLACE PROCEDURE test(date1 IN DATE, date2 IN DATE) AS
BEGIN
merge into Dataholder dh
using dual
on ((date1 < dh.fdate) and (date2 < dh.tdate))
when not matched then
insert (dh.fdate, dh.tdate)
values(date1, date2);
if sql%rowcount > 0
then
dbms_output.put_line('success');
else
dbms_output.put_line('invalid');
end if;
END;
Your select statement fetches more than record whereas your code expects only one, since you're fetching into single-value variables. You could use BULK COLLECT and collect all the dates into a collection of dates, but I think you can improve on it with the code below:
CREATE OR REPLACE PROCEDURE test(date1 IN DATE, date2 IN DATE) AS
ddate1 DATE;
ddate2 DATE;
invalidRecords NUMBER := 0;
quer VARCHAR2(50);
BEGIN
SELECT COUNT(1) INTO invalidRecords FROM dataHolder WHERE fdate < date1 AND tdate > date2;
IF (invalidRecords > 0) THEN
quer := 'invalid';
ELSE
INSERT INTO dataHolder VALUES (date1, date2);
quer := 'success';
END IF;
DBMS_OUTPUT.PUT_LINE(quer);
END;
/
Since COUNT(1) will always return just one record, it will never throw an ORA-01422 error. Also, it will always return data, so you don't need to worry about NO_DATA_FOUND, as the value 0 will be fetched if there are no invalid records.
Some small optmization of Nuno Guerreiro's answer
SELECT COUNT(1) INTO invalidRecords
FROM dual
WHERE exists
(SELECT 1 FROM dataHolder WHERE fdate < date1 AND tdate > date2);
It will allow to keep out of counting.