SQL-Oracle 使用 LAG() 从自身更新表

Posted

技术标签:

【中文标题】SQL-Oracle 使用 LAG() 从自身更新表【英文标题】:SQL-Oracle Update table from itself using LAG() 【发布时间】:2017-09-15 09:07:00 【问题描述】:

假设我们有一张这样的表格:

| Year | Month | Day | ID_Office | Operations | Operations yesterday |
|------|-------|-----|-----------|------------|----------------------|
| 2016 | 12    | 31  | 9555      | 500        | 0                    |
| 2017 | 1     | 1   | 9555      | 600        | 0                    |
| 2017 | 1     | 2   | 9555      | 750        | 0                    |
| 2017 | 1     | 3   | 9556      | 800        | 0                    |

我想使用上一行中的“操作”更新“昨天的操作”中的值。所以结果表一定是这样的:

| Year | Month | Day | ID_Office | Operations | Operations yesterday |
|------|-------|-----|-----------|------------|----------------------|
| 2016 | 12    | 31  | 9555      | 500        | 0                    |
| 2017 | 1     | 1   | 9555      | 600        | 500                  |
| 2017 | 1     | 2   | 9555      | 750        | 600                  |
| 2017 | 1     | 3   | 9556      | 800        | 0                    |

我将以下代码与函数 LAG() 一起使用,但它没有更新正确的值。

Update table1 F1
Set f1.operations_yesterday = 
                       (Select LAG(f1.operations, 1, 0) OVER(ORDER BY year, month, day)
                        From table1 F2
                        Where 
                        F1.year= F2.year And
                        F1.month= F2.month And
                        F1.day= F2.day);

你能从我的代码中给我一些关于到底哪里错的建议吗? 我在“Insert Into”语句上使用 lag() 函数取得了成功,但在这种情况下,我必须提供一个 Update 语句解决方案。

注意: 请注意,排序的字段可能超过三个。我仅以年、月和日为例,但还有更多与日期无关的字段。 此外,重要的是要详细说明并非每天都有价值。

提前谢谢你!

【问题讨论】:

我自己没有使用过LAG(),但我想知道您是否不应该在以下脚本中使用f2,而不是f1...Select LAG(f1.operations, 1, 0)...跨度> @PeterAbolins 确实,这是最初尝试过的,但它仍然无法正常工作。 【参考方案1】:

不建议冗余存储数据。不过……

您想在记录中存储前一天的数据。您假设每天都有一条记录,因此 day before 也将是按日期排序的 record before。但是这些知识并没有真正的帮助,因为选择前一天比选择前一天的记录要容易得多。

将日、月和年分开存储而不仅仅是日期,这很奇怪。我不知道你想用这个实现什么。我们必须笨拙地转换它。

update table1 today
set operations_yesterday =
(
  select operations
  from table1 yesterday
  where to_date(yesterday.year * 10000 + yesterday.month * 100 + yesterday.day, 'yyyymmdd')
      = to_date(today.year * 10000 + today.month * 100 + today.day, 'yyyymmdd') - 1
);

如果您存储日期,那将是简单的:

update table1 today
set operations_yesterday =
      (select operations from table1 yesterday where yesterday.date = today.date - 1);

添加COALESCE(或Oracle 的NVL),如果你想要0 而不是昨天没有的null。

【讨论】:

感谢托尔斯滕·凯特纳。但是,我错过了一些可能会改变您解决问题的方式的解释。我只放了三个字段(年、月、日),但还有更多,其中一些与日期无关。此外,您的第二个解决方案不是一个选项,因为并非每一天都有价值。 @Roy90:其他专栏将如何发挥作用?您要更新的列不止一列吗? “并非每一天都有价值”是什么意思?您分隔列是因为值可以为空,例如“2016 年 11 月”没有一天,或者“每年 2 月 10 日”,无论哪一年? 如前所述,无论如何您都不应该这样做。选择前一天的记录非常容易。如果将这些值存储在当天的记录中,则会冗余存储数据,这可能会导致不一致。 我已经编辑了我的初始帖子,增加了一个字段。关于“并非每一天都有值”,我的意思是可能是一些随机的日子我没有在表中插入值,如果您尝试访问这个具体日期,将没有数据。最后,我并没有完全存储前一天的值,我希望这个值从当前值中减去,然后存储结果。 关于附加栏ID_Office:我们不做任何事情;它保持原样。应该是这样。至于错过的日子:然后我们用 null 更新。应该是这样。至于存储结果:仍然冗余存储数据。您总是可以从另一个值中减去一个值。如果您存储这些值,您可能会在某一天由于某些错误找到前一天 = 100,当天 = 200,存储结果 = 50。那该怎么办?哪个值是正确的?最好不要存储结果。如图所示,选择前一天非常简单。【参考方案2】:

只要您在子查询中传递年、月和日的值,就只有一行可用于延迟函数,它总是会给您一个空值。正确的方式应该是:

UPDATE table1 f1
   SET f1.operations_yesterday =
       (WITH table1_lag AS (SELECT ff.YEAR,
                                   ff.MONTH,
                                   ff.DAY,
                                   lag(ff.operations, 1, 0) over(ORDER BY ff.YEAR, ff.MONTH, ff.DAY) AS yesterday
                              FROM table1 ff)
           SELECT f2.yesterday
             FROM table1_lag f2
            WHERE f1.year = f2.year
              AND f1.month = f2.month
              AND f1.day = f2.day);

【讨论】:

感谢 San,我仍在修改您的解决方案并进行尝试。有事我会第一时间回复你的。 我在 ORDER BY 的末尾和最后的 WHERE 处添加了“ID_Office”,但结果不正确。它应该从同一个 ID_Office 获取前一行,但现在它获取前一行忽略 ID_OFFICE。有没有办法可以使用您的代码按 ID_OFFICE 分组?因为,除了这个字段的添加,它似乎工作正常。【参考方案3】:

如果缺少几天,并且您想要前天的值,那么您可以使用merge

merge into table1 t1
using (select lag(operations, 1, 0) 
              over (partition by id_office order by year, month, day) lop
         from table1 t) t2
on (t1.rowid = t2.rowid)
when matched then update set operations_yesterday = t2.lop;

测试数据:

create table table1(Year number(4), Month number(2), Day number(2), 
    ID_Office number(5), Operations number(4), Operations_yesterday number(4));

insert into table1 values (2016, 12, 31, 9555, 500, null);
insert into table1 values (2017,  1,  1, 9555, 600, null);
insert into table1 values (2017,  1,  2, 9555, 750, null);
insert into table1 values (2017,  1,  3, 9556, 800, null);
insert into table1 values (2017,  1,  5, 9556, 400, null);

...在merge之后:

 select * from table1;

 YEAR MONTH DAY ID_OFFICE OPERATIONS OPERATIONS_YESTERDAY
----- ----- --- --------- ---------- --------------------
 2016    12  31      9555        500                    0
 2017     1   1      9555        600                  500
 2017     1   2      9555        750                  600
 2017     1   3      9556        800                    0
 2017     1   5      9556        400                  800

【讨论】:

以上是关于SQL-Oracle 使用 LAG() 从自身更新表的主要内容,如果未能解决你的问题,请参考以下文章

将值提高到 n 次方时 SQL-Oracle 代码出错 - 需要帮助

基于其他两列并使用 LAG 函数更新 Oracle 过程中的列

Kafka的Lag计算误区及正确实现

Kafka 的 Lag 计算误区及正确实现

带有“lag()”窗口函数的 PostgreSQL 更新查询

将多个记录更新为单个记录