web-dev-qa-db-fra.com

MySQL INNER JOIN sélectionne une seule ligne de la deuxième table

J'ai une table users et une table payments. Pour chaque utilisateur, ceux qui ont des paiements peuvent avoir plusieurs paiements associés dans la table payments. Je souhaite sélectionner tous les utilisateurs qui ont des paiements, mais uniquement leur dernier paiement. J'essaie ce SQL mais je n'ai jamais essayé d'instructions SQL imbriquées auparavant, alors je veux savoir ce que je fais de travers. Apprécier 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
70
Wasim

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
105
John Woo
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 ceci sqlfiddle

10
Matei Mihai
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 lorsque certains paiements ont le même utilisateur et la même date.

4
Finesse
   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
3
valex

La réponse de @John Woo m'a aidé à résoudre un problème similaire. J'ai amélioré sa réponse en définissant également 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

Je ne sais pas si c'est efficace, cependant.

1
GTCrais

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

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 l'ordre de ne fera aucune différence dans le regroupement mais il peut ordonner le résultat final fourni par le groupe. J'ai essayé et cela a fonctionné pour moi.

1
hassan

Votre requête pose deux problèmes:

  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 la connaissez ne renvoie qu'une période de ligne, mais vous souhaitez en réalité une ligne pour chaque utilisateur . Pour cela, vous avez besoin d’une requête pour obtenir la date maximale, puis de la même jointure pour obtenir la ligne entière.

En supposant qu'il n'y ait 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
1
lc.

Ma réponse directement inspirée de @valex est très utile si vous avez besoin de plusieurs colonnes 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
0
Jérôme B