【问题标题】:Oracle: How to update master with newest row from detail table?Oracle:如何使用明细表中的最新行更新主数据?
【发布时间】:2010-05-13 20:01:13
【问题描述】:

我们有两张桌子:

车辆:

  • 身份证
  • 注册号
  • 上一个分配用户名
  • 最后分配日期
  • LastAllocationId

分配:

  • 身份证
  • 车辆编号
  • 用户名
  • 日期

用最新分配更新 Vehicle 表中的每一行的最有效(最简单)的方法是什么?在 SQL Server 中,我将使用 UPDATE FROM 并加入具有最新分配的每辆车。 Oracle 没有 UPDATE FROM。你是如何在甲骨文中做到这一点的?

** 编辑 **

我要求更新的最佳 SQL 查询。我将使用触发器来更新主表中的数据。我知道如何编写触发器。我要问的是如何编写 SQL 查询来更新 Vehicle 表。例子会很好。谢谢。

【问题讨论】:

  • 最好从 VEHICLE 表中删除违规列,并使用视图提供最新的分配信息。此设置要求您在 ALLOCATIONS 表上使用触发器来维持糟糕的决策选择。
  • 数据模型最初可能不应该是这样的。冗余数据在金融系统中很常见;冗余的易失性数据很丑。
  • 您从哪里得到视图效率低下的想法?它是准备好的 SQL 语句——Oracle 解释计划中没有关于使用视图的内容。不使用函数的 WHERE 子句条件(谓词)被推入视图查询本身。您需要在视图中比较查询中的解释计划,并运行视图本身以了解您的误解。
  • 我认为这是一种承认,事实上在使用视图时没有什么本质上是低效的。如果连接成本如此之高,那么您只需在数据库中使用一个整体表。
  • @LukLed:当你反复说“视图效率低下”时,你真的认为你在暗示什么?使用视图本身并没有什么低效的,因为在解释计划中没有任何东西可以支持您的主张。 视图与它封装的查询一样好。 Oracle 支持物化视图,顺便说一句。

标签: sql oracle plsql oracle9i


【解决方案1】:

正如大多数其他人所指出的:由于您的数据模型,您遇到了一个大问题。为这个模型编写的大多数代码将比它需要的要困难得多。我已经通过上下投票以及在某些 cmets 中说过,但还不够。

如果您继续前进,那么下面的代码将演示需要做什么。希望它吓到你:-)

示例表:

SQL> create table vehicles (id,registrationnumber,lastallocationusername,lastallocationdate,lastallocationid)
  2  as
  3  select 1, 1, 'Me', sysdate-1, 2 from dual union all
  4  select 2, 2, 'Me', sysdate, 3 from dual
  5  /

Table created.

SQL> create table allocations (id,vehicleid,username,mydate)
  2  as
  3  select 1, 1, 'Me', sysdate-2 from dual union all
  4  select 2, 1, 'Me', sysdate-1 from dual union all
  5  select 3, 2, 'Me', sysdate-1 from dual
  6  /

Table created.

触发器必须查看它自己的表以确定最后一次分配。 Oracle 通过引发变异表错误来防止这种类型的脏读。为了避免这种情况,我创建了一个 SQL 类型和一个包:

SQL> create type t_vehicle_ids is table of number;
  2  /

Type created.

SQL> create package allocations_mutating_table
  2  as
  3    procedure reset_vehicleids;
  4    procedure store_vehicleid (p_vehicle_id in vehicles.id%type);
  5    procedure adjust_vehicle_last_allocation;
  6  end allocations_mutating_table;
  7  /

Package created.

SQL> create package body allocations_mutating_table
  2  as
  3    g_vehicle_ids t_vehicle_ids := t_vehicle_ids()
  4    ;
  5    procedure reset_vehicleids
  6    is
  7    begin
  8      g_vehicle_ids.delete;
  9    end reset_vehicleids
 10    ;
 11    procedure store_vehicleid (p_vehicle_id in vehicles.id%type)
 12    is
 13    begin
 14      g_vehicle_ids.extend;
 15      g_vehicle_ids(g_vehicle_ids.count) := p_vehicle_id;
 16    end store_vehicleid
 17    ;
 18    procedure adjust_vehicle_last_allocation
 19    is
 20    begin
 21      update vehicles v
 22         set ( v.lastallocationusername
 23             , v.lastallocationdate
 24             , v.lastallocationid
 25             ) =
 26             ( select max(a.username) keep (dense_rank last order by a.mydate)
 27                    , max(a.mydate)
 28                    , max(a.id) keep (dense_rank last order by a.mydate)
 29                 from allocations a
 30                where a.vehicleid = v.id
 31             )
 32       where v.id in (select column_value from table(cast(g_vehicle_ids as t_vehicle_ids)))
 33      ;
 34    end adjust_vehicle_last_allocation
 35    ;
 36  end allocations_mutating_table;
 37  /

Package body created.

然后3个数据库触发器将更新代码从行级别移动到语句级别,从而规避了变异表错误:

SQL> create trigger allocations_bsiud
  2    before insert or update or delete on allocations
  3  begin
  4    allocations_mutating_table.reset_vehicleids;
  5  end;
  6  /

Trigger created.

SQL> create trigger allocations_ariud
  2    after insert or update or delete on allocations
  3    for each row
  4  begin
  5    allocations_mutating_table.store_vehicleid(nvl(:new.vehicleid,:old.vehicleid));
  6  end;
  7  /

Trigger created.

SQL> create trigger allocations_asiud
  2    after insert or update or delete on allocations
  3  begin
  4    allocations_mutating_table.adjust_vehicle_last_allocation;
  5  end;
  6  /

Trigger created.

还有一个小测试来验证它是否可以在单用户环境中工作:

SQL> select * from vehicles
  2  /

        ID REGISTRATIONNUMBER LA LASTALLOCATIONDATE  LASTALLOCATIONID
---------- ------------------ -- ------------------- ----------------
         1                  1 Me 13-05-2010 14:03:43                2
         2                  2 Me 14-05-2010 14:03:43                3

2 rows selected.

SQL> insert into allocations values (4, 1, 'Me', sysdate)
  2  /

1 row created.

SQL> select * from vehicles
  2  /

        ID REGISTRATIONNUMBER LA LASTALLOCATIONDATE  LASTALLOCATIONID
---------- ------------------ -- ------------------- ----------------
         1                  1 Me 14-05-2010 14:03:43                4
         2                  2 Me 14-05-2010 14:03:43                3

2 rows selected.

SQL> update allocations
  2     set mydate = mydate - 2
  3   where id = 4
  4  /

1 row updated.

SQL> select * from vehicles
  2  /

        ID REGISTRATIONNUMBER LA LASTALLOCATIONDATE  LASTALLOCATIONID
---------- ------------------ -- ------------------- ----------------
         1                  1 Me 13-05-2010 14:03:43                2
         2                  2 Me 14-05-2010 14:03:43                3

2 rows selected.

SQL> delete allocations
  2   where id in (2,4)
  3  /

2 rows deleted.

SQL> select * from vehicles
  2  /

        ID REGISTRATIONNUMBER LA LASTALLOCATIONDATE  LASTALLOCATIONID
---------- ------------------ -- ------------------- ----------------
         1                  1 Me 12-05-2010 14:03:43                1
         2                  2 Me 14-05-2010 14:03:43                3

2 rows selected.

现在您所要做的就是添加一些序列化以使其在多用户环境中 100% 工作。但希望这个例子足够可怕。

问候, 抢。

【讨论】:

  • 感谢您的大力回答。我不会使用包。我将使用全局临时表。这要容易得多。还是很高兴看到一些包使用和keep (dense_rank last,非常有用。
【解决方案2】:

当前设置要求您在 ALLOCATIONS 表上使用触发器来维持糟糕的决策选择。也就是说,使用:

UPDATE VEHICLE
   SET (LastAllocationUserName, LastAllocationDate, LastAllocationId) =
       (SELECT a.username,
               a.date,
               a.id
          FROM ALLOCATIONS a
          JOIN (SELECT b.vehicleid, 
                       MAX(b.date) AS max_date
                  FROM ALLOCATIONS b
              GROUP BY b.vehicleid) x ON x.vehicleid = a.vehicleid
                                     AND x.max_date = a.date
         WHERE a.vehicleid = VEHICLE.id)

从 VEHICLE 表中删除有问题的列,并使用视图提供最新的分配信息会更好地解决这个问题。

【讨论】:

    【解决方案3】:

    从设计的角度来看,我希望在 Vehicle 表上积极维护这三个字段,并将“Allocations”填充为历史表(可能由触发器)。将父表上的更新推送到子表上的插入比相反要容易得多。

    【讨论】:

    • +1:关于 ALLOCATIONS 用作历史记录/审计表的要点。
    • 实际情况并非如此。事实上,这些表中的字段要多得多,并且必须编辑历史条目。车辆也没有分配表中的所有字段。我的问题不是关于数据库设计,而是关于 sql 查询。表格不可修改。
    【解决方案4】:

    在 Oracle 中使用另一个表“更新”的最简单方法是使用 MERGE。

    MERGE INTO vehicle v
    USING (
      -- subquery to get info you need
    ) s ON (v.id = s.vehicleId)
    WHEN MATCHED THEN UPDATE SET 
      username = s.username 
      ...
    

    http://psoug.org/reference/merge.html

    【讨论】:

    • Oracle 9i 不适用于 MERGE。我想在匹配或不匹配时更新。不匹配时,值应为 NULL。它在 9i 中不起作用。
    【解决方案5】:

    您是否在触发器内寻找更新?

    CREATE TRIGGER ALLOCATION_I
    AFTER INSERT ON ALLOCATION
    REFERENCING NEW AS NEW
    FOR EACH ROW
    Begin
    
    UPDATE Vehicle 
     set LastAllocationUserName = :NEW.Username 
     ,LastAllocationDate = :NEW.date 
     ,LastAllocationId = :NEW.id 
    WHERE Id = :NEW.VehicleId;
    
    END;
    

    【讨论】:

      【解决方案6】:
      UPDATE VEHICLE V
         SET (LastAllocationId, LastAllocationDate, LastAllocationUserName) =
         (SELECT a.id
                 ,a.date
                 ,a.username
            FROM ALLOCATIONS a
            where a.VehicleId = V.id
              and a.date = ( select max(Last_a.date) from ALLOCATIONS Last_a
                             where Last_a.VehicleId = V.id )
          )
      

      你是对的。带有历史表的视图很慢。没有快速“加入最后一个记录”这样的东西。触发器是最好的解决方案。 如果可以,第一次使用 PL 填充。它更容易理解和维护。

      DECLARE
         Last_date DATE;
         Last_User Varchar2(100);
         Last_ID number;
      Begin
      FOR V IN ( Select * from VEHICLE )
      LOOP
         select max(date) into Last_date 
         from ALLOCATIONS Last_a
         where Last_a.VehicleId = V.id;
      
         IF Last_date is NULL then 
            Last_User := NULL;
            Last_ID := NULL;
         else
            select Id,UserName into Last_id, Last_user
            from ALLOCATIONS Last_a
            where Last_a.VehicleId = V.id
            and Last_a.date = Last_date;
         END IF;
      
         UPDATE Vehicle 
           set LastAllocationUserName = Last_User
               ,LastAllocationDate = Last_date
               ,LastAllocationId Last_id
         Where id = V.id;
      
      END LOOP;
      End;
      

      警告:写在这里,未经测试。

      【讨论】:

      • -1 表示这里写的每一句话,以及极其低效的代码
      • 如果要运行一次,应该清楚没有效率。无论如何,您的有效解决方案在哪里?
      • @Lucio M. Tato:不是运行一次,而是在 Allocations 表的触发器中频繁运行。我什至不确定您的查询是否会起作用,因为有嵌套的子查询来获取最大日期,它使用 V 别名,而 Oracle 不允许这样做,AFAIK。我会在家里检查一下。这就是我问这个问题的原因。 Oracle 有一些限制,使这种查询看起来很丑。
      • @LukLed:如果我错了,请纠正我,但在分配表的触发器中会有一个简单的更新。更新车辆设置 LastAllocationUserName = :NEW.Username , LastAllocationDate = :NEW.date , LastAllocationId = :NEW.id 其中 Id = :NEW.VehicleId;假设分配是按时间顺序插入的。
      • 不,不会。分配不必按时间顺序插入。我也不想对表中的每一行进行一次更新。我想先收集 VehicleId(在每个触发器之前),然后更新适当的车辆(在触发器之后)。
      猜你喜欢
      • 2019-07-11
      • 1970-01-01
      • 2014-09-08
      • 1970-01-01
      • 2015-04-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多