MySQL INNER JOIN chỉ chọn một hàng từ bảng thứ hai


104

Tôi có một usersbảng và một paymentsbảng, đối với mỗi người dùng, những người trong số họ có thanh toán, có thể có nhiều khoản thanh toán được liên kết trong paymentsbảng. Tôi muốn chọn tất cả người dùng có thanh toán, nhưng chỉ chọn thanh toán mới nhất của họ. Tôi đang thử SQL này nhưng tôi chưa bao giờ thử các câu lệnh SQL lồng nhau trước đây nên tôi muốn biết mình đang làm gì sai. Đánh giá cao sự giúp đỡ

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

Câu trả lời:


148

Bạn cần phải có một truy vấn phụ để có được ngày mới nhất của chúng 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 bạn có một câu trả lời rất hay Part 1 - Joins and Unions. :) đánh dấu!
John Woo

1
Cảm ơn người bạn đời, đã viết nó chỉ cho những tình huống như thế này, bật lên một câu trả lời nhanh với SQL phù hợp, sau đó đề xuất một liên kết đến bài đọc sâu về con quái vật đó để mọi người có thể hiểu mã đề xuất. Lập kế hoạch thêm vào nó để mở rộng nó hơn nữa. Chào mừng bạn đến tham gia nếu bạn thích, tôi nên bật lên một fiddle cho mã thực sự ...
Fluffeh

@JohnWoo cảm ơn câu trả lời của bạn, câu trả lời đã hoạt động hoàn hảo. Và cảm ơn Fluffeh về phần Hỏi & Đáp, tôi sẽ xem xét nó!
Wasim

Tôi nghĩ rằng câu trả lời của tôi đơn giản hơn và nhanh hơn vì tôi chỉ sử dụng một phép nối bên trong. Có lẽ tôi sai?
Mihai Matei

2
Có cách nào để thực hiện truy vấn này mà không cần nối bên trong không? Tôi muốn trả về giá trị tối đa từ bảng c HOẶC lấy lại giá trị rỗng nếu không có hàng nào khớp. Thay đổi JOIN thành LEFT JOIN rõ ràng không hoạt động.
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
)

Giải pháp này tốt hơn câu trả lời được chấp nhận vì nó hoạt động chính xác khi có một số khoản thanh toán với cùng một người dùng và ngày tháng.


đó là một cách tiếp cận tốt mà bạn sử dụng. nhưng tôi có câu hỏi về việc tìm nạp một hình ảnh so với một sản phẩm như thế nào. Một sản phẩm có nhiều (4) hình ảnh nhưng tôi chỉ muốn hiển thị một hình ảnh so với sản phẩm đó.
Ali Raza

Bạn không nghĩ rằng nó sẽ rất chậm để sử dụng? khi bạn đang xử lý mọi bản ghi trong truy vấn phụ của bạn cho kết nối bên trong. Câu trả lời được chấp nhận có thể là câu trả lời tốt nhất có thể sau một chỉnh sửa nhỏ.
Hamees A. Khan

@ HameesA.Khan, tôi chưa kiểm tra việc thực thi truy vấn. Bạn có thể đúng, nhưng giải pháp được chấp nhận làm cho phép nối 3 giai đoạn cũng có thể chậm. Tôi e rằng điều chỉnh sẽ không nhỏ (đây là chủ đề của câu hỏi).
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

Kiểm tra sqlfiddle này


6
Các limit 1khoản sẽ chỉ trả lại 1 người mà không phải là những gì OP muốn.
Fluffeh

6
@MateiMihai, cách này không hiệu quả. Truy vấn chỉ cung cấp ngày tối đa, không cung cấp toàn bộ hàng với ngày tối đa. Bạn có thể thấy nó trong datecột fiddle: khác với max(p.date). Nếu bạn bổ sung thêm cột trong paymentsbảng (ví dụ cost), tất cả những gì các cột sẽ không từ hàng cần thiết
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

Có hai vấn đề với truy vấn của bạn:

  1. Mọi bảng và truy vấn con đều cần có tên, vì vậy bạn phải đặt tên cho truy vấn con INNER JOIN (SELECT ...) AS p ON ....
  2. Truy vấn con khi bạn có nó chỉ trả về một khoảng thời gian hàng, nhưng bạn thực sự muốn một hàng cho mỗi người dùng . Đối với điều đó, bạn cần một truy vấn để lấy ngày tối đa và sau đó tự nối lại để lấy toàn bộ hàng.

Giả sử không có ràng buộc nào payments.date, hãy thử:

    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

đó là một cách tiếp cận tốt mà bạn sử dụng. nhưng tôi có câu hỏi về việc tìm nạp một hình ảnh so với một sản phẩm như thế nào. Một sản phẩm có nhiều (4) hình ảnh nhưng tôi chỉ muốn hiển thị một hình ảnh so với sản phẩm đó.
Ali Raza

Tôi tìm thấy điều này nhanh nhất trong trường hợp của tôi. Thay đổi duy nhất tôi đã làm là thêm mệnh đề where trong truy vấn phụ để lọc dữ liệu người dùng đã chọn chỉ From payments as p Where p.user_id =@user_idkhi truy vấn đang thực hiện một nhóm trên toàn bộ bảng.
Vikash Rathee

2

Câu trả lời của @John Woo đã giúp tôi giải quyết một vấn đề tương tự. Tôi đã cải thiện câu trả lời của anh ấy bằng cách đặt thứ tự chính xác. Điều này đã làm việc cho tôi:

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

Tuy nhiên, tôi không chắc điều này hiệu quả như thế nào.


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

Điều này sẽ làm cho nó hoạt động


1

Matei Mihai đã đưa ra một giải pháp đơn giản và hiệu quả nhưng nó sẽ không hoạt động cho đến khi đưa MAX(date)vào phần CHỌN để truy vấn này sẽ trở thành:

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

Và thứ tự theo sẽ không tạo ra bất kỳ sự khác biệt nào trong việc phân nhóm nhưng nó có thể sắp xếp kết quả cuối cùng được cung cấp bởi nhóm bởi. Tôi đã thử nó và nó làm việc cho tôi.


1

Câu trả lời của tôi lấy cảm hứng trực tiếp từ @valex rất hữu ích, nếu bạn cần một số cols trong mệnh đề 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

Bạn có thể thử điều này:

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
Mặc dù đoạn mã này có thể giải quyết câu hỏi, bao gồm một lời giải thích thực sự giúp cải thiện chất lượng bài đăng của bạn. Hãy nhớ rằng bạn đang trả lời câu hỏi cho người đọc trong tương lai và những người đó có thể không biết lý do cho đề xuất mã của bạn.
Alessio
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.