【问题标题】:GROUP_CONCAT with limitGROUP_CONCAT 有限制
【发布时间】:2014-06-29 18:46:14
【问题描述】:

我的表与player-s 与skill-s 存在多对多关系

目标是通过单个查询列出玩家及其“前 3 项技能”。

fiddle

create table player(
  id int primary key
);

create table skill(
  id int primary key,
  title varchar(100)
);

create table player_skills (
  id int primary key,
  player_id int,
  skill_id int,
  value int
);

查询:

SELECT 
p.id,  
group_concat(s.title  SEPARATOR ', ') as skills

FROM player p
LEFT JOIN player_skills ps ON ps.player_id = p.id
LEFT JOIN skill s ON s.id = ps.skill_id

WHERE ps.value > 2
-- skills limit 3 some how ...
group by p.id 
order by s.id


-- expected result
-- player_ID, skills
-- 1 , 'one'
-- 2 , 'one'
-- 3 , 'two, three, four'

正如您在小提琴中看到的那样,查询结果仅缺少 3 个技能的限制。
我尝试了几种子查询的变体.. 连接等等,但没有任何效果。

【问题讨论】:

标签: mysql group-concat


【解决方案1】:

一种有点老套的方法是对GROUP_CONCAT的结果进行后处理:

substring_index(group_concat(s.title SEPARATOR ','), ',', 3) as skills

当然,这假设您的技能名称不包含逗号,并且它们的数量相当小。

fiddle

遗憾的是,用于支持显式 LIMIT 子句的 GROUP_CONCATfeature request 仍未解决。

更新:正如用户 Strawberry 指出的那样,表 player_skills 应该将元组 (player_id, skill_id) 作为其主键,否则架构允许将相同的技能分配给播放器多次,在这种情况下group_concat 将无法按预期工作。

【讨论】:

  • brilliant :) 这比几个嵌套查询要好得多,因为技能会受到任何限制。
  • 因为奇怪的(而且可能是多余的)PK,这应该是 GROUP_CONCAT(DISTINCT...
  • @Strawberry 谢谢,我在回答中添加了对此的评论。添加适当的约束可能是要走的路
  • 我希望有更正确的方法,但这个 hack 更简洁,更适合我的情况,然后是几个嵌套查询。感谢您开箱即用的想法。
  • 非常好。我会关心结果的顺序,并会在 GROUP_CONCAT 中添加 order by 子句。
【解决方案2】:

使用GLOBAL group_concat_max_len 增加GROUP_CONCAT 函数长度 GROUP_CONCAT() 最大长度为 1024 个字符。
您可以做的是在 mysql

中设置GLOBAL group_concat_max_len
SET GLOBAL group_concat_max_len = 1000000;

试试这个,它肯定会起作用。

【讨论】:

    【解决方案3】:

    有一个更清洁的解决方案。将其包裹在另一个 SELECT 语句中。

    SELECT GROUP_CONCAT(id) FROM (
        SELECT DISTINCT id FROM people LIMIT 4
    ) AS ids;
    
    /* Result 134756,134754,134751,134750 */
    

    【讨论】:

    • MySQL 无法处理子查询中的限制。这已经让我发疯了很长一段时间了:-/
    【解决方案4】:

    如果你使用MariaDB 10.3.3+,这是可能的:

    支持 GROUP_CONCAT() 中的 LIMIT 子句 (MDEV-11297)

    SELECT p.id,  
       GROUP_CONCAT(s.title ORDER BY title  SEPARATOR ', ' LIMIT 3) as skills
    FROM player p
    LEFT JOIN player_skills ps ON ps.player_id = p.id
    LEFT JOIN skill s ON s.id = ps.skill_id
    WHERE ps.value > 2
    GROUP BY p.id 
    ORDER BY s.id;
    

    db<>fiddle demo

    【讨论】:

    • SELECT GROUP_CONCAT(questionbankID ORDER BY RAND() SEPARATOR ', ' LIMIT 3 ) as questionbankID FROM questionbank WHERE clientID = 82 AND courseID = 184 AND questionType = 'objective' AND type = 'A' 非常相似查询给我错误..
    【解决方案5】:

    这是另一个解决方案。它包括一种用于解决关系的任意机制,并采用与您的略有不同的架构......

    SELECT a.player_id
         , GROUP_CONCAT(s.title ORDER BY rank) skills
      FROM
         ( SELECT x.*, COUNT(*) rank
             FROM player_skills x
             JOIN player_skills y 
               ON y.player_id = x.player_id
              AND (y.value > x.value
               OR (y.value = x.value AND y.skill_id <= x.skill_id))
            GROUP 
               BY player_id, value, skill_id
           HAVING COUNT(*) <= 3
         ) a
      JOIN skill s
        ON s.skill_id = a.skill_id
     GROUP 
        BY player_id;
    

    http://sqlfiddle.com/#!2/34497/18

    顺便说一句,如果您有表示层/应用程序级代码,那么请考虑在那里执行所有 GROUP_CONCAT 的内容。它更灵活。

    【讨论】:

      【解决方案6】:

      您可以按照上面提到的说明来解决此类问题。

      指令1:设置组连接限制,然后编写您的查询。

      SET SESSION group_concat_max_len = 1200000;
      

      说明2:然后你可以按照给定的两个例子找出你的解决方案。

      示例 1:

      SELECT GROUP_CONCAT(app_id) AS ids FROM (
            SELECT DISTINCT app_id FROM email_queue 
      ) AS ids;
      

      示例 2:

      select GROUP_CONCAT(caption)  from email_queue group BY process_type_id;
      

      注1:这是example1example2

      查询的表结构

      注意 2:这里,1200000 表示查询最多允许 1200000 个字符用于组连接数据。

      【讨论】:

      • 你能解释一下为什么应该设置限制吗?那需要什么?接受的答案不包含此
      • 当 GROUP_CONCAT 的相关列包含超过 1024 个字符时,您需要使用它来获取 GROUP_CONCAT 函数的完整结果。更多详情:namasteui.com/mysql-group_concat-maximum-length
      • 请将所有此类解释添加到答案本身,而不是评论部分
      【解决方案7】:

      您可以使用用户变量模拟分区的row_number,然后限制行并应用group_concat

      select p.id,
          group_concat(s.title separator ', ') as skills
      from player p
      left join (
          select distinct ps.player_id,
              ps.skill_id,
              @rn := if(@player_id = player_id, @rn+1, if(@player_id := player_id, 1, 1)) as seqnum
          from player_skills ps
          cross join (select @rn := 0, @player_id := null) x
          where ps.value > 2
          order by player_id, value desc
          ) ps on p.id = ps.player_id and ps.seqnum <= 3
      left join skill s on ps.skill_id = s.id
      group by p.id;
      

      Demo

      此方法不需要任何表多次读取。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-04-28
        • 2011-09-01
        • 1970-01-01
        • 1970-01-01
        • 2017-02-27
        • 2012-07-28
        • 2016-03-22
        相关资源
        最近更新 更多