ChatGPT解决这个技术问题 Extra ChatGPT

MySQL INNER JOIN select only one row from second table

I have a users table and a payments table, for each user, those of which have payments, may have multiple associated payments in the payments table. I would like to select all users who have payments, but only select their latest payment. I'm trying this SQL but i've never tried nested SQL statements before so I want to know what i'm doing wrong. Appreciate the help

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

o
oldergod

You need to have a subquery to get their latest date 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

@Fluffeh you have a very nice answer Part 1 - Joins and Unions. :) bookmarked!
Thanks mate, wrote it for just these sort of situations, pop up a quick answer with the right SQL, then suggest a link to that monster indepth read so that folks can understand the suggested code. Planning on adding to it to further expand it. Welcome to join in if you like, I should pop up a fiddle for the code really...
@JohnWoo thanks for your answer, that has worked perfectly. And thanks Fluffeh for the Q&A, i'll take a look into it!
I think that my answer is more simple and is faster because I'm using just one inner join. Maybe I'm wrong?
Is there a way to do this query without the inner joins? I want to return the max from table c OR get null back if no rows match. Changing the JOIN to LEFT JOIN doesn't work obviously.
F
Finesse
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
)

Or

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

These solutions are better than the accepted answer because they work correctly when there are multiple payments with same user and date. You can try on SQL Fiddle.


it is a good approach you use. but i have question about fetching one image against one product like. One product has many (4) images but i only want to show only one image against that product.
Don't you think it will be very slow to use? as you are processing for every record in your sub query for inner join. Accepted answer can be the best possible answer after a minor tweak.
@HameesA.Khan, I haven't examined the queries execution. You may be right, but the accepted solution makes a 3-dimentional join which can be slow too. I'm afraid the tweak won't be minor (this is the subject of the question).
As a fact this helped me for another task. voted up. Good knowledge of SQL, great thanks. Respect.
s
shodanshok
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

Check out this sqlfiddle


The limit 1 clause will only return 1 user which isn't what the OP wants.
@MateiMihai , this don't work. The query gives only max date, not whole row with max date. You can see it in fiddle: date column is different from max(p.date). If you add more columns in payments table (e.g. cost), all that columns will be not from needed row
v
valex
   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

D
Dino

You can try this:

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

While this code snippet may solve the question, including an explanation really helps to improve the quality of your post. Remember that you are answering the question for readers in the future, and those people might not know the reasons for your code suggestion.
l
lc.

There are two problems with your query:

Every table and subquery needs a name, so you have to name the subquery INNER JOIN (SELECT ...) AS p ON .... The subquery as you have it only returns one row period, but you actually want one row for each user. For that you need one query to get the max date and then self-join back to get the whole row.

Assuming there are no ties for payments.date, try:

    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

it is a good approach you use. but i have question about fetching one image against one product like. One product has many (4) images but i only want to show only one image against that product.
I found this most fast in my case. The only change I did is added a where clause in sub query to filter selected user data only From payments as p Where p.user_id =@user_id as the query is doing a group by on whole table.
G
GTCrais

@John Woo's answer helped me solve a similar problem. I've improved upon his answer by setting the correct ordering as well. This has worked for 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

I'm not sure how efficient this is, though.


J
Justice Eziefule
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

This will get it working


K
KyleMit

Matei Mihai given a simple and efficient solution but it will not work until put a MAX(date) in SELECT part so this query will become:

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

And order by will not make any difference in grouping but it can order the final result provided by group by. I tried it and it worked for me.


J
Jérôme B

My answer directly inspired from @valex very usefull, if you need several cols in the ORDER BY clause.

    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

A
Aaron J Spetner

This is quite simple do The inner join and then group by user_id and use max aggregate function in payment_id assuming your table being user and payment query can be

SELECT user.id, max(payment.id)
FROM user INNER JOIN payment ON (user.id = payment.user_id)
GROUP BY user.id