net*_*ope 268 sql indexing select join greatest-n-per-group
假设我有一张顾客表和一张购买表.每次购买都属于一个客户.我想在一个SELECT语句中获取所有客户的列表以及他们上次购买的列表.什么是最佳做法?有关构建索引的建议吗?
请在答案中使用这些表/列名称:
在更复杂的情况下,通过将最后一次购买放入客户表中,是否(性能方面)有利于对数据库进行非规范化?
如果(购买)ID保证按日期排序,是否可以通过使用类似的方式简化语句LIMIT 1
?
Bil*_*win 416
这是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;
Run Code Online (Sandbox Code Playgroud)
说明:给定一行p1
,不应该有p2
相同客户的行和更晚的日期(或者在关系的情况下,稍后id
).当我们发现这是真的时,则p1
是该客户最近的购买.
对于指数,我会在创建复合指数purchase
在列(customer_id
,date
,id
).这可以允许使用覆盖索引来完成外连接.请务必在您的平台上进行测试,因为优化与实现有关.使用RDBMS的功能来分析优化计划.例如EXPLAIN
在MySQL上.
有些人使用子查询而不是我上面显示的解决方案,但我发现我的解决方案可以更容易地解决关系.
Adr*_*der 111
您也可以尝试使用子选择执行此操作
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
Run Code Online (Sandbox Code Playgroud)
选择应加入所有客户及其上次购买日期.
Mad*_*mir 24
您尚未指定数据库.如果它是允许分析函数的那个,那么使用这种方法可能比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
Run Code Online (Sandbox Code Playgroud)
Ste*_*erl 23
另一种方法是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
)
)
Run Code Online (Sandbox Code Playgroud)
Tat*_*ton 23
如果您使用的是 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
Run Code Online (Sandbox Code Playgroud)
请注意,DISTINCT ON
此处的字段customer_id
必须与ORDER BY
子句中最左边的字段匹配。
警告:这是一个非标准条款。
Mat*_*hee 16
我发现这个线程是我问题的解决方案.
但是当我尝试它们时,性能很低.贝娄是我提高表现的建议.
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
Run Code Online (Sandbox Code Playgroud)
希望这会有所帮助.
试试这个,会有所帮助。
我在项目中使用了这个。
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]
Run Code Online (Sandbox Code Playgroud)
我需要你所需要的东西,尽管已经过去很多年了,并且尝试了两个最流行的答案。这些都没有结出想要的果实。这就是我必须提供的...为了清楚起见,我更改了一些名称。
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;
Run Code Online (Sandbox Code Playgroud)
在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
)
Run Code Online (Sandbox Code Playgroud)
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
)
Run Code Online (Sandbox Code Playgroud)
MySQL小提琴:http://sqlfiddle.com/#!9/202613/7
在 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
Run Code Online (Sandbox Code Playgroud)
聚合max()
函数将确保从每个组中选择最新的购买(但假设日期列的格式是 max() 给出最新的 - 通常是这种情况)。如果您想处理同一日期的购买,那么您可以使用max(p.date, p.id)
。
就索引而言,我将在购买时使用索引(customer_id、日期、[您想要在选择中返回的任何其他购买列])。
(LEFT OUTER JOIN
与 相对INNER JOIN
)将确保从未购买过的客户也包括在内。