web-dev-qa-db-fra.com

GROUP_CONCAT avec limite

J'ai une table avec player- s dans une relation plusieurs à plusieurs avec skill- s

L'objectif est de répertorier les joueurs et leurs "3 compétences principales" avec une seule requête.

violon

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
);

Question:

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'

Comme vous pouvez le voir dans le violon, le résultat de la requête ne manque que la limite de 3 compétences.
J'ai essayé plusieurs variantes de sous-requêtes .. se joint et donc sans effet.

32
d.raev

Une façon un peu compliquée de le faire est de post-traiter le résultat de GROUP_CONCAT:

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

Bien sûr, cela suppose que les noms de vos compétences ne contiennent pas de virgules et que leur montant est raisonnablement faible.

_ { violon } _

Une demande de fonctionnalité pour GROUP_CONCAT afin de prendre en charge une clause explicite LIMIT n'est malheureusement toujours pas résolue.

UPDATE: comme l'utilisateur Strawberry , la table player_skills devrait avoir le tuple (player_id, skill_id) comme clé primaire, sinon le schéma permet d'attribuer la même compétence à un joueur plusieurs fois, dans ce cas, group_concat ne fonctionnerait pas comme prévu.

70
Niklas B.

Augmentez la longueur de la fonction GROUP_CONCAT à l'aide de GLOBAL group_concat_max_lenGROUP_CONCAT(). La longueur maximale est de 1024 caractères.
Ce que vous pouvez faire est de définir le GLOBAL group_concat_max_len dans mysql  

SET GLOBAL group_concat_max_len = 1000000;

Essayez ceci et cela fonctionnera à coup sûr.

15
Zaib Khan

Il existe une solution beaucoup plus propre. Enveloppez-le dans une autre instruction SELECT.

SELECT GROUP_CONCAT(id) FROM (
    SELECT DISTINCT id FROM people LIMIT 4
) AS ids;

/* Result 134756,134754,134751,134750 */
6
Romain Bruckert

Voici une autre solution. Il inclut un mécanisme arbitraire de résolution des liens et utilise un schéma légèrement différent du vôtre ...

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

Incidemment, si vous avez un code au niveau de la couche de présentation/de l'application, envisagez de faire tout le travail GROUP_CONCAT. C'est plus flexible.

5
Strawberry

C'est possible si vous utilisez MariaDB 10.3.3+ :

Prise en charge de la clause LIMIT dans GROUP_CONCAT () ( 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

1
Lukasz Szozda

Vous pouvez simuler le numéro de ligne partitionné à l'aide de variables utilisateur, puis limiter le nombre de lignes et appliquer 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;

Démo

Cette méthode ne nécessite aucune table à lire plus d'une fois.

0
Gurwinder Singh