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

时间:2012-03-06 22:23:30

标签: sql sql-server-2008

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

我有这两个查询 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

最终我想要一个查询,为我提供具有最新日期的推荐的careplans,如果它没有它,则为引用无效

像这样:

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

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

3 个答案:

答案 0 :(得分:43)

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版本),这会强制进行循环连接。

答案 1 :(得分:8)

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

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

<强>答案

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)。推荐标识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将您的联接扩展到限制。

答案 2 :(得分:1)

只是一个猜测。我不确定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;