多维度交易金额拆分的SQL性能优化问题
需求说明
需要按维度组合聚合交易数据,每个维度组合对应的金额为交易初始金额乘以各维度项的分配百分比(百分比需转换为小数,如50%即0.5)。现有查询在大数据集下性能极差,新增维度时性能进一步下降,寻求更优实现方案。
表结构
交易表(transaction)
| 字段名 | 数据类型 |
|---|
| transaction_id | INT |
| amount | FLOAT |
| date | DATETIME |
维度表(dimension)
维度项表(dimensionitem)
| 字段名 | 数据类型 |
|---|
| id | INT |
| dimension_id | INT |
| name | VARCHAR |
维度项-交易关联表(dimensionitemtotransaction)
| 字段名 | 数据类型 |
|---|
| dimension_item_id | INT |
| transaction_id | INT |
| percentage | FLOAT |
示例数据
交易记录
| transaction_id | amount |
|---|
| 2885776 | 10000 |
维度表数据
| id | name |
|---|
| 1 | Dimension 1 |
| 2 | Dimension 2 |
| 3 | Dimension 3 |
维度项表数据
| id | dimension_id | name |
|---|
| 57112 | 1 | Item 57112 of dimension 1 |
| 56093 | 1 | Item 56093 of dimension 1 |
| 54232 | 2 | Item 54232 of dimension 2 |
| 54340 | 2 | Item 54340 of dimension 2 |
| 101 | 3 | Item 101 of dimension 3 |
维度项-交易关联表数据
| transaction_id | dimension_item_id | percentage |
|---|
| 2885776 | 57112 | 50 |
| 2885776 | 56093 | 50 |
| 2885776 | 54232 | 60 |
| 2885776 | 54340 | 40 |
| 2885776 | 101 | 100 |
现有查询(性能不佳)
子查询部分
(
SELECT distinct
tl.transaction_id,
dimension_1.id as d1,
dimension_1.percentage as d1_percentage,
dimension_2.id as d2,
dimension_2.percentage as d2_percentage,
dimension_3.id as d3,
dimension_3.percentage as d3_percentage,
tl.amount
FROM transaction tl
left join (
select dtt.transaction_id, dtt.percentage, od.id as id
from dimensionitemtotransaction dtt
join dimensionitem od on dtt.dimension_item_id = od.id
where od.dimension_id = 1
) dimension_1 on tl.transaction_id = dimension_1.transaction_id
left join (
select dtt.transaction_id, dtt.percentage, od.id as id
from dimensionitemtotransaction dtt
join dimensionitem od on dtt.dimension_item_id = od.id
where od.dimension_id = 2
) dimension_2 on tl.transaction_id = dimension_2.transaction_id
left join (
select dtt.transaction_id, dtt.percentage, od.id as id
from dimensionitemtotransaction dtt
join dimensionitem od on dtt.dimension_item_id = od.id
where od.dimension_id = 3
) dimension_3 on tl.transaction_id = dimension_3.transaction_id
) sq
主查询部分
select distinct
transaction_id, d1, d2, d3,
coalesce(sum(
amount * coalesce(d1_percentage, 100) / 100 * coalesce(d2_percentage, 100) / 100 * coalesce(d3_percentage, 100) / 100
), 0.0) as amount
from
( above subquery here ) sq
group by transaction_id, d1, d2, d3;
优化方案
方案思路
避免多次LEFT JOIN产生大量冗余中间数据,改为先按交易+维度提取各维度项数据,再通过CROSS JOIN组合维度项,最后关联交易表计算金额。这种方式能大幅减少中间数据量,提升查询效率。
优化后SQL
WITH transaction_dim_data AS (
SELECT
dtt.transaction_id,
od.dimension_id,
od.id AS dimension_item_id,
dtt.percentage
FROM dimensionitemtotransaction dtt
JOIN dimensionitem od ON dtt.dimension_item_id = od.id
),
d1_data AS (
SELECT transaction_id, dimension_item_id AS d1, percentage AS d1_percent
FROM transaction_dim_data
WHERE dimension_id = 1
),
d2_data AS (
SELECT transaction_id, dimension_item_id AS d2, percentage AS d2_percent
FROM transaction_dim_data
WHERE dimension_id = 2
),
d3_data AS (
SELECT transaction_id, dimension_item_id AS d3, percentage AS d3_percent
FROM transaction_dim_data
WHERE dimension_id = 3
)
SELECT
t.transaction_id,
COALESCE(d1.d1, 'N/A') AS d1,
COALESCE(d2.d2, 'N/A') AS d2,
COALESCE(d3.d3, 'N/A') AS d3,
t.amount *
COALESCE(d1.d1_percent / 100, 1) *
COALESCE(d2.d2_percent / 100, 1) *
COALESCE(d3.d3_percent / 100, 1) AS amount
FROM transaction t
LEFT JOIN d1_data d1 ON t.transaction_id = d1.transaction_id
LEFT JOIN d2_data d2 ON t.transaction_id = d2.transaction_id
LEFT JOIN d3_data d3 ON t.transaction_id = d3.transaction_id
-- 若需强制保留所有维度组合(即使某交易无对应维度项),替换为以下关联逻辑:
-- CROSS JOIN d1_data d1
-- CROSS JOIN d2_data d2
-- CROSS JOIN d3_data d3
-- WHERE t.transaction_id = d1.transaction_id
-- AND t.transaction_id = d2.transaction_id
-- AND t.transaction_id = d3.transaction_id
ORDER BY t.transaction_id;
额外优化建议
- 索引优化:
- 在
dimensionitemtotransaction表上创建复合索引:(transaction_id, dimension_item_id) - 在
dimensionitem表上创建复合索引:(id, dimension_id) - 确保
transaction表的transaction_id为主键(已索引)
- 动态维度扩展:新增维度时,只需在CTE中添加对应维度的数据集即可,核心关联逻辑无需修改
- 数据类型优化:将
percentage和amount改为DECIMAL类型,避免FLOAT精度丢失问题
内容的提问来源于stack exchange,提问作者jbar9999