MYSQL:选择前N名候选人

时间:2018-05-27 00:17:11

标签: mysql sql count max inner-join

我有大学选举的以下数据库计划:

db scheme

对于每个部门,我有以下职位:

1 CHEF (这是candidate_position = 1)

&安培; 6位成员candidate_position = 2)

我想获得各部门选举的获胜者。

为获得“Informatique”部门的CHEF职位获胜者,我做了以下查询:

SELECT doctor.firstname, doctor.lastname, votes

FROM (SELECT COUNT(*) AS votes FROM candidate_votes WHERE candidate_votes.candidate_position = 1 GROUP BY candidate_votes.candidate_id) AS votes, doctor 

INNER JOIN department_candidates ON department_candidates.doctor_id = doctor.id 

INNER JOIN department ON department.id = department_candidates.department_id AND department.name = 'Informatique' 

INNER JOIN candidate_votes ON candidate_votes.candidate_id = doctor.id AND candidate_votes.candidate_position = 1 

GROUP BY candidates_votes.candidate_id

请注意我没有使用LIMIT 1,因为可能在多个候选人之间有一个平局(或平局)

根据结果,我认为我选择Chef位置获胜者的查询是正确的,但我想知道如何选择Member位置的前6位候选人? / p>

数据集:

--
-- Table structure for table `candidate_votes`
--

DROP TABLE IF EXISTS `candidate_votes`;
CREATE TABLE IF NOT EXISTS `candidate_votes` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `candidate_id` int(11) NOT NULL,
  `voter_id` int(11) NOT NULL,
  `candidate_position` tinyint(1) NOT NULL COMMENT '1: chef, 2: member',
  `date` date NOT NULL,
  PRIMARY KEY (`id`),
  KEY `fk-candidate_votes-voter_id` (`voter_id`),
  KEY `fk-candidate_votes-candidate_id_idx` (`candidate_id`)
) ENGINE=InnoDB AUTO_INCREMENT=36 DEFAULT CHARSET=utf8;

--
-- Dumping data for table `candidate_votes`
--

INSERT INTO `candidate_votes` (`id`, `candidate_id`, `voter_id`, `candidate_position`, `date`) VALUES
(24, 2, 1, 1, '2018-05-26'),
(25, 1, 1, 2, '2018-05-26'),
(26, 6, 1, 2, '2018-05-26'),
(27, 5, 1, 2, '2018-05-26'),
(28, 7, 1, 2, '2018-05-26'),
(29, 8, 1, 2, '2018-05-26'),
(30, 9, 1, 2, '2018-05-26'),
(31, 2, 2, 1, '2018-05-16'),
(32, 3, 7, 1, '2018-05-22'),
(33, 3, 8, 1, '2018-05-22'),
(34, 4, 6, 2, '2018-05-29'),
(35, 7, 6, 2, '2018-05-29');

-- --------------------------------------------------------

--
-- Table structure for table `department`
--

DROP TABLE IF EXISTS `department`;
CREATE TABLE IF NOT EXISTS `department` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `department-name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

--
-- Dumping data for table `department`
--

INSERT INTO `department` (`id`, `name`) VALUES
(1, 'Informatique'),
(2, 'Mathematique'),
(4, 'physique');

-- --------------------------------------------------------

--
-- Table structure for table `department_candidates`
--

DROP TABLE IF EXISTS `department_candidates`;
CREATE TABLE IF NOT EXISTS `department_candidates` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `department_id` int(11) NOT NULL,
  `doctor_id` int(11) NOT NULL,
  `candidate_position` tinyint(1) NOT NULL COMMENT '1: chef, 2: member',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8;

--
-- Dumping data for table `department_candidates`
--

INSERT INTO `department_candidates` (`id`, `department_id`, `doctor_id`, `candidate_position`) VALUES
(5, 1, 3, 1),
(7, 1, 4, 2),
(8, 1, 1, 2),
(9, 1, 2, 1),
(10, 1, 6, 2),
(11, 1, 5, 2),
(12, 1, 7, 2),
(13, 1, 8, 2),
(14, 1, 9, 2);

-- --------------------------------------------------------

--
-- Table structure for table `doctor`
--

DROP TABLE IF EXISTS `doctor`;
CREATE TABLE IF NOT EXISTS `doctor` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `firstname` varchar(255) NOT NULL,
  `lastname` varchar(255) NOT NULL,
  `department_id` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;

--
-- Dumping data for table `doctor`
--

INSERT INTO `doctor` (`id`, `firstname`, `lastname`, `department_id`) VALUES
(1, 'doc1_fn', 'doc1_ln', 1),
(2, 'doc2_fn', 'doc2_ln', 1),
(3, 'doc3_fn', 'doc3_ln', 1),
(4, 'doc4_fn', 'doc4_ln', 1),
(5, 'doc5_fn', 'doc5_ln', 1),
(6, 'doc6_fn', 'doc6_ln', 1),
(7, 'doc7_fn', 'doc7_ln', 1),
(8, 'doc8_fn', 'doc8_ln', 1),
(9, 'doc9_fn', 'doc9_ln', 1);

-- --------------------------------------------------------

Sqlfiddle DEMO

3 个答案:

答案 0 :(得分:1)

请考虑以下事项:

SELECT x.*
     , CASE WHEN @prev_position = candidate_position THEN CASE WHEN @prev_total = total THEN @i:=@i ELSE @i:=@i+1 END ELSE @i:=1 END i
     , @prev_position := candidate_position prev_position
     , @prev_total := total prev_total
  FROM
     (
SELECT candidate_id
     , candidate_position
     , COUNT(*) total 
  FROM candidate_votes  
 GROUP 
    BY candidate_id
     , candidate_position
     ) x
  JOIN 
     ( SELECT @prev_position := null,@prev_total:=null,@i:=0) vars
 ORDER
    BY candidate_position
     , total DESC;

+--------------+--------------------+-------+------+---------------+------------+
| candidate_id | candidate_position | total | i    | prev_position | prev_total |
+--------------+--------------------+-------+------+---------------+------------+
|            2 |                  1 |     2 |    1 |             1 |          2 |
|            3 |                  1 |     2 |    1 |             1 |          2 |

|            7 |                  2 |     2 |    1 |             2 |          2 |

|            8 |                  2 |     1 |    2 |             2 |          1 |
|            9 |                  2 |     1 |    2 |             2 |          1 |
|            1 |                  2 |     1 |    2 |             2 |          1 |
|            4 |                  2 |     1 |    2 |             2 |          1 |
|            5 |                  2 |     1 |    2 |             2 |          1 |
|            6 |                  2 |     1 |    2 |             2 |          1 |
+--------------+--------------------+-------+------+---------------+------------+

在此示例中,i表示排名。对于位置1,我们可以看到两名候选人并列第一名。对于位置2,有一个直接赢家,剩下的所有候选人都争夺第二名。

答案 1 :(得分:1)

显然我试图在我的其他答案中太聪明,你可以实现这样一个简单的排名表:

SELECT cast(dc.candidate_position AS UNSIGNED) AS position, dc.doctor_id, doc.firstname, doc.lastname, v.votes
FROM department_candidates dc
JOIN department dept ON dept.id=dc.department_id AND dept.name='Informatique'
JOIN doctor doc ON doc.id=dc.doctor_id
JOIN (SELECT candidate_position AS cp, candidate_id AS cid, count(candidate_id) AS votes
      FROM candidate_votes
      GROUP BY cid) v
  ON v.cid=doc.id AND v.cp = dc.candidate_position
ORDER BY position, v.votes DESC

输出:

position    doctor_id   firstname   lastname    votes
1           3           doc3_fn     doc3_ln     2
1           2           doc2_fn     doc2_ln     2

2           7           doc7_fn     doc7_ln     2
2           4           doc4_fn     doc4_ln     1
2           1           doc1_fn     doc1_ln     1
2           6           doc6_fn     doc6_ln     1
2           5           doc5_fn     doc5_ln     1
2           8           doc8_fn     doc8_ln     1
2           9           doc9_fn     doc9_ln     1

Demo

答案 2 :(得分:0)

此查询将为您提供获得某个位置所需的投票数(请注意我已将其与变量进行参数化):

SET @position = 2;
SET @numwinners = 3;
SELECT @rank := @rank+1 AS rank, votes
FROM (SELECT COUNT(candidate_id) AS votes 
      FROM candidate_votes 
      WHERE candidate_position = @position
      GROUP BY candidate_id
      ORDER BY votes DESC) vr
JOIN (select @rank := 0) r
GROUP BY rank
HAVING rank = @numwinners

使用@position=2@numwinners=3输出您的数据:

rank    votes
3       1

在确定需要多少票之后,我们只需要找到所有具有所需票数的候选人。由于结果基于投票数,因此会自动处理关系。此查询将生成该输出:

SET @position = 2;
SET @numwinners = 3;
SELECT doc.firstname, doc.lastname, v.votes
FROM department_candidates dc
JOIN department dept ON dept.id=dc.department_id AND dept.name='Informatique'
JOIN doctor doc ON doc.id=dc.doctor_id
JOIN (SELECT candidate_id AS cid, count(candidate_id) AS votes
      FROM candidate_votes
      WHERE candidate_position = @position
      GROUP BY cid) v
  ON v.cid=dc.id
WHERE v.votes >= (SELECT votes
                  FROM (SELECT @rank := @rank+1 AS rank, votes
                        FROM (SELECT COUNT(candidate_id) AS votes 
                              FROM candidate_votes 
                              WHERE candidate_position = @position
                              GROUP BY candidate_id
                              ORDER BY votes DESC) vr
                        JOIN (select @rank := 0) r
                        GROUP BY rank
                        HAVING rank = @numwinners
                       ) vt
                  )
ORDER BY v.votes DESC

使用@position=2@numwinners=3输出您的数据:

firstname   lastname    votes
doc7_fn     doc7_ln     2
doc4_fn     doc4_ln     1
doc1_fn     doc1_ln     1
doc6_fn     doc6_ln     1
doc5_fn     doc5_ln     1
doc8_fn     doc8_ln     1
doc9_fn     doc9_ln     1

使用@position=1@numwinners=1输出您的数据:

firstname   lastname    votes
doc3_fn     doc3_ln     2
doc2_fn     doc2_ln     2

如果您想确保查询有效,即使@numwinners的值大于候选人数,请更改:

HAVING rank = @numwinners

HAVING rank = LEAST(@numwinners, (SELECT COUNT(DISTINCT candidate_id)
                                  FROM candidate_votes
                                  WHERE candidate_position = @position))

Demo