【问题标题】:Oracle SQL - return multiple columns from subqueryOracle SQL - 从子查询返回多列
【发布时间】:2019-09-26 02:33:45
【问题描述】:

让我们在Oracle中做一个简单的查询:

SELECT
    CASE.ID,
    CASE.TYPE,
    CASE.DATE_RAISED
FROM
    CASE
WHERE
    CASE.DATE_RAISED > '2019-01-01'

现在假设另一个表 EVENT 包含多个事件,这些事件可能与每个案例相关联(通过 EVENT.CASE_ID 链接)。或者根本不存在。我想报告每个案例中最早日期的未来事件 - 或者如果不存在任何内容,则返回 NULL。我可以使用 SELECT 子句中的子查询来做到这一点,如下所示:

SELECT
    CASE.ID,
    CASE.TYPE,
    CASE.DATE_RAISED,
    (
    SELECT
        MIN(EVENT.DATE)
    FROM
        EVENT
    WHERE
        EVENT.CASE_ID = CASE.ID
        AND EVENT.DATE >= CURRENT_DATE
    ) AS MIN_EVENT_DATE
FROM
    CASE
WHERE
    CASE.DATE_RAISED > '2019-01-01'

这将返回一个像这样的表:

Case ID Case Type   Date Raised Min Event Date
76      A           03/01/2019  10/05/2019
43      B           02/02/2019  [NULL]
89      A           29/01/2019  08/07/2019
90      A           04/03/2019  [NULL]
102     C           15/04/2019  20/05/2019

请注意,如果不存在任何符合条件的事件,则仍会返回该行但没有值。这是因为子查询在 SELECT 子句中。这工作得很好。

然而,我的问题是,如果我想从 EVENT 表返回多于一列 - 同时仍然保留 EVENT 表中没有匹配行的可能性。上述代码仅将 EVENT.DATE 作为单个子查询结果返回到主查询的 ONE 列。但是如果我还想返回 EVENT.ID 或 EVENT.TYPE 怎么办?同时仍然允许它们为 NULL(如果没有找到 CASE 中的匹配记录)?

我想我可以在 SELECT 子句中使用多个子查询:每个子查询只返回一列。但这似乎非常低效,因为每个子查询都基于相同的条件(CASE ID 与主查询匹配的最短日期事件;如果没有找到此类事件,则为 NULL)。

我怀疑一些漂亮的连接会是答案 - 尽管我很难理解到底是哪些。

请注意,以上示例是我实际代码的极大简化版本,其中已经包含多个“旧式”Oracle 格式的连接,例如:

WHERE
    CASE.ID(+) = EVENT.CASE_ID

这是有原因的 - 因此请求任何回答这个问题的人,请您演示这种编码风格的任何解决方案,因为我的 SQL 还不够先进,无法重新考虑“更新" 样式加入现有代码。

【问题讨论】:

  • 我强烈建议您停止使用“旧式”连接语法编写查询。将现有查询重构为新样式一般并不困难(除非现有 sql 中存在一些复杂怪异的连接。
  • 要重构,您需要确定所涉及表的连接条件。如果您编辑问题以添加查询示例,我们可以为您重构它。 “新”(它已经存在好几年了)语法更容易理解,因为您的连接条件不再与谓词混在一起。
  • @Boneist - 我使用旧样式,因为这是我第一次学习它的地方,因为它是由 Business Objects 应用程序以那种形式生成的。我开始调整这个生成的代码并引入我自己的复杂性——同时保持我学到的相同风格。我正在使用的大多数代码以多种方式连接多个表,我无法理解如何以“新”样式执行此操作。问题中的这个最小示例可能很容易转换 - 但我不知道从哪里开始,有 10 多个表以不同的方式链接。
  • 我也是第一次学习旧方法,并使用了大约 10 年。 “新方式”更好。你可以用它做比旧方法更多的事情,而且真的没那么难。但是,如果您被某个应用程序生成的 sql 卡住了,那就更难了。
  • @Boneist - 如果我要提供我正在使用的实际代码的示例(由于保护问题而更改了一些名称),您能否向我展示一个重构因素?不过,这超出了这个问题的范围,所以不确定在哪里提问(代码审查?)

标签: sql oracle join subquery


【解决方案1】:

使用您想要的列创建对象类型并从子查询中返回它。您的查询将类似于

SELECT
    CASE.ID,
    CASE.TYPE,
    CASE.DATE_RAISED,
    (
    SELECT
        t_your_new_type ( MIN(EVENT.DATE) , min ( EVENT.your_another_column ) )
    FROM
        EVENT
    WHERE
        EVENT.CASE_ID = CASE.ID
        AND EVENT.DATE >= CURRENT_DATE
    ) AS MIN_EVENT_DATE
FROM
    CASE
WHERE
    CASE.DATE_RAISED > '2019-01-01'

【讨论】:

    【解决方案2】:

    这是你的意思吗?我只是用旧的 Oracle 连接语法和你的代码风格重写了 Gordon 的答案。

    SELECT
        CASE.ID,
        CASE.TYPE,
        CASE.DATE_RAISED,
        MIN_E.DATE AS MIN_EVENT_DATE
    FROM
        CASE,
        (SELECT EVENT.*,
            ROW_NUMBER() OVER (PARTITION BY EVENT.CASE_ID ORDER BY EVENT.DATE DESC) AS SEQNUM
            FROM
                EVENT
            WHERE 
                EVENT.DATE >= CURRENT_DATE
        ) MIN_E
    WHERE
        CASE.DATE_RAISED > DATE '2019-01-01'
        AND MIN_E.CASE_ID (+) = CASE.ID
        AND MIN_E.SEQNUM (+) = 1;
    

    【讨论】:

    • 使用古老的外连接语法,甚至 Oracle 也不想让任何人再使用,这会引起我的胃部反应。
    【解决方案3】:

    您可以使用join 和窗口函数。例如:

    select c.*, e.*
    from c left join
         (select e.*,
                 row_number() over (partition by e.case_id order by e.date desc) as seqnum
          from events e
         ) e
         on e.case_id = c.id and e.seqnum = 1;
    where c.date_raised > date '2019-01-01';  -- assuming the value is a date
    

    【讨论】:

    • 您能否将其重新考虑到我在问题中要求的样式中?我不知道如何将这种样式合并到我当前的代码中。
    • @ChrisMelville 。 . .我不知道你的评论是什么意思。此代码应按原样运行(尽管您可能应该明确列出您想要的列,而不是使用*)。
    • 谢谢戈登。我知道你打算用这个去哪里 - 它基本上是有效的,但是因为我不熟悉如何使用这种编码风格。 @kfinity 使用了您的逻辑并将其分解为我理解的形式。不过,请投赞成票:)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多