MySQL INNER JOIN从第二个表中只选择一行


104

我有一个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

Answers:


148

您需要有一个子查询,以获取每个日期的最新日期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 2012年

1
谢谢伙伴,为这种情况编写了代码,用正确的SQL弹出了一个快速答案,然后建议一个指向该怪物深入阅读的链接,以便人们可以理解建议的代码。计划对其进行添加以进一步扩展它。欢迎加入,如果您愿意,我真的应该为代码弹出一个小提琴...
Fluffeh 2012年

@JohnWoo感谢您的回答,效果很好。同时感谢Fluffeh的问答,我来看看!
Wasim 2012年

我认为我的答案更简单,更快,因为我仅使用一个内部联接。也许我错了?
Mihai Matei 2012年

2
有没有内部联接的方法来执行此查询?我想从表c中返回最大值,或者如果没有行匹配,则返回null。将JOIN更改为LEFT JOIN显然不起作用。
斯科特(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
)

此解决方案比接受的答案更好,因为当一些付款使用相同的用户和日期时,它可以正常工作。


这是您使用的好方法。但是我有一个关于针对一种产品获取一张图像的问题。一种产品具有许多(4)图像,但我只想针对该产品仅显示一张图像。
阿里·拉扎

您不觉得使用起来会很慢吗?在处理子查询中用于内部联接的每条记录时。稍作调整后,可接受的答案可能是最好的答案。
Hamees A. Khan

@ HameesA.Khan,我尚未检查查询的执行情况。您可能是对的,但是公认的解决方案会产生一个3维的联接,该联接也可能很慢。恐怕调整不会太小(这是问题的主题)。
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

看看这个sqlfiddle


6
limit 1子句只会返回1个用户,这不是OP想要的。
Fluffeh 2012年

6
@MateiMihai,这不起作用。该查询仅给出最大日期,而不给出具有最大日期的整行。您可以在小提琴中看到它:date列不同于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)图像,但我只想针对该产品仅显示一张图像。
阿里·拉扎

我发现这是最快的。我所做的唯一更改是在子查询中添加了where子句,以仅From payments as p Where p.user_id =@user_id在查询对整个表进行分组时过滤选定的用户数据。
维卡什·拉特

2

@吴宇森的答案帮助我解决了类似的问题。我还通过设置正确的顺序改进了他的回答。这对我有用:

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

不过,我不确定这有多有效。


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

这将使其正常工作


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

并且order by不会对分组产生任何影响,但是可以对group by提供的最终结果进行排序。我尝试过,它对我有用。


1

如果您在ORDER BY子句中需要多个列,则直接从@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 (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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.