优化 SELECT 中的子查询
Optimize subquery in SELECT
我的table架构如下:
索引:
- products.id 主键
- products.description 独一无二
- expenses.id 主键
- expenses.product_id 外键到 product.id
我的目标是加载
- 当月每个产品的成本(AS costs_november)
- 上个月每个产品的成本(AS costs_october)
- 当月成本与上月相比的变化
(当月成本 - 上月成本)(AS 成本)
- 当月成本与上月相比的百分比变化
(上月成本 * 100 / 当前月成本)(AS percent_diff)
我已经成功地编写了 SQL 的代码:
SELECT description, (SUM(cost) - IFNULL(
(
SELECT SUM(cost)
FROM expenses
WHERE month = 9 AND year = 2019 AND product_id = e.product_id
GROUP BY product_id
), 0)) AS costs,
SUM(cost) * 100 /
(
SELECT SUM(cost)
FROM expenses
WHERE month = 9 AND year = 2019 AND product_id = e.product_id
GROUP BY product_id
) AS percent_diff,
SUM(cost) AS costs_october,
IFNULL(
(
SELECT SUM(cost)
FROM expenses
WHERE month = 9 AND year = 2019 AND product_id = e.product_id
GROUP BY product_id
), 0) AS costs_september
FROM expenses e
JOIN products p ON (e.product_id = p.id)
WHERE month = 10 AND year = 2019
GROUP BY product_id
ORDER BY product_id;
但是复制粘贴同一个子查询三次真的是解决方案吗?理论上,每个产品需要 运行 四个查询。有没有更优雅的方式?
感谢您的帮助!
您可以一次计算所有月份和所有产品:
SELECT year, month,
SUM(costs) as curr_month_costs,
LAG(SUM(costs)) OVER (ORDER BY year, month) as prev_month_costs,
(SUM(costs) -
LAG(SUM(costs)) OVER (ORDER BY year, month)
) as diff,
LAG(SUM(costs)) OVER (ORDER BY year, month) * 100 / SUM(costs)
FROM expenses e JOIN
products p
ON e.product_id = p.id
GROUP BY product_id, year, month
ORDER BY year, month, product_id;
如果只想 select 当前月份,可以使用子查询。
我会用条件聚合来解决这个问题:
select
p.description,
sum(case when e.month = 11 then e.cost else 0 end) costs_november,
sum(case when e.month = 10 then e.cost else 0 end) costs_october,
sum(case when e.month = 11 then e.cost else -1 * e.cost end) costs,
sum(case when e.month = 10 then e.cost else 0 end)
* 100
/ nullif(
sum(case when e.month = 11 then e.cost else 0 end),
0
) percent_diff
from expenses e
inner join products p on p.id = e.product_id
where e.year = 2019 and e.month in (10, 11)
goup by e.product_id
您可以通过使用子查询来避免重复相同的条件和(您的 RDBMS 可能无论如何都会对其进行优化,但这往往会使查询更具可读性):
select
description,
costs_november,
costs_october,
costs_november - costs_october costs,
costs_october * 100 / nullif(costs_november, 0) percent_diff
from (
select
p.description,
sum(case when e.month = 11 then e.cost else 0 end) costs_november,
sum(case when e.month = 10 then e.cost else 0 end) costs_october
from expenses e
inner join products p on p.id = e.product_id
where e.year = 2019 and e.month in (10, 11)
goup by e.product_id
) t
我的table架构如下:
索引:
- products.id 主键
- products.description 独一无二
- expenses.id 主键
- expenses.product_id 外键到 product.id
我的目标是加载
- 当月每个产品的成本(AS costs_november)
- 上个月每个产品的成本(AS costs_october)
- 当月成本与上月相比的变化
(当月成本 - 上月成本)(AS 成本) - 当月成本与上月相比的百分比变化
(上月成本 * 100 / 当前月成本)(AS percent_diff)
我已经成功地编写了 SQL 的代码:
SELECT description, (SUM(cost) - IFNULL(
(
SELECT SUM(cost)
FROM expenses
WHERE month = 9 AND year = 2019 AND product_id = e.product_id
GROUP BY product_id
), 0)) AS costs,
SUM(cost) * 100 /
(
SELECT SUM(cost)
FROM expenses
WHERE month = 9 AND year = 2019 AND product_id = e.product_id
GROUP BY product_id
) AS percent_diff,
SUM(cost) AS costs_october,
IFNULL(
(
SELECT SUM(cost)
FROM expenses
WHERE month = 9 AND year = 2019 AND product_id = e.product_id
GROUP BY product_id
), 0) AS costs_september
FROM expenses e
JOIN products p ON (e.product_id = p.id)
WHERE month = 10 AND year = 2019
GROUP BY product_id
ORDER BY product_id;
但是复制粘贴同一个子查询三次真的是解决方案吗?理论上,每个产品需要 运行 四个查询。有没有更优雅的方式?
感谢您的帮助!
您可以一次计算所有月份和所有产品:
SELECT year, month,
SUM(costs) as curr_month_costs,
LAG(SUM(costs)) OVER (ORDER BY year, month) as prev_month_costs,
(SUM(costs) -
LAG(SUM(costs)) OVER (ORDER BY year, month)
) as diff,
LAG(SUM(costs)) OVER (ORDER BY year, month) * 100 / SUM(costs)
FROM expenses e JOIN
products p
ON e.product_id = p.id
GROUP BY product_id, year, month
ORDER BY year, month, product_id;
如果只想 select 当前月份,可以使用子查询。
我会用条件聚合来解决这个问题:
select
p.description,
sum(case when e.month = 11 then e.cost else 0 end) costs_november,
sum(case when e.month = 10 then e.cost else 0 end) costs_october,
sum(case when e.month = 11 then e.cost else -1 * e.cost end) costs,
sum(case when e.month = 10 then e.cost else 0 end)
* 100
/ nullif(
sum(case when e.month = 11 then e.cost else 0 end),
0
) percent_diff
from expenses e
inner join products p on p.id = e.product_id
where e.year = 2019 and e.month in (10, 11)
goup by e.product_id
您可以通过使用子查询来避免重复相同的条件和(您的 RDBMS 可能无论如何都会对其进行优化,但这往往会使查询更具可读性):
select
description,
costs_november,
costs_october,
costs_november - costs_october costs,
costs_october * 100 / nullif(costs_november, 0) percent_diff
from (
select
p.description,
sum(case when e.month = 11 then e.cost else 0 end) costs_november,
sum(case when e.month = 10 then e.cost else 0 end) costs_october
from expenses e
inner join products p on p.id = e.product_id
where e.year = 2019 and e.month in (10, 11)
goup by e.product_id
) t