MySQL INNER JOIN hanya memilih satu baris dari tabel kedua

104

Saya memiliki usersmeja dan paymentsmeja, untuk setiap pengguna, yang memiliki pembayaran, mungkin memiliki beberapa pembayaran terkait di paymentstabel. Saya ingin memilih semua pengguna yang memiliki pembayaran, tetapi hanya memilih pembayaran terakhir mereka. Saya mencoba SQL ini tetapi saya belum pernah mencoba pernyataan SQL bersarang sebelumnya, jadi saya ingin tahu apa yang saya lakukan salah. Hargai bantuannya

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
sumber

Jawaban:

148

Anda perlu memiliki subkueri untuk mendapatkan tanggal terbaru mereka 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
John Woo
sumber
1
@Fluffeh Anda memiliki jawaban yang sangat bagus Part 1 - Joins and Unions. :) di-bookmark!
John Woo
1
Terima kasih sobat, menulisnya hanya untuk situasi seperti ini, munculkan jawaban cepat dengan SQL yang benar, lalu sarankan tautan ke bacaan mendalam monster itu sehingga orang-orang dapat memahami kode yang disarankan. Berencana menambahkannya untuk mengembangkannya lebih jauh. Selamat bergabung jika Anda mau, saya harus memunculkan biola untuk kode benar-benar ...
Fluffeh
@JohnWoo terima kasih atas jawaban Anda, yang telah bekerja dengan sempurna. Dan terima kasih Fluffeh atas T&J, saya akan memeriksanya!
Wasim
Saya rasa jawaban saya lebih sederhana dan lebih cepat karena saya hanya menggunakan satu gabungan dalam. Mungkin aku salah?
Mihai Matei
2
Apakah ada cara untuk melakukan kueri ini tanpa gabungan dalam? Saya ingin mengembalikan nilai maks dari tabel c ATAU mendapatkan null kembali jika tidak ada baris yang cocok. Mengubah JOIN menjadi LEFT JOIN tidak bekerja dengan jelas.
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
)

Solusi ini lebih baik daripada jawaban yang diterima karena berfungsi dengan benar ketika ada beberapa pembayaran dengan pengguna dan tanggal yang sama.

Kemahiran
sumber
itu adalah pendekatan yang baik yang Anda gunakan. tetapi saya memiliki pertanyaan tentang mengambil satu gambar terhadap satu produk seperti. Satu produk memiliki banyak (4) gambar tetapi saya hanya ingin menampilkan hanya satu gambar terhadap produk itu.
Ali Raza
Tidakkah menurut Anda itu akan sangat lambat untuk digunakan? saat Anda memproses untuk setiap rekaman di sub kueri Anda untuk gabungan dalam. Jawaban yang diterima bisa menjadi jawaban terbaik setelah sedikit perubahan.
Hamees A. Khan
@ HameesA.Khan, saya belum memeriksa eksekusi kueri. Anda mungkin benar, tetapi solusi yang diterima membuat gabungan 3 dimensi yang juga bisa lambat. Saya khawatir perubahannya tidak akan kecil (ini adalah subjek pertanyaannya).
Finesse
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

Lihat sqlfiddle ini

Mihai Matei
sumber
6
The limit 1klausul hanya akan kembali 1 pengguna yang tidak apa yang ingin OP.
Fluffeh
6
@MateiMihai, ini tidak berhasil. Kueri hanya memberikan tanggal maks, bukan seluruh baris dengan tanggal maks. Anda dapat melihatnya di biola: datekolom berbeda dari max(p.date). Jika Anda menambahkan lebih banyak kolom dalam paymentstabel (misalnya cost), semua kolom itu tidak akan berasal dari baris yang diperlukan
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
sumber
2

Ada dua masalah dengan kueri Anda:

  1. Setiap tabel dan subkueri membutuhkan nama, jadi Anda harus menamai subkueri tersebut INNER JOIN (SELECT ...) AS p ON ....
  2. Subkueri yang Anda miliki hanya mengembalikan satu baris periode, tetapi Anda sebenarnya menginginkan satu baris untuk setiap pengguna . Untuk itu Anda memerlukan satu query untuk mendapatkan tanggal maksimal dan kemudian bergabung kembali untuk mendapatkan seluruh baris.

Dengan asumsi tidak ada ikatan untuk payments.date, coba:

    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.
sumber
itu adalah pendekatan yang baik yang Anda gunakan. tetapi saya memiliki pertanyaan tentang mengambil satu gambar terhadap satu produk seperti. Satu produk memiliki banyak (4) gambar tetapi saya hanya ingin menampilkan hanya satu gambar terhadap produk itu.
Ali Raza
Saya menemukan ini paling cepat dalam kasus saya. Satu-satunya perubahan yang saya lakukan adalah menambahkan klausa di mana dalam sub kueri untuk memfilter data pengguna yang dipilih hanya From payments as p Where p.user_id =@user_idkarena kueri tersebut melakukan grup dengan di seluruh tabel.
Vikash Rathee
2

@ Jawaban John Woo membantu saya memecahkan masalah serupa. Saya telah memperbaiki jawabannya dengan mengatur urutan yang benar juga. Ini berhasil untuk saya:

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

Saya tidak yakin seberapa efisien ini.

GTCrais
sumber
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

Ini akan membuatnya bekerja

Keadilan Eziefule
sumber
1

Matei Mihai diberikan solusi yang sederhana dan efisien tetapi tidak akan berhasil sampai dimasukkan MAX(date)di bagian SELECT sehingga query ini akan menjadi:

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

Dan urutan berdasarkan tidak akan membuat perbedaan dalam pengelompokan tetapi dapat mengurutkan hasil akhir yang diberikan oleh kelompok. Saya mencobanya dan berhasil untuk saya.

Hassan Dad Khan
sumber
1

Jawaban saya langsung terinspirasi dari @valex sangat berguna, jika Anda membutuhkan beberapa kolom di klausa 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
sumber
1

Anda bisa mencoba ini:

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
sumber
1
Meskipun cuplikan kode ini dapat menyelesaikan pertanyaan, menyertakan penjelasan sangat membantu meningkatkan kualitas posting Anda. Ingatlah bahwa Anda menjawab pertanyaan untuk pembaca di masa mendatang, dan orang-orang itu mungkin tidak tahu alasan saran kode Anda.
Alessio