使用两个相关子查询重构SELECT

时间:2014-09-10 22:05:54

标签: sql oracle

使用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' NEW'编写目录条目。然后进入第二个流程,流程B,抓住所有“新”'消息,然后将PUBLISH_STATUS更改为“已发布”'。

我需要编写一个查询来获取所有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两个相关的子查询来执行此操作。有人可以帮我写一个更好的查询吗?

3 个答案:

答案 0 :(得分:2)

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

在您的情况下,加入条件是"已发布"具有稍后(更高)消息的相同条目的行,如果。

此查询产生您想要的输出:

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的数据类型更改为数字类型。

答案 1 :(得分:1)

在线之间阅读,我认为这可能有效:

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!
    );

答案 2 :(得分:1)

@Laurence的查询看起来不错,但为了满足我的好奇心,你是否介意解析这个问题呢?

我认为存储为varchar的那些数字会在TO_NUMBER()时终止你的索引使用功能,但我不确定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)
相关问题