在我最近的问题Select information from last item and join to the total amount之后,我在生成表时出现了一些内存问题
我有两张表sales1
和sales2
,如下所示:
id |日期|客户|销售
使用此表定义:
CREATE TABLE sales (
id int auto_increment primary key,
dates date,
customer int,
sale int
);
sales1
和sales2
具有相同的定义,但sales2
在每个字段中都有sale=-1
。客户可以在任何一个,一个或两个表中。两个表都有大约300.000条记录和比此处所示更多的字段(大约50个字段)。他们是InnoDB。
我想为每个客户选择:
我使用的查询是:
SELECT a.customer, count(a.sale), max_sale
FROM sales a
INNER JOIN (SELECT customer, sale max_sale
from sales x where dates = (select max(dates)
from sales y
where x.customer = y.customer
and y.sale > 0
)
)b
ON a.customer = b.customer
GROUP BY a.customer, max_sale;
问题是:
我必须得到结果,我需要进行某些计算,分开日期:2012年的信息,2013年的信息,以及所有年份的信息。
每当我做一年时间,存储所有信息大约需要2-3分钟。
但是当我尝试收集这些年来的信息时,数据库崩溃了,我得到的消息如下:
InternalError: (InternalError) (1205, u'Lock wait timeout exceeded; try restarting transaction')
加入如此庞大的表似乎对数据库来说太过分了。当我explain
查询时,几乎所有时间百分比都来自creating tmp table
。
我想把数据收集分成几个季度。我们每三个月获得一次结果,然后加入并对其进行排序。但我想最终的连接和排序对于数据库来说太多了。
那么,只要我无法更改表格结构,您会建议什么才能优化这些查询?
答案 0 :(得分:13)
300k行不是一张大桌子。我们经常看到3亿行表。
您的查询的最大问题是您正在使用相关子查询,因此它必须在外部查询中为每行重新执行子查询。
通常情况下,您不需要在一个SQL语句中执行 all 您的工作。将它分解为几个更简单的SQL语句是有好处的:
SELECT customer, COUNT(sale) AS number_of_purchases
FROM sales
GROUP BY customer;
销售指数(客户,销售)最适合此查询。
这是经常出现的greatest-n-per-group问题。
SELECT a.customer, a.sale as max_sale
FROM sales a
LEFT OUTER JOIN sales b
ON a.customer=b.customer AND a.dates < b.dates
WHERE b.customer IS NULL;
换句话说,尝试将行a
与具有相同客户和更长日期的假设行b
匹配。如果找不到这样的行,则a
必须具有该客户的最大日期。
销售指数(客户,日期,销售)最适合此查询。
如果您在最大的日期可能为客户进行多次销售,则此查询将为每个客户返回多行。你需要找到另一个栏来打破平局。如果你使用自动增量主键,它适合作为平局,因为它保证是唯一的,并且它往往会按时间顺序增加。
SELECT a.customer, a.sale as max_sale
FROM sales a
LEFT OUTER JOIN sales b
ON a.customer=b.customer AND (a.dates < b.dates OR a.dates = b.dates and a.id < b.id)
WHERE b.customer IS NULL;
SELECT customer, SUM(sale) AS total_purchases
FROM sales
WHERE sale > 0
GROUP BY customer;
销售指数(客户,销售)最适合此查询。
您应该考虑使用NULL来表示缺少的销售值而不是-1。像SUM()和COUNT()这样的聚合函数忽略NULL,因此您不必使用WHERE子句来排除具有sale&lt;的行。 0
重新:您的评论
我现在拥有的是一个包含字段年,季度,total_sale(关于对(年,季))和销售的表。我想收集的是有关特定时期的信息:本季度,季度,2011年...信息必须分配给顶级客户,销售额较大的客户等。是否有可能从客户那里获得最后的购买价值total_purchases大于5?
SELECT customer, SUM(sale) AS total_purchases
FROM sales
WHERE (year, quarter) = (2012, 4) AND sale > 0
GROUP BY customer
ORDER BY total_purchases DESC
LIMIT 5;
我想根据实际数据对其进行测试,但我认为销售指数(年,季度,客户,销售)最适合此查询。
SELECT a.customer, a.sale as max_sale
FROM sales a
INNER JOIN sales c ON a.customer=c.customer
LEFT OUTER JOIN sales b
ON a.customer=b.customer AND (a.dates < b.dates OR a.dates = b.dates and a.id < b.id)
WHERE b.customer IS NULL
GROUP BY a.id
HAVING COUNT(*) > 5;
与上面其他最大的每组查询一样,销售指数(客户,日期,销售)最适合此查询。它可能无法优化连接和组,因此这将产生临时表。但至少它只会做一个临时表而不是很多。
这些查询足够复杂。您不应该尝试编写一个可以提供所有这些结果的SQL查询。请记住Brian Kernighan的经典引用:
每个人都知道调试的难度是首先编写程序的两倍。所以,如果你在写作时就像你一样聪明,你将如何调试呢?
答案 1 :(得分:1)
我认为您应该尝试在sales(customer, date)
上添加索引。子查询可能是性能瓶颈。
答案 2 :(得分:1)
你可以让这只小狗尖叫。转储整个内连接查询。真。这是一个几乎没有人知道的技巧。
假设dates
是日期时间,将转换为可排序的字符串,连接您想要的值,最大(或min),子串,强制转换。您可能需要调整日期转换功能(这个在MS-SQL中工作),但这个想法可以在任何地方使用:
SELECT customer, count(sale), max_sale = cast(substring(max(convert(char(19), dates, 120) + str(sale, 12, 2)), 20, 12) as numeric(12, 2))
FROM sales a
group by customer
VOILÀ。如果您需要更多结果列,请执行以下操作:
SELECT yourkey
, maxval = left(val, N1) --you often won't need this
, result1 = substring(val, N1+1, N2)
, result2 = substring(val, N1+N2+1, N3) --etc. for more values
FROM ( SELECT yourkey, val = max(cast(maxval as char(N1))
+ cast(resultCol1 as char(N2))
+ cast(resultCol2 as char(N3)) )
FROM yourtable GROUP BY yourkey ) t
请确保除最后一个字段外的所有字段都有固定长度。这需要一些工作来解决问题,但是非常容易学习和重复。它可以在任何数据库引擎上运行,即使你有排名函数,这通常会明显优于它们。
有关这一非常常见的挑战的更多信息here。