Oracle SQL:对 CASE WHEN 重复使用子查询,而无需重复子查询

Posted

技术标签:

【中文标题】Oracle SQL:对 CASE WHEN 重复使用子查询,而无需重复子查询【英文标题】:Oracle SQL: Re-use subquery for CASE WHEN without having to repeat subquery 【发布时间】:2016-03-14 14:41:36 【问题描述】:

我有一个 Oracle SQL 查询,它在其列输出中包含计算。在这个简化的示例中,我们正在查找日期在某个范围内的记录,其中某些字段与特定事物匹配;然后对于这些记录,获取 ID(不是唯一的)并再次在表中搜索具有相同 ID 的记录,但其中某些字段与其他内容匹配并且日期在主记录的日期之前。然后返回最早的日期。以下代码完全按预期工作:

SELECT
    TblA.ID, /* Not a primary key: there may be more than one record with the same ID */
    (
    SELECT
        MIN(TblAAlias.SomeFieldDate)
    FROM
        TableA TblAAlias
    WHERE
        TblAAlias.ID = TblA.ID /* Here is the link reference to the main query */
        TblAAlias.SomeField = 'Another Thing'
        AND TblAAlias.SomeFieldDate <= TblA.SomeFieldDate /* Another link reference */
    ) AS EarliestDateOfAnotherThing
FROM
    TableA TblA
WHERE
    TblA.SomeField = 'Something'
    AND TblA.SomeFieldDate BETWEEN TO_DATE('2015-01-01','YYYY-MM-DD') AND TO_DATE('2015-12-31','YYYY-MM-DD')

然而,除此之外,我还想包含另一个计算列,它根据 EarliestDateOfAnotherThing 的实际情况返回文本输出。我可以使用 CASE WHEN 语句来做到这一点,如下所示:

CASE WHEN
    (
    SELECT
        MIN(TblAAlias.SomeFieldDate)
    FROM
        TableA TblAAlias
    WHERE
        TblAAlias.ID = TblA.ID /* Here is the link reference to the main query */
        TblAAlias.SomeField = 'Another Thing'
        AND TblAAlias.SomeFieldDate <= TblA.SomeFieldDate /* Another link reference */
    ) BETWEEN TO_DATE('2000-01-01','YYYY-MM-DD') AND TO_DATE('2004-12-31','YYYY-MM-DD')
    THEN 'First period'
    WHEN
    (
    SELECT
        MIN(TblAAlias.SomeFieldDate)
    FROM
        TableA TblAAlias
    WHERE
        TblAAlias.ID = TblA.ID /* Here is the link reference to the main query */
        TblAAlias.SomeField = 'Another Thing'
        AND TblAAlias.SomeFieldDate <= TblA.SomeFieldDate /* Another link reference */
    ) BETWEEN TO_DATE('2005-01-01','YYYY-MM-DD') AND TO_DATE('2009-12-31','YYYY-MM-DD')
    THEN 'Second period'
    ELSE 'Last period'
END

这一切都很好。然而问题是我正在重新运行完全相同的子查询——这让我觉得效率很低。我想做的是只运行一次子查询,然后获取输出并将其应用于各种情况。就好像我可以按如下方式使用 VBA 语句“SELECT CASE”:

''''' Note that this is pseudo-VBA not SQL:
Select case (Subquery which returns a date)
    Case Between A and B
        "Output 1"
    Case Between C and D
        "Output 2"
    Case Between E and F
        "Output 3"
End select
' ... etc

我的调查表明 SQL 语句“DECODE”可以完成这项工作:但事实证明,DECODE 仅适用于离散值,而不适用于日期范围。我还发现了一些关于将子查询放在 FROM 部分的事情 - 然后在 SELECT 的多个位置重新使用输出。然而,这失败了,因为子查询本身并没有站起来,而是依赖于将值与主查询进行比较......并且在执行主查询之前无法进行这些比较(因此进行循环引用,因为FROM 部分本身就是主查询的一部分)。

如果有人能告诉我实现我想要的简单方法,我将不胜感激 - 因为到目前为止,唯一可行的方法是在我想要的每个地方手动重新使用子查询代码,但作为程序员效率低下让我很痛苦!

编辑: 感谢您到目前为止的答案。但是我想我将不得不在这里粘贴真实的、未简化的代码。我试图简化它以使概念清晰,并删除潜在的识别信息 - 但到目前为止的答案清楚地表明它比我的基本 SQL 知识所允许的更复杂。我试图围绕人们给出的建议来思考,但我无法将这些概念与我的实际代码相匹配。例如,我的实际代码包含多个表,我在主查询中从中选择。

我想我将不得不硬着头皮展示我的(仍然是简化的,但更准确的)实际代码,我一直在尝试让“FROM 子句中的子查询”工作。也许某个善良的人将能够使用它来更准确地指导我如何使用到目前为止在我的实际代码中引入的概念?谢谢。

SELECT
    APPLICANT.ID,
    APPLICANT.FULL_NAME,
    EarliestDate,
    CASE
        WHEN EarliestDate BETWEEN TO_DATE('2000-01-01','YYYY-MM-DD') AND TO_DATE('2004-12-31','YYYY-MM-DD') THEN 'First Period'
        WHEN EarliestDate BETWEEN TO_DATE('2005-01-01','YYYY-MM-DD') AND TO_DATE('2009-12-31','YYYY-MM-DD') THEN 'Second Period'
        WHEN EarliestDate >= TO_DATE('2010-01-01','YYYY-MM-DD') THEN 'Third Period'
    END
FROM
    /* Subquery in FROM - trying to get this to work */
    (
    SELECT
        MIN(PERSON_EVENTS_Sub.REQUESTED_DTE) /* Earliest date of the secondary event */
    FROM
        EVENTS PERSON_EVENTS_Sub
    WHERE
        PERSON_EVENTS_Sub.PER_ID = APPLICANT.ID /* Link the person ID */
        AND PERSON_EVENTS_Sub.DEL_IND IS NULL /* Not a deleted event */
        AND PERSON_EVENTS_Sub.EVTYPE_SDV_VALUE IN (/* List of secondary events */)
        AND PERSON_EVENTS_Sub.COU_SDV_VALUE = PERSON_EVENTS.COU_SDV_VALUE /* Another link from the subQ to the main query */
        AND PERSON_EVENTS_Sub.REQUESTED_DTE <= PERSON_EVENTS.REQUESTED_DTE /* subQ event occurred before main query event */
        AND ROWNUM = 1 /* To ensure only one record returned, in case multiple rows match the MIN date */
    ) /* And here - how would I alias the result of this subquery as "EarliestDate", for use above? */,
    /* Then there are other tables from which to select */
    EVENTS PERSON_EVENTS,
    PEOPLE APPLICANT
WHERE
    PERSON_EVENTS.PER_ID=APPLICANT.ID
    AND PERSON_EVENTS.EVTYPE_SDV_VALUE IN (/* List of values - removed ID information */)
    AND PERSON_EVENTS.REQUESTED_DTE BETWEEN '01-Jan-2014' AND '31-Jan-2014'

【问题讨论】:

我没有读得太近,但你不能把case表达式放在子查询中吗?子查询的结果将是您翻译的字符串而不是日期值。 shawnt00,我想在几个地方重复使用子查询结果。无论是返回日期还是翻译后的字符串,事实仍然是它仍然只会在一个地方返回一件事......这意味着我必须在每个我想使用它的地方运行子查询。 我只是离开上面的查询。如果您将 case 表达式放在子查询中,那么您可以消除子查询中的重复:case when min(dt) between A and B then 'x' when min(dt) between C and D then 'y' end) 或许可以查看“横向”和“交叉应用”,以获得让您最后一次尝试工作的干净方法。 抱歉,我没有详细阅读整个问题,所以我可能忽略了一些东西。我已经使用lateral 在下面添加了一个答案,我希望它可以在您的Oracle 版本中使用。 (实际上 lateral 可能只是 cross apply 的同义词,但我不能 100% 确定,因为 Oracle 不是我的专长。) 【参考方案1】:

着眼于重构现有查询(而不是逻辑或功能上不同的方法)

对我来说,最简单的方法就是将其作为嵌套查询来执行... - 内部查询将是您的基本查询,没有 CASE 语句 - 它还将您的相关子查询作为附加的字段 - 然后外部查询可以将该字段嵌入到 CASE 语句中

SELECT
    nested_query.ID,
    nested_query.FULL_NAME,
    nested_query.EarliestDate,
    CASE
        WHEN nested_query.EarliestDate BETWEEN TO_DATE('2000-01-01','YYYY-MM-DD') AND TO_DATE('2004-12-31','YYYY-MM-DD') THEN 'First Period'
        WHEN nested_query.EarliestDate BETWEEN TO_DATE('2005-01-01','YYYY-MM-DD') AND TO_DATE('2009-12-31','YYYY-MM-DD') THEN 'Second Period'
        WHEN nested_query.EarliestDate >= TO_DATE('2010-01-01','YYYY-MM-DD') THEN 'Third Period'
    END   AS CaseStatementResult
FROM
(
    SELECT
        APPLICANT.ID,
        APPLICANT.FULL_NAME,
        (
        SELECT
            MIN(PERSON_EVENTS_Sub.REQUESTED_DTE) /* Earliest date of the secondary event */
        FROM
            EVENTS PERSON_EVENTS_Sub
        WHERE
            PERSON_EVENTS_Sub.PER_ID = APPLICANT.ID /* Link the person ID */
            AND PERSON_EVENTS_Sub.DEL_IND IS NULL /* Not a deleted event */
            AND PERSON_EVENTS_Sub.EVTYPE_SDV_VALUE IN (/* List of secondary events */)
            AND PERSON_EVENTS_Sub.COU_SDV_VALUE = PERSON_EVENTS.COU_SDV_VALUE /* Another link from the subQ to the main query */
            AND PERSON_EVENTS_Sub.REQUESTED_DTE <= PERSON_EVENTS.REQUESTED_DTE /* subQ event occurred before main query event */
            AND ROWNUM = 1 /* To ensure only one record returned, in case multiple rows match the MIN date */
        )
            AS EarliestDate
    FROM
        EVENTS PERSON_EVENTS,
        PEOPLE APPLICANT
    WHERE
        PERSON_EVENTS.PER_ID=APPLICANT.ID
        AND PERSON_EVENTS.EVTYPE_SDV_VALUE IN (/* List of values - removed ID information */)
        AND PERSON_EVENTS.REQUESTED_DTE BETWEEN '01-Jan-2014' AND '31-Jan-2014'
)   nested_query

【讨论】:

【参考方案2】:

您可以在没有相关子查询或子查询分解 (WITH .. AS ( ... )) 子句的情况下使用分析函数(并在单个表扫描中)进行此操作:

SELECT ID,
       EarliestDateOfAnotherThing
FROM   (
  SELECT ID,
         MIN( CASE WHEN SomeField = 'Another Thing' THEN SomeFieldDate END )
           OVER( PARTITION BY ID
                 ORDER BY     SomeFieldDate
                 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW )
           AS EarliestDateOfAnotherThing
  FROM  TableA
)
WHERE SomeField = 'Something'
AND   SomeFieldDate BETWEEN TO_DATE('2015-01-01','YYYY-MM-DD')
                        AND TO_DATE('2015-12-31','YYYY-MM-DD')

您可以将扩展案例示例如下:

SELECT ID,
       CASE
         WHEN DATE '2000-01-01' <= EarliestDateOfAnotherThing
              AND EarliestDateOfAnotherThing < DATE '2005-01-01'
         THEN 'First Period'
         WHEN DATE '2005-01-01' <= EarliestDateOfAnotherThing
              AND EarliestDateOfAnotherThing < DATE '2010-01-01'
         THEN 'Second Period'
         ELSE 'Last Period'
       END AS period
FROM   (
  SELECT ID,
         MIN( CASE WHEN SomeField = 'Another Thing' THEN SomeFieldDate END )
           OVER( PARTITION BY ID
                 ORDER BY     SomeFieldDate
                 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW )
           AS EarliestDateOfAnotherThing
  FROM  TableA
)
WHERE SomeField = 'Something'
AND   SomeFieldDate BETWEEN TO_DATE('2015-01-01','YYYY-MM-DD')
                        AND TO_DATE('2015-12-31','YYYY-MM-DD')

【讨论】:

问题:当您将子查询放在 FROM 子句中时,是否可以在该 FROM 子句中包含其他表?我的示例被高度简化:实际代码包括其他几个表。 几乎可以肯定,但没有一个例子很难明确说明。试试看。【参考方案3】:

您没有在 FROM 子句中为您的尝试提供子查询,这会很有用,因为这是可以做到的一种方式:

SELECT
    TblA.ID,
    ED.MinSomeFieldDate,
    CASE...
FROM
    TableA A
LEFT OUTER JOIN
(
    SELECT
        SQ_A.Id,
        MIN(SQ_A.SomeFieldDate) AS MinSomeFieldDate
    FROM
        TableA SQ_A
    WHERE
        SQ_A.SomeField = 'Another Thing'
    GROUP BY
        SQ_A.Id
) AS ED ON
    ED.Id = A.Id AND
    ED.MinSomeFieldDate <= A.SomeFieldDate  -- We can do this outside of the subquery since it's MIN and <=
WHERE
    A.SomeField = 'Something' AND
    A.SomeFieldDate BETWEEN TO_DATE('2015-01-01','YYYY-MM-DD') AND TO_DATE('2015-12-31','YYYY-MM-DD')

【讨论】:

感谢 Tom H。不幸的是,我的 SQL 新手技能不允许我将这个概念转化为我的实际代码,而不仅仅是简化的示例。您能否看一下我编辑的问题,看看是否有可能?谢谢。 首先,不要将所有的表都放在FROM 子句中——找到一个很好的SQL 教程并学习JOIN 子句。接下来,您将为子查询设置别名,就好像它只是另一个表一样——一组数据在这方面就是一组数据。然后,您可以将该别名视为一个表,其中包含您需要的聚合数据。您还需要在子查询中为聚合函数起别名,以便您有一些东西可以引用它们。然后就像它们是您的子查询的“表”中的列一样。如果你还在苦苦挣扎,那么你可能应该打开一个新的 Q【参考方案4】:

Oracle 可能足够聪明地优化这两个子查询,但何必费心呢?我认为使用 CTE 更清楚地编写查询:

with q as (
      <your query here>
     )
select q.*,
       (case . . . 
        end) as another_calculated_column
from q;

这是一般结构。您可能需要在 q 中包含其他列以供您使用。

【讨论】:

谢谢戈登。但是有一件事:这个 CTE 是否可以与像我这样的子查询一起使用,它不是独立的,而是依赖于主查询中的引用值?我似乎记得我不久前曾出于另一个目的尝试过 WITH x AS,但它失败了,因为 CTE 查询不能独立存在。我可以完成这项工作吗?【参考方案5】:

除了其他答案之外,您可以将查询包装在外部查询中并在外部查询中使用 case 语句,例如:

select id,
       case when earliestdateofanotherthing between a and b then 'Output 1a'
            when earliestdateofanotherthing between c and d then 'Output 2a'
            when earliestdateofanotherthing between e and f then 'Output 3a'
            else 'default clause output - a' -- null if excluded
       end some_col1,
       case when earliestdateofanotherthing between a and b then 'Output 1b'
            when earliestdateofanotherthing between c and d then 'Output 2b'
            when earliestdateofanotherthing between e and f then 'Output 3b'
            else 'default clause output - b' -- null if excluded
       end some_col2
from  (select
           tbla.id, /* Not a primary key: there may be more than one record with the same ID */
           (
           select
               min(tblaalias.somefielddate)
           from
               tablea tblaalias
           where
               tblaalias.id = tbla.id /* Here is the link reference to the main query */
               and tblaalias.somefield = 'Another Thing'
               and tblaalias.somefielddate <= tbla.somefielddate /* Another link reference */
           ) as earliestdateofanotherthing
       from
           tablea tbla
       where
           tbla.somefield = 'Something'
           and tbla.somefielddate between to_date('2015-01-01','YYYY-MM-DD') and to_date('2015-12-31','YYYY-MM-DD'));

或者,您可以使用子查询分解(又名公用表表达式(CTE)/ WITH 子句)将主查询拉入子查询,然后从中进行选择:

with main_qry as (select
                      tbla.id, /* Not a primary key: there may be more than one record with the same ID */
                      (
                      select
                          min(tblaalias.somefielddate)
                      from
                          tablea tblaalias
                      where
                          tblaalias.id = tbla.id /* Here is the link reference to the main query */
                          and tblaalias.somefield = 'Another Thing'
                          and tblaalias.somefielddate <= tbla.somefielddate /* Another link reference */
                      ) as earliestdateofanotherthing
                  from
                      tablea tbla
                  where
                      tbla.somefield = 'Something'
                      and tbla.somefielddate between to_date('2015-01-01','YYYY-MM-DD') and to_date('2015-12-31','YYYY-MM-DD'))
select id,
       case when earliestdateofanotherthing between a and b then 'Output 1a'
            when earliestdateofanotherthing between c and d then 'Output 2a'
            when earliestdateofanotherthing between e and f then 'Output 3a'
            else 'default clause output - a' -- null if excluded
       end some_col1,
       case when earliestdateofanotherthing between a and b then 'Output 1b'
            when earliestdateofanotherthing between c and d then 'Output 2b'
            when earliestdateofanotherthing between e and f then 'Output 3b'
            else 'default clause output - b' -- null if excluded
       end some_col2
from   main_qry;

将子查询保留在 select 子句中的好处是,如果有适当的索引,您可以从子查询缓存中受益。它的性能可能与@MT0 使用分析函数查找earliestdateofanotherthing 列的解决方案一样或更高;您需要针对您的数据和表结构测试这两种解决方案,以确定哪一种最好。

(注意,我怀疑@MT0 的解决方案将是最好的解决方案;我主要提出这个答案作为示例,说明如何重用一列而无需计算两次。)


关于您问题中的更新查询,这可能会满足您的要求:

with main_qry as (SELECT
                      APPLICANT.ID,
                      APPLICANT.FULL_NAME,
                      case when min(case when person_events.del_ind is null
                                              and evtype_sdv_value in (/* List of secondary events */)
                                              then person_events.REQUESTED_DTE
                                    end) over (partition by person_events.per_id, person_events.cou_sdv_value) <= person_events.requested_dte then
                               min(case when person_events.del_ind is null
                                             and evtype_sdv_value in (/* List of secondary events */)
                                             then person_events.REQUESTED_DTE
                                   end) over (partition by person_events.per_id, person_events.cou_sdv_value)
                      end earliest_date
                  FROM
                      EVENTS PERSON_EVENTS,
                      inner join PEOPLE APPLICANT on (PERSON_EVENTS.PER_ID=APPLICANT.ID)
                  WHERE 
                      PERSON_EVENTS.EVTYPE_SDV_VALUE IN (/* List of values - removed ID information */)
                      AND PERSON_EVENTS.REQUESTED_DTE BETWEEN to_date('01-Jan-2014', 'dd-mm-yyyy') AND to_date('31-Jan-2014', 'dd-mm-yyyy'))
select id,
       full_name,
       earliest_date,
       CASE
           WHEN EarliestDate >= TO_DATE('2010-01-01','YYYY-MM-DD') THEN 'Third Period'
           WHEN EarliestDate >= TO_DATE('2005-01-01','YYYY-MM-DD') THEN 'Second Period'
           WHEN EarliestDate >= TO_DATE('2000-01-01','YYYY-MM-DD') THEN 'First Period'
       END period_type
from   main_qry;

显然,您必须对其进行测试!

请注意:

    我在 main_qry 的 where 子句中添加了字符串的显式转换为日期,使用 to_date;依赖隐式日期转换不是一个好主意,尤其是在生产代码中! NLS_DATE_FORMAT 参数可以轻松更改,这将导致难以识别错误! 我修改了您的案例陈述,以考虑您的最早日期字段可能介于裂缝之间的情况(例如,如果它的日期为“31/12/2004 13:03:23”)。它还使阅读更容易!

【讨论】:

我已根据您更新的查询更新了我的答案以包含一个解决方案。 嗯。我正在尝试您的解决方案,但出现“无效字符”错误。我正在使用的引擎没有指定错误发生的位置。是的,我确实记得用实际值列表替换我的注释文本。编辑:啊哈 - 无效字符显然是 ;在末尾!我的引擎(业务对象)不喜欢它。但是现在又出现了另一个错误:“表或视图不存在”...我迷路了... 我认为您有 EVENTS 和 PEOPLE 表?您是否从与拥有对象的架构相同的架构运行查询?如果没有,请尝试将模式名称添加到表中(例如OWNING_SCHEMA.EVENTS)。此外,请确保您运行的架构对这两个表都具有适当的权限。 是的,我的两个表都在同一个模式中。但是...通过将您的 CTE 格式与我现有的知识相结合,我刚刚成功地完成了我想做的事情!我完全迷失了“分区依据”的东西,我尝试使用该代码导致了错误。我所做的只是获取我现有的查询,然后按照您向我展示的方式将其命名为 CTE (main_qry) - 然后,我能够重新使用日期字段!谢谢:) 如果您不熟悉分析函数(“分区依据”),我强烈建议您研究它们——它们非常有用。它们与聚合查询(“分组依据”)非常相似,只是它不是将数据压缩到每组一行,而是在原始行集中输出每行的结果。 “分区依据”在某种程度上等同于“分组依据”。无论如何,我很高兴你让它工作,不管怎样! *:-)【参考方案6】:

这是lateral 解决方案。我重新排序了您的表并使用 ANSI 连接语法进行内部连接。此外,我实际上从未在 Oracle 上写过 lateralcross apply 查询,我注意到这主要是因为我可能犯了一个小的语法错误,而且我略读的文档并没有让我清楚是否有区别。

虽然我认为将其转换为仅使用另一个内部联接的表单并不困难,但我确实认为这是您在提出问题时正在寻找的概念。

SELECT
    APPLICANT.ID,
    APPLICANT.FULL_NAME,
    EarliestDate,
    CASE
        WHEN EarliestDate BETWEEN
            TO_DATE('2000-01-01','YYYY-MM-DD') AND TO_DATE('2004-12-31','YYYY-MM-DD')
        THEN 'First Period'
        WHEN EarliestDate BETWEEN
            TO_DATE('2005-01-01','YYYY-MM-DD') AND TO_DATE('2009-12-31','YYYY-MM-DD')
        THEN 'Second Period'
        WHEN EarliestDate >= TO_DATE('2010-01-01','YYYY-MM-DD')
        THEN 'Third Period'
    END
FROM
    EVENTS PERSON_EVENTS inner join PEOPLE APPLICANT
        on APPLICANT.ID = PERSON_EVENTS.PER_ID
    LATERAL /* or possibly just CROSS APPLY */
    (
        SELECT
            MIN(PERSON_EVENTS_Sub.REQUESTED_DTE) EarliestDate
        FROM
            EVENTS PERSON_EVENTS_Sub
        WHERE
            PERSON_EVENTS_Sub.PER_ID = APPLICANT.ID
            AND PERSON_EVENTS_Sub.DEL_IND IS NULL
            AND PERSON_EVENTS_Sub.EVTYPE_SDV_VALUE IN (...)
            AND PERSON_EVENTS_Sub.COU_SDV_VALUE = PERSON_EVENTS.COU_SDV_VALUE
            AND PERSON_EVENTS_Sub.REQUESTED_DTE <= PERSON_EVENTS.REQUESTED_DTE
    ) /* I don't think you need an alias here? */
WHERE
        PERSON_EVENTS.EVTYPE_SDV_VALUE IN (...)
    AND PERSON_EVENTS.REQUESTED_DTE BETWEEN '01-Jan-2014' AND '31-Jan-2014'

【讨论】:

【参考方案7】:

好的,伙计们,非常感谢你们的帮助。我能理解其中的一些——而其他部分让我头晕目眩。我是一个SQL初学者,所以我相信我会及时学习这些更复杂的概念。但是,我现在设法做的是使用答案中向我建议的概念来解决我的问题,但它仍然使用我熟悉的简单直接的代码。

基本上,我没有将日期输出子查询作为 CTE,而是将整个主要的原始查询作为 CTE(自然是独立的);然后通过在进一步查询中使用该 CTE,我可以随意操作该日期列。如下:

WITH MAIN_QUERY AS
    (
    SELECT
        APPLICANT.ID,
        APPLICANT.FULL_NAME,
        (ltrim(decode(to_char(APPLICANT.DOB_DD)||'/'||to_char(APPLICANT.DOB_MM)||'/'||to_char(APPLICANT.DOB_YYYY),'//',null,lpad(to_char(APPLICANT.DOB_DD),2,'0')||'/'||lpad(to_char(APPLICANT.DOB_MM),2,'0')||'/'||to_char(APPLICANT.DOB_YYYY)),'/')) AS DOB,
        PERSON_EVENTS.REQUESTED_DTE,
        PERSON_EVENTS.COU_SDV_VALUE,
        /* Find the date of EARLIEST secondary event */
        (
        SELECT
            MIN(PERSON_EVENTS3.REQUESTED_DTE)
        FROM
            EVENTS PERSON_EVENTS3
        WHERE
            PERSON_EVENTS3.PER_ID=APPLICANT.ID /* Ensure same person ID as main query */
            AND PERSON_EVENTS3.DEL_IND IS NULL
            AND PERSON_EVENTS3.EVTYPE_SDV_VALUE IN (/* Secondary event values */)
            AND ROWNUM = 1
        ) AS EarliestDate /* Alias this subquery to a column name, for use later on */
    FROM
        EVENTS PERSON_EVENTS,
        PEOPLE APPLICANT
    WHERE
        PERSON_EVENTS.PER_ID(+)=APPLICANT.ID
        AND PERSON_EVENTS.EVTYPE_SDV_VALUE IN (/* Primary event values */)
        AND PERSON_EVENTS.REQUESTED_DTE BETWEEN TO_DATE('2010-01-01','YYYY-MM-DD') AND TO_DATE('2011-01-01','YYYY-MM-DD')
    )
SELECT
    ID,
    FULL_NAME,
    DOB,
    REQUESTED_DTE,
    COU_SDV_VALUE,
    EarliestDate, /* Use the date by alias - this works */
    /* Then the alias can be further used how I like, without having to re-run the subquery :) */
    CASE
        WHEN EarliestDate BETWEEN TO_DATE('2000-01-01','YYYY-MM-DD') AND TO_DATE('2004-12-31','YYYY-MM-DD') THEN 'First period'
        WHEN EarliestDate BETWEEN TO_DATE('2005-01-01','YYYY-MM-DD') AND TO_DATE('2009-12-31','YYYY-MM-DD') THEN 'Second period'
        WHEN EarliestDate >= TO_DATE('2010-01-01','YYYY-MM-DD') THEN 'Third period'
    END
FROM MAIN_QUERY

所以 - 再次感谢大家。我在此处发布此解决方案作为我的首选解决方案(鉴于我的 SQL 专业知识水平),但如果没有您的帮助,我将无法做到这一点。我希望这个线程对任何寻求重用子查询结果而不必重复的人有用。

【讨论】:

以上是关于Oracle SQL:对 CASE WHEN 重复使用子查询,而无需重复子查询的主要内容,如果未能解决你的问题,请参考以下文章

case-when oracle sql的动态评估

有条件地使用 CASE...WHEN - Oracle SQL

CASE .. Oracle SQL 中的 WHEN 表达式

oracle case的这种写法怎么对应多个值 case x when……

sql 中 case when 语法

Oracle SQL 查询使用 case when,压缩空字段