MySQL INNER JOIN เลือกเพียงหนึ่งแถวจากตารางที่สอง


110

ฉันมีusersตารางและpaymentsตารางสำหรับผู้ใช้แต่ละรายที่มีการชำระเงินอาจมีการชำระเงินที่เกี่ยวข้องหลายรายการในpaymentsตาราง ฉันต้องการเลือกผู้ใช้ทั้งหมดที่มีการชำระเงิน แต่เลือกเฉพาะการชำระเงินล่าสุดเท่านั้น ฉันกำลังลองใช้ SQL นี้ แต่ฉันไม่เคยลองใช้คำสั่ง SQL ที่ซ้อนกันมาก่อนเลยอยากรู้ว่าฉันทำอะไรผิด ขอบคุณสำหรับความช่วยเหลือ

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

คำตอบ:


156

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 Part 1 - Joins and Unionsคุณมีคำตอบที่ดีมาก :) คั่นหน้า!
John Woo

1
ขอบคุณเพื่อนเขียนมันสำหรับสถานการณ์แบบนี้ป๊อปอัพคำตอบด่วนพร้อม SQL ที่ถูกต้องจากนั้นแนะนำลิงก์ไปยังมอนสเตอร์ตัวนั้นอ่านแบบเจาะลึกเพื่อให้ผู้คนเข้าใจโค้ดที่แนะนำ การวางแผนในการเพิ่มเพื่อขยายเพิ่มเติม ยินดีต้อนรับเข้าร่วมถ้าคุณต้องการฉันควรจะเล่นซอสำหรับรหัสจริงๆ ...
Fluffeh

@JohnWoo ขอบคุณสำหรับคำตอบของคุณที่ทำงานได้อย่างสมบูรณ์แบบ และขอบคุณ Fluffeh สำหรับคำถาม & คำตอบฉันจะตรวจสอบให้!
Wasim

ฉันคิดว่าคำตอบของฉันง่ายกว่าและเร็วกว่าเพราะฉันใช้การรวมภายในเพียงอันเดียว บางทีฉันผิด?
Mihai Matei

2
มีวิธีทำแบบสอบถามนี้โดยไม่มีการรวมภายในหรือไม่? ฉันต้องการคืนค่าสูงสุดจากตาราง c หรือรับค่าว่างกลับหากไม่มีแถวที่ตรงกัน การเปลี่ยน JOIN เป็น LEFT JOIN ไม่ได้ผลอย่างชัดเจน
Scott

36
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
)

หรือ

SELECT u.*, p.*
FROM users AS u
INNER JOIN payments AS p ON p.user_id = u.id
WHERE NOT EXISTS (
    SELECT 1
    FROM payments AS p2
    WHERE
        p2.user_id = p.user_id AND
        (p2.date > p.date OR (p2.date = p.date AND p2.id > p.id))
)

โซลูชันนี้ดีกว่าคำตอบที่ยอมรับเนื่องจากทำงานได้อย่างถูกต้องเมื่อมีการชำระเงินหลายครั้งโดยใช้ผู้ใช้และวันที่เดียวกัน คุณสามารถลอง SQL ซอ


เป็นแนวทางที่ดีที่คุณใช้ แต่ฉันมีคำถามเกี่ยวกับการดึงรูปภาพหนึ่งภาพเทียบกับผลิตภัณฑ์หนึ่งเช่น ผลิตภัณฑ์หนึ่งมีรูปภาพ (4) จำนวนมาก แต่ฉันต้องการแสดงเพียงรูปภาพเดียวกับผลิตภัณฑ์นั้น
Ali Raza

คุณไม่คิดว่ามันจะใช้งานช้ามากเหรอ? ขณะที่คุณกำลังประมวลผลสำหรับทุกระเบียนในแบบสอบถามย่อยของคุณสำหรับการรวมภายใน คำตอบที่ได้รับการยอมรับอาจเป็นคำตอบที่ดีที่สุดหลังจากปรับแต่งเล็กน้อย
Hamees A. Khan

@ HameesA.Khan ฉันยังไม่ได้ตรวจสอบการดำเนินการสืบค้น คุณอาจจะคิดถูก แต่วิธีการแก้ปัญหาที่ได้รับการยอมรับจะทำให้การเข้าร่วม 3 มิติซึ่งอาจช้าเกินไป ฉันกลัวว่าการปรับแต่งจะไม่เป็นรอง (นี่คือหัวข้อของคำถาม)
กลเม็ดเด็ดพราย

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

ลองดูsqlfiddleนี้


6
limit 1ประโยคเดียวที่จะกลับมาใช้ 1 ซึ่งไม่ได้เป็นสิ่งที่ OP ต้องการ
Fluffeh

6
@MateiMihai นี้ไม่ได้ผล แบบสอบถามให้วันที่สูงสุดเท่านั้นไม่ใช่ทั้งแถวที่มีวันที่สูงสุด คุณสามารถดูได้ใน fiddle: datecolumn แตกต่างจากmax(p.date). หากคุณเพิ่มคอลัมน์เพิ่มเติมในpaymentsตาราง (เช่นcost) คอลัมน์ทั้งหมดนั้นจะไม่มาจากแถวที่ต้องการ
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

มีสองปัญหากับคำถามของคุณ:

  1. INNER JOIN (SELECT ...) AS p ON ...ทุกตารางและแบบสอบถามย่อยต้องการชื่อเพื่อให้คุณมีการตั้งชื่อแบบสอบถามย่อย
  2. แบบสอบถามย่อยที่คุณมีเพียงผลตอบแทนระยะเวลาหนึ่งแถว แต่คุณต้องการหนึ่งแถวจริงสำหรับผู้ใช้แต่ละคน สำหรับสิ่งนั้นคุณต้องมีหนึ่งแบบสอบถามเพื่อรับวันที่สูงสุดจากนั้นเข้าร่วมด้วยตนเองเพื่อรับทั้งแถว

สมมติว่าไม่มีความสัมพันธ์ให้payments.dateลอง:

    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

เป็นแนวทางที่ดีที่คุณใช้ แต่ฉันมีคำถามเกี่ยวกับการดึงรูปภาพหนึ่งภาพเทียบกับผลิตภัณฑ์หนึ่งเช่น ผลิตภัณฑ์หนึ่งชิ้นมีรูปภาพ (4) จำนวนมาก แต่ฉันต้องการแสดงเพียงรูปภาพเดียวกับผลิตภัณฑ์นั้น
Ali Raza

ฉันพบว่าสิ่งนี้เร็วที่สุดในกรณีของฉัน การเปลี่ยนแปลงเดียวที่ฉันทำคือเพิ่มคำสั่ง where ในคิวรีย่อยเพื่อกรองข้อมูลผู้ใช้ที่เลือกเฉพาะFrom payments as p Where p.user_id =@user_idในขณะที่แบบสอบถามกำลังทำกลุ่มตามทั้งตาราง
Vikash Rathee

2

คำตอบของ @John Woo ช่วยฉันแก้ปัญหาที่คล้ายกัน ฉันได้ปรับปรุงคำตอบของเขาด้วยการตั้งลำดับที่ถูกต้องเช่นกัน สิ่งนี้ได้ผลสำหรับฉัน:

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

ฉันไม่แน่ใจว่ามันมีประสิทธิภาพแค่ไหน



1

Matei Mihai ให้วิธีแก้ปัญหาที่ง่ายและมีประสิทธิภาพ แต่จะใช้ไม่ได้จนกว่าจะใส่MAX(date)ในส่วน SELECT ดังนั้นข้อความค้นหานี้จะกลายเป็น:

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

และเรียงลำดับตามจะไม่สร้างความแตกต่างในการจัดกลุ่ม แต่สามารถเรียงลำดับผลลัพธ์สุดท้ายที่จัดกลุ่มโดย ฉันลองแล้วและได้ผลสำหรับฉัน


1

คำตอบของฉันได้รับแรงบันดาลใจโดยตรงจาก @valex มีประโยชน์มากหากคุณต้องการหลาย cols ในคำสั่ง 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

คุณสามารถลองสิ่งนี้:

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
แม้ว่าข้อมูลโค้ดนี้จะช่วยแก้ปัญหาได้ แต่การมีคำอธิบายจะช่วยปรับปรุงคุณภาพของโพสต์ของคุณได้มาก โปรดจำไว้ว่าคุณกำลังตอบคำถามสำหรับผู้อ่านในอนาคตและบุคคลเหล่านั้นอาจไม่ทราบสาเหตุของการแนะนำโค้ดของคุณ
Alessio
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.