3

根据我最近的问题Select information from last item and join to the total amount,我在生成表时遇到了一些内存问题

我有两张桌子sales1sales2像这样:

编号 | 日期 | 客户 | 销售

使用此表定义:

CREATE TABLE sales (
    id int auto_increment primary key, 
    dates date,
    customer int,
    sale int
);

sales1sales2具有相同的定义,但在每个领域都有sales2sale=-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.

我想把收集的数据分成四等份。我们每三个月获得一次结果,然后加入并对其进行排序。但我想这个最终的连接和排序对于数据库来说又太多了。

那么,只要我不能更改表结构,您的专家会建议如何优化这些查询?

4

3 回答 3

13

300k 行不是一个巨大的表。我们经常看到 3 亿行表。

查询的最大问题是您使用的是相关子查询,因此它必须为外部查询中的每一行重新执行子查询。

通常情况下,您不需要在一个 SQL 语句中完成所有工作。将其分解为几个更简单的 SQL 语句有以下优点:

  • 更容易编码。
  • 更容易优化。
  • 更容易调试。
  • 更容易阅读。
  • 如果/当您必须实施新要求时,更容易维护。

购买次数

SELECT customer, COUNT(sale) AS number_of_purchases
FROM sales 
GROUP BY customer;

sales(customer,sale) 的索引最适合此查询。

最后购买价值

这是经常出现的每组最大 n问题。

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;

sales(customer,sale) 的索引最适合此查询。

您应该考虑使用 NULL 而不是 -1 来表示缺失的销售值。SUM() 和 COUNT() 等聚合函数会忽略 NULL,因此您不必使用 WHERE 子句来排除 sale < 0 的行。


回复:你的评论

我现在拥有的是一张表,其中包含年、季度、total_sale(关于(年、季度)这对)和销售字段。我想收集的是有关某个时期的信息:本季度,季度,2011 年......信息必须分为顶级客户,销售额较大的客户等。是否有可能从客户那里获得最后的购买价值total_purchases 大于 5?

2012 年第四季度前五名客户

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;

我想根据真实数据对其进行测试,但我相信销售索引(年、季度、客户、销售)最适合此查询。

总购买次数 > 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;

与上面的其他每组最大 n 查询一样,sales(customer,dates,sale) 的索引最适合此查询。它可能无法同时优化连接和分组依据,因此这将产生一个临时表。但至少它只会做一个临时表而不是很多。


这些查询足够复杂。您不应该尝试编写可以给出所有这些结果的单个 SQL 查询。记住 Brian Kernighan 的经典名言:

每个人都知道,调试的难度是一开始编写程序的两倍。因此,如果您在编写它时尽可能地聪明,您将如何调试它?

于 2013-02-21T15:24:44.270 回答
1

我认为您应该尝试在sales(customer, date). 子查询可能是性能瓶颈。

于 2013-02-21T14:10:53.843 回答
1

你可以让这只小狗尖叫。转储整个内部连接查询。真的。这是一个几乎没人知道的技巧。

假设dates是一个日期时间,其转换为可排序的字符串,连接您想要的值,最大值(或最小值)、子字符串强制转换。您可能需要调整日期转换功能(这个在 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

瞧。如果您需要更多结果列,请执行以下操作:

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

确保除了最后一个字段之外的所有字段都具有固定长度。这需要一些工作才能让你明白,但它是非常可学习和可重复的。它适用于任何数据库引擎,即使你有排名功能,它通常也会大大优于它们。

更多关于这个非常常见的挑战在这里

于 2013-02-21T14:59:28.287 回答