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 如果任何字段更改,则添加新记录的主要内容,如果未能解决你的问题,请参考以下文章

在oracle数据库表中没有添加rowid字段为啥会出现

oracle 有记录就更新没有添加问题

oracle 查询每组的最大值

从表中检索所有不同的记录,如果两个相似的不同记录之间发生任何更改,则需要同时考虑两者。使用选择查询

oracle 有记录就更新没有添加问题

oracle查询重复数据方法