395

假设我有一张客户表和一张采购表。每次购买都属于一位客户。SELECT我想在一个声明中获取所有客户的列表以及他们最后一次购买。最佳做法是什么?关于建立索引的任何建议?

请在您的答案中使用这些表/列名称:

  • 客户:idname
  • 购买:id, customer_id, item_id,date

在更复杂的情况下,通过将最后一次购买放入客户表中来非规范化数据库是否(在性能方面)有益?

如果保证(购买)id按日期排序,可以使用类似的东西来简化语句LIMIT 1吗?

4

13 回答 13

570

greatest-n-per-group这是StackOverflow 上经常出现的问题的一个示例。

以下是我通常建议的解决方法:

SELECT c.*, p1.*
FROM customer c
JOIN purchase p1 ON (c.id = p1.customer_id)
LEFT OUTER JOIN purchase p2 ON (c.id = p2.customer_id AND 
    (p1.date < p2.date OR (p1.date = p2.date AND p1.id < p2.id)))
WHERE p2.id IS NULL;

解释:给定一个 row p1,不应该有p2同一个客户和较晚日期的行(或者在 tie 的情况下,一个较晚的日期id)。当我们发现这是真的时,p1就是该客户最近的一次购买。

关于索引,我会purchase在列 ( customer_id, date, id) 上创建一个复合索引。这可能允许使用覆盖索引完成外部连接。请务必在您的平台上进行测试,因为优化是依赖于实现的。使用 RDBMS 的功能来分析优化计划。例如EXPLAIN在 MySQL 上。


有些人使用子查询而不是我上面展示的解决方案,但我发现我的解决方案更容易解决关系。

于 2010-01-21T17:35:24.310 回答
163

您也可以尝试使用子选择来执行此操作

SELECT  c.*, p.*
FROM    customer c INNER JOIN
        (
            SELECT  customer_id,
                    MAX(date) MaxDate
            FROM    purchase
            GROUP BY customer_id
        ) MaxDates ON c.id = MaxDates.customer_id INNER JOIN
        purchase p ON   MaxDates.customer_id = p.customer_id
                    AND MaxDates.MaxDate = p.date

选择应加入所有客户及其最后购买日期。

于 2010-01-21T17:40:54.597 回答
43

另一种方法是NOT EXISTS在您的连接条件中使用一个条件来测试以后的购买:

SELECT *
FROM customer c
LEFT JOIN purchase p ON (
       c.id = p.customer_id
   AND NOT EXISTS (
     SELECT 1 FROM purchase p1
     WHERE p1.customer_id = c.id
     AND p1.id > p.id
   )
)
于 2013-06-12T08:10:26.363 回答
33

您尚未指定数据库。如果它是一种允许分析功能的方法,那么使用这种方法可能比 GROUP BY 方法更快(在 Oracle 中肯定更快,在 SQL Server 后期版本中很可能更快,不知道其他版本)。

SQL Server 中的语法为:

SELECT c.*, p.*
FROM customer c INNER JOIN 
     (SELECT RANK() OVER (PARTITION BY customer_id ORDER BY date DESC) r, *
             FROM purchase) p
ON (c.id = p.customer_id)
WHERE p.r = 1
于 2010-01-21T18:23:30.190 回答
32

如果您使用的是 PostgreSQL,您可以使用它DISTINCT ON来查找组中的第一行。

SELECT customer.*, purchase.*
FROM customer
JOIN (
   SELECT DISTINCT ON (customer_id) *
   FROM purchase
   ORDER BY customer_id, date DESC
) purchase ON purchase.customer_id = customer.id

PostgreSQL Docs - Distinct On

请注意,DISTINCT ON此处的字段必须与子句customer_id中最左边的字段匹配。ORDER BY

警告:这是一个非标准条款。

于 2018-06-20T05:08:22.563 回答
28

我发现这个线程可以解决我的问题。

但是当我尝试它们时,性能很低。贝娄是我对更好性能的建议。

With MaxDates as (
SELECT  customer_id,
                MAX(date) MaxDate
        FROM    purchase
        GROUP BY customer_id
)

SELECT  c.*, M.*
FROM    customer c INNER JOIN
        MaxDates as M ON c.id = M.customer_id 

希望这会有所帮助。

于 2014-10-23T06:29:29.187 回答
12

试试这个,它会有所帮助。

我在我的项目中使用了这个。

SELECT 
*
FROM
customer c
OUTER APPLY(SELECT top 1 * FROM purchase pi 
WHERE pi.customer_id = c.Id order by pi.Id desc) AS [LastPurchasePrice]
于 2018-01-16T05:07:32.713 回答
4

在 SQLite 上测试:

SELECT c.*, p.*, max(p.date)
FROM customer c
LEFT OUTER JOIN purchase p
ON c.id = p.customer_id
GROUP BY c.id

聚合函数将max()确保从每个组中选择最新的购买(但假设日期列采用 max() 给出最新的格式 - 通常是这种情况)。如果您想处理同一日期的购买,那么您可以使用max(p.date, p.id).

在索引方面,我会使用一个购买索引(customer_id,date,[任何其他你想在你的选择中返回的购买列])。

LEFT OUTER JOIN相对于INNER JOIN)将确保从未购买过的客户也包括在内。

于 2018-01-27T04:25:24.870 回答
2

请试试这个,

SELECT 
c.Id,
c.name,
(SELECT pi.price FROM purchase pi WHERE pi.Id = MAX(p.Id)) AS [LastPurchasePrice]
FROM customer c INNER JOIN purchase p 
ON c.Id = p.customerId 
GROUP BY c.Id,c.name;
于 2016-06-25T09:25:12.623 回答
2

我需要你需要的东西,尽管很多年后,并尝试了两个最受欢迎的答案。这些都没有结出想要的果实。所以这就是我必须提供的...为了清楚起见,我更改了一些名称。

SELECT 
  cc.pk_ID AS pk_Customer_ID, 
  cc.Customer_Name AS Customer_Name, 
  IFNULL(pp.pk_ID, '') AS fk_Purchase_ID,
  IFNULL(pp.fk_Customer_ID, '') AS fk_Customer_ID,
  IFNULL(pp.fk_Item_ID, '') AS fk_Item_ID,
  IFNULL(pp.Purchase_Date, '') AS Purchase_Date
FROM customer cc
LEFT JOIN purchase pp ON (
  SELECT zz.pk_ID 
  FROM purchase zz 
  WHERE cc.pk_ID = zz.fk_Customer_ID 
  ORDER BY zz.Purchase_Date DESC LIMIT 1) = pp.pk_ID
ORDER BY cc.pk_ID;
于 2021-03-23T22:07:00.510 回答
1

无需先进入代码,逻辑/算法如下:

  1. 转到transaction具有多个相同记录的表client

  2. 使用和选择客户活动的记录clientIDlatestDategroup by clientIDmax(transactionDate)

       select clientID, max(transactionDate) as latestDate 
       from transaction 
       group by clientID
    
  3. inner join带有步骤 2 的结果的transaction表格,那么您将获得transaction表格的完整记录,其中只有每个客户的最新记录。

       select * from 
       transaction t 
       inner join (
         select clientID, max(transactionDate) as latestDate
         from transaction 
         group by clientID) d 
       on t.clientID = d.clientID and t.transactionDate = d.latestDate) 
    
  4. 您可以使用第 3 步的结果来加入您想要获得不同结果的任何表。

于 2020-10-05T20:17:10.873 回答
1

SQL Server上,您可以使用:

SELECT *
FROM customer c
INNER JOIN purchase p on c.id = p.customer_id
WHERE p.id = (
    SELECT TOP 1 p2.id
    FROM purchase p2
    WHERE p.customer_id = p2.customer_id
    ORDER BY date DESC
)

SQL Server 小提琴:http ://sqlfiddle.com/#!18/262fd/2

MySQL上,您可以使用:

SELECT c.name, date
FROM customer c
INNER JOIN purchase p on c.id = p.customer_id
WHERE p.id = (
    SELECT p2.id
    FROM purchase p2
    WHERE p.customer_id = p2.customer_id
    ORDER BY date DESC
    LIMIT 1
)

MySQL 小提琴:http ://sqlfiddle.com/#!9/202613/7

于 2021-04-08T20:41:46.263 回答
0

表:

Customer => id, name
Purchase => id, customer_id, item_id, date

询问 :

SELECT C.id, C.name, P.id, P.date
  FROM customer AS C
  LEFT JOIN purchase AS P ON 
    (
      P.customer_id = C.id 
      AND P.id IN (
        SELECT MAX(PP.id) FROM purchase AS PP GROUP BY PP.customer_id
      )
    )

您还可以在sub select查询中指定一些条件

于 2021-08-13T16:47:16.647 回答