我正在尝试优化MySQL查询。我正在尝试使用特定商店每15分钟移动平均一件商品的价格更新表格列。
我的表格具有以下结构
╔═════╦═════════════════════╦════════════╦══════╦════════════════╗
║ ID ║ DATETIME ║ NAME ║Price ║ 15_MIN_AVERAGE ║
╠═════╬═════════════════════╬════════════╬══════╬════════════════╣
║ 1 ║ 2000-01-01 00:00:05 ║ WALMART ║ 1 ║ ║
║ 2 ║ 2000-01-01 00:00:05 ║ BESTBUY ║ 6 ║ ║
║ 3 ║ 2000-01-01 00:00:05 ║ RADIOSHACK ║ 2 ║ ║
║ 4 ║ 2000-01-01 00:00:10 ║ WALMART ║ 6 ║ ║
║ 5 ║ 2000-01-01 00:00:10 ║ BESTBUY ║ 2 ║ ║
║ 6 ║ 2000-01-01 00:00:10 ║ RADIOSHACK ║ 8 ║ ║
║ 7 ║ 2000-01-01 00:00:15 ║ WALMART ║ 10 ║ ║
║ 8 ║ 2000-01-01 00:00:15 ║ BESTBUY ║ 2 ║ ║
║ 9 ║ 2000-01-01 00:00:15 ║ RADIOSHACK ║ 3 ║ ║
║ 10 ║ 2000-01-01 00:00:20 ║ WALMART ║ 6 ║ ║
║ 11 ║ 2000-01-01 00:00:20 ║ BESTBUY ║ 4 ║ ║
║ 12 ║ 2000-01-01 00:00:20 ║ RADIOSHACK ║ 5 ║ ║
║ 13 ║ 2000-01-01 00:00:25 ║ WALMART ║ 1 ║ ║
║ 14 ║ 2000-01-01 00:00:25 ║ BESTBUY ║ 0 ║ ║
║ 15 ║ 2000-01-01 00:00:25 ║ RADIOSHACK ║ 5 ║ ║
║ 16 ║ 2000-01-01 00:00:30 ║ WALMART ║ 1 ║ ║
║ 17 ║ 2000-01-01 00:00:30 ║ BESTBUY ║ 6 ║ ║
║ 18 ║ 2000-01-01 00:00:30 ║ RADIOSHACK ║ 2 ║ ║
║ 19 ║ 2000-01-01 00:00:35 ║ WALMART ║ 6 ║ ║
║ 20 ║ 2000-01-01 00:00:35 ║ BESTBUY ║ 2 ║ ║
║ 21 ║ 2000-01-01 00:00:35 ║ RADIOSHACK ║ 8 ║ ║
║ 22 ║ 2000-01-01 00:00:40 ║ WALMART ║ 10 ║ ║
║ 23 ║ 2000-01-01 00:00:40 ║ BESTBUY ║ 2 ║ ║
║ 24 ║ 2000-01-01 00:00:40 ║ RADIOSHACK ║ 3 ║ ║
║ 25 ║ 2000-01-01 00:00:45 ║ WALMART ║ 6 ║ ║
║ 26 ║ 2000-01-01 00:00:45 ║ BESTBUY ║ 4 ║ ║
║ 27 ║ 2000-01-01 00:00:45 ║ RADIOSHACK ║ 5 ║ ║
║ 28 ║ 2000-01-01 00:00:48 ║ WALMART ║ 1 ║ ║
║ 29 ║ 2000-01-01 00:00:48 ║ BESTBUY ║ 0 ║ ║
║ 30 ║ 2000-01-01 00:00:48 ║ RADIOSHACK ║ 5 ║ ║
║ 31 ║ 2000-01-01 00:00:50 ║ WALMART ║ 6 ║ ║
║ 32 ║ 2000-01-01 00:00:50 ║ BESTBUY ║ 4 ║ ║
║ 33 ║ 2000-01-01 00:00:50 ║ RADIOSHACK ║ 5 ║ ║
║ 34 ║ 2000-01-01 00:00:55 ║ WALMART ║ 1 ║ ║
║ 35 ║ 2000-01-01 00:00:55 ║ BESTBUY ║ 0 ║ ║
║ 36 ║ 2000-01-01 00:00:55 ║ RADIOSHACK ║ 5 ║ ║
║ 37 ║ 2000-01-01 00:01:00 ║ WALMART ║ 1 ║ ║
║ 38 ║ 2000-01-01 00:01:00 ║ BESTBUY ║ 0 ║ ║
║ 39 ║ 2000-01-01 00:01:00 ║ RADIOSHACK ║ 5 ║ ║
╚═════╩═════════════════════╩════════════╩══════╩════════════════╝
我的查询是:
UPDATE my_table AS t
INNER JOIN
( select ID,
(select avg(price) from my_table as t2
where
t2.datetime between subtime(t1.datetime, '00:14:59') and t1.datetime AND
t2.name = t1.name
) as average
from my_table as t1
where
minute(datetime) in (0,15,30,45) ) as sel
ON t.ID = sel.ID
SET 15_MIN_AVERAGE = average
我在DATETIME列上有一个索引(DATETIME类型),但我认为在where子句中使用诸如minute()和subtime()之类的函数基本上会使索引无效。
我的桌子有大约160万条记录(每5分钟大约有一条记录)。目前,运行此查询(超过一小时)需要很长时间,这是不可接受的。
您建议如何优化它?
非常感谢!
答案 0 :(得分:0)
我认为最好为此创建一个range
表。这是一个很好的例子
像这样的表格10年* 365天* 24小时* 4季度= 350k行。但该指数将完美无缺。
所以你的表应该是这样的:
id start end
1 2016-11-10 10:00:00 2016-11-10 10:04:59
2 2016-11-10 10:05:00 2016-11-10 10:09:59
3 2016-11-10 10:10:00 2016-11-10 10:14:59
您的查询将为每个日期时间分配和ID
SELECT t.name, r.id, AVG(t.price)
FROM my_table t
JOIN range r
ON t.`DATETIME` BETWEEN r.start
AND r.end
GROUP BY t.name, r.id
替代
id start end
1 2016-11-10 10:00:00 2016-11-10 10:05:00
2 2016-11-10 10:05:00 2016-11-10 10:10:00
3 2016-11-10 10:10:00 2016-11-10 10:15:00
SELECT t.name, r.id, AVG(t.price)
FROM my_table t
JOIN range r
ON t.`DATETIME` >= r.start AND t.`DATETIME` < r.end
GROUP BY t.name, r.id
答案 1 :(得分:0)
这是Juan Carlos Oropeza提出的范围提案的变体。我怀疑实际上只将15分钟的平均值存储在自己的表中是有道理的,但在这里我已按要求应用它。但请注意,我不能将自己称为“datetime”这样的保留字,因此我使用了“pricingatetime”。
有一个固有的假设,你不需要超过1000个15分钟的间隔,如果你这样做,你需要调整交叉连接的数量等,以将笛卡尔积扩大到更大。
另外假设只有在添加新数据时才需要这样做,逻辑将重新处理存储平均值为空的日期的所有行。
update table1
inner join (
select
dr.start_date
, dr.end_date
, avg(t.price) avg_price
from table1 t
inner join (
SELECT
(x.a + (y.b*10)+(z.c*100))+ 1 n
, TRIM(min_date + INTERVAL 15*(x.a + (y.b*10)+(z.c*100)) MINUTE) start_date
, TRIM(min_date + INTERVAL 15*(x.a + (y.b*10)+(z.c*100)) MINUTE) + INTERVAL 15 MINUTE end_date
FROM (
select
cast(date(min(pricedatetime)) as datetime) min_date
, cast(date(max(pricedatetime)) as datetime) max_date
from Table1
where 15_MIN_AVERAGE IS NULL
) m
CROSS JOIN (
SELECT 0 AS a UNION ALL
SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL
SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL
SELECT 9
) x
CROSS JOIN (
SELECT 0 AS b UNION ALL
SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL
SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL
SELECT 9
) y
CROSS JOIN (
SELECT 0 AS c UNION ALL
SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL
SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL
SELECT 9
) z
where TRIM(min_date + INTERVAL 15*((x.a + (y.b*10)+(z.c*100))-1) MINUTE) < max_date
) dr on t.pricedatetime >= dr.start_date and t.pricedatetime < dr.end_date
group by
dr.start_date
, dr.end_date
) g on table1.pricedatetime >= g.start_date and table1.pricedatetime < g.end_date
set `15_MIN_AVERAGE` = g.avg_price
;
请注意我非常故意避免使用它们之间。在 NOT 之间是日期范围的一个好选项,因为它包括较低和较高的边界,因此可以对行进行双重计算。而只需使用&gt; =与&lt;的组合。那个问题完全消失了。另请注意,使用此方法,如果您在范围之间使用时间段精确到秒或亚秒,则无关紧要。
作为工作演示版提供答案 2 :(得分:0)
计划A:升级到MariaDB 10.2并使用“窗口函数”来做这样的“移动平均线”。
计划B:每15秒钟在表格中回顾15分钟并计算当前3行的所有平均值。将它们(通过INSERT
,而非UPDATE
)存储到单独的表格中。你永远不需要重新计算它们。通过在datetime
上建立索引,您不需要查看超过180行来进行计算。这将比你需要计算下一组平均值之前的15秒少得多。
新表上没有id
,旧表也没有。你有一个非常好的'自然'主键(name, datetime)
。如果您同时需要JOIN
和price
,则可以average
原始表中的“摘要表”。
计划C:切换到“指数移动平均线”;计算起来要简单得多:新的平均值是
old_average + 0.1 * (new_value - old_average)
如果您希望平均值更平滑,请选择较小的值(小于0.1);一个更大的值,使其响应更快。