使用生成大量计算统计数据的 MariaDB 视图 - 如何移动到计算 table?
Working with a MariaDB view that generates a lot of calculated statistics - How to move to a calculated table?
我目前有一个 MariaDB 数据库,每天都会填充不同的产品(大约 800 种),并且还会获取这些产品的价格更新。
我在 prices/products table 之上创建了一个视图,它生成过去 7、15 和 30 天的平均值、均值和众数等统计数据,并计算差异从今天的价格到 7、15 和 30 天的平均值。
问题是,每当我 运行 这个视图时,生成数据都需要将近 50 秒。我看到一些关于切换到计算 table 的评论,其中当新数据输入 table 时计算将更新,但我对此持怀疑态度,因为我在一天中的一个特定时间插入大约 1000 个价格点,这将影响 table 上的所有计算。计算的 table 是只更新已更新行的东西,还是会重新计算所有内容?我担心这可能导致的开销(内存不是服务器的问题)。
我已将产品和价格 tables 和视图粘贴到 DBFiddle,此处:https://dbfiddle.uk/?rdbms=mariadb_10.2&fiddle=4cf594a85f950bed34f64d800601baa9
产品代码可以看到计算22141
仅供参考,这些是视图完成的一些计算(在 fiddle 上也可用):
ROUND((((SELECT preconormal
FROM precos
WHERE codigowine = vinhos.codigowine
AND timestamp >= CURRENT_DATE - INTERVAL 9 HOUR) / (SELECT AVG(preconormal)
FROM precos
WHERE codigowine = vinhos.codigowine
AND timestamp >= CURRENT_DATE - INTERVAL 7 DAY) - 1) * 100), 2) as dif_7_dias,
ROUND((((SELECT preconormal
FROM precos
WHERE codigowine = vinhos.codigowine
AND timestamp >= CURRENT_DATE - INTERVAL 9 HOUR) / (SELECT AVG(preconormal)
FROM precos
WHERE codigowine = vinhos.codigowine
AND timestamp >= CURRENT_DATE - INTERVAL 15 DAY) - 1) * 100), 2) as dif_15_dias,
ROUND((((SELECT preconormal
FROM precos
WHERE codigowine = vinhos.codigowine
AND timestamp >= CURRENT_DATE - INTERVAL 9 HOUR) / (SELECT AVG(preconormal)
FROM precos
WHERE codigowine = vinhos.codigowine
AND timestamp >= CURRENT_DATE - INTERVAL 30 DAY) - 1) * 100), 2) as dif_30_dias
如果切换到计算 table,是否有最佳方法?
"calculated table" 不是 MySQL / MariaDB 的功能。所以我猜你的意思是另一个 table 来自你的原始数据,当你需要这些统计数据时你会使用它。
你说 table 是 "populated every day..."。你是说它是从头开始重新加载的,还是说又添加了 800 行? "every day" 是指一天中的特定时间,还是全天持续。
您总是需要 select 视图中的所有行,还是有时可以 SELECT columns FROM view WHERE something = 'constant';
' 这很重要,因为优化技术在全行情况和少数行情况下不同.
你如何有效地处理这个问题?
您可以优化用于定义视图的查询,使其更快。这很可能是一个好方法。
MariaDB 有一种称为持久计算列的列。这些是在插入或更新行时计算的。然后它们可供快速参考。但是它们有局限性;它们不能用子查询定义。
您可以定义一个事件(计划的 SQL 作业)来执行以下操作。
- 创建一个新的空 "calculated" table,名称类似于
tbl_new
.
- 使用您的(慢速)视图插入所需的行。
- 翻转您的 table,这样新的会替换当前的,而您会保留一些旧的。这将为您提供一个简短的 window,其中
tbl
不存在。
- 如果存在则删除 TABLE;tbl_old_2;
- 重命名 TABLE tbl_old 到 tbl_old_2, tbl 到 tbl_old, tbl_new 到 tbl;
查看您的查询:尝试重构它以消除尽可能多的依赖子查询,而不是连接到子查询。消除那些依赖子查询将产生 巨大的 性能差异。
求模是在数据集中寻找极值的详细记录的应用。如果将其用作子查询
WITH freq AS (
SELECT COUNT(*) freq,
ROUND(preconormal, 2) preconormal,
codigowine
FROM precos
WHERE timestamp >= CURRENT_DATE - INTERVAL 7 DAY
GROUP BY ROUND(preconormal, 2), codigowine
),
most AS (
SELECT MAX(freq) freq,
codigowine
FROM freq
GROUP BY codigowine
),
mode AS (
SELECT GROUP_CONCAT(preconormal ORDER BY preconormal DESC) modeps,
freq.codigowine
FROM freq
JOIN most ON freq.freq = most.freq
GROUP BY freq.codigowine
)
SELECT * FROM mode
您可以找到每件商品的最常见价格。第一个 CTE freq
获取价格及其频率。
第二个 CTE,most
,找到最频繁的价格(或价格)的频率。
第三个 CTE mode
使用 JOIN 从 freq
中提取最频繁的价格。它还使用 GROUP_CONCAT() 因为可能有不止一种模式——最常见的价格。
对于您的统计数据,您可以这样做:
WITH s7 AS (
SELECT ROUND(MIN(preconormal), 2) minp,
ROUND(AVG(preconormal), 2) meanp,
ROUND(MAX(preconormal), 2) maxp,
codigowine
FROM precos
WHERE timestamp >= CURRENT_DATE - INTERVAL 7 DAY
GROUP BY codigowine
),
s15 AS (
SELECT ROUND(MIN(preconormal), 2) minp,
ROUND(AVG(preconormal), 2) meanp,
ROUND(MAX(preconormal), 2) maxp,
codigowine
FROM precos
WHERE timestamp >= CURRENT_DATE - INTERVAL 15 DAY
GROUP BY codigowine
),
s30 AS (
SELECT ROUND(MIN(preconormal), 2) minp,
ROUND(AVG(preconormal), 2) meanp,
ROUND(MAX(preconormal), 2) maxp,
codigowine
FROM precos
WHERE timestamp >= CURRENT_DATE - INTERVAL 30 DAY
GROUP BY codigowine
),
m7 AS (
WITH freq AS (
SELECT COUNT(*) freq,
ROUND(preconormal, 2) preconormal,
codigowine
FROM precos
WHERE timestamp >= CURRENT_DATE - INTERVAL 7 DAY
GROUP BY ROUND(preconormal, 2), codigowine
),
most AS (
SELECT MAX(freq) freq,
codigowine
FROM freq
GROUP BY codigowine
),
mode AS (
SELECT GROUP_CONCAT(preconormal ORDER BY preconormal DESC) modeps,
freq.codigowine
FROM freq
JOIN most ON freq.freq = most.freq
GROUP BY freq.codigowine
)
SELECT * FROM mode
)
SELECT v.codigowine, v.nomevinho, DATE(timestamp) AS data_adc,
s7.minp min_7_dias, s7.maxp max_7_dias, s7.meanp media_7_dias, m7.modeps moda_7_dias,
s15.minp min_15_dias, s15.maxp max_15_dias, s15.meanp media_15_dias,
s30.minp min_30_dias, s30.maxp max_30_dias, s30.meanp media_30_dias
FROM vinhos v
LEFT JOIN s7 ON v.codigowine = s7.codigowine
LEFT JOIN m7 ON v.codigowine = m7.codigowine
LEFT JOIN s15 ON v.codigowine = s15.codigowine
LEFT JOIN s30 ON v.codigowine = s30.codigowine
我会留给你做 15 天和 30 天的模式。
这是一个很好的查询。你最好希望下一个从事这项工作的人不会诅咒你的名字。 :-)
这是一大堆相关的子查询,迫切需要适当的索引。
对于由查询 return 编辑的合理数量的行,相关子查询可以提供合理的性能。但是如果外部查询是 return 数千行,那将是数千次子查询的执行。
我倾向于避免 运行宁多个 SELECT 对同一个 table,以获得最后 7 天,最后 15 天,最后 30 天,然后重复得到 AVG,重复得到 MAX,再重复得到 MIN。
相反,我倾向于使用条件聚合,通过 table.
...暂停一下,注意视图可能会影响性能;来自外部查询的谓词可能不会被推送到视图查询中。我们没有看到整个视图定义在做什么,但我怀疑我们可能正在具体化一个大集合。
考虑这样的查询:
SELECT ...
, ROUND( ( n.mal / a.avg_07_day - 1)*100 ,2) AS dif_7_dias
, ROUND( ( n.mal / a.avg_15_day - 1)*100 ,2) AS dif_15_dias
, ROUND( ( n.mal / a.avg_30_day - 1)*100 ,2) AS dif_30_dias
, ...
FROM vinhos
LEFT
JOIN ( SELECT h.codigowine
, AVG(IF( h.timestamp >= CURRENT_DATE + INTERVAL -30 DAY, h.preconormal, NULL)) AS avg_30_day
, MAX(IF( h.timestamp >= CURRENT_DATE + INTERVAL -30 DAY, h.preconormal, NULL)) AS max_30_day
, MIN(IF( h.timestamp >= CURRENT_DATE + INTERVAL -30 DAY, h.preconormal, NULL)) AS min_30_day
, AVG(IF( h.timestamp >= CURRENT_DATE + INTERVAL -15 DAY, h.preconormal, NULL)) AS avg_15_day
, MAX(IF( h.timestamp >= CURRENT_DATE + INTERVAL -15 DAY, h.preconormal, NULL)) AS max_15_day
, MIN(IF( h.timestamp >= CURRENT_DATE + INTERVAL -15 DAY, h.preconormal, NULL)) AS min_15_day
, AVG(IF( h.timestamp >= CURRENT_DATE + INTERVAL -7 DAY, h.preconormal, NULL)) AS avg_07_day
, MAX(IF( h.timestamp >= CURRENT_DATE + INTERVAL -7 DAY, h.preconormal, NULL)) AS max_07_day
, MIN(IF( h.timestamp >= CURRENT_DATE + INTERVAL -7 DAY, h.preconormal, NULL)) AS min_07_day
FROM precos h
GROUP
BY h.codigowine
HAVING h.codigowine IS NOT NULL
) a
ON a.codigowine = vinhos.codigowine
LEFT
JOIN ( SELECT s.codigowine
, MAX(s.precnormal) AS mal
, MIN(s.precnormal) AS mil
FROM precos s
WHERE s.timestamp >= CURRENT_DATE - INTERVAL 9 HOUR
GROUP
BY s.codigowine
HAVING s.codigowine IS NOT NULL
) n
ON n.codigowine = vinhos.codigowine
考虑内联视图查询 a
。
请注意,我们可以 运行 和 SELECT 分开,并得到一个结果集 returned,就像我们 return 来自 table 的结果一样.我们希望这可以通过引用的 table 进行 单次 传递。可能有一些谓词(WHERE 子句中的条件)会过滤我们的行,或者使我们能够更好地利用索引。如当前所写,查询可以使用前导列为 codigowine
的索引来避免(可能昂贵的)"Using filesort" 操作来满足 GROUP BY
.
我对 - INTERVAL 9 HOUR 的查询感到有点困惑。在我看来,这些子查询可能 return 不止一行。没有 LIMIT 子句(也没有 ORDER BY)......但看起来我们期待一个单一的值(标量),给定除法运算。
在不了解我们要在那里实现的目标,不了解规范的情况下,我将我的困惑包装起来并将其放入另一个内联视图 n
... 这并不是我们想要的想做,但只是为了(再次)说明一个内联视图 returning 一个结果集。无论我们试图从 - INTERVAL 9 HOUR 子查询中获取什么值,我认为我们也可以 return 将它们作为一个集合。
综上所述,我们现在可以开始回答提出的问题:添加 "calculated table"。
如果我们不需要第二个结果,但可以使用缓存的统计信息,我会考虑将内联视图 a
中的结果集具体化为 table,然后重写上面的查询以将内联视图 a
替换为对缓存 table.
的引用
CREATE TABLE calc_stats_n_days
( codigowine <datatype> PRIMARY KEY
, avg_30_day DOUBLE
, max_30_day DOUBLE
, min_30_day DOUBLE
, avg_15_day DOUBLE
, ...
对于初始人口...
INSERT INTO calc_stats_n_days
( codigowine, avg_30_day, maxg_30_day, min_30_day, avg_15_day, ... )
SELECT h.codigowine
, AVG(IF( h.timestamp >= CURRENT_DATE + INTERVAL -30 DAY, h.preconormal, NULL)) AS avg_30_day
, MAX(IF( h.timestamp >= CURRENT_DATE + INTERVAL -30 DAY, h.preconormal, NULL)) AS max_30_day
, MIN(IF( h.timestamp >= CURRENT_DATE + INTERVAL -30 DAY, h.preconormal, NULL)) AS min_30_day
, AVG(IF( h.timestamp >= CURRENT_DATE + INTERVAL -15 DAY, h.preconormal, NULL)) AS avg_15_day
, ...
对于持续同步,我可能会创建一个临时 table,用相同的查询填充它,然后在临时 table 和目标 table 之间进行同步.也许 INSERT ... ON DUPLICATE KEY
和 DELETE
反连接(删除旧行)。
在考虑其他选项之前,请尝试提高查询效率。从长远来看,这是有益的:即使您最终转向计算 table,您仍将利用更高效的刷新查询。
您的查询有 15-20 个内联子查询,它们都处理相同的相关 table(据我所知)并对同一列 precos(preconormal)
(最小值、最大值、平均值, 出现次数最多的值)。每个指标在从 9 小时前到 1 个月前的日期范围内计算多次。就这样:
SELECT
codigowine,
nomevinho,
DATE(timestamp) AS data_adc,
-- ...
/* Medidas estatísticas para 7 dias - min, max, media e moda */
ROUND(
(
SELECT MIN(preconormal)
FROM precos
WHERE
codigowine = vinhos.codigowine
AND timestamp >= CURRENT_DATE - INTERVAL 7 DAY
),
2
) AS min_7_dias,
ROUND(
(
SELECT MAX(preconormal)
FROM precos
WHERE
codigowine = vinhos.codigowine
AND timestamp >= CURRENT_DATE - INTERVAL 7 DAY
),
2
) AS max_7_dias,
-- ... and so on ...
FROM vinhos
使用条件聚合一次完成所有计算似乎效率更高:
select
codigowine,
min(preconormal) min_30d
max(preconormal) max_30d,
avg(preconormal) avg_30d,
min(case when timestamp >= current_date - interval 15 day) min_15d,
max(case when timestamp >= current_date - interval 15 day) max_15d,
avg(case when timestamp >= current_date - interval 15 day) avg_15d,
min(case when timestamp >= current_date - interval 7 day) min_07d,
max(case when timestamp >= current_date - interval 7 day) max_07d,
avg(case when timestamp >= current_date - interval 7 day) avg_07d
from precos
where timestamp >= current_date - interval 30 day
group by codigowine
为了性能,您需要 (codigowine, timestamp, preconormal)
上的索引。
然后就可以和原来的table合并了:
select
v.nomevinho,
date(v.timestamp) data_adc,
p.*
from vinhos v
inner join (
select
codigowine,
min(preconormal) min_30d
max(preconormal) max_30d,
avg(preconormal) avg_30d,
min(case when timestamp >= current_date - interval 15 day then preconormal end) min_15d,
max(case when timestamp >= current_date - interval 15 day then preconormal end) max_15d,
avg(case when timestamp >= current_date - interval 15 day then preconormal end) avg_15d,
min(case when timestamp >= current_date - interval 7 day then preconormal end) min_07d,
max(case when timestamp >= current_date - interval 7 day then preconormal end) max_07d,
avg(case when timestamp >= current_date - interval 7 day then preconormal end) avg_07d
from precos
where timestamp >= current_date - interval 30 day
group by codigowine
) p on p.codigowine = v.codigowine
这应该是一个明智的基础查询。要获取其他计算值(每个周期出现次数最多的值、最新值),您可以添加其他连接或使用内联查询。
最后:这是基本查询的另一个版本,它在 连接后聚合。根据您的数据在两个 table 中的传播方式,这可能会更有效,也可能不会更有效(如果 table [=17= 中有重复项 codigowine
,则不会等效]):
select
v.nomevinho,
date(v.timestamp) data_adc,
p.codigowine,
date(v.timestamp) data_adc,
min(p.preconormal) min_30d
max(p.preconormal) max_30d,
avg(p.preconormal) avg_30d,
min(case when p.timestamp >= current_date - interval 15 day then p.preconormal end) min_15d,
max(case when p.timestamp >= current_date - interval 15 day then p.preconormal end) max_15d,
avg(case when p.timestamp >= current_date - interval 15 day then p.preconormal end) avg_15d,
min(case when p.timestamp >= current_date - interval 7 day then p.preconormal end) min_07d,
max(case when p.timestamp >= current_date - interval 7 day then p.preconormal end) max_07d,
avg(case when p.timestamp >= current_date - interval 7 day then p.preconormal end) avg_07d
from vinhos v
inner join precos p
on p.codigowine = v.codigowine
and p.timestamp >= current_date - interval 30 day
group by v.codigowine, v.nomevinho
我目前有一个 MariaDB 数据库,每天都会填充不同的产品(大约 800 种),并且还会获取这些产品的价格更新。
我在 prices/products table 之上创建了一个视图,它生成过去 7、15 和 30 天的平均值、均值和众数等统计数据,并计算差异从今天的价格到 7、15 和 30 天的平均值。
问题是,每当我 运行 这个视图时,生成数据都需要将近 50 秒。我看到一些关于切换到计算 table 的评论,其中当新数据输入 table 时计算将更新,但我对此持怀疑态度,因为我在一天中的一个特定时间插入大约 1000 个价格点,这将影响 table 上的所有计算。计算的 table 是只更新已更新行的东西,还是会重新计算所有内容?我担心这可能导致的开销(内存不是服务器的问题)。
我已将产品和价格 tables 和视图粘贴到 DBFiddle,此处:https://dbfiddle.uk/?rdbms=mariadb_10.2&fiddle=4cf594a85f950bed34f64d800601baa9
产品代码可以看到计算22141
仅供参考,这些是视图完成的一些计算(在 fiddle 上也可用):
ROUND((((SELECT preconormal
FROM precos
WHERE codigowine = vinhos.codigowine
AND timestamp >= CURRENT_DATE - INTERVAL 9 HOUR) / (SELECT AVG(preconormal)
FROM precos
WHERE codigowine = vinhos.codigowine
AND timestamp >= CURRENT_DATE - INTERVAL 7 DAY) - 1) * 100), 2) as dif_7_dias,
ROUND((((SELECT preconormal
FROM precos
WHERE codigowine = vinhos.codigowine
AND timestamp >= CURRENT_DATE - INTERVAL 9 HOUR) / (SELECT AVG(preconormal)
FROM precos
WHERE codigowine = vinhos.codigowine
AND timestamp >= CURRENT_DATE - INTERVAL 15 DAY) - 1) * 100), 2) as dif_15_dias,
ROUND((((SELECT preconormal
FROM precos
WHERE codigowine = vinhos.codigowine
AND timestamp >= CURRENT_DATE - INTERVAL 9 HOUR) / (SELECT AVG(preconormal)
FROM precos
WHERE codigowine = vinhos.codigowine
AND timestamp >= CURRENT_DATE - INTERVAL 30 DAY) - 1) * 100), 2) as dif_30_dias
如果切换到计算 table,是否有最佳方法?
"calculated table" 不是 MySQL / MariaDB 的功能。所以我猜你的意思是另一个 table 来自你的原始数据,当你需要这些统计数据时你会使用它。
你说 table 是 "populated every day..."。你是说它是从头开始重新加载的,还是说又添加了 800 行? "every day" 是指一天中的特定时间,还是全天持续。
您总是需要 select 视图中的所有行,还是有时可以 SELECT columns FROM view WHERE something = 'constant';
' 这很重要,因为优化技术在全行情况和少数行情况下不同.
你如何有效地处理这个问题?
您可以优化用于定义视图的查询,使其更快。这很可能是一个好方法。
MariaDB 有一种称为持久计算列的列。这些是在插入或更新行时计算的。然后它们可供快速参考。但是它们有局限性;它们不能用子查询定义。
您可以定义一个事件(计划的 SQL 作业)来执行以下操作。
- 创建一个新的空 "calculated" table,名称类似于
tbl_new
. - 使用您的(慢速)视图插入所需的行。
- 翻转您的 table,这样新的会替换当前的,而您会保留一些旧的。这将为您提供一个简短的 window,其中
tbl
不存在。- 如果存在则删除 TABLE;tbl_old_2;
- 重命名 TABLE tbl_old 到 tbl_old_2, tbl 到 tbl_old, tbl_new 到 tbl;
- 创建一个新的空 "calculated" table,名称类似于
查看您的查询:尝试重构它以消除尽可能多的依赖子查询,而不是连接到子查询。消除那些依赖子查询将产生 巨大的 性能差异。
求模是在数据集中寻找极值的详细记录的应用。如果将其用作子查询
WITH freq AS (
SELECT COUNT(*) freq,
ROUND(preconormal, 2) preconormal,
codigowine
FROM precos
WHERE timestamp >= CURRENT_DATE - INTERVAL 7 DAY
GROUP BY ROUND(preconormal, 2), codigowine
),
most AS (
SELECT MAX(freq) freq,
codigowine
FROM freq
GROUP BY codigowine
),
mode AS (
SELECT GROUP_CONCAT(preconormal ORDER BY preconormal DESC) modeps,
freq.codigowine
FROM freq
JOIN most ON freq.freq = most.freq
GROUP BY freq.codigowine
)
SELECT * FROM mode
您可以找到每件商品的最常见价格。第一个 CTE freq
获取价格及其频率。
第二个 CTE,most
,找到最频繁的价格(或价格)的频率。
第三个 CTE mode
使用 JOIN 从 freq
中提取最频繁的价格。它还使用 GROUP_CONCAT() 因为可能有不止一种模式——最常见的价格。
对于您的统计数据,您可以这样做:
WITH s7 AS (
SELECT ROUND(MIN(preconormal), 2) minp,
ROUND(AVG(preconormal), 2) meanp,
ROUND(MAX(preconormal), 2) maxp,
codigowine
FROM precos
WHERE timestamp >= CURRENT_DATE - INTERVAL 7 DAY
GROUP BY codigowine
),
s15 AS (
SELECT ROUND(MIN(preconormal), 2) minp,
ROUND(AVG(preconormal), 2) meanp,
ROUND(MAX(preconormal), 2) maxp,
codigowine
FROM precos
WHERE timestamp >= CURRENT_DATE - INTERVAL 15 DAY
GROUP BY codigowine
),
s30 AS (
SELECT ROUND(MIN(preconormal), 2) minp,
ROUND(AVG(preconormal), 2) meanp,
ROUND(MAX(preconormal), 2) maxp,
codigowine
FROM precos
WHERE timestamp >= CURRENT_DATE - INTERVAL 30 DAY
GROUP BY codigowine
),
m7 AS (
WITH freq AS (
SELECT COUNT(*) freq,
ROUND(preconormal, 2) preconormal,
codigowine
FROM precos
WHERE timestamp >= CURRENT_DATE - INTERVAL 7 DAY
GROUP BY ROUND(preconormal, 2), codigowine
),
most AS (
SELECT MAX(freq) freq,
codigowine
FROM freq
GROUP BY codigowine
),
mode AS (
SELECT GROUP_CONCAT(preconormal ORDER BY preconormal DESC) modeps,
freq.codigowine
FROM freq
JOIN most ON freq.freq = most.freq
GROUP BY freq.codigowine
)
SELECT * FROM mode
)
SELECT v.codigowine, v.nomevinho, DATE(timestamp) AS data_adc,
s7.minp min_7_dias, s7.maxp max_7_dias, s7.meanp media_7_dias, m7.modeps moda_7_dias,
s15.minp min_15_dias, s15.maxp max_15_dias, s15.meanp media_15_dias,
s30.minp min_30_dias, s30.maxp max_30_dias, s30.meanp media_30_dias
FROM vinhos v
LEFT JOIN s7 ON v.codigowine = s7.codigowine
LEFT JOIN m7 ON v.codigowine = m7.codigowine
LEFT JOIN s15 ON v.codigowine = s15.codigowine
LEFT JOIN s30 ON v.codigowine = s30.codigowine
我会留给你做 15 天和 30 天的模式。
这是一个很好的查询。你最好希望下一个从事这项工作的人不会诅咒你的名字。 :-)
这是一大堆相关的子查询,迫切需要适当的索引。
对于由查询 return 编辑的合理数量的行,相关子查询可以提供合理的性能。但是如果外部查询是 return 数千行,那将是数千次子查询的执行。
我倾向于避免 运行宁多个 SELECT 对同一个 table,以获得最后 7 天,最后 15 天,最后 30 天,然后重复得到 AVG,重复得到 MAX,再重复得到 MIN。
相反,我倾向于使用条件聚合,通过 table.
...暂停一下,注意视图可能会影响性能;来自外部查询的谓词可能不会被推送到视图查询中。我们没有看到整个视图定义在做什么,但我怀疑我们可能正在具体化一个大集合。
考虑这样的查询:
SELECT ...
, ROUND( ( n.mal / a.avg_07_day - 1)*100 ,2) AS dif_7_dias
, ROUND( ( n.mal / a.avg_15_day - 1)*100 ,2) AS dif_15_dias
, ROUND( ( n.mal / a.avg_30_day - 1)*100 ,2) AS dif_30_dias
, ...
FROM vinhos
LEFT
JOIN ( SELECT h.codigowine
, AVG(IF( h.timestamp >= CURRENT_DATE + INTERVAL -30 DAY, h.preconormal, NULL)) AS avg_30_day
, MAX(IF( h.timestamp >= CURRENT_DATE + INTERVAL -30 DAY, h.preconormal, NULL)) AS max_30_day
, MIN(IF( h.timestamp >= CURRENT_DATE + INTERVAL -30 DAY, h.preconormal, NULL)) AS min_30_day
, AVG(IF( h.timestamp >= CURRENT_DATE + INTERVAL -15 DAY, h.preconormal, NULL)) AS avg_15_day
, MAX(IF( h.timestamp >= CURRENT_DATE + INTERVAL -15 DAY, h.preconormal, NULL)) AS max_15_day
, MIN(IF( h.timestamp >= CURRENT_DATE + INTERVAL -15 DAY, h.preconormal, NULL)) AS min_15_day
, AVG(IF( h.timestamp >= CURRENT_DATE + INTERVAL -7 DAY, h.preconormal, NULL)) AS avg_07_day
, MAX(IF( h.timestamp >= CURRENT_DATE + INTERVAL -7 DAY, h.preconormal, NULL)) AS max_07_day
, MIN(IF( h.timestamp >= CURRENT_DATE + INTERVAL -7 DAY, h.preconormal, NULL)) AS min_07_day
FROM precos h
GROUP
BY h.codigowine
HAVING h.codigowine IS NOT NULL
) a
ON a.codigowine = vinhos.codigowine
LEFT
JOIN ( SELECT s.codigowine
, MAX(s.precnormal) AS mal
, MIN(s.precnormal) AS mil
FROM precos s
WHERE s.timestamp >= CURRENT_DATE - INTERVAL 9 HOUR
GROUP
BY s.codigowine
HAVING s.codigowine IS NOT NULL
) n
ON n.codigowine = vinhos.codigowine
考虑内联视图查询 a
。
请注意,我们可以 运行 和 SELECT 分开,并得到一个结果集 returned,就像我们 return 来自 table 的结果一样.我们希望这可以通过引用的 table 进行 单次 传递。可能有一些谓词(WHERE 子句中的条件)会过滤我们的行,或者使我们能够更好地利用索引。如当前所写,查询可以使用前导列为 codigowine
的索引来避免(可能昂贵的)"Using filesort" 操作来满足 GROUP BY
.
我对 - INTERVAL 9 HOUR 的查询感到有点困惑。在我看来,这些子查询可能 return 不止一行。没有 LIMIT 子句(也没有 ORDER BY)......但看起来我们期待一个单一的值(标量),给定除法运算。
在不了解我们要在那里实现的目标,不了解规范的情况下,我将我的困惑包装起来并将其放入另一个内联视图 n
... 这并不是我们想要的想做,但只是为了(再次)说明一个内联视图 returning 一个结果集。无论我们试图从 - INTERVAL 9 HOUR 子查询中获取什么值,我认为我们也可以 return 将它们作为一个集合。
综上所述,我们现在可以开始回答提出的问题:添加 "calculated table"。
如果我们不需要第二个结果,但可以使用缓存的统计信息,我会考虑将内联视图 a
中的结果集具体化为 table,然后重写上面的查询以将内联视图 a
替换为对缓存 table.
CREATE TABLE calc_stats_n_days
( codigowine <datatype> PRIMARY KEY
, avg_30_day DOUBLE
, max_30_day DOUBLE
, min_30_day DOUBLE
, avg_15_day DOUBLE
, ...
对于初始人口...
INSERT INTO calc_stats_n_days
( codigowine, avg_30_day, maxg_30_day, min_30_day, avg_15_day, ... )
SELECT h.codigowine
, AVG(IF( h.timestamp >= CURRENT_DATE + INTERVAL -30 DAY, h.preconormal, NULL)) AS avg_30_day
, MAX(IF( h.timestamp >= CURRENT_DATE + INTERVAL -30 DAY, h.preconormal, NULL)) AS max_30_day
, MIN(IF( h.timestamp >= CURRENT_DATE + INTERVAL -30 DAY, h.preconormal, NULL)) AS min_30_day
, AVG(IF( h.timestamp >= CURRENT_DATE + INTERVAL -15 DAY, h.preconormal, NULL)) AS avg_15_day
, ...
对于持续同步,我可能会创建一个临时 table,用相同的查询填充它,然后在临时 table 和目标 table 之间进行同步.也许 INSERT ... ON DUPLICATE KEY
和 DELETE
反连接(删除旧行)。
在考虑其他选项之前,请尝试提高查询效率。从长远来看,这是有益的:即使您最终转向计算 table,您仍将利用更高效的刷新查询。
您的查询有 15-20 个内联子查询,它们都处理相同的相关 table(据我所知)并对同一列 precos(preconormal)
(最小值、最大值、平均值, 出现次数最多的值)。每个指标在从 9 小时前到 1 个月前的日期范围内计算多次。就这样:
SELECT
codigowine,
nomevinho,
DATE(timestamp) AS data_adc,
-- ...
/* Medidas estatísticas para 7 dias - min, max, media e moda */
ROUND(
(
SELECT MIN(preconormal)
FROM precos
WHERE
codigowine = vinhos.codigowine
AND timestamp >= CURRENT_DATE - INTERVAL 7 DAY
),
2
) AS min_7_dias,
ROUND(
(
SELECT MAX(preconormal)
FROM precos
WHERE
codigowine = vinhos.codigowine
AND timestamp >= CURRENT_DATE - INTERVAL 7 DAY
),
2
) AS max_7_dias,
-- ... and so on ...
FROM vinhos
使用条件聚合一次完成所有计算似乎效率更高:
select
codigowine,
min(preconormal) min_30d
max(preconormal) max_30d,
avg(preconormal) avg_30d,
min(case when timestamp >= current_date - interval 15 day) min_15d,
max(case when timestamp >= current_date - interval 15 day) max_15d,
avg(case when timestamp >= current_date - interval 15 day) avg_15d,
min(case when timestamp >= current_date - interval 7 day) min_07d,
max(case when timestamp >= current_date - interval 7 day) max_07d,
avg(case when timestamp >= current_date - interval 7 day) avg_07d
from precos
where timestamp >= current_date - interval 30 day
group by codigowine
为了性能,您需要 (codigowine, timestamp, preconormal)
上的索引。
然后就可以和原来的table合并了:
select
v.nomevinho,
date(v.timestamp) data_adc,
p.*
from vinhos v
inner join (
select
codigowine,
min(preconormal) min_30d
max(preconormal) max_30d,
avg(preconormal) avg_30d,
min(case when timestamp >= current_date - interval 15 day then preconormal end) min_15d,
max(case when timestamp >= current_date - interval 15 day then preconormal end) max_15d,
avg(case when timestamp >= current_date - interval 15 day then preconormal end) avg_15d,
min(case when timestamp >= current_date - interval 7 day then preconormal end) min_07d,
max(case when timestamp >= current_date - interval 7 day then preconormal end) max_07d,
avg(case when timestamp >= current_date - interval 7 day then preconormal end) avg_07d
from precos
where timestamp >= current_date - interval 30 day
group by codigowine
) p on p.codigowine = v.codigowine
这应该是一个明智的基础查询。要获取其他计算值(每个周期出现次数最多的值、最新值),您可以添加其他连接或使用内联查询。
最后:这是基本查询的另一个版本,它在 连接后聚合。根据您的数据在两个 table 中的传播方式,这可能会更有效,也可能不会更有效(如果 table [=17= 中有重复项 codigowine
,则不会等效]):
select
v.nomevinho,
date(v.timestamp) data_adc,
p.codigowine,
date(v.timestamp) data_adc,
min(p.preconormal) min_30d
max(p.preconormal) max_30d,
avg(p.preconormal) avg_30d,
min(case when p.timestamp >= current_date - interval 15 day then p.preconormal end) min_15d,
max(case when p.timestamp >= current_date - interval 15 day then p.preconormal end) max_15d,
avg(case when p.timestamp >= current_date - interval 15 day then p.preconormal end) avg_15d,
min(case when p.timestamp >= current_date - interval 7 day then p.preconormal end) min_07d,
max(case when p.timestamp >= current_date - interval 7 day then p.preconormal end) max_07d,
avg(case when p.timestamp >= current_date - interval 7 day then p.preconormal end) avg_07d
from vinhos v
inner join precos p
on p.codigowine = v.codigowine
and p.timestamp >= current_date - interval 30 day
group by v.codigowine, v.nomevinho