MySQL INNER JOIN, ikinci tablodan yalnızca bir satır seçer


105

Bir usersmasam ve bir paymentsmasam var, her kullanıcı için ödemesi olanlar, paymentstabloda birden fazla ilişkili ödemeye sahip olabilir . Ödemeleri olan tüm kullanıcıları seçmek istiyorum, ancak yalnızca son ödemelerini seçmek istiyorum. Bu SQL'i deniyorum ama daha önce hiç iç içe geçmiş SQL ifadelerini denemedim, bu yüzden neyi yanlış yaptığımı bilmek istiyorum. Yardımı takdir et

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

Yanıtlar:


150

Son tarihlerini almak için bir alt sorgunuz olması gerekir 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 çok güzel bir cevabın var Part 1 - Joins and Unions. :) yer imlerine eklendi!
John Woo

1
Teşekkürler dostum, bunu sadece bu tür durumlar için yazdım, doğru SQL ile hızlı bir cevap aç, sonra o canavarın derinlemesine okunması için bir bağlantı öner ki insanlar önerilen kodu anlayabilsin . Daha da genişletmek için ona eklemeyi planlıyor. Katılmaya hoş geldiniz, isterseniz, kod için gerçekten bir keman
açmalıyım

@JohnWoo cevabınız için teşekkürler, mükemmel bir şekilde çalıştı. Ve Soru-Cevap için teşekkürler Fluffeh, bir göz atacağım!
Wasim

Cevabımın daha basit ve daha hızlı olduğunu düşünüyorum çünkü sadece bir iç birleşim kullanıyorum. Belki de ben hatalıyım?
Mihai Matei

2
Bu sorguyu iç birleşimler olmadan yapmanın bir yolu var mı? Tablo c'den maksimum döndürmek istiyorum VEYA hiçbir satır eşleşmezse null geri almak istiyorum. JOIN'i LEFT JOIN olarak değiştirmek açıkça işe yaramıyor.
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
)

Bu çözüm, kabul edilen cevaptan daha iyidir çünkü aynı kullanıcı ve tarih ile bazı ödemeler olduğunda doğru çalışır.


Kullandığınız iyi bir yaklaşım. ancak tek bir ürüne karşı bir resim getirme konusunda sorum var. Bir ürünün çok sayıda (4) resmi var, ancak o ürüne karşı yalnızca bir resim göstermek istiyorum.
Ali Raza

Kullanmanın çok yavaş olacağını düşünmüyor musun? iç birleştirme için alt sorgunuzdaki her kayıt için işlem yaparken. Kabul edilen yanıt, küçük bir değişiklikten sonra mümkün olan en iyi yanıt olabilir.
Hamees A. Khan

@ HameesA.Khan, sorguların yürütülmesini incelemedim. Haklı olabilirsiniz, ancak kabul edilen çözüm 3 boyutlu bir birleştirme yapar ve bu da yavaş olabilir. Korkarım ince ayar küçük olmayacak (sorunun konusu bu).
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

Bu sqlfiddle'a göz atın


6
limit 1Madde sadece OP istemeyiz 1 kullanıcı dönecektir.
Fluffeh

6
@MateiMihai, bu işe yaramıyor. Sorgu, maksimum tarih içeren satırın tamamını değil, yalnızca maksimum tarihi verir. Bunu keman olarak görebilirsiniz: datesütun farklıdır max(p.date). paymentscost
Tabloya

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

Sorgunuzla ilgili iki sorun var:

  1. Her tablo ve alt sorgunun bir adı olması gerekir, bu nedenle alt sorguyu adlandırmanız gerekir INNER JOIN (SELECT ...) AS p ON ....
  2. Sahip olduğunuz alt sorgu yalnızca bir satır dönemi döndürür, ancak aslında her kullanıcı için bir satır istersiniz . Bunun için, maksimum tarihi almak için bir sorguya ihtiyacınız var ve ardından tüm satırı almak için tekrar birleşmelisiniz.

Hiçbir bağ olmadığını varsayarak şunu payments.datedeneyin:

    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

Kullandığınız iyi bir yaklaşım. ancak tek bir ürüne karşı bir resim getirme konusunda sorum var. Bir ürünün çok sayıda (4) resmi var, ancak o ürüne karşı yalnızca bir resim göstermek istiyorum.
Ali Raza

Bunu en hızlı benim durumumda buldum. Yaptığım tek değişiklik, yalnızca From payments as p Where p.user_id =@user_idsorgu tüm tabloda bir grup oluştururken, seçilen kullanıcı verilerini filtrelemek için alt sorguya bir where cümlesi eklemek oldu .
Vikash Rathee

2

@John Woo'nun cevabı benzer bir sorunu çözmeme yardımcı oldu. Doğru sıralamayı da belirleyerek cevabını geliştirdim. Bu benim için çalıştı:

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

Yine de bunun ne kadar verimli olduğundan emin değilim.


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

Bu onu çalıştıracak


1

Matei Mihai basit ve etkili bir çözüm verdi, ancak bu, MAX(date)SELECT kısmına bir eklenene kadar çalışmayacağı için bu sorgu şöyle olacaktır:

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

Ve sıralama, gruplamada bir fark yaratmaz, ancak grup tarafından sağlanan nihai sonucu sıralayabilir. Denedim ve benim için çalıştı.


1

ORDER BY cümlesinde birkaç kolona ihtiyacınız varsa, cevabım doğrudan @valex'ten çok yararlıdır.

    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

Bunu deneyebilirsiniz:

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
Bu kod parçacığı soruyu çözebilirken, bir açıklama eklemek, yayınınızın kalitesini artırmaya gerçekten yardımcı olur. Gelecekte okuyucular için soruyu yanıtladığınızı ve bu kişilerin kod önerinizin nedenlerini bilmeyebileceklerini unutmayın.
Alessio
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.