复合查询

时间:2012-04-13 00:19:24

标签: mysql

包含以下列的表:

Player_id (primary key), Event_type(A,B,C), Points.

每个event_type

可能会多次出现1个玩家

我想显示所有事件类型的DESC SUM(Points)GROUP BY player_id的整体排名,同时提出一些条件:

  • 事件类型A每个player_id最多5个结果
  • 事件类型B每个player_id只有2个最佳结果
  • 事件类型C
  • 每个player_id只有3个最佳结果

我徒劳无功:

SUM(points) WHERE event_type ="X" 
GROUP BY Player_id ORDER BY SUM(points) LIMIT N

我一直在为这个头痛而奋斗一个星期,在包含子查询,UNION或临时表时非常困惑。我无法弄清楚如何将所有碎片放在一起......

我的梦想是让这个整体排名运行,并且能够在点击后访问每位玩家的详细分数....

对此开放给予任何帮助......谢谢!

源表的示例:

player_id ------ ------- EVENT_TYPE得分-----

--- 1 -------------------甲----------------的 5 ----------

--- 1 -------------------甲---------------的 10 ---------

--- 1 -------------------甲----------------的 5 ---------

--- 1 -------------------甲---------------- 5 ------ ---

--- 1 -------------------甲---------------- 2 ------ ---

--- 1 -------------------甲----------------的 15 ---------

--- 1 -------------------甲----------------的 10 ---------

--- 1 -------------------Ç---------------- 20 ------ ---

--- 1 -------------------乙----------------的 5 ---------

--- 1 -------------------乙---------------- 5 ------ ---

--- 1 -------------------乙----------------的 20 ---------

--- 2 -------------------甲----------------的 50 ---------

--- 2 -------------------乙----------------的 55 ---------

根据此示例的所需输出:

秩--- ------- player_id ----- overall_score

---- 1 ---------- 2 ----------- 105分 [50来自A(最佳5)+ 55来自B(最佳2)] ---------

---- 2 ---------- 1 ----------- 90分 [45来自A(最佳5)+ 20来自C(最佳3)+ 25来自B(最佳2)] ---------

2 个答案:

答案 0 :(得分:3)

首先:您想要的功能称为sliding windowranking。 Oracle使用OVER - 关键字和rank() - 函数实现这些功能。 MySQL不支持这些功能,因此我们必须解决这个问题。

我使用this answer创建了以下查询。如果这对你有帮助,也给他一个+1

SELECT 
    `player_id`, `event`, `points`,
    (SELECT 1 + count(*) 
     FROM `points` 
     WHERE `l`.`player_id` = `player_id` 
         AND `l`.`event` = `event` 
         AND `points` > `l`.`points`
    ) AS `rank`
FROM
    `points` `l`

这将输出player_id的每个eventpoints的排名。例如: 假设(player_id, event, points)(1,A,10), (1,A,5), (1,A,2), (1,A,2), (1,A,1), (2,A,0),则输出为

player_id    event   points   rank
  1            A       10       1
  1            A        5       2
  1            A        2       3
  1            A        2       3
  1            A        1       5
  2            A        0       1

排名不是密集的,所以如果你有重复的元组,你将得到具有相同排名的输出元组以及你的排名数的差距。

要获取每个Nplayer_id的最高event *元组,您可以创建视图或在条件中使用子查询。视图是首选方式,但您没有权限在许多服务器上创建视图。

创建包含rank列的视图。

CREATE VIEW `points_view`
AS SELECT 
    `player_id`, `event`, `points`,
    (SELECT 1 + count(*) 
         FROM `points` 
         WHERE `l`.`player_id` = `player_id` 
             AND `l`.`event` = `event` 
             AND `points` > `l`.`points`
        ) as `rank`
FROM
    `points` `l`

从视图中获取所需的前N个结果:

SELECT
    `player_id`, `event`, `points`
FROM `points_view`
WHERE 
     `event` = 'A' AND `rank` <= 5
OR
     `event` = 'B' AND `rank` <= 2
OR
     `event` = 'C' AND `rank` <= 3

在条件中使用排名

SELECT 
    `player_id`, `event`, `points`
FROM
    `points` `l`
WHERE
    (SELECT 1 + count(*) 
     FROM `points` 
     WHERE `l`.`player_id` = `player_id` 
         AND `l`.`event` = `event` 
         AND `points` > `l`.`points`
    ) <= N

要根据您的活动进一步获得不同数量的元组,您可以

SELECT 
    `player_id`, `event`, `points`
FROM
    `points` `l`
WHERE
        `event` = 'A' AND
        (SELECT 1 + count(*) 
         FROM `points` 
         WHERE `l`.`player_id` = `player_id` 
             AND `l`.`event` = `event` 
             AND `points` > `l`.`points`
        ) <= 5
    OR
        `event` = 'B' AND
        (SELECT 1 + count(*) 
         FROM `points` 
         WHERE `l`.`player_id` = `player_id` 
             AND `l`.`event` = `event` 
             AND `points` > `l`.`points`
        ) <= 2
    OR
        `event` = 'C' AND
        (SELECT 1 + count(*) 
         FROM `points` 
         WHERE `l`.`player_id` = `player_id` 
             AND `l`.`event` = `event` 
             AND `points` > `l`.`points`
        ) <= 3

我只使用你的N的最大值为5而忽略其他事件类型的其他元组,因为MySQL没有优化这个查询,导致3个独立的从属子查询。如果性能不是问题或者您没有太多数据,请保持这种方式。

*正如我解释的那样rank并不密集,因此使用rank <= N获取所有元组通常会导致超过N个元组。额外的元组是重复的。

从示例表中可以看到,简单地删除重复项是一个坏主意。如果您想要player_id = 1event = A的前5个结果,则需要两个元组(1,A,2)。他们都排名第3位。但如果你删除其中一个,你最终只会获得前4个结果(1,A,10,1)(1,A,5,2)(1,A,2,3)(1,A,1,5)

要获得密集排名,您可以使用此子查询

(SELECT count(DISTINCT `points`) 
 FROM `points` 
 WHERE `l`.`player_id` = `player_id` 
     AND `l`.`event` = `event` 
     AND `points` >= `l`.`points`
) as `dense_rank`

要小心,因为这仍然会产生重复的等级。

修改

要将所有活动的积分与一个积分相加,请使用GROUP BY

SELECT
    `player_id`, SUM(`points`)
FROM `points_view`
WHERE 
     `event` = 'A' AND `rank` <= 5
OR
     `event` = 'B' AND `rank` <= 2
OR
     `event` = 'C' AND `rank` <= 3

GROUP BY `player_id`
ORDER BY SUM(`points`) DESC

在分区(GROUP BY)之前,结果包含正确的最高分数,因此您可以简单地将所有分数相加。

您面临的一个重大问题是rankdense_rank都不会为您提供工具,每个player_idevent都会获得5个元组。例如:如果有人为事件A获得1000点1分,他将获得1000分,因为所有分数都将获得rankdense_rank 1

还有ROWNUM但是:MySQL不支持这个,所以我们必须模仿它。 ROWNUM的问题在于它将为所有元组生成复合数字。但我们想要player_idevent组的复合数字。我仍然在研究这个解决方案。

<强> EDIT2

使用this answer我发现这个解决方案有效:

select
  player_id, sum( points )
from
(
select
  player_id,
  event,
  points,
  /* increment current_pos and reset to 0 if player_id or event changes */
  @current_pos := if (@current_player = player_id AND 
      @current_event = event, @current_pos, 0) + 1 as position,
  @current_player := player_id,
  @current_event := event
from
  (select 
    /* global variable init */
    @current_player := null, 
    @current_event := null, 
    @current_pos := 0) set_pos,
  points
order by
  player_id,
  event,
  points desc
) pos
WHERE
     pos.event = 'A' AND pos.position <= 5
OR
     pos.event = 'B' AND pos.position <= 2
OR
     pos.event = 'C' AND pos.position <= 3
GROUP BY player_id
ORDER BY SUM( points ) DESC

内部查询选择(player_id,event,points)-tuples,按player_id和event对它们进行排序,最后给每个元组一个复合数字,每当player_id或event更改时,该数字将重置为0。由于顺序,具有相同player_id的所有元组将是连续的。外部查询与先前使用的查询对视图执行的操作相同。

编辑3 (见评论)

您可以使用OLAP ROLLUP-operator创建中间总和或不同类型的分区。例如,查询将如下所示:

select
  player_id, event, sum( points )
from
(
select
  player_id,
  event,
  points,
  /* increment current_pos and reset to 0 if player_id or event changes */
  @current_pos := if (@current_player = player_id AND 
      @current_event = event, @current_pos, 0) + 1 as position,
  @current_player := player_id,
  @current_event := event
from
  (select 
    /* global variable init */
    @current_player := null, 
    @current_event := null, 
    @current_pos := 0) set_pos,
  points
order by
  player_id,
  event,
  points desc
) pos
WHERE
     pos.event = 'A' AND pos.position <= 5
OR
     pos.event = 'B' AND pos.position <= 2
OR
     pos.event = 'C' AND pos.position <= 3
GROUP BY player_id, event WITH ROLLUP
/* NO ORDER BY HERE. SEE DOCUMENTATION ON MYSQL's ROLLUP FOR REASON */

结果现在首先按player_id, event分组,然后仅按player_id分组,最后归零(汇总所有行)。

第一组看起来像(player_id, event, sum(points)) = {(1, A, 20), (1,B,5)},其中20和5是关于player_idevent的点的总和。第二组看起来像(player_id, event, sum(points)) = {(1,NULL,25)}。 25是关于player_id的所有点的总和。希望有所帮助。 : - )

答案 1 :(得分:0)

你可能需要给总和(点数)一个名字。

所以:

select player,sum(points) as points from table where event_type = "x" group by player order by points desc limit 5;

(我需要看看你确切的表架构,把它写成你可以插入的东西,但这是它的要点)