MySQL Subselect排名-分组的前N个结果

时间:2018-12-21 09:35:46

标签: mysql sql subquery

我需要列出每个国家/地区中排名前2位的姓名(从“帐户和国家/地区”表中)。我进行了很多搜索,还找到了一些有效的答案,但无法获得正确的结果。

请在此处查看我的SQL Fiddle:

http://sqlfiddle.com/#!9/cd1296/5

CREATE TABLE IF NOT EXISTS `country` (
  `id` int(6) unsigned NOT NULL,
  `iso` varchar(3) NOT NULL,
  `country_name` varchar(24) NOT NULL,
  PRIMARY KEY (id)
) DEFAULT CHARSET=utf8;

INSERT INTO `country` (`id`, `iso`,`country_name`) VALUES
  ('1', 'DEU','Germany'),
  ('2', 'USA','United States'),
  ('3', 'CAN','Canada'),
  ('4', 'JPN','Japan');

CREATE TABLE IF NOT EXISTS `accounts` (
  id int(6) unsigned NOT NULL,
  name varchar(50) NOT NULL,
  iso3 varchar(3) NOT NULL,
  PRIMARY KEY (`id`)
) DEFAULT CHARSET=utf8;

INSERT INTO `accounts` (`id`,`name`, `iso3`) VALUES
  ('1', 'Hans', 'DEU'),
  ('2', 'Willi', 'DEU'),
  ('3', 'Peter', 'DEU'),
  ('4', 'Susanne', 'DEU'),
  ('5', 'John', 'USA'),
  ('6', 'Jane', 'USA'),
  ('7', 'Peter', 'USA'),
  ('8', 'Paul', 'USA'),
  ('9', 'Mary', 'USA'),
  ('10', 'Gerard', 'CAN'),
  ('11', 'Mirelle', 'CAN'),
  ('12', 'Hiko', 'JPN'),
  ('13', 'Miko', 'JPN'),
  ('14', 'Susanne', 'DEU'),
  ('15', 'Peter', 'DEU'),
  ('16', 'John', 'USA'),
  ('17', 'Paul', 'USA'),
  ('18', 'Susanne', 'DEU'),
  ('19', 'Bob', 'DEU'),
  ('20', 'John', 'USA'),
  ('21', 'Paul', 'USA'),
  ('33', 'Gerard', 'CAN'),
  ('22', 'Maribelle', 'CAN'),  
  ('23', 'Gerd', 'CAN'),
  ('24', 'Mira', 'CAN'),
  ('25', 'Huko', 'JPN'),
  ('26', 'Hako', 'JPN'),
  ('27', 'Hiko', 'JPN'),
('28', 'Jon', 'USA'),
('29', 'Jim', 'USA'),
('30', 'John', 'USA'),
('31', 'JJ', 'USA'),
('32', 'Bob', 'USA'),
('34', 'Bob', 'USA'),
('35', 'Miko', 'JPN'),
('36', 'Miko', 'JPN');

使用此语句可使列表按正确的顺序排列,但不会在第二个结果之后停止:

SELECT country_name, iso, name, COUNT(name) AS name_count
 FROM accounts
 JOIN country ON country.iso = accounts.iso3
 GROUP BY country.iso,  name
 ORDER BY country.iso ASC, name_count DESC;

如其他问题/答案中所建议,解决方案可以使用“ MySQL会话变量”(基于https://www.databasejournal.com/features/mysql/selecting-the-top-n-results-by-group-in-mysql.html)。

我的问题: country_rank的填充不正确,因此没有给出正确的结果。我在做什么错了?

SET @current_country = ""; 
SET @country_rank = 0; 

 SELECT country_name, name, name_count, rank
 FROM
 (
    SELECT country_name, iso, name, COUNT(name) AS name_count,
    @country_rank := IF( @current_country = iso, 
                         @country_rank + 1, 
                         1 
                       ) AS rank, 
    @current_country := iso 
    FROM accounts
    JOIN country ON country.iso = accounts.iso3
    GROUP BY country.iso,  name
    ORDER BY country.iso ASC, name_count DESC
) AS ranked
WHERE rank<=2;

2 个答案:

答案 0 :(得分:1)

您需要在子查询中进行分组,以便对分组结果进行排名。

Schema

Fiddle

答案 1 :(得分:1)

MySQL不保证SELECT中表达式的求值顺序。因此,在一个表达式中定义一个变量然后在另一个表达式中使用它是危险的。也就是说,分配变量和使用它们都应该在一个表达式中。

问题可能是断断续续的,因此代码看起来可以在一种情况下工作,而不能在另一种情况下工作。因此,我建议将其编写为:

SELECT country_name, name, name_count, rank
FROM (SELECT country_name, iso, name, name_count,
             (@rn := IF(@c = iso, @rn + 1,
                        IF(@c := iso, 1, 1)
                       )
             ) as rank
      FROM (SELECT c.country_name, c.iso, a.name, COUNT(*) AS name_count
            FROM accounts a JOIN
                 country c
                 ON c.iso = a.iso3
            GROUP BY country.iso,  name
            ORDER BY c.country_name, c.iso ASC, name_count DESC
           ) c CROSS JOIN
           (SELECT @c := '', @rn := 0) params
      ) c
WHERE rank <= 2;