Rien de sorcier, il s'agit d'une table contenant des "fichiers", une table contenant des "tags" et une table de jonction (files_tags) pour associer des tags à des fichiers.
Je cherche a faire un requête capable de me retourner en une passe (semble plus optimisé) tous les fichiers ainsi que leur tags:
SELECT files.file_id, files.file_name, tags.tag_id FROM files, tags, files_tags WHERE files.file_id = files_tags.file_id AND files_tags._tag_id = tags.tag_id
Le problème de cette première requête: je ne récupère que les fichiers avec au moins un tag associé et plusieurs rows avec les même file_id sont retournées.
SELECT files.file_id, files.file_name, GROUP_CONCAT(tags.tag_id) FROM files, tags, files_tags WHERE files.file_id = files_tags.file_id AND files_tags._tag_id = tags.tag_id
Cette fois les ids des tags sont bien groupés mais je n'ai toujours pas les fichiers sans tags...
SELECT
F.file_id,
F.file_name,
GROUP_CONCAT(T.tag_name) AS tags
FROM
files F
LEFT JOIN files_tags FT
ON F.file_id = FT.file_id
LEFT JOIN tags T
ON FT._tag_id = T.tag_id
GROUP BY
F.file_id,
F.file_name
C'est super merci. En fait ce qui me manquait le plus c’était le GROUP BY. Sans lui, avec le GROUP_CONCAT, le LEFT JOIN ne "fonctionnait pas" (je ne recevais que les fichiers avec un tag).
Heu je viens de rencontrer un autre soucis du coup: si ne souhaite que les fichiers qui ont un tag_id spécifique mais qui retourne quand même tous les tags associés à ce fichier, comment faire ?
SELECT
F.file_id,
F.file_name,
GROUP_CONCAT(T.tag_name) AS tags
FROM
files F
LEFT JOIN files_tags FT
ON F.file_id = FT.file_id
LEFT JOIN tags T
ON FT._tag_id = T.tag_id
WHERE
T.tag_id = 1
GROUP BY
F.file_id,
F.file_name
Cette requete ne va me retourner que [1] comme tags pour les fichiers
Une solution serait de rajouter une jointure vers les tags avec ta condition :
SELECT
F.file_id,
F.file_name,
GROUP_CONCAT(T.tag_name) AS tags
FROM
files F
INNER JOIN files_tags FT1
ON F.file_id = FT1.file_id
INNER JOIN files_tags FT
ON F.file_id = FT.file_id
INNER JOIN tags T
ON FT._tag_id = T.tag_id
WHERE FT1.tag_id = 1
GROUP BY
F.file_id,
F.file_name
Ici plus besoin des jointures externes (LEFT JOIN) puisque ton fichier a forcément au moins un tag.
- si j'omets le where, la concatenation double les tags (ça à la limite ok, je peux les virer à la fin) => [1, 1, 2, 2]
- si le where vaut FT1.tag_id = 1 AND FT1.tag_id = 2, alors il n'y a pas de résultats (pas ok ) => ça semble logique aussi car 1 != 2, mais du coup je ne vois pas comment avoir les fichiers qui ont à la fois le tag 1 et 2
- si le where vaut FT.tag_id = 1 AND FT1.tag_id = 2, alors il n'y a que [1] comme tag (pas ok non plus )
Mon objectif final si cela peut clarifier les choses:
Obtenir le plus rapidement possible (donc en une passe je suppose) une liste de fichiers avec certaines contraintes (ex: has id, has tags, has name, etc...), ces contraintes doivent rester génériques et sont potentiellement complexes (ex: fichier avec nom 01.png et avec tag 1 mais pas tag 2). Les rows retournées doivent être uniques en fonction de l'id du fichier et pour chaque fichier ont doit avoir la liste des tags (ids) qui lui sont associés.
Quelle serait la meilleure démarche pour obtenir ce résultat ?
si j'omets le where, la concatenation double les tags
Si tu veux omettre le WHERE, c'est alors toute la jointure qu'il faut virer ...
lifaon74 a écrit:
si le where vaut FT1.tag_id = 1 AND FT1.tag_id = 2, alors il n'y a pas de résultats
C'est normal, pour un enregistrement donné, le tag_id ne peut pas être égal à 1 et à 2 en même temps ...
lifaon74 a écrit:
si le where vaut FT.tag_id = 1 AND FT1.tag_id = 2, alors il n'y a que [1] comme tag
Normal aussi puisque tu limites la jointure vers les tags ... Si tu veux tous les tags il ne faut pas mettre de condition sur FT.
lifaon74 a écrit:
Obtenir le plus rapidement possible (donc en une passe je suppose) une liste de fichiers avec certaines contraintes (ex: has id, has tags, has name, etc...), ces contraintes doivent rester génériques et sont potentiellement complexes
A contraintes complexes, réponses complexes
Ce que tu cherches à faire est une intersection d'ensembles, SQL prévoit ceci avec le mot clé INTERSECT (MySQL ne l'accepte pas au passage).
Il faut alors rédiger ta requête comme ceci :
SELECT file_id
FROM ...
WHERE ...
INTERSECT
SELECT file_id
FROM ...
WHERE ...
INTERSECT
etc.
Sinon tu peux approfondir les jointures. Avec une jointure par condition imposée :
-- Fichier (et tous ses tags) ayant les tags 1 ET 2
SELECT
F.file_id,
F.file_name,
GROUP_CONCAT(T.tag_name) AS tags
FROM
files F
INNER JOIN files_tags FT1
ON F.file_id = FT1.file_id
AND FT1.tag_id = 1
INNER JOIN files_tags FT2
ON F.file_id = FT2.file_id
AND FT2.tag_id = 2
INNER JOIN files_tags FT
ON F.file_id = FT.file_id
INNER JOIN tags T
ON FT._tag_id = T.tag_id
GROUP BY
F.file_id,
F.file_name
Je conçois que ce soit relou de construire ta requête ainsi, mais ce sera le plus performant ...
Attention aussi à la différence entre ET et OU ... ici c'est du ET ...
× Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
× Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
N'oubliez pas d'activer les erreurs PDO.