【问题标题】:MySQL Ranking of Subselect - Top N Results by Group子选择的 MySQL 排名 - 按组排列的前 N ​​个结果
【发布时间】:2018-12-21 09:35:45
【问题描述】:

我需要获取每个国家/地区前 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;

【问题讨论】:

  • 你用的是哪个版本的mysql?
  • @RadimBača 5.7.24
  • 您应该升级到 v8 并为此使用窗口函数

标签: mysql sql subquery


【解决方案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;

【讨论】:

  • 比较两个答案,我可以重现@gordon 提到的奇怪行为。到目前为止,此方法在我测试过的所有扫描仪中都可以正常工作!
【解决方案2】:

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

SELECT country_name, name, name_count, rank
FROM (
    SELECT country_name, iso, name, name_count,
        @country_rank := IF( @current_country = iso, 
                             @country_rank + 1, 
                             1 
                           ) AS rank, 
        @current_country := iso 
    FROM (
        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
    ) AS ordered
) AS ranked
CROSS JOIN (SELECT @country_rank = 0, @current_country = '') AS vars
WHERE rank<=2;

Fiddle

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-01-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-09
    • 2018-05-17
    • 1970-01-01
    相关资源
    最近更新 更多