今天碰到个案例和大家分享下,需求为:用某些可选的明细表条件,过滤表头。比如,查询商品名称包含XXX的订单
假设订单单头表为t_order
, 订单明细表为t_order_dtl,结构如下:
CREATE TABLE `t_order` (
`id` bigint NOT NULL COMMENT 'id',
`customer_id` bigint unsigned NOT NULL DEFAULT '0' COMMENT '客户id',
`store_id` bigint unsigned NOT NULL DEFAULT '0' COMMENT '分公司id',
`paper_no` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '订单号',
`order_type_name` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '订单类型名称',
`delivery_date` datetime NOT NULL DEFAULT '2020-01-01 00:00:00' COMMENT '送货时间 到时分秒',
`tag` int NOT NULL DEFAULT '0' COMMENT '订单状态:0.订单提交;1.订单取消;2.订单审核;',
`note` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '备注',
`c_t` bigint NOT NULL DEFAULT '0' COMMENT '创建时间',
`c_u` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '创建人',
`c_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '创建人名称',
`u_t` bigint NOT NULL DEFAULT '0' COMMENT '最后修改时间',
`u_u` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '更新人',
`u_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '修改人名称',
`version` int NOT NULL DEFAULT '0' COMMENT '版本号 用于乐观锁',
PRIMARY KEY (`id`,`store_id`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='订单表'
/*!50100 PARTITION BY KEY (store_id)
PARTITIONS 8 */;
CREATE TABLE `t_order_dtl` (
`id` bigint NOT NULL COMMENT 'id',
`store_id` bigint unsigned NOT NULL DEFAULT '0' COMMENT '分公司id',
`paper_no` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'app订单号',
`item` int NOT NULL DEFAULT '0' COMMENT '订单项次',
`ssu_id` bigint NOT NULL DEFAULT '0' COMMENT '商品ssu-id',
`ssu_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '商品名称',
`spec` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '商品规格',
`unit` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '商品单位',
`order_qty` decimal(26,6) NOT NULL DEFAULT '0.000000' COMMENT '实订数量',
`order_price` decimal(26,6) NOT NULL DEFAULT '0.000000' COMMENT '订货单价',
`ssu_amount` decimal(26,6) NOT NULL DEFAULT '0.000000' COMMENT '商品金额',
`price_paper_no` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '价格单号',
`note` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '明细备注',
`status` tinyint NOT NULL DEFAULT '1' COMMENT '状态, 0 删除,1 生效',
`c_t` bigint NOT NULL DEFAULT '0' COMMENT '创建时间',
`c_u` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '创建人',
`c_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '创建人名称',
`u_t` bigint NOT NULL DEFAULT '0' COMMENT '最后修改时间',
`u_u` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '更新人',
`u_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '修改人名称',
`version` int unsigned NOT NULL DEFAULT '0' COMMENT '版本号 用于乐观锁',
PRIMARY KEY (`id`,`store_id`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='订单明细表'
/*!50100 PARTITION BY KEY (store_id)
PARTITIONS 8 */;
假设以前的查询单头语句为:
SELECT
*
from t_order
where 1 = 1
/* 以下为查询单头的条件 */
同事如下做法:
SELECT
o.*
from t_order o
left join t_order_dtl d on o.paper_no = d.paper_no
where 1 = 1
/* 以下为查询单头的条件 */
and o.paper_no = 'XXX'
/* 若选择明细条件则加上该条件*/
and d.ssu_name like '%XXX%'
很快发现多了重复单头,于是又加上group by 滤重
SELECT
o.*
from t_order o
left join t_order_dtl d on o.paper_no = d.paper_no
where 1 = 1
/* 以下为查询单头的条件 */
and o.paper_no = 'XXX'
/* 若选择明细条件则加上该条件*/
and d.ssu_name like '%XXX%'
GROUP BY [o.id](http://o.id)
后面结果经过反复折腾,貌似正确了。可是,这样做有巨多风险:
1、明细条件是可选的,上面语句为了某些场景可能用的明细条件,关联上明细表,导致所有场景都查明细表,性能有所损失。
2、因为关联上了明细表,单头为了滤重,强行加上了group by(或者distinct),导致语义变模糊,增加维护成本。
优选实践:
改成下面这样虽然好了一点,但仍不优雅
SELECT
o.*
from t_order o
/* 若存在明细查询条件则加上明细表关联 */
left join t_order_dtl d on o.paper_no = d.paper_no
where 1 = 1
/* 以下为查询单头的条件 */
and d.ssu_name like '%XXX%'
使用EXISTS更为优雅,具体如下:
SELECT
o.*
from t_order o
where 1 = 1
/* 以下为查询单头的条件 */
and o.paper_no = 'XXX'
/* 若选择明细条件则加上该条件*/
and EXISTS (
SELECT 1
from t_order_dtl d
where o.paper_no = d.paper_no
and d.ssu_name like '%XXX%'
)
总结:
案例非常简单,仅是Mysql一个关键字的使用,分享的核心不是这个,而是实际工作中过程中吗,面对问题解决问题的过程。