我有一个非常简单的多对多架构,如下所示:alt text http://img406.imageshack.us/img406/8207/partialschema.png
我要做的是选择所有具有一些仲裁条件的球员,我还想选择他们最近的比赛,如果他们在一场比赛中进行了比赛。
我设法做的是:
SELECT tblPlayer.PlayerId, tblPlayer.Surname, tblPlayer.Forename,
(SELECT TOP 1 tblMatch.HomeClub + ' v ' + tblMatch.OpponentClub + ' ' + tblMatch.AgeGroup + ' (' + CONVERT(VARCHAR, tblMatch.MatchDateTime, 103) + ')'
FROM tblAppearance
INNER JOIN tblMatch ON tblAppearance.MatchID = tblMatch.MatchID
WHERE tblAppearance.PlayerID = tblPlayer.PlayerID
ORDER BY MatchDateTime DESC) AS Match
FROM tblPlayer
LEFT JOIN tblAppearance ON tblAppearance.PlayerId = tblPlayer.PlayerId
LEFT JOIN tblMatch ON tblMatch.MatchId = tblAppearance.MatchId
WHERE tblPlayer.Forename LIKE '%rob%' AND tblPlayer.Surname LIKE '%white%'
ORDER BY tblPlayer.Surname, tblPlayer.Forename, tblPlayer.DOB, tblMatch.MatchDateTime DESC
问题是,这会选择玩家所在的所有比赛,而不仅仅是他们最近的比赛。我知道这应该很简单,但我似乎无法为它获得正确的语法。
另外,我宁愿将匹配表中的单独列作为单独的列返回,而不是作为格式化的块。
要求提供更多信息的答案:
是的,有一个MatchDateTime列,我打算用它进行排序。
是的我确实希望那些尚未参加比赛的球员,左联盟是故意的。
答案 0 :(得分:3)
对于这些类型的问题(获得组中的第一行),我在性能和可维护性方面取得了最大的成功,使用ROW_NUMBER()
和CTE。模式很简单:CTE选择您想要的列,并为每个组中的ROW_NUMBER()
添加一个额外的列(当然,按您所需的顺序排序)。然后,查询的后CTE部分将结果限制为ROW_NUMBER()
为1的那些
像这样:
WITH cte AS
(
SELECT tblPlayer.PlayerId, Surname, Forename, HomeClub, OpponentClub, AgeGroup, MatchDateTime, DOB,
ROW_NUMBER () OVER (PARTITION BY tblPlayer.PlayerId ORDER BY MatchDateTime DESC) AS RowNum
FROM tblPlayer
LEFT JOIN tblAppearance ON tblAppearance.PlayerId = tblPlayer.PlayerId
LEFT JOIN tblMatch ON tblMatch.MatchId = tblAppearance.MatchId
WHERE Forename LIKE '%rob%' AND Surname LIKE '%white%'
)
SELECT PlayerId, Surname, Forename, HomeClub, OpponentClub, AgeGroup, MatchDateTime, DOB
FROM cte
WHERE RowNum = 1
ORDER BY Surname, Forename, DOB, MatchDateTime
请注意,我并未假设任何ID的排序方式与MatchDateTime相同 - 有很多原因(例如提前调度)为什么这个假设可能不成立。但是,如果外观ID的排序与日期相同,则上述查询可以更加高效,因为您不必进行任何连接即可找到您正在寻找的MatchID。
请注意,如果您有非常多的播放器(100,000+)且经常运行此查询,那么您需要在此处进行优化,因为每次运行此查询时都会进行表扫描播放器表格,以支持您的LIKE
过滤器。如果是这种情况,您可能希望在Surname,Forename上创建覆盖索引,并让SQL分阶段运行您的查询:首先使用覆盖索引过滤Player记录,然后进行连接,最后拉出其他Player聚集索引中的列。通常很难让SQL执行这样的计划(你可能需要一个临时表来获得中间结果),但对于非常大的表,perf的胜利是值得的。如果你有少量玩家,请忽略前面的段落。 : - )
答案 1 :(得分:2)
如果我理解正确,你可以试试这个
DECLARE @User TABLE(
UserID INT
)
DECLARE @Matches TABLE(
MatchID INT
)
DECLARE @UserMatches TABLE(
UserMatchID INT,
UserID INT,
MatchID INT
)
INSERT INTO @User SELECt 1
INSERT INTO @User SELECt 2
INSERT INTO @Matches SELECt 1
INSERT INTO @Matches SELECt 2
INSERT INTO @UserMatches SELECt 1, 1, 1
INSERT INTO @UserMatches SELECt 2, 1, 2
INSERT INTO @UserMatches SELECt 3, 2, 2
SELECT u.*,
m.*
FROm @UserMatches um INNER JOIN
(
SELECT UserID,
MAX(UserMatchID) MaxID
FROM @UserMatches um
GROUP BY UserID
) MaxIdsPerUser ON um.UserID = MaxIdsPerUser.UserID
AND um.UserMatchID = MaxID INNER JOIN
@User u ON um.UserID = u.UserID INNER JOIN
@Matches m ON um.MatchID = m.MatchID
如果你有一个DateTime来确定最近的匹配,你可以使用它来获得最大值。
答案 2 :(得分:1)
不使用连接中的tblAppearance,而是使用派生表,使用APPLY选择播放器的最后一个外观,并在此派生表上连接:
SELECT ...
FROM tblPlayer
OUTER APPLY (
SELECT TOP (1) *
FROM tblAppearance
WHERE tblPlayer.PlayerId = tblAppearance.PlayerId
ORDER BY MatchDateTime DESC) AS lastAppearance
LEFT JOIN tblMatch ON tblMatch.MatchId = lastAppearance.MatchId
WHERE ...