日常总结:大数量级表多层JOIN连接查询效率慢问题的解决方案
Posted 兴趣使然的草帽路飞
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了日常总结:大数量级表多层JOIN连接查询效率慢问题的解决方案相关的知识,希望对你有一定的参考价值。
1、订单信息表
假设有一张 TB 级别的大表(订单信息表) oeder_info,表字段如下:
prov_id(省ID) | city_id(市ID) | area_id(区ID) | town_id(街道ID) | date(下单日期) | order_type(订单类型:tc同城/not_tc非同城) | order_money(订单金额:元) |
---|---|---|---|---|---|---|
001 | 00101 | 0010101 | 001010101 | 2021-12-06 | tc | 100 |
001 | 00101 | 0010102 | 001010203 | 2021-12-06 | tc | 100 |
002 | 00202 | 0020202 | 002020202 | 2021-12-07 | not_tc | 500 |
003 | 00303 | 0030303 | 003030303 | 2021-12-08 | tc | 800 |
… | … | … | … | … | … | … |
2、订单行政区域维度表
另一张 TB 级别的大表(订单行政区域维度表) dim_order_area ,表字段如下:
prov_id(省ID) | prov_name(省名称) | city_id(市ID) | city_name(市名称) | area_id(区ID) | area_name(区名称) | town_id(街道ID) | town_name(街道名称) |
---|---|---|---|---|---|---|---|
001 | 河南省 | 00101 | 洛阳市 | 0010101 | 涧西区 | 001010101 | 西苑街道 |
001 | 河南省 | 00101 | 洛阳市 | 0010102 | 洛龙区 | 001010203 | 开元大道 |
001 | … | … | … | … | … | … | … |
002 | 浙江省 | 00202 | 杭州市 | 0020202 | 西湖区 | 002020202 | 蒋村街道 |
002 | … | … | … | … | … | … | … |
003 | 上海 | 00303 | 上海市 | 0030303 | 杨浦区 | 003030303 | 五角场街道 |
003 | … | … | … | … | … | … | … |
… | … | … | … | … | … | … | … |
3、两表的拼接需求
现在我们需要将两张表进行关联,让 oeder_info 外连接 dim_order_area 得到包含省/市/区ID,以及省/市/区名称的完整的订单明细表,要求明细表中包含各个街道维度的订单总数、订单总金额、非同城订单总数、按照订单日期(天维度)分组,且只筛选 2021 年的订单。
prov_id(省ID) | prov_name(省名称) | city_id(市ID) | city_name(市名称) | area_id(区ID) | area_name(区名称) | town_id(街道ID) | town_name(街道名称) | date(下单日期) | not_tc_order_count(街道维度的非同城上门订单数量) | 街total_order_money(道维度的总交易金额) |
---|---|---|---|---|---|---|---|---|---|---|
001 | 河南省 | 00101 | 洛阳市 | 0010102 | 洛龙区 | 001010203 | 开元大道 | 2021-12-06 | 98 | 9800 |
我使用如下SQL语句去查询(未优化调整):
SELECT
order_tab.prov_id AS prov_id, -- 省ID
area_tab.prov_name AS prov_name, -- 省名称
order_tab.city_id AS city_id, -- 市ID
area_tab.name AS city_name, -- 市名称
order_tab.area_id AS area_id, -- 区ID
area_tab.area_name AS area_name, -- 区名称
order_tab.town_id AS town_id, -- 街道ID
area_tab.town_name AS town_name, -- 街道名称
order_tab.date AS sign_date, -- 订单日期
COUNT(*) AS town_total, -- 统计总订单数量
-- 统计非同城订单总数
SUM(CASE WHEN order_tab.order_type = 'not_tc' THEN 1 ELSE 0 END) AS not_tc_order_count,
SUM(order_tab.order_money) AS total_order_money -- 统计街道维度的订单总金额
FROM
order_info AS order_tab -- 订单信息表
LEFT JOIN
-- 左外连接订单行政区域维度表
dim_order_area AS area_tab
ON
order_tab.prov_id = area_tab.prov_id AND
order_tab.city_id = area_tab.city_id AND
order_tab.area_id = area_tab.area_id AND
order_tab.town_id = area_tab.town_id
GROUP BY -- 按照如下字段进行分组
order_tab.city_id
, order_tab.prov_id
, order_tab.area_id
, order_tab.town_id
, order_tab.date
,area_tab.prov_name
,area_tab.city_name
,area_tab.county_name
,area_tab.town_name
HAVING
YEAR(sign_date) = 2021; -- 只筛选2021年的订单
以上SQL虽然可以解决上述查询需求,但是会遇到一个问题,如下所示:
...
LEFT JOIN
-- 左外连接订单行政区域维度表
dim_order_area AS area_tab
ON --
order_tab.prov_id = area_tab.prov_id AND
order_tab.city_id = area_tab.city_id AND
order_tab.area_id = area_tab.area_id AND
order_tab.town_id = area_tab.town_id
...
这里通过一连串的AND
拼接判断条件(并集),得到的结果可能会有误差,假如某个订单信息表中的town_id是空,其他省市区ID是正常的,那么他就无法被该LEFT JOIN拼接的条件匹配到,会被直接舍弃。
改进方案:
将LEFT JOIN
并集拼接判断条件方式改成单个逐次拼接:
LEFT JOIN
-- 左外连接订单行政区域维度表(拼接省份名称,*并通过DISTINCT去重复*)
(SELECT prov_name,prov_id FROM dim_order_area) AS area_tab1
ON
order_tab.prov_id = area_tab1.prov_id
LEFT JOIN
-- 左外连接订单行政区域维度表(拼接市名称,*并通过DISTINCT去重复*)
(SELECT city_id,city_name FROM dim_order_area) AS area_tab2
ON
order_tab.city_id = area_tab2.city_id
LEFT JOIN
-- 左外连接订单行政区域维度表(拼接区名称,*并通过DISTINCT去重复*)
(SELECT county_id,county_name FROM dim_order_area) AS area_tab3
ON
order_tab.area_id = area_tab3.county_id
LEFT JOIN
-- 左外连接订单行政区域维度表(拼接街道名称,*并通过DISTINCT去重复*)
(SELECT town_id,town_name FROM dim_order_area) AS area_tab4
ON
order_tab.town_id = area_tab4.town_id
但是也会遇到一个问题:查询时间过长!
- 原因:多次
LEFT JOIN
,每次子查询都要将两张表拼接之后生成一个中间表,然后连续4次做笛卡尔积拼接。 - 解决方案:
DISTINCT
关键字去重:去掉每次子查询中重复的数据(eg: 第一次只筛选出省份去重复,第二次只筛选出城市去重复…),这样就可以很大程度上降低中间表生成的成本,加快查询时间。
调整优化过后的SQL语句:
SELECT
order_tab.prov_id AS prov_id, -- 省ID
area_tab1.prov_name AS prov_name, -- 省名称
order_tab.city_id AS city_id, -- 市ID
area_tab2.name AS city_name, -- 市名称
order_tab.area_id AS area_id, -- 区ID
area_tab3.area_name AS area_name, -- 区名称
order_tab.town_id AS town_id, -- 街道ID
area_tab4.town_name AS town_name, -- 街道名称
order_tab.date AS sign_date, -- 订单日期
COUNT(*) AS town_total, -- 统计总订单数量
-- 统计非同城订单总数
SUM(CASE WHEN order_tab.order_type = 'not_tc' THEN 1 ELSE 0 END) AS not_tc_order_count,
SUM(order_tab.order_money) AS order_money -- 统计街道维度的订单总金额
FROM
order_info AS order_tab -- 订单信息表
LEFT JOIN
-- 左外连接订单行政区域维度表(拼接省份名称,*并通过DISTINCT去重复*)
(SELECT DISTINCT prov_name,prov_id FROM dim_order_area) AS area_tab1
ON
order_tab.prov_id = area_tab1.prov_id
LEFT JOIN
-- 左外连接订单行政区域维度表(拼接市名称,*并通过DISTINCT去重复*)
(SELECT DISTINCT city_id,city_name FROM dim_order_area) AS area_tab2
ON
order_tab.city_id = area_tab2.city_id
LEFT JOIN
-- 左外连接订单行政区域维度表(拼接区名称,*并通过DISTINCT去重复*)
(SELECT DISTINCT county_id,county_name FROM dim_order_area) AS area_tab3
ON
order_tab.area_id = area_tab3.county_id
LEFT JOIN
-- 左外连接订单行政区域维度表(拼接街道名称,*并通过DISTINCT去重复*)
(SELECT DISTINCT town_id,town_name FROM dim_order_area) AS area_tab4
ON
order_tab.town_id = area_tab4.town_id
GROUP BY -- 按照如下字段进行分组
order_tab.city_id
, order_tab.prov_id
, order_tab.area_id
, order_tab.town_id
, order_tab.date
,area_tab1.prov_name
,area_tab2.city_name
,area_tab3.county_name
,area_tab4.town_name
HAVING
YEAR(sign_date) = 2021; -- 只筛选2021年的订单
以上是关于日常总结:大数量级表多层JOIN连接查询效率慢问题的解决方案的主要内容,如果未能解决你的问题,请参考以下文章
关于SQL 查询效率问题 left join 改成 inner join union
在sql语句多表连接中,in、exists、join哪个效率更高一点?