按多种条件对竞争对手进行排序
Order competitors by multiple conditions
我使用一个具体但假设的示例。
考虑一个包含射击比赛结果的数据库,其中每个参赛者都投了几个系列的球。数据库包含 3 个表:Competitors、Series 和 Shots.
竞争对手:
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)
我需要的是根据多个条件对竞争对手进行排序,分别是:
- 所有系列的总分
- 中锋命中次数(中锋命中得分为10分)
- 下一步的订购方式是:
- 上届联赛最高分
- 仅次于上一场意甲的最高分
- 倒数第二个意甲联赛的最高分
- ...
比赛中的系列数依此类推。
使用前两个顺序条件的查询非常简单:
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;
我使用一个具体但假设的示例。
考虑一个包含射击比赛结果的数据库,其中每个参赛者都投了几个系列的球。数据库包含 3 个表:Competitors、Series 和 Shots.
竞争对手:
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)
我需要的是根据多个条件对竞争对手进行排序,分别是:
- 所有系列的总分
- 中锋命中次数(中锋命中得分为10分)
- 下一步的订购方式是:
- 上届联赛最高分
- 仅次于上一场意甲的最高分
- 倒数第二个意甲联赛的最高分
- ...
比赛中的系列数依此类推。
使用前两个顺序条件的查询非常简单:
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;