在 Oracle SQL 中查询不返回单行时更新

Posted

技术标签:

【中文标题】在 Oracle SQL 中查询不返回单行时更新【英文标题】:Update when the query does not return a single row, in Oracle SQL 【发布时间】:2019-07-08 14:06:28 【问题描述】:

一个初学者的问题。

前提:我知道这里的关键问题是 SQL 表中没有固有顺序,因此为了向表中“添加”一列,我必须指定一些键匹配。但是,我正在寻找一个优雅的解决方案。

问题: 我想用通过窗口函数获得的V1 的转换替换列V1 的值(我用最接近的已知值V1 填充NULL 值,按data 排序)。

示例数据集(请注意我没有识别行的键):

create table Tab1(data date, V1 number, val number);
insert into Tab1 values (date '2000-01-01', 1, 100);
insert into Tab1 values (date '2000-02-01', 1, 110);
insert into Tab1 values (date '2000-03-01', 1, 100);
insert into Tab1 values (date '2000-03-01', 1, 130);
insert into Tab1 values (date '2000-05-01', NULL, 100);
insert into Tab1 values (date '2000-06-01', NULL, 100);
insert into Tab1 values (date '2000-03-01', 2, 110);
insert into Tab1 values (date '2000-03-01', 2, 105);
insert into Tab1 values (date '2000-04-01', 2, 190);
insert into Tab1 values (date '2000-05-01', NULL, 200);
insert into Tab1 values (date '2000-06-01', NULL, 150);

select * from Tab1;

DATA       V1   val
2000-01-01  1   100
2000-02-01  1   110
2000-03-01  1   100
2000-03-01  1   130
2000-04-01  1   100
2000-05-01      100
2000-06-01      100
2000-03-01  2   110
2000-03-01  2   105
2000-04-01  2   190
2000-05-01      200
2000-06-01      150

我想避免像在

中那样创建第二个表
  create table Tab2 as
    select A.*, 
    (case when V1 is null 
               then last_value(V1) ignore nulls 
               over (partition by V1 order by data 
               range between unbounded preceding and 1 preceding) 
          else V1 
          end) V2
 from Tab1 A;

实际上,窗口功能仍然没有做我想要的,但这是一个单独的问题(如果你有解决方案,非常欢迎)。最后,我想要的是以下内容,其中 V1 被其版本替换,空值被最接近的非缺失值替换:

DATA       V1   val 
2000-01-01  1   100
2000-02-01  1   110
2000-03-01  1   100
2000-03-01  1   130
2000-04-01  1   100
2000-05-01  1   100
2000-06-01  1   100
2000-03-01  2   110
2000-03-01  2   105
2000-04-01  2   190
2000-05-01  2   200
2000-06-01  2   150

我不能使用update,因为不允许使用 windows 函数,并且带有 windows 函数的子查询会检索多行。

同样,merge into 语句也不起作用,因为我无法给出一个 on 条件来标识要匹配的单行(dataV1 是不够的)。

有没有办法将V2 直接“添加”到Tab1 而无需创建新表?

【问题讨论】:

如何添加不足以识别行的相同值将有助于创建标识? 添加了样本数据和想要的结果。 【参考方案1】:

首先是好消息,这是您问题的答案。

同样,merge into 语句也不起作用,因为我无法给出一个 on 条件来标识要匹配的单行(数据和 V1 不够)。

您可以为此使用rowid。所以:

merge into tab1 t using
( select t1.rowid row_id, 
    (case when t1.V1 is null 
               then last_value(t1.V1) ignore nulls 
               over (partition by null order by data 
               range between unbounded preceding and 1 preceding) 
          else t1.V1 
          end) new_V1
 from Tab1 t1
) u
on ( t.rowid = u.row_id )
when matched then update set t.v1 = u.new_v1;

现在是坏消息。

没有办法写一个窗口函数来做你想做的事。

考虑输入数据中的这两行:

DATA       V1   val
2000-01-01  1   100
2000-02-01  1   110
2000-03-01  1   100
2000-03-01  1   130
2000-04-01  1   100
2000-05-01      100   <== this one
2000-06-01      100
2000-03-01  2   110
2000-03-01  2   105
2000-04-01  2   190
2000-05-01      200   <== and this one
2000-06-01      150

我们的逻辑无法使用这些行来知道第一个应该更新为V1 = 1,第二个应该更新为V1 = 2。我们唯一拥有的是您列出它们的顺序,但该信息不会存储在您的数据模型中的任何地方。

【讨论】:

谢谢。关于“合并...”,您使用了partition by null,这最终似乎使查询继续根据报告数据的顺序找到last_value(V1)......这让我感到困惑表格中没有固有顺序的概念。 partition by null 不会按随机顺序检索它们吗? @Giuseppe 不,订单由ORDER BY 控制。 PARTITION BY NULL 在这种情况下意味着:查看结果集中的所有行,按data 对它们进行排序,然后给我V1 的最后一个非空值。你的方式,PARTITION BY V1 的意思是:查看结果集中在我正在更新的行中具有相同V1 的所有行,按data 对它们进行排序,然后给我@987654337 的最后一个非空值@。但是,窗口的最后一个非空 V1 与您要更新的行相同,因为 PARTITION BY V1 窗口只查看 V1 与当前行相同的行。 感谢您的进一步解释。此处建议使用coalesce 的另一种可能的解决方法:***.com/a/56941067/3019236。【参考方案2】:

你只需要换一种方式。

无需创建另一个表。 WITH 子句适用于这种情况。

使用以下查询:

-- your update query
UPDATE TAB1
SET
    V1 = (
        WITH UPDATED_VAL AS (
            SELECT /*+ materialize */
                A.ROWID   RID,
                ( LAST_VALUE(V1) IGNORE NULLS OVER(
                    PARTITION BY DATE1
                    ORDER BY
                        DATE1
                    RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING
                ) ) V2
            FROM
                TAB1 A
        )
        SELECT
            V2
        FROM
            UPDATED_VAL
        WHERE
            TAB1.ROWID = UPDATED_VAL.RID
    )
WHERE
    V1 IS NULL

db<>fiddle demo

【讨论】:

谢谢,但不幸的是,正如我在问题中所说,我没有行 id:替换应该仅基于表的当前顺序(我承认它是SQL 中的东西很乱,但这是我正在处理的东西)。我将添加一个示例数据集来澄清。 我在问题中添加了一个示例,以澄清。

以上是关于在 Oracle SQL 中查询不返回单行时更新的主要内容,如果未能解决你的问题,请参考以下文章

Oracle函数-更新中

sql系统关键字转义

SQL 查询在 SSMS 中运行时间过长

SQL多表联查总结

mysqli->query 在更新时返回 true,但不影响行

SQL - 从复杂查询更新 Oracle 10g 数据库