【发布时间】: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 不是我的专长。)
标签: sql oracle subquery code-reuse