SQL Server:与 TOP 1 的 LEFT OUTER JOIN 最多选择一行

Posted

技术标签:

【中文标题】SQL Server:与 TOP 1 的 LEFT OUTER JOIN 最多选择一行【英文标题】:SQL Server: LEFT OUTER JOIN with TOP 1 to Select at Most One Row 【发布时间】:2012-03-06 22:23:30 【问题描述】:

我基本上需要对 2 个表(CarePlan 和推荐人)进行左外连接问题是我需要最新的推荐人如果存在,如果不存在也没关系。

我有这 2 个问题 1. 加入 CarePlan/Referral 表 - 如果一个护理计划有多个推荐,或者根本没有推荐信息(左外连接),则创建重复的护理计划 2. 给定 CarePlanId,根据日期选择前 1 个推荐人

我想将这两个结合起来,所以我会获取所有的护理计划及其转介(如果存在),如果存在 - 只接受最新的转介

select * from CarePlan c //query 1
left outer join Referral r on 
r.CarePlanId = c.CarePlanId


select top 1 * from Referral r //query 2
where r.CarePlanId = '1'
order by ReferralDate desc

编辑:

第一个查询给了我这样的信息:

CarePlanID    ReferralId     ReferralDate
----------    ----------     ------------
1             1              05/15/12
2             NULL           NULL
1             2              05/10/12  //Old date, dont want this careplan

第二个查询会给我最新日期的推荐

ReferralId    ReferralDate
----------    ------------
1             05/15/12

推荐数据,可能有 0 个或多个属于 Careplan 的推荐

ReferralID  CarePlanId    Date
----------  ----------    ----
1           1             05/15/12
2           1             05/10/12

最终我想要一个查询,它可以为我提供具有最新日期的推荐的护理计划,如果没有,则推荐为 null

像这样:

CarePlanId   ReferralId    ReferralDate
----------   ----------    ------------
1            1             05/15/12
2            NULL          NULL

谢谢 - 我希望这是有道理的

【问题讨论】:

在阅读您想要的内容时遇到问题,但我认为您需要查找如何制作 cte 或派生表。 你能展示样本数据和想要的结果吗?像@HLGEM 一样,不清楚你从哪里开始,你想在哪里结束。 【参考方案1】:
select *
from CarePlan c
outer apply (
    select top 1 * --top N rows
    from Referral r
    where r.CarePlanId = c.CarePlanId --join condition
    order by /*fill this in!*/
) x

请注意,由于直到 2014 版(包括版本 2014)的优化器弱点,这会强制循环连接。

【讨论】:

【参考方案2】:

我知道这个问题比较老,但我觉得还有另一种方法未被充分利用:

您可以将表连接回自身并使用运算符查找“最新”记录。

回答

SELECT CP.CarePlanId, R.ReferralId, R.ReferralDate
FROM CarePlan CP
LEFT OUTER JOIN Referral R ON R.CarePlanId = CP.CarePlanId
LEFT OUTER JOIN Referral R_NEWER ON R.CarePlanId = R_NEWER.CarePlanId AND R.ReferralDate < R_NEWER.ReferralDate
WHERE R_NEWER.ReferralId IS NULL

结果:

CP.CarePlanId   R.ReferralId    R.ReferralDate
----------      ----------      ------------
1               1               05/15/12
2               NULL            NULL

说明

让我们分解一下。您基本上是在说,对于每个推荐记录,(左外)加入与相同 CarePlanId 相关联的所有其他推荐记录,但仅在有更新的 ReferralDate 的情况下。

这是没有 where 子句的查询(以及来自 R_NEWER 表的一些附加信息):

SELECT CP.CarePlanId, R.ReferralId, R.ReferralDate, R_NEWER.ReferralId, R.NEWER.ReferralDate
FROM CarePlan CP
LEFT OUTER JOIN Referral R ON R.CarePlanId = CP.CarePlanId
LEFT OUTER JOIN Referral R_NEWER ON R.CarePlanId = R_NEWER.CarePlanId AND R.ReferralDate < R_NEWER.ReferralDate

这是该查询的结果:

CP.CarePlanId   R.ReferralId    R.ReferralDate  R_NEWER.ReferralId  R_NEWER.ReferralDate
----------      ----------      ------------    ------------        ------------    
1               1               05/15/12        NULL                NULL
2               NULL            NULL            NULL                NULL
1               2               05/10/12        1                   05/15/12

如您所见,只有推荐 ID 2(上面的第 3 条记录)在推荐表中找到要加入的“更新”记录(即推荐 ID 1)。推荐人 ID 1(上面的第一条记录)没有找到“较新”的推荐人(对于相同的 CarePlanId)。

因此,考虑到这一点,现在我们只需将 where 子句添加回去:

SELECT CP.CarePlanId, R.ReferralId, R.ReferralDate, R_NEWER.ReferralId, R.NEWER.ReferralDate
FROM CarePlan CP
LEFT OUTER JOIN Referral R ON R.CarePlanId = CP.CarePlanId
LEFT OUTER JOIN Referral R_NEWER ON R.CarePlanId = R_NEWER.CarePlanId AND R.ReferralDate < R_NEWER.ReferralDate
WHERE R_NEWER.ReferralId IS NULL

然后得到:

CP.CarePlanId   R.ReferralId    R.ReferralDate  R_NEWER.ReferralId  R_NEWER.ReferralDate
----------      ----------      ------------    ------------        ------------    
1               1               05/15/12        NULL                NULL
2               NULL            NULL            NULL                NULL

此时,只需从您的 SELECT 中删除您的 R_NEWER 列,因为它们不再需要并且您有答案。

重要的是要记住,“where”在连接发生后应用,但 ON 语句发生在连接时。为了让我更容易理解这一点,我总是尝试编写 SELECT 和 JOIN,并从我要加入的每个表中返回列,然后在我清楚地了解返回的内容后添加我的 WHERE 子句。

警告 这种方法在大多数情况下效果很好,但如果您有 2 个推荐人(对于相同的 CarePlanId),日期为 05/15/12 并且该日期是“最近的”,则可能会有重复的行。为了解决这个问题,如果出现这种情况,您可以根据“最高”的 ReferralId 扩展您的联接以限制。

【讨论】:

5 年前回答,但这个答案是迄今为止最好的,也适用于 JPA 2.1。【参考方案3】:

只是猜测。我不确定 EF 是否会遇到 CTE 语法问题——你能强制 EF 调用存储过程,这样你就不会被 EF 支持的功能子集束缚吗?

;WITH r AS 
(
  SELECT CarePlanId, MAX(ReferralDate)
    FROM dbo.Referrals GROUP BY CarePlanId
)
SELECT * FROM dbo.CarePlan AS c
LEFT OUTER JOIN r 
  ON r.CarePlanId = c.CarePlanId;

【讨论】:

抱歉——我想这更像是一个一般的 SQL 问题,实体框架在这里不重要——至少我希望如此。我也希望返回的护理计划是不同的(只使用最新推荐的) 只有你可以告诉我们后者 - 你真的运行查询吗?它会返回您期望的结果吗?如果您向我们展示示例数据以及您对输出的期望,我们可以更轻松地尝试我们的答案,然后再将它们扔给您。它还可以节省每个人的时间。

以上是关于SQL Server:与 TOP 1 的 LEFT OUTER JOIN 最多选择一行的主要内容,如果未能解决你的问题,请参考以下文章

SQL Server 中的 LEFT JOIN 与 LEFT OUTER JOIN

SQL Server超时诊断和调优

INNER JOIN与LEFT JOIN在SQL Server的性能

SQL GROUP BY 与 LEFT JOIN MS SQL Server

利用Zabbix ODBC monitoring监控SQL Server

安装和配置SQL Server 2016 With SP1