文章目录
- 一、业务背景
- 二、初级写法(反面案例)
-
- [问题 1:索引用不上](#问题 1:索引用不上)
- [问题 2:数据量大时性能灾难](#问题 2:数据量大时性能灾难)
- 三、基础优化写法(适合中小数据量)
- 四、企业实战解决方案
-
- [方案 1:生成列(STORED)+ 索引](#方案 1:生成列(STORED)+ 索引)
-
- [1. 增加生成列](#1. 增加生成列)
- [2. 查询变得非常快](#2. 查询变得非常快)
- [方案 2:数据仓库 / 报表系统的标准:日期维表 dim_date](#方案 2:数据仓库 / 报表系统的标准:日期维表 dim_date)
- 五、医院真实案例演示
- 六、总结
在医院信息系统中,我们经常需要统计各种业务数据,例如门诊到访人数、挂号量、就诊量等。
但一个常见需求是:
只统计工作日(周一 ~ 周五),周六周日不算!
乍一看很简单,但在实际数据库中,如果处理不好,很容易出现:
- 查询很慢(全表扫描 ALL)
- 索引用不上(在字段上套函数)
- 数据量大时 CPU 飙高
- 报表卡顿
一、业务背景
某医院有一张到访记录表:
sql
CREATE TABLE IF NOT EXISTS t_visit (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
hospital_id INT,
creatime DATETIME,
visit_num INT
);
每天有大量患者到访,后台需要统计:
每家医院在最近一个月的工作日到访总人数(排除周六周日)
二、初级写法(反面案例)
sql
SELECT
SUM(visit_num)
FROM t_visit
WHERE WEEKDAY(creatime) < 5;
这看似正确,但这会导致:
问题 1:索引用不上
因为你对 creatime 使用了函数:
WEEKDAY(creatime)
使得 MySQL 无法利用索引,explain 一般是:
type: ALL
rows: 全表行数

问题 2:数据量大时性能灾难
医院一天几十万条,一年几千万条,
全表扫 + 函数计算,直接拖垮服务器。
所以这不是企业级可接受的写法。
三、基础优化写法(适合中小数据量)
加上时间范围,利用 creatime 索引进行范围扫描
sql
CREATE INDEX idx_visit_creatime ON t_visit(creatime);
查询:
sql
EXPLAIN
SELECT
SUM(visit_num)
FROM t_visit
WHERE
creatime >= '2025-01-01'
AND creatime < '2025-02-01'
AND WEEKDAY(creatime) < 5;
EXPLAIN
SELECT
SUM(visit_num)
FROM t_visit FORCE INDEX (idx_visit_creatime)
WHERE
creatime >= '2025-01-01'
AND creatime < '2025-02-01'
AND WEEKDAY(creatime) < 5;
优势:
- MySQL 会先利用时间范围做索引扫描
- 再对扫描结果执行 weekday 过滤
- 比全表扫好很多
但 WEEKDAY() 仍然不是最佳方案。

四、企业实战解决方案
方案 1:生成列(STORED)+ 索引
医院数据往往需要长期统计、同比环比、报表分析,
企业里最常用的做法是:
把星期几提前算好,存在表里,并建立索引
1. 增加生成列
sql
ALTER TABLE t_visit
ADD COLUMN weekday TINYINT AS (WEEKDAY(creatime)) STORED,
ADD COLUMN is_workday TINYINT(1) AS (
CASE WHEN WEEKDAY(creatime) < 5 THEN 1 ELSE 0 END
) STORED,
ADD INDEX idx_visit_workday (is_workday, creatime);
weekday:0=周一,6=周日is_workday:1=工作日,0=周末

2. 查询变得非常快
sql
SELECT
hospital_id,
SUM(visit_num)
FROM t_visit
WHERE
is_workday = 1 -- 索引用得上!
AND creatime BETWEEN '2025-01-01' AND '2025-02-01'
GROUP BY hospital_id;

企业大部分系统都这么做。
方案 2:数据仓库 / 报表系统的标准:日期维表 dim_date
大型医院(尤其三甲)数据量很大,
通常会有数据仓库(DW)或 BI 系统。
在 DW 里几乎必建:
日期维表(dim_date)
例子:
sql
CREATE TABLE dim_date (
date_key DATE PRIMARY KEY,
weekday TINYINT,
is_workday TINYINT,
is_holiday TINYINT,
holiday_name VARCHAR(20)
);
再加关联字段:
sql
ALTER TABLE t_visit
ADD COLUMN visit_date DATE AS (DATE(creatime)) STORED,
ADD INDEX idx_visit_date (visit_date);
然后:
sql
SELECT
v.hospital_id,
SUM(v.visit_num) AS total_visit
FROM t_visit v
JOIN dim_date d
ON v.visit_date = d.date_key
WHERE
d.is_workday = 1
AND v.visit_date BETWEEN '2025-01-01' AND '2025-02-01'
GROUP BY v.hospital_id;
优势:
- 查询速度快(走索引)
- 节假日/补班规则可随时调整
- 所有报表口径统一
- 信息科、财务、运营都能复用这个维表

五、医院真实案例演示
需求:统计 2025 年 1 月各医院工作日的到访人数
SQL:
sql
SELECT
v.hospital_id,
d.is_workday,
SUM(v.visit_num) AS visit_total,
COUNT(*) AS visit_times
FROM t_visit v
JOIN dim_date d
ON v.visit_date = d.date_key
WHERE
v.visit_date >= '2025-01-01'
AND v.visit_date < '2025-02-01'
AND d.is_workday = 1 -- 只算工作日
GROUP BY v.hospital_id, d.is_workday
ORDER BY hospital_id;
输出示例:

六、总结
能跑的是 SQL,能跑快的是架构。
企业里处理"周一到周五统计"一般不会直接用 weekday 函数,而是通过生成列或日期维表实现高性能统计。