我正在尝试编写一个查询来返回当前未满足的培训要求。
涉及的4个主要表是:
这是我到目前为止所拥有的:
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 或子查询,但我无法计算出来了。
因此,如果没有实际数据来运行此操作,我会在黑暗中摸索(即我无法验证这是否确实达到了您想要的效果),但它听起来就像您想要吸引需要参加课程的用户,或者接受了它,但超过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())
如果每年都需要进行一次培训,多年来用户培训会积累多条记录。我只想与最近完成的进行比较,
一般来说,我会在这里推荐一种
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
这个版本的表达式具有相同的含义,但毫不夸张地说,速度可以快几个数量级!真的。