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