Disons que j'ai besoin de trouver tous les articles, qui sont étiquetés avec les trois balises: nourriture
, lifestyle
et santé
. Quelle est la manière la plus efficace de faire cela dans MySQL? Je suis sorti avec cette solution:
select * from articles where exists ( select * from tags join article_tag on article_tag.tag_id = tags.id where article_tag.article_id = articles.id and tags.tag = 'food' ) and exists ( select * from tags join article_tag on article_tag.tag_id = tags.id where article_tag.article_id = articles.id and tags.tag = 'lifestyle' ) and exists ( select * from tags join article_tag on article_tag.tag_id = tags.id where article_tag.article_id = articles.id and tags.tag = 'health' )
Cela fonctionne bien, mais cela ressemble à beaucoup de répétition. Quelle est la requête la plus efficace pour résoudre ce problème?
3 Réponses :
Vous pouvez utiliser l'agrégation conditionnelle à la place:
select a.* from articles a join article_tag at on at.article_id = a.id join tags t on at.tag_id = t.id where t.tag in ('food', 'lifestyle', 'health') group by a.id having count(*) = 3;
Ceci fait deux hypothèses raisonnables:
id
est la clé primaire dans article
(ou au moins unique) tag_id
n'a pas de doublons
Vous avez une faute de frappe ici, c'est dans
, pas i
. Oui, je connais cette approche, mais pour une raison quelconque, je la trouve instable en cas de doublons dans la colonne tags.tag
ou la paire article_tag
. Je sais qu'il existe des index uniques, mais parfois ils ne conviennent pas.
select a.* from articles a join ( select articles.id from articles join article_tag on article_tag.article_id = articles.id join tags on article_tag.tag_id = tags.id where tags.tag in ('food','lifestyle','health') group by articles.id having SUM(CASE WHEN tags.tag = 'food' THEN 1 ELSE 0 END) >= 1 AND SUM(CASE WHEN tags.tag = 'lifestyle' THEN 1 ELSE 0 END) >= 1 AND SUM(CASE WHEN tags.tag = 'health' THEN 1 ELSE 0 END) >= 1) b on a.id = b.id
Désolé, vous devriez lister les colonnes que vous souhaitez sélectionner dans la clause group by, je modifie la réponse pour résoudre ce problème
Mais je veux toutes les colonnes.
Voici la solution:
select a.* from articles a inner join( select at.article_id from article_tag at inner join tags t on t.id = at.tag_id where t.tag in ('food', 'lifestyle', 'health') group by at.article_id having count(distinct t.tag) = 3 ) at on at.article_id = a.id
Étonnant! Vous vous attendez à ce que les gens vous aident avec une requête sans nous dire quel est votre schéma. De plus, vous voulez une "requête efficace" sans rien dire sur les clés.
C'est une question théorique ouverte. Vous pouvez voir le schéma de la requête, pourquoi auriez-vous besoin de moi pour le répéter séparément? Et en ce qui concerne les clés, supposez simplement qu'elles sont là où elles devraient être (dans les colonnes que vous correspondez). Vous faites partie de ces moutons de Stack Overflow, qui s'attendent à des questions qui ne vous font pas réfléchir.