【问题标题】:Refactoring SELECT with two correlated subqueries使用两个相关子查询重构 SELECT
【发布时间】:2014-09-11 07:36:06
【问题描述】:

使用 Oracle 10g,我有一个如下所示的表(为简洁起见,缩短了语法):

CREATE TABLE "BUZINESS"."CATALOG" 
(   "ID" NUMBER PRIMARY KEY, 
    "ENTRY_ID" VARCHAR2(40 BYTE) NOT NULL,
    "MSG_ID" VARCHAR2(40 BYTE) NOT NULL, 
    "PUBLISH_STATUS" VARCHAR2(30 BYTE) NOT NULL, /* Can be NEW or PUBLISHED */
     CONSTRAINT "CATALOG_UN1" UNIQUE (ENTRY_ID, MSG_ID)
)

一个进程,进程 A,写入目录条目,其 PUBLISH_STATUS 为“新”。第二个进程,进程 B,然后进来,获取所有“新”消息,然后将 PUBLISH_STATUS 更改为“PUBLISHED”。

我需要编写一个查询来获取所有 PUBLISH_STATUS='NEW' 行,但是

我试图防止乱序获取,因此如果进程 B 将行标记为 PUBLISH_STATUS='PUBLISHED' 且 MSG_ID 为“1000”,然后进程 A 将乱序行写入为 PUBLISH_STATUS='NEW ' 使用 MSG_ID '999',查询在抓取所有 'NEW' 行时将永远不会获取该行。

所以,如果我从数据开始:

INSERT INTO BUZINESS.CATALOG VALUES (1, '1000', '999', 'NEW');
INSERT INTO BUZINESS.CATALOG VALUES (2, '1000', '1000', 'PUBLISHED');
INSERT INTO BUZINESS.CATALOG VALUES (3, '1000', '1001', 'NEW');

INSERT INTO BUZINESS.CATALOG VALUES (4, '2000', '1999', 'NEW');
INSERT INTO BUZINESS.CATALOG VALUES (5, '2000', '2000', 'PUBLISHED');
INSERT INTO BUZINESS.CATALOG VALUES (6, '2000', '2001', 'NEW');

INSERT INTO BUZINESS.CATALOG VALUES (7, '3000', '3001', 'NEW');

然后我的查询应该只抓取具有 ID 的行: 3、6、7

然后我必须将这些行与其他数据连接起来,因此结果需要是 JOINable。

到目前为止,我有一个非常大、丑陋的查询 UNIONing 两个相关的子查询来做到这一点。有人可以帮我写一个更好的查询吗?

【问题讨论】:

    标签: sql oracle


    【解决方案1】:

    最好通过过滤掉匹配连接(只留下不匹配的连接)的外连接来解决要求不存在可连接数据的问题。

    在您的情况下,连接条件是同一条目的“已发布”行以及稍后(更高)的消息 if。

    这个查询产生你想要的输出:

    select t1.*
    from buziness_catalog t1
    left join buziness_catalog t2
        on t2.entry_id = t1.entry_id
        and to_number(t2.msg_id) > to_number(t1.msg_id)
        and t2.publish_status = 'PUBLISHED'
    where t1.publish_status = 'NEW'
    and t2.id is null
    order by t1.id
    

    请参阅此查询的live demo 处理您的示例数据以产生您想要的输出。请注意,使用的表名是“buziness_catalog”而不是“buziness.catalog”,因此演示会运行 - 您必须将下划线改回点。

    作为一个连接,而不是基于 exists 相关子查询,这将执行得相当好。

    如果您的msg_id 列是数字类型(不需要从字符转换为数字),此查询会更简单一些。如果您的 ID 数据实际上是数字,请考虑将 entry_id 和 msg_id 的数据类型更改为数字类型。

    【讨论】:

    • 希望我能为设置现场演示投两票。感谢您的帮助。
    • 您应该尝试将 Oracle 中的查询计划与相关的不存在进行比较。从版本 6 开始,Oracle 就能够对此进行优化。
    • 例如见sqlfiddle.com/#!4/cb639/1。查询计划中的内容很少,但如果有一个更好的话,它似乎是not exists。我已将内容转换为数字并删除了 order by 以鼓励使用索引。
    • @Laurence 这并不总是与性能有关,只要它表现良好。它也同样与可读性和风格有关。 join 版本更紧凑,即使 oracle 可以优化现有版本,但其他数据库可能不会,因此 join 版本更便携。
    【解决方案2】:

    字里行间,我认为这可能有效:

    select
        *
    from
        buziness.catalog b1
    where 
        b1.publish_status = 'NEW' and
        not exists (
            select
                'x'
            from
                buziness.catalog b2
            where
                b1.entry_id = b2.entry_id and
                b2.publish_status = 'PUBLISHED' and
                to_number(b2.msg_id) > to_number(b1.msg_id) -- store numbers as numbers!
        );
    

    【讨论】:

      【解决方案3】:

      @Laurence 的查询看起来不错,但只是为了满足我的好奇心,你介意解释一下这个查询吗?

      我认为在TO_NUMBER() 中存储为 varchar 的这些数字会破坏您的索引使用能力,但我不确定 Oracle,所以您最好检查一下。
      万一他们这样做了,您可以随时添加额外的数字列,在编辑行时使用触发器更新这些列 - 这样您就不会破坏原始设计。

      SELECT * 
      FROM buziness b1
      WHERE PUBLISH_STATUS = 'NEW'
          AND TO_NUMBER(msg_id) > COALESCE((
              SELECT MAX(TO_NUMBER(msg_id)) 
              FROM buziness b2
              WHERE PUBLISH_STATUS = 'PUBLISHED'
              AND b2.entry_id = b1.entry_id
          ), 0)
      

      【讨论】:

      • +1 表示列类型建议。也许我终于可以证明将其更改为数字是合理的。 :)
      猜你喜欢
      • 1970-01-01
      • 2014-07-13
      • 1970-01-01
      • 2019-09-15
      • 1970-01-01
      • 2011-12-23
      • 1970-01-01
      • 2021-11-19
      • 1970-01-01
      相关资源
      最近更新 更多