按多种条件对竞争对手进行排序

Order competitors by multiple conditions

我使用一个具体但假设的示例。

考虑一个包含射击比赛结果的数据库,其中每个参赛者都投了几个系列的球。数据库包含 3 个表:CompetitorsSeriesShots.

竞争对手:

id name
1 A
2 B

系列:

id competitorId
1 1
2 1
3 1
4 2
5 2
6 2

射门数:

id serieId score
1 1 8
2 1 8
3 1 8
4 2 10
5 2 7
6 2 6
7 3 10
8 3 8
9 3 6
10 4 8
11 4 8
12 4 7
13 5 7
14 5 10
15 5 7
16 6 7
17 6 10
18 6 7

(带有上述语句的 DDL: dbfiddle)

我需要的是根据多个条件对竞争对手进行排序,分别是:

  1. 所有系列的总分
  2. 中锋命中次数(中锋命中得分为10分)
  3. 下一步的订购方式是:
    1. 上届联赛最高分
    2. 仅次于上一场意甲的最高分
    3. 倒数第二个意甲联赛的最高分
    4. ...

比赛中的系列数依此类推。

使用前两个顺序条件的查询非常简单:

SELECT comp.name,
    SUM(shots.score) AS score,       
    SUM(IIF(shots.score = 10, 1, 0)) AS centerHits         
FROM Shots shots
INNER JOIN Series series ON series.id = shots.serieId
INNER JOIN Competitors comp ON comp.id = series.competitorId
GROUP BY comp.name
ORDER BY score DESC, centerHits DESC

它产生以下结果:

name score centerHits
A 71 2
B 71 2

在三阶条件下,我预计 B 竞争对手会高于 A,因为两者的总数相同 score、相同 centerHits 且得分相同对于最后一个系列赛 (24),但是 B 的倒数第二个系列赛的分数是 24,而 A 的分数只有 23.

我想知道是否可以使用第三个和以下顺序条件进行查询。

您似乎需要一个 multi-level 查询,每一个都建立在一个之前。

带有别名 PQ 的 INNER-MOST 查询是对每个 SerieID 的简单求和,它获得每个相应集合的总中心命中率和总分。类似于您的计数。

据此,您需要知道哪个系列是最新的(最近的),然后再回到之前的系列。通过使用 OVER / PARTITION,我加入了系列 table 以获取竞争对手的 ID 和名称。

通过根据每个竞争对手对数据进行分区,并根据 SerieID 降序应用顺序,我得到的行号将把最新的 row_number() 变为 1、2 和 3分别如此,对于具有 SerieID 1、2、3 的竞争对手 A,最后的“MostRecent”列分别为 3、2 和 1,因此 SerieID 3 = 1——最新的,SerieID 1 = 3最古老的系列或竞争对手。

同理第二个选手B,SerieIDs 4、5、6分别变为3、2、1。所以现在,你有一个基础来知道什么是最新的(1 = 最近的),倒数第二的(2 = 下一个最近的),以及倒数第二的(3 ...)

现在这两个部分都设置好了,我可以总结各自的总数,中心命中,现在明确地知道最近的 (1) 是它的排序,第二个最新的 (2) 和倒数第三个(3) 是。这些被添加到组中。

现在,如果一个参赛者有 6 个射击系列,而另一个有 4 个系列(并不是说在真正的比赛中会发生这种情况,而是要了解上下文),6 个系列的 LATEST 为 MostRecent = 1,与 4 个系列类似,第 4 个系列将是 MostRecent = 1.

所以最后的分组是在竞争者级别,你可以评估所有有问题的部分。

select
        F.Name,
        F.CompetitorID,
        sum( F.SeriesTotalScore ) TotalScore,
        sum( F.CenterHits ) CenterHits,
        sum( case when F.MostRecent = 1 
                    then F.SeriesTotalScore else 0 end ) MostRecentScore,
        sum( case when F.MostRecent = 2 
                    then F.SeriesTotalScore else 0 end ) SecondToMostRecentScore,
        sum( case when F.MostRecent = 3 
                    then F.SeriesTotalScore else 0 end ) ThirdToMostRecentScore
    from
        ( select
                c.Name,
                Se.CompetitorID,
                PQ.SerieId,
                PQ.CenterHits,
                PQ.SeriesTotalScore,
                ROW_NUMBER() OVER( PARTITION BY Se.CompetitorID 
                                    order by PQ.SerieId DESC) AS MostRecent
            from
                ( select
                        s.serieId,
                        sum( case when s.score = 10 then 1 else 0 end ) as CenterHits,
                        sum( s.Score ) SeriesTotalScore
                    from
                        Shots s
                    group by
                        s.SerieID ) PQ
                    Join Series Se
                        on PQ.SerieID = se.id
                        JOIN Competitors c
                            on Se.CompetitorID = c.id 
        ) F
    group by
        F.Name,
        F.CompetitorID
    order by
        sum( F.SeriesTotalScore ) desc,
        sum( F.CenterHits ),
        sum( case when F.MostRecent = 1 
                    then F.SeriesTotalScore else 0 end ) desc,
        sum( case when F.MostRecent = 2 
                    then F.SeriesTotalScore else 0 end ) desc,
        sum( case when F.MostRecent = 3 
                    then F.SeriesTotalScore else 0 end ) desc

您应该能够非常简单地做到这一点,因为您的要求可以通过正常的聚合和 window 函数来完成。

每个排序级别:

  • "所有系列的总分"所有分数相加即可满足
  • "中心命中数(中心命中得10分)"条件计数即可满足
  • 要按每个系列按日期倒序排序,我们可以使用 STRING_AGG 聚合每个系列的总分(我们使用 window 函数计算),按日期排序聚合(或ID)。然后,如果我们按该聚合对最终查询进行排序,则后面的系列将首先排序。

    This method allows you to order by an arbitrary number of series, as opposed to the other answer.

不清楚您如何定义“稍后”和“较早”,因为您没有日期列,但我使用 series.id 作为代表。

SELECT
  comp.name,
  SUM(shots.score) as totalScore,
  COUNT(CASE WHEN shots.score = 10 THEN 1 END) AS centerHits,
  STRING_AGG(NCHAR(shots.MaxScore + 65), ',') WITHIN GROUP (ORDER BY series.id DESC) as AllShots
FROM (
    SELECT *,
      SUM(shots.score) OVER (PARTITION BY shots.serieID) MaxScore
    FROM Shots shots
) shots
INNER JOIN Series series ON series.id = shots.serieId
INNER JOIN Competitors comp ON comp.id = series.competitorId
GROUP BY
  comp.id,
  comp.name
ORDER BY
  totalScore DESC,
  centerHits DESC,
  AllShots DESC;

请注意,按名称分组时,还应将主键添加到 GROUP BY,因为名称可能不是唯一的。


一个类似但稍微复杂的查询是在派生的 table 中查询 pre-aggregate shots。这可能比使用 window 函数执行得更好。

SELECT
  comp.name,
  SUM(shots.totalScore) as totalScore,
  SUM(centerHits) AS centerHits,
  STRING_AGG(NCHAR(shots.totalScore + 65), ',') WITHIN GROUP (ORDER BY series.id DESC) as AllShots
FROM (
    SELECT
      shots.serieId,
      SUM(shots.score) as totalScore,
      COUNT(CASE WHEN shots.score = 10 THEN 1 END) AS centerHits
    FROM Shots shots
    GROUP BY
      shots.serieId
) shots
INNER JOIN Series series ON series.id = shots.serieId
INNER JOIN Competitors comp ON comp.id = series.competitorId
GROUP BY
  comp.id,
  comp.name
ORDER BY
  totalScore DESC,
  centerHits DESC,
  AllShots DESC;

db<>fiddle