如何连接两个表,您只想要连接一侧的最大日期记录?

问题描述 投票:0回答:2

我正在尝试编写一个查询来返回当前未满足的培训要求。

涉及的4个主要表是:

  • TrainingRequirements 表:存储 CourseId 和 UserId。需要完成的用户和课程。
  • UserTraining 表:存储 CourseId、UserId 以及训练何时完成。
  • 用户表:接受培训的用户。
  • 课程表:所参加培训的课程。存储培训的有效期为多少个月。

这是我到目前为止所拥有的:

SELECT * 
FROM TrainingRequirements
    INNER JOIN Users on Users.Id = TrainingRequirements.UserId
    INNER JOIN Courses on Courses.Id = TrainingRequirements.CourseId
    LEFT OUTER JOIN UserTrainings on UserTrainings .CourseId = TrainReq.CourseId 
    AND UserTrainings .UserId = TrainingRequirements.UserId
WHERE 
    Users.Active = 1 AND 
    TrainingRequirements.Active = 1 AND 
    (UserTrainings.Id is null OR 
       --covers if they never took the training at all
    GETDATE() >= DATEADD(month, Courses.MonthsValid, UserTrainings.DateCompleted)) 
       --covers if they took the training but it has expired and must be retaken

这几乎有效。它返回给我他们从未参加过培训的实例(因此 UserTraining.Id 为空,因为左外连接的右侧不存在匹配的记录),但它也返回给我一堆不再适用的旧的已完成培训.

如果每年都需要进行一次培训,多年来用户培训会积累多条记录。我只想将其与最近的完成情况进行比较,即每个用户每次训练的最高 DateCompleted,所以我假设我需要在连接中使用某种 MAX() 或 Group By 或子查询,但我无法计算出来了。

sql-server t-sql group-by max
2个回答
0
投票

因此,如果没有实际数据来运行此操作,我会在黑暗中摸索(即我无法验证这是否确实达到了您想要的效果),但它听起来就像您想要吸引需要参加课程的用户,或者接受了它,但超过n个月前。

为此,当按

UserID
订购时,您需要为
CourseID
DateCompleted
的每个组合提供第一行。函数 row_number 是一个 window 函数,可以帮助解决这个问题。它在每个“分区”上添加一系列数字(即 1、2、3 等)(例如,在您的情况下为
UserID
CourseId
),按您选择的任何内容(在本例中为
DateCompleted desc
)排序,并且因为您按
DateCompleted desc
排序,值为 1 的行将始终是该分区的最新行。

您应该能够运行类似的命令来查看它的样子:

select
    RID = row_number() over (partition by UserID, CourseId order by DateCompleted desc),
    CourseId,
    UserID,
    DateCompleted
from UserTrainings

从那里开始,又回到了之前所做的事情,只不过不是连接到实际的

UserTrainings
表,而是连接到使用
CTE
创建的
row_number
,然后添加额外的连接谓词
 where u.RID = 1
仅抓取
CTE
中的行,其中
DateCompleted
是最新的。

这就是我的想法。再说一遍,如果没有实际数据,您可能需要对此进行一些调整,但我认为这应该会让您走上正确的轨道。

;with ut as
(
    select
        RID = row_number() over (partition by UserID, CourseId order by DateCompleted desc),
        CourseId,
        UserID,
        DateCompleted
    from UserTrainings
)
select *
from TrainingRequirements tr
inner join users u
    on tr.UserID = u.UserID
inner join courses c
    on tr.CourseId = c.CourseID
left outer join ut u
    on r.CourseId = u.CourseId
        and r.UserID = u.UserId
        and u.RID = 1 -- This gets the most recent record for each user/course in UserTrainings, sorted by DateCompleted
where u.CourseID is null -- they haven't taken it
    or ut.DateCompleted < dateadd(month, r.MonthsValid, getdate()) 

0
投票

如果每年都需要进行一次培训,多年来用户培训会积累多条记录。我只想与最近完成的进行比较,

一般来说,我会在这里推荐一种

row_number()
窗口函数方法(并且已经有另一个答案可以做到这一点)。

但是,如果您只想显示年度所需培训的最新日期(或空),则可以在 ON 子句中包含一个要求,以便连接仅使用去年的记录:

LEFT OUTER JOIN UserTrainings on ... AND UserTrainings.DateCompleted > DATEADD(year, -1, get_date())

当然,对于一年多前完成培训的人来说,这会显示

NULL
而不是旧日期,但这听起来实际上可能是可取的。

我进一步注意到WHERE子句中的这个条件:

 GETDATE() >= DATEADD(month, Courses.MonthsValid, UserTrainings.DateCompleted))

相反,我们可以将其移至 JOIN 的 ON 条件,也许会得到更好的结果。但如果我们这样做(或者即使我们不这样做!),我们应该进行修改以更好地使用索引(sargable):

DATEAD(month, -Courses.MonthsValid, GETDATE()) >= UserTrainings.DateCompleted

这个版本的表达式具有相同的含义,但毫不夸张地说,速度可以快几个数量级!真的。

© www.soinside.com 2019 - 2024. All rights reserved.