ORACLE 如果任何字段更改,则添加新记录
Posted
技术标签:
【中文标题】ORACLE 如果任何字段更改,则添加新记录【英文标题】:ORACLE Add new record if any field changes 【发布时间】:2018-10-10 18:06:48 【问题描述】:我正在尝试编写一个 Oracle 程序。我有一张桌子,目前我正在使用合并语句。当一条记录发生变化时,它会更新它,如果它是新的,它会添加它。
但是,我们希望跟踪更改的记录。所以我添加了三个字段:startdate、enddate、currentflag。如果有任何更改,我不想更新记录,我想添加一条新记录。但我确实想添加一个结束日期并更改旧记录上的标志。
所以,如果我有这样一张桌子:
TableID
Field1
Field2
Field3
StartDate
EndDate
CurrentFlag
它有这样的数据
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
我想合并一些数据
TableID Field1 Field2 Field3
001 NewData Cow Black
002 DataB Horse Dapple
005 Data3 Cat Black
让决赛桌看起来像这样
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
我的伪代码是
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
我不确定如何将该伪代码转换为 Oracle SQL 代码。我可以使用相同的 MERGE 语句,但在 WHEN MATCHED 中添加检查以查看其他字段是否不同?
我将为几个表执行此操作,其中一些表有很多记录和很多字段。所以我需要找出一些可行的方法,而且不像糖蜜那么慢。
更新 我按照建议创建了一个程序,并进行了一些修改,所以它可以工作:
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
在我的小桌子上,效果很好。现在我正在我的一个较大的表(800k 记录,但绝不是最大的表)上尝试它,并且在它运行时我正在更新这个问题。已经快一个小时了,显然这是不可接受的。一旦我的程序返回,我将在 TableID、TableID 和 CurrentFlag 上添加索引。如果指数没有帮助,对于糖蜜方面的缓慢方面有什么建议吗?
【问题讨论】:
【参考方案1】:您可以为此编写一个简单的过程:
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;
OP 修改:这导致了正确的方向。下面的代码可以解决问题,前提是 TableID 和 (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
【讨论】:
您能解释一下每个步骤中发生了什么吗?我正在浏览每个源记录(光标)。如果找到完全匹配,它将添加到计数中……但在这种情况下,我不应该修改我当前的标志:在这种情况下,我什么都不做。我想也许这给了我一些线索,但可能不是正确的逻辑。也许吧。 例如,是什么阻止它插入源表中的每条记录?如果存在完全匹配,则不需要发生。 我认为触发器可能是更好的选择并努力解决这个问题,但我会在早上回到这个。 如果不断更新同一条记录,触发器可能会变成变异触发器。 此代码将检查您的记录是否完全匹配,它将使用 flag = N 更新现有记录并始终插入新记录。我相信这是您的要求。【参考方案2】:也许您可以使用触发器来执行此操作。
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;
通过这种方式,您可以更新与旧值相关的注册表并使用新值插入新行。
我希望这可以帮助您解决问题。
【讨论】:
IF INSERTING 和 IF UPDATING 是否知道记录是否已经完全匹配?我不想做的是添加另一个精确的记录副本,并具有新的开始日期。我会玩这个。 您需要在插入或更新记录之前检查记录是否存在,我已经编辑了代码以检查记录是否已经存在。在 IF INSERTING 和 IF UPDATING 中,您将根据需要实现插入或更新的逻辑。 我创建了一个可以编译的触发器。我不能使用我现有的合并语句,因为我收到一个错误:表正在变异,触发器/函数可能看不到它。同上直接插入语句从我的源表中选择所有。 我有 IF INSERTING 和 v_exists > 1 然后 NULL 否则插入语句。并且 IF UPDATING 和 v_exists > 1 然后是更新和插入语句。但是,仅仅因为触发器编译,这并不意味着我的语法是正确的。您是否有机会根据我的假示例表添加有关 DML 语句的更多详细信息? 对于变异表使用复合触发器。您将有一个 BEFORE STATEMENT 部分和一个 BEFORE EACH ROW。在 BEFORE STATEMENT 中,您将使用 select 语句,在 EACH ROW 中,您将使用 merge 语句。以上是关于ORACLE 如果任何字段更改,则添加新记录的主要内容,如果未能解决你的问题,请参考以下文章