JOINT GAUCHE uniquement sur la première ligne

138

J'ai lu de nombreux threads sur l'obtention uniquement de la première ligne d'une jointure gauche, mais, pour une raison quelconque, cela ne fonctionne pas pour moi.

Voici ma structure (simplifiée bien sûr)

Flux

id |  title | content
----------------------
1  | Feed 1 | ...

Artistes

artist_id | artist_name
-----------------------
1         | Artist 1
2         | Artist 2

feeds_artists

rel_id | artist_id | feed_id
----------------------------
1      |     1     |    1 
2      |     2     |    1 
...

Maintenant, je veux obtenir les articles et rejoindre uniquement le premier artiste et j'ai pensé à quelque chose comme ceci:

SELECT *
    FROM feeds 
    LEFT JOIN feeds_artists ON wp_feeds.id = (
        SELECT feeds_artists.feed_id FROM feeds_artists
        WHERE feeds_artists.feed_id = feeds.id 
    LIMIT 1
    )
WHERE feeds.id = '13815'

juste pour obtenir uniquement la première ligne des feeds_artists, mais déjà cela ne fonctionne pas.

Je ne peux pas utiliser à TOPcause de ma base de données et je ne peux pas regrouper les résultats par feeds_artists.artist_idcar je dois les trier par date (j'ai obtenu des résultats en les regroupant de cette façon, mais les résultats n'étaient pas les plus récents)

J'ai également essayé quelque chose avec OUTER APPLY - pas de succès non plus. Pour être honnête, je ne peux pas vraiment imaginer ce qui se passe dans ces rangées - probablement la principale raison pour laquelle je ne peux pas faire fonctionner cela.

SOLUTION:

SELECT *
FROM feeds f
LEFT JOIN artists a ON a.artist_id = (
    SELECT artist_id
    FROM feeds_artists fa 
    WHERE fa.feed_id = f.id
    LIMIT 1
)
WHERE f.id = '13815'
KddC
la source
voici la solution stackoverflow.com/a/7588442/612987
Wasim A.
Vous devriez mettre la solution comme réponse afin que nous puissions la voter et qu'elle soit plus visible pour les futurs visiteurs
fguillen
Je suppose que vous avez raison - je l'ai également ajouté comme réponse.
KddC

Réponses:

97

Si vous pouvez supposer que les identifiants d'artiste augmentent avec le temps, alors le MIN(artist_id)sera le plus ancien.

Alors essayez quelque chose comme ça (non testé ...)

SELECT *
  FROM feeds f
  LEFT JOIN artists a ON a.artist_id = (
    SELECT
      MIN(fa.artist_id) a_id
    FROM feeds_artists fa 
    WHERE fa.feed_id = f.feed_id
  ) a
Matt Dodge
la source
Merci pour votre réponse rapide. Ce n'était pas la réponse exacte, mais m'a totalement mis sur la bonne voie. J'ai toujours essayé de joindre les deux au même niveau au lieu de faire dépendre l'un de l'autre. Merci beaucoup de m'avoir conduit sur la bonne voie. Edité le premier message
KddC
4
À ce stade, une simple sous-requête ne serait-elle pas meilleure? Parce que maintenant vous avez une jointure et une sous-requête. Je demande juste parce que je cherche une solution au même problème :)
galdikas
2
la sous-requête est trop lente.
Sinux
1
@Sinux définit "trop ​​lent". Cela dépend du nombre d'enregistrements et des exigences données.
observateur
1
Cela ne fonctionnera pas! La sous-requête ne permet PAS de passer le champ de la requête parent !!!
Jeudi Sinh
53

Version sans sous-sélection:

   SELECT f.title,
          f.content,
          MIN(a.artist_name) artist_name
     FROM feeds f
LEFT JOIN feeds_artists fa ON fa.feed_id = f.id
LEFT JOIN artists a ON fa.artist_id = a.artist_id
 GROUP BY f.id
Denis Khvorostin
la source
2
Je ne pense pas que ce sera la première ligne (premier identifiant ou premier autre élément), c'est juste en choisir une au hasard parmi les lignes de jointure de gauche.
Sinux
26
Cette requête est erronée. Il sélectionnera le nom d'artiste "le plus bas" et non le nom des premiers artistes de l'ensemble.
Glapa
5
Mauvais pour la question… mais exactement ce que je veux. Dans mon cas, je veux juste la première pièce d'identité de la table à laquelle je me joins.
Drew Stephens
2
Le problème ici est que si vous décidez de faire un COUNT ou SUM, vous aurez foiré des données. Le problème avec Subselect est qu'il est plus lourd pour obtenir les données car il a créé une table temporaire ... Je souhaite que MySql puisse avoir une LIMITE au niveau LEFT JOIN.
Shadoweb
Cette solution présente un problème de performances. Utilisez plutôt la solution Min / Max.
Sadegh PM le
25

La réponse de @Matt Dodges m'a mis sur la bonne voie. Merci encore pour toutes les réponses, qui ont aidé beaucoup de gars dans l'intervalle. Ça marche comme ça:

SELECT *
FROM feeds f
LEFT JOIN artists a ON a.artist_id = (
    SELECT artist_id
    FROM feeds_artists fa 
    WHERE fa.feed_id = f.id
    LIMIT 1
)
WHERE f.id = '13815'
KddC
la source
Cette réponse ne fonctionne que pour la première ligne. Se brise lorsqu'il n'y a pas de f.idcondition.
Tajni
2

J'ai utilisé autre chose (je pense mieux ...) et je veux le partager:

J'ai créé une VIEW qui a une clause "group"

CREATE VIEW vCountries AS SELECT * PROVINCES GROUP BY country_code

SELECT * FROM client INNER JOIN vCountries on client_province = province_id

Je veux dire encore, que je pense que nous devons faire cette solution PARCE QUE NOUS AVONS FAIT QUELQUE CHOSE DE FAUX DANS L'ANALYSE ... du moins dans mon cas ... mais parfois c'est moins cher de faire cela que de tout repenser ...

J'espère que ça aide!

Ari Waisberg
la source
En supposant qu'il y ait plusieurs lignes (provinces?) Pour chacune country_code, c'est GROUP BYinapproprié. Voir ONLY_FULL_GROUP_BY.
Rick James
Vous n'êtes pas obligé de créer une vue à utiliser uniquement pour la JOINTURE GAUCHE / INTÉRIEURE?! Peut utiliser une sous-requête ou une WITH x AS (clause?
kiradotee
2

basé sur plusieurs réponses ici, j'ai trouvé quelque chose qui a fonctionné pour moi et je voulais généraliser et expliquer ce qui se passe.

convertir:

LEFT JOIN table2 t2 ON (t2.thing = t1.thing)

à:

LEFT JOIN table2 t2 ON (t2.p_key = (SELECT MIN(t2_.p_key) 
    FROM table2 t2_ WHERE (t2_.thing = t1.thing) LIMIT 1))

la condition qui connecte t1 et t2 est déplacée de ONet vers la requête interne WHERE. la MIN(primary key)ou LIMIT 1fait en sorte que seule une ligne est renvoyée par la requête interne.

après avoir sélectionné une ligne spécifique, nous devons dire à ONquelle ligne il s'agit. c'est pourquoi le ONcompare la clé primaire de la table jointe.

vous pouvez jouer avec la requête interne (c'est-à-dire ordre + limite) mais elle doit retourner une clé primaire de la ligne souhaitée qui indiquera la ONligne exacte à rejoindre.

oriadam
la source
remarques: il fonctionnera également avec seulement LIMITou seulement MIN. la ONcondition doit être sur la clé primaire de la table jointe pour éviter un impact sur les performances.
oriadam
1

Je veux donner une réponse plus générale . Celui qui gérera tous les cas lorsque vous souhaitez sélectionner uniquement le premier élément dans une jointure à gauche .

Vous pouvez utiliser une sous-requête qui GROUP_CONCATS ce que vous voulez (triée aussi!), Puis diviser simplement le résultat de GROUP_CONCAT et ne prendre que son premier élément, comme ceci ...

LEFT JOIN Person ON Person.id = (
    SELECT SUBSTRING_INDEX(
        GROUP_CONCAT(FirstName ORDER BY FirstName DESC SEPARATOR "_" ), '_', 1)
    ) FROM Person
);

Puisque nous avons DESC comme option ORDER BY , cela renverra un identifiant de personne pour quelqu'un comme "Zack". Si nous voulions quelqu'un avec le nom comme "Andy", nous changerions ORDER BY FirstName DESC en ORDER BY FirstName ASC .

C'est agile, car cela place le pouvoir de commander totalement entre vos mains. Mais, après de nombreux tests, il ne s'adaptera pas bien dans une situation avec beaucoup d'utilisateurs et beaucoup de données.

Il est cependant utile pour exécuter des rapports gourmands en données pour l'administrateur.

HoldOffHunger
la source
L' GROUP_CONCATastuce est bonne tant que le nombre de valeurs est limité. La limite par défaut est de 1024 caractères.
Rick James