MySQL INNER JOIN ne sélectionne qu'une seule ligne dans la deuxième table

104

J'ai un userstableau et un paymentstableau, pour chaque utilisateur, ceux dont ont des paiements, peuvent avoir plusieurs paiements associés dans le paymentstableau. Je souhaite sélectionner tous les utilisateurs qui ont effectué des paiements, mais uniquement leur dernier paiement. J'essaye ce SQL mais je n'ai jamais essayé les instructions SQL imbriquées avant donc je veux savoir ce que je fais mal. Appréciez l'aide

SELECT u.* 
FROM users AS u
    INNER JOIN (
        SELECT p.*
        FROM payments AS p
        ORDER BY date DESC
        LIMIT 1
    )
    ON p.user_id = u.id
WHERE u.package = 1
Wasim
la source

Réponses:

149

Vous devez avoir une sous-requête pour obtenir leur dernière date par user ID.

SELECT  a.*, c.*
FROM users a 
    INNER JOIN payments c
        ON a.id = c.user_ID
    INNER JOIN
    (
        SELECT user_ID, MAX(date) maxDate
        FROM payments
        GROUP BY user_ID
    ) b ON c.user_ID = b.user_ID AND
            c.date = b.maxDate
WHERE a.package = 1
John Woo
la source
1
@Fluffeh vous avez une très belle réponse Part 1 - Joins and Unions. :) mis en signet!
John Woo
1
Merci mon pote, l'a écrit pour ce genre de situations, affiche une réponse rapide avec le bon SQL, puis suggère un lien vers ce monstre à lire en profondeur afin que les gens puissent comprendre le code suggéré. Prévoyez d'y ajouter pour l'étendre davantage. Bienvenue à vous joindre si vous le souhaitez, je devrais faire apparaître un violon pour le code vraiment ...
Fluffeh
@JohnWoo merci pour votre réponse, cela a parfaitement fonctionné. Et merci Fluffeh pour les questions-réponses, je vais y jeter un coup d'œil!
Wasim
Je pense que ma réponse est plus simple et plus rapide car je n'utilise qu'une seule jointure interne. J'ai peut-être tort?
Mihai Matei
2
Existe-t-il un moyen de faire cette requête sans les jointures internes? Je veux renvoyer le maximum de la table c OU récupérer null si aucune ligne ne correspond. Changer le JOIN en LEFT JOIN ne fonctionne évidemment pas.
Scott
32
SELECT u.*, p.*
FROM users AS u
INNER JOIN payments AS p ON p.id = (
    SELECT id
    FROM payments AS p2
    WHERE p2.user_id = u.id
    ORDER BY date DESC
    LIMIT 1
)

Cette solution est meilleure que la réponse acceptée car elle fonctionne correctement lorsqu'il y a des paiements avec le même utilisateur et la même date.

Délicatesse
la source
c'est une bonne approche que vous utilisez. mais j'ai une question sur la récupération d'une image contre un produit comme. Un produit a plusieurs (4) images, mais je ne veux afficher qu'une seule image par rapport à ce produit.
Ali Raza
Ne pensez-vous pas qu'il sera très lent à utiliser? pendant que vous traitez pour chaque enregistrement de votre sous-requête pour la jointure interne. La réponse acceptée peut être la meilleure réponse possible après une modification mineure.
Hamees A. Khan
@ HameesA.Khan, je n'ai pas examiné l'exécution des requêtes. Vous avez peut-être raison, mais la solution acceptée crée une jointure en 3 dimensions qui peut également être lente. J'ai peur que le tweak ne soit pas mineur (c'est le sujet de la question).
Fin du
12
SELECT u.*, p.*, max(p.date)
FROM payments p
JOIN users u ON u.id=p.user_id AND u.package = 1
GROUP BY u.id
ORDER BY p.date DESC

Découvrez ce sqlfiddle

Mihai Matei
la source
6
La limit 1clause ne renverra qu'un utilisateur, ce qui n'est pas ce que souhaite l'OP.
Fluffeh
6
@MateiMihai, cela ne fonctionne pas. La requête ne donne que la date maximale, pas la ligne entière avec la date maximale. Vous pouvez le voir en violon: la datecolonne est différente de max(p.date). Si vous ajoutez plus de colonnes dans le paymentstableau (par exemple cost), toutes ces colonnes ne seront pas de la ligne nécessaire
zxcat
3
   SELECT u.* 
        FROM users AS u
        INNER JOIN (
            SELECT p.*,
             @num := if(@id = user_id, @num + 1, 1) as row_number,
             @id := user_id as tmp
            FROM payments AS p,
                 (SELECT @num := 0) x,
                 (SELECT @id := 0) y
            ORDER BY p.user_id ASC, date DESC)
        ON (p.user_id = u.id) and (p.row_number=1)
        WHERE u.package = 1
Valex
la source
2

Il y a deux problèmes avec votre requête:

  1. Chaque table et sous-requête a besoin d'un nom, vous devez donc nommer la sous-requête INNER JOIN (SELECT ...) AS p ON ....
  2. La sous-requête telle que vous l'avez ne renvoie qu'une seule période de ligne, mais vous voulez en fait une ligne pour chaque utilisateur . Pour cela, vous avez besoin d'une requête pour obtenir la date maximale, puis de la jointure automatique pour obtenir la ligne entière.

En supposant qu'il n'y a pas de liens pour payments.date, essayez:

    SELECT u.*, p.* 
    FROM (
        SELECT MAX(p.date) AS date, p.user_id 
        FROM payments AS p
        GROUP BY p.user_id
    ) AS latestP
    INNER JOIN users AS u ON latestP.user_id = u.id
    INNER JOIN payments AS p ON p.user_id = u.id AND p.date = latestP.date
    WHERE u.package = 1
lc.
la source
c'est une bonne approche que vous utilisez. mais j'ai une question sur la récupération d'une image contre un produit comme. Un produit a plusieurs (4) images, mais je ne veux afficher qu'une seule image par rapport à ce produit.
Ali Raza
J'ai trouvé cela le plus rapidement dans mon cas. Le seul changement que j'ai fait est l'ajout d'une clause where dans la sous-requête pour filtrer les données utilisateur sélectionnées uniquement From payments as p Where p.user_id =@user_idcar la requête effectue un groupe sur la table entière.
Vikash Rathee
2

La réponse de @John Woo m'a aidé à résoudre un problème similaire. J'ai également amélioré sa réponse en définissant le bon ordre. Cela a fonctionné pour moi:

SELECT  a.*, c.*
FROM users a 
    INNER JOIN payments c
        ON a.id = c.user_ID
    INNER JOIN (
        SELECT user_ID, MAX(date) as maxDate FROM
        (
            SELECT user_ID, date
            FROM payments
            ORDER BY date DESC
        ) d
        GROUP BY user_ID
    ) b ON c.user_ID = b.user_ID AND
           c.date = b.maxDate
WHERE a.package = 1

Cependant, je ne suis pas sûr de son efficacité.

GTCrais
la source
2
SELECT U.*, V.* FROM users AS U 
INNER JOIN (SELECT *
FROM payments
WHERE id IN (
SELECT MAX(id)
FROM payments
GROUP BY user_id
)) AS V ON U.id = V.user_id

Cela le fera fonctionner

Le juge Eziefule
la source
1

Matei Mihai a donné une solution simple et efficace mais cela ne fonctionnera pas tant que vous n'aurez pas mis une MAX(date)partie SELECT, donc cette requête deviendra:

SELECT u.*, p.*, max(date)
FROM payments p
JOIN users u ON u.id=p.user_id AND u.package = 1
GROUP BY u.id

Et commander par ne fera aucune différence dans le groupement mais il peut ordonner le résultat final fourni par group by. Je l'ai essayé et cela a fonctionné pour moi.

Hassan Dad Khan
la source
1

Ma réponse directement inspirée de @valex très utile, si vous avez besoin de plusieurs cols dans la clause ORDER BY.

    SELECT u.* 
    FROM users AS u
    INNER JOIN (
        SELECT p.*,
         @num := if(@id = user_id, @num + 1, 1) as row_number,
         @id := user_id as tmp
        FROM (SELECT * FROM payments ORDER BY p.user_id ASC, date DESC) AS p,
             (SELECT @num := 0) x,
             (SELECT @id := 0) y
        )
    ON (p.user_id = u.id) and (p.row_number=1)
    WHERE u.package = 1
Jérôme B
la source
1

Vous pouvez essayer ceci:

SELECT u.*, p.*
FROM users AS u LEFT JOIN (
    SELECT *, ROW_NUMBER() OVER(PARTITION BY userid ORDER BY [Date] DESC) AS RowNo
    FROM payments  
) AS p ON u.userid = p.userid AND p.RowNo=1
Shekhar
la source
1
Bien que cet extrait de code puisse résoudre la question, inclure une explication aide vraiment à améliorer la qualité de votre message. N'oubliez pas que vous répondez à la question aux lecteurs à l'avenir, et que ces personnes pourraient ne pas connaître les raisons de votre suggestion de code.
Alessio