假设我有一个客户表和一个采购表。每次购买都属于一个客户。我想在一个SELECT语句中获得所有客户的列表以及他们的最后一次购买。最佳做法是什么?关于建立索引有什么建议吗?
请在您的答案中使用这些表/列名称:
- 客户:身份证,姓名
- 购买:id,customer_id,item_id,日期
并且在更复杂的情况下,通过将最后一次购买放入客户表中来对数据库进行非规范化(在性能方面)是否有益?
如果保证(购买)ID按日期排序,可以使用类似的语句简化语句LIMIT 1
吗?
假设我有一个客户表和一个采购表。每次购买都属于一个客户。我想在一个SELECT语句中获得所有客户的列表以及他们的最后一次购买。最佳做法是什么?关于建立索引有什么建议吗?
请在您的答案中使用这些表/列名称:
并且在更复杂的情况下,通过将最后一次购买放入客户表中来对数据库进行非规范化(在性能方面)是否有益?
如果保证(购买)ID按日期排序,可以使用类似的语句简化语句LIMIT 1
吗?
Answers:
这是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;
说明:给定一行p1
,就不应有p2
同一位客户和一个较晚的日期(或者在有联系的情况下,较晚的日期id
)。当我们发现这是事实时,则p1
是该客户的最近一次购买。
对于指数,我会在创建复合指数purchase
在列(customer_id
,date
,id
)。这可能允许使用覆盖索引完成外部联接。确保优化在您的平台上进行测试,因为优化取决于实现。使用RDBMS的功能来分析优化计划。例如EXPLAIN
在MySQL上。
有些人使用子查询来代替我上面显示的解决方案,但是我发现我的解决方案使解决联系更加容易。
您也可以尝试使用子选择来执行此操作
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
选择应加入所有客户及其最后购买日期。
INNER JOIN
改为LEFT OUTER JOIN
。
purchase
表中获得的唯一信息就是日期和customer_id,但是查询会询问表中的所有字段。
您尚未指定数据库。如果是允许分析功能的方法,则使用此方法的速度可能比使用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
另一种方法是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
)
)
AND NOT EXISTS
简单的话来解释这部分吗?
我发现此线程可以解决我的问题。
但是当我尝试它们时,性能却很差。贝娄是我建议更好的性能。
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
希望这会有所帮助。
top 1
和ordered it by
MaxDatedesc
如果您使用的是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
请注意,DISTINCT ON
此处的字段customer_id
必须与ORDER BY
子句中最左边的字段匹配。
注意:这是一个非标准条款。
试试这个,会有所帮助。
我已经在我的项目中使用了它。
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]
在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,日期,[您要在选择中返回的任何其他采购列])。
在LEFT OUTER JOIN
(相对于INNER JOIN
)将确保也包括那些从未购买的客户。
请尝试一下
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;