SQL连接:以一对多关系select最后的logging

假设我有一个客户表和一个购买表。 每笔购买都属于一个客户。 我想在一个SELECT语句中获得所有客户的列表以及最后一次购买。 最佳做法是什么? 任何关于build立索引的build议?

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

  • 客户:身份证,姓名
  • 购买:id,customer_id,item_id,date

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

如果(购买)ID保证按datesorting,那么可以通过使用类似LIMIT 1简化来简化语句?

这是StackOverflow上定期出现的greatest-n-per-group问题的一个例子。

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

 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; 

说明:给定一行p1 ,不应该有同一客户和较晚date的行p2 (或者在关系的情况下,后面的id )。 当我们发现这是真的,那么p1是该客户的最近购买。

关于索引,我会在列( customer_iddateid )上创build一个复合索引。 这可能允许使用覆盖索引完成外部联接。 一定要在你的平台上testing,因为优化是依赖于实现的。 使用RDBMS的function来分析优化计划。 例如MySQL上的EXPLAIN


有些人使用子查询,而不是我上面显示的解决scheme,但我发现我的解决scheme可以更容易地解决关系。

你也可以尝试使用子select

 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 

select应join所有客户和最后一次购买date。

您尚未指定数据库。 如果它是一个允许分析函数的方法,那么使用这种方法可能会比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 pr = 1 

另一种方法是在连接条件中使用NOT EXISTS条件来testing以后购买:

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

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

但是,当我尝试他们的performance很低。 贝娄是我更好的performance的build议。

 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 

希望这会有所帮助。

请试试这个,

 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;