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_id
, date
, id
)上创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;