web-dev-qa-db-fra.com

Utilisation de GROUP_CONCAT sur une sous-requête dans MySQL

J'ai une requête MySQL dans laquelle je veux inclure une liste d'ID d'une autre table. Sur le site Web, les utilisateurs peuvent ajouter certains éléments, puis les ajouter à leurs favoris. Je veux essentiellement obtenir la liste des pièces d'identité des personnes qui ont mis en favoris cet élément (c'est un peu simplifié, mais c'est à cela qu'il se résume).

Fondamentalement, je fais quelque chose comme ça:

SELECT *,
GROUP_CONCAT((SELECT userid FROM favourites WHERE itemid = items.id) SEPARATOR ',') AS idlist
FROM items
WHERE id = $someid

De cette façon, je serais en mesure de montrer qui a favorisé certains éléments, en divisant la liste d'id plus tard en un tableau dans PHP plus loin dans mon code, mais j'obtiens l'erreur MySQL suivante:

1242 - La sous-requête renvoie plus d'une ligne

Je pensais que c'était un peu l'intérêt d'utiliser GROUP_CONCAT au lieu de, par exemple, CONCAT? Suis-je en train de mal faire ça?


Ok, merci pour les réponses jusqu'à présent, cela semble fonctionner. Cependant, il y a un hic. Les éléments sont également considérés comme favoris s'ils ont été ajoutés par cet utilisateur. J'aurais donc besoin d'une vérification supplémentaire pour vérifier si creator = userid. Quelqu'un peut-il m'aider à trouver un moyen intelligent (et, espérons-le, efficace) de le faire?

Je vous remercie!

Edit: je viens d'essayer de faire ceci:

SELECT [...] LEFT JOIN favourites ON (userid = itemid OR creator = userid)

Et idlist est vide. Notez que si j'utilise INNER JOIN au lieu de LEFT JOIN J'obtiens un résultat vide. Même si je suis sûr qu'il existe des lignes qui répondent à l'exigence ON.

38
Aistina

Vous ne pouvez pas accéder aux variables de la portée externe dans de telles requêtes (vous ne pouvez pas utiliser items.id Là). Vous devriez plutôt essayer quelque chose comme

SELECT
    items.name,
    items.color,
    CONCAT(favourites.userid) as idlist
FROM
    items
INNER JOIN favourites ON items.id = favourites.itemid
WHERE
    items.id = $someid
GROUP BY
    items.name,
    items.color;

Développez la liste des champs selon vos besoins (nom, couleur ...).

13
soulmerge

OP a presque raison. GROUP_CONCAT devrait encapsuler les colonnes dans la sous-requête et non la sous-requête complète (j'écarte le séparateur car la virgule est la valeur par défaut):

SELECT i.*,
(SELECT GROUP_CONCAT(userid) FROM favourites f WHERE f.itemid = i.id) AS idlist
FROM items i
WHERE i.id = $someid

Cela donnera le résultat souhaité et signifie également que la réponse acceptée est partiellement fausse, car vous pouvez accéder aux variables de portée externe dans une sous-requête.

88
nietonfir

Je pense que vous pouvez avoir le "userid = itemid" mal, ne devrait-il pas être comme ceci:

SELECT ITEMS.id,GROUP_CONCAT(FAVOURITES.UserId) AS IdList
FROM FAVOURITES 
INNER JOIN ITEMS ON (ITEMS.Id = FAVOURITES.ItemId OR FAVOURITES.UserId = ITEMS.Creator)
WHERE ITEMS.Id = $someid
GROUP BY ITEMS.ID
2
Turnkey

Oui, la solution de soulmerge est correcte. Mais j'avais besoin d'une requête où je devais collecter des données à partir de plusieurs tables enfants, par exemple:

  • table principale: sessions (sessions de présentation) (uid, nom, ..)
  • 1ère table enfant: événements avec la clé session_id (uid, session_uid, date, time_start, time_end)
  • 2ème table enfant: accessories_needed (ordinateur portable, projecteur, microphones, etc.) avec clé session_id (uid, session_uid, accessory_name)
  • 3ème table enfant: session_presenters (présentateurs) avec clé session_id (uid, session_uid, presenter_name, address ...)

Chaque session a plus de lignes dans les tables des tables enfants (plus de calendriers, plus d'accessoires)

Et je devais rassembler dans une collection pour chaque session à afficher dans la ligne de minerai (certains d'entre eux):

session_id | session_name | date | time_start | time_end | accessories | presenters

Ma solution (après plusieurs heures d'expériences):

SELECT sessions.uid, sessions.name,
    ,(SELECT GROUP_CONCAT( `events`.date SEPARATOR '</li><li>') 
            FROM `events` 
            WHERE `events`.session_id = sessions.uid ORDER BY `events`.date) AS date
    ,(SELECT GROUP_CONCAT( `events`.time_start SEPARATOR '</li><li>') 
            FROM `events` 
            WHERE `events`.session_id = sessions.uid ORDER BY `events`.date) AS time_start
    ,(SELECT GROUP_CONCAT( `events`.time_end SEPARATOR '</li><li>') 
            FROM `events` 
            WHERE `events`.session_id = sessions.uid ORDER BY `events`.date) AS time_end
    ,(SELECT GROUP_CONCAT( accessories.name SEPARATOR '</li><li>') 
            FROM accessories 
            WHERE accessories.session_id = sessions.uid ORDER BY accessories.name) AS accessories
    ,(SELECT GROUP_CONCAT( presenters.name SEPARATOR '</li><li>') 
            FROM presenters
            WHERE presenters.session_id = sessions.uid ORDER BY presenters.name) AS presenters

    FROM sessions

Donc pas besoin de JOIN ou GROUP BY. Une autre chose utile pour afficher des données conviviales (lors de leur "écho"):

  • vous pouvez encapsuler les événements.date, time_start, time_end, etc. dans "<UL> <LI> ... </LI> </UL>" afin que le "<LI> </LI>" soit utilisé comme séparateur dans la requête séparera les résultats dans les éléments de la liste.

J'espère que ça aidera quelqu'un. À votre santé!

1
Ambrus Laszlo

Le but de GROUP_CONCAT est correct mais la sous-requête n'est pas nécessaire et provoque le problème. Essayez plutôt ceci:

SELECT ITEMS.id,GROUP_CONCAT(FAVOURITES.UserId)
FROM FAVOURITES INNER JOIN ITEMS ON ITEMS.Id = FAVOURITES.ItemId
WHERE ITEMS.Id = $someid
GROUP BY ITEMS.ID
1
Turnkey