MySQL INNER JOIN seleziona solo una riga dalla seconda tabella


104

Ho una userstabella e una paymentstabella, per ogni utente, quelli di cui hanno i pagamenti, possono avere più pagamenti associati nella paymentstabella. Vorrei selezionare tutti gli utenti che hanno pagamenti, ma selezionare solo il loro ultimo pagamento. Sto provando questo SQL ma non ho mai provato prima le istruzioni SQL annidate, quindi voglio sapere cosa sto facendo di sbagliato. Apprezzo l'aiuto

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

Risposte:


148

È necessario disporre di una sottoquery per ottenere la data più recente per 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

1
@Fluffeh hai una risposta molto carina Part 1 - Joins and Unions. :) segnalibro!
John Woo,

1
Grazie amico, l'ho scritto solo per questo tipo di situazioni, fai apparire una risposta rapida con l'SQL corretto, quindi suggerisci un collegamento a quella lettura approfondita mostruosa in modo che la gente possa capire il codice suggerito. Pianificazione di aggiungerlo per espanderlo ulteriormente. Benvenuto a partecipare se vuoi, dovrei far apparire un violino per il codice davvero ...
Fluffeh

@JohnWoo grazie per la tua risposta, che ha funzionato perfettamente. E grazie Fluffeh per le domande e risposte, ci darò un'occhiata!
Wasim,

Penso che la mia risposta sia più semplice e più veloce perché sto usando solo un inner join. Forse sto sbagliando?
Mihai Matei,

2
C'è un modo per eseguire questa query senza i join interni? Voglio restituire il massimo dalla tabella c OPPURE ottenere di nuovo null se nessuna riga corrisponde. Cambiare JOIN in LEFT JOIN non funziona ovviamente.
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
)

Questa soluzione è migliore della risposta accettata perché funziona correttamente quando ci sono alcuni pagamenti con lo stesso utente e data.


è un buon approccio che usi. ma ho una domanda sul recupero di un'immagine su un prodotto simile. Un prodotto ha molte (4) immagini ma desidero mostrare solo un'immagine per quel prodotto.
Ali Raza

Non pensi che sarà molto lento da usare? mentre stai elaborando ogni record nella tua sottoquery per inner join. La risposta accettata può essere la migliore risposta possibile dopo una piccola modifica.
Hamees A. Khan

@ HameesA.Khan, non ho esaminato l'esecuzione delle query. Potresti avere ragione, ma la soluzione accettata crea un'unione tridimensionale che può essere anche lenta. Temo che il tweak non sarà minore (questo è l'argomento della domanda).
Finezza

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

Dai un'occhiata a questo sqlfiddle


6
La limit 1clausola restituirà solo 1 utente che non è ciò che vuole l'OP.
Fluffeh

6
@MateiMihai, questo non funziona. La query fornisce solo la data massima, non l'intera riga con la data massima. Puoi vederlo in violino: la datecolonna è diversa da max(p.date). Se aggiungi più colonne nella paymentstabella (ad esempio cost), tutte quelle colonne non saranno dalla riga necessaria
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

2

Ci sono due problemi con la tua query:

  1. Ogni tabella e sottoquery necessita di un nome, quindi devi nominare la sottoquery INNER JOIN (SELECT ...) AS p ON ... .
  2. La sottoquery così com'è restituisce solo un periodo di riga, ma in realtà vuoi una riga per ogni utente . Per questo è necessaria una query per ottenere la data massima e quindi unirsi nuovamente per ottenere l'intera riga.

Supponendo che non ci siano legami per payments.date, prova:

    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

è un buon approccio che usi. ma ho una domanda sul recupero di un'immagine su un prodotto simile. Un prodotto ha molte (4) immagini ma desidero mostrare solo un'immagine per quel prodotto.
Ali Raza

L'ho trovato più veloce nel mio caso. L'unica modifica che ho apportato è stata l'aggiunta di una clausola where nella sottoquery per filtrare i dati utente selezionati solo From payments as p Where p.user_id =@user_idpoiché la query sta facendo un gruppo su tutta la tabella.
Vikash Rathee

2

La risposta di @John Woo mi ha aiutato a risolvere un problema simile. Ho migliorato la sua risposta impostando anche l'ordinamento corretto. Questo ha funzionato per me:

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

Non sono sicuro di quanto sia efficiente, però.


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

Questo lo farà funzionare


1

Matei Mihai ha fornito una soluzione semplice ed efficiente ma non funzionerà fino a quando non verrà inserita una MAX(date)parte SELEZIONA, quindi questa query diventerà:

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

E l'ordine per non farà alcuna differenza nel raggruppamento ma può ordinare il risultato finale fornito dal gruppo da. L'ho provato e ha funzionato per me.


1

La mia risposta si ispira direttamente a @valex molto utile, se hai bisogno di più colonne nella clausola 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

1

Puoi provare questo:

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

1
Sebbene questo snippet di codice possa risolvere la domanda, includere una spiegazione aiuta davvero a migliorare la qualità del tuo post. Ricorda che stai rispondendo alla domanda per i lettori in futuro e quelle persone potrebbero non conoscere i motivi del tuo suggerimento sul codice.
Alessio
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.