您好,登录后才能下订单哦!
密码登录
登录注册
点击 登录注册 即表示同意《亿速云用户服务条款》
# 用SQL只统计工作日数据的方法步骤
## 引言
在日常数据分析和报表生成过程中,经常需要统计工作日的业务数据。与自然日不同,工作日通常指周一至周五(排除法定节假日)。本文将详细介绍如何使用SQL实现仅统计工作日数据的多种方法,包括基础日期函数、日历表设计、递归CTE应用以及结合编程语言的混合方案。
## 一、理解工作日统计的核心问题
### 1.1 工作日定义
- 标准工作日:周一至周五(不考虑节假日)
- 实际工作日:排除法定节假日和调休日
- 特殊行业:如医院、工厂可能有不同排班规则
### 1.2 常见业务场景
- 计算工作日平均销售额
- 生成工作日KPI报表
- 分析工作日/节假日流量差异
## 二、基础解决方案:使用日期函数
### 2.1 标准工作日判断(不考虑节假日)
```sql
-- MySQL示例
SELECT
order_date,
COUNT(*) AS order_count
FROM orders
WHERE DAYOFWEEK(order_date) BETWEEN 2 AND 6 -- 周一(2)到周五(6)
GROUP BY order_date;
-- SQL Server示例
SELECT
order_date,
COUNT(*) AS order_count
FROM orders
WHERE DATEPART(WEEKDAY, order_date) BETWEEN 2 AND 6 -- 注意周日=1
GROUP BY order_date;
数据库 | 函数 | 返回值范围 | 注意事项 |
---|---|---|---|
MySQL | DAYOFWEEK() | 1(日)-7(六) | 周日为1 |
SQL Server | DATEPART(WEEKDAY,) | 1(日)-7(六) | 依赖@@DATEFIRST设置 |
Oracle | TO_CHAR(d,’D’) | 1(日)-7(六) | 依赖NLS_TERRITORY设置 |
PostgreSQL | EXTRACT(DOW FROM d) | 0(日)-6(六) | 周日为0 |
CREATE TABLE calendar (
date_id DATE PRIMARY KEY,
day_of_week TINYINT, -- 1-7
is_weekend BOOLEAN,
is_holiday BOOLEAN,
holiday_name VARCHAR(50),
is_working_day BOOLEAN
);
-- 生成2020-2030年的日历数据
DELIMITER //
CREATE PROCEDURE GenerateCalendar(start_year INT, end_year INT)
BEGIN
DECLARE curr_date DATE;
SET curr_date = DATE(CONCAT(start_year, '-01-01'));
WHILE YEAR(curr_date) <= end_year DO
INSERT INTO calendar (
date_id,
day_of_week,
is_weekend,
is_holiday,
is_working_day
) VALUES (
curr_date,
DAYOFWEEK(curr_date),
DAYOFWEEK(curr_date) IN (1,7),
FALSE, -- 需要手动更新节假日
DAYOFWEEK(curr_date) BETWEEN 2 AND 6
) ON DUPLICATE KEY UPDATE
day_of_week = VALUES(day_of_week),
is_weekend = VALUES(is_weekend);
SET curr_date = DATE_ADD(curr_date, INTERVAL 1 DAY);
END WHILE;
END //
DELIMITER ;
-- 统计2023年Q1工作日销售额
SELECT
c.date_id,
SUM(o.amount) AS daily_sales
FROM orders o
JOIN calendar c ON o.order_date = c.date_id
WHERE c.date_id BETWEEN '2023-01-01' AND '2023-03-31'
AND c.is_working_day = TRUE
AND c.is_holiday = FALSE
GROUP BY c.date_id;
-- 添加节假日数据
UPDATE calendar
SET is_holiday = TRUE,
holiday_name = '元旦',
is_working_day = FALSE
WHERE date_id IN ('2023-01-01', '2023-01-02');
-- 处理调休工作日(周末变为工作日)
UPDATE calendar
SET is_working_day = TRUE,
is_weekend = FALSE
WHERE date_id = '2023-01-28'; -- 春节调休示例
对于需要频繁更新节假日的情况,可以创建单独的表:
CREATE TABLE holidays (
date_id DATE PRIMARY KEY,
holiday_name VARCHAR(100),
is_working_day BOOLEAN -- 调休日标记
);
然后通过视图关联:
CREATE VIEW vw_working_days AS
SELECT
c.date_id,
c.day_of_week,
CASE
WHEN h.date_id IS NOT NULL THEN h.is_working_day
ELSE c.is_working_day
END AS actual_working_day
FROM calendar c
LEFT JOIN holidays h ON c.date_id = h.date_id;
-- SQL Server示例
WITH DateSeries AS (
SELECT CAST('2023-01-01' AS DATE) AS date_id
UNION ALL
SELECT DATEADD(DAY, 1, date_id)
FROM DateSeries
WHERE DATEADD(DAY, 1, date_id) <= '2023-12-31'
)
SELECT date_id
FROM DateSeries
WHERE DATEPART(WEEKDAY, date_id) NOT IN (1, 7)
OPTION (MAXRECURSION 366);
-- 使用generate_series函数
SELECT day_date
FROM generate_series(
'2023-01-01'::date,
'2023-12-31'::date,
'1 day'::interval
) AS day_date
WHERE EXTRACT(DOW FROM day_date) NOT IN (0, 6);
-- 为日历表创建索引
CREATE INDEX idx_calendar_date ON calendar(date_id);
CREATE INDEX idx_calendar_workingday ON calendar(is_working_day);
-- 为业务表创建日期索引
CREATE INDEX idx_orders_date ON orders(order_date);
-- PostgreSQL示例
CREATE MATERIALIZED VIEW mv_workingday_sales AS
SELECT
o.order_date,
SUM(o.amount) AS total_sales
FROM orders o
JOIN calendar c ON o.order_date = c.date_id
WHERE c.is_working_day = TRUE
GROUP BY o.order_date;
-- 定期刷新
REFRESH MATERIALIZED VIEW mv_workingday_sales;
-- 使用时区转换函数
SELECT
DATE(CONVERT_TZ(order_time, '+00:00', '+08:00')) AS local_date,
COUNT(*) AS orders
FROM international_orders
GROUP BY local_date
HAVING DAYOFWEEK(local_date) BETWEEN 2 AND 6;
SELECT
CASE
WHEN HOUR(order_time) < 12 THEN '上午'
ELSE '下午'
END AS time_period,
COUNT(*) AS orders
FROM orders
WHERE DAYOFWEEK(order_date) BETWEEN 2 AND 6
GROUP BY time_period;
DELIMITER //
CREATE PROCEDURE GetWorkingDaysReport(
IN start_date DATE,
IN end_date DATE
)
BEGIN
-- 创建临时表存储结果
CREATE TEMPORARY TABLE temp_results (
report_date DATE,
metric_value DECIMAL(15,2)
);
-- 使用游标遍历日期范围
DECLARE curr_date DATE DEFAULT start_date;
DECLARE done INT DEFAULT FALSE;
WHILE curr_date <= end_date DO
-- 检查是否为工作日
IF (DAYOFWEEK(curr_date) BETWEEN 2 AND 6) THEN
-- 执行业务计算
INSERT INTO temp_results
SELECT
curr_date,
SUM(amount)
FROM orders
WHERE DATE(order_time) = curr_date;
END IF;
SET curr_date = DATE_ADD(curr_date, INTERVAL 1 DAY);
END WHILE;
-- 返回结果
SELECT * FROM temp_results;
DROP TEMPORARY TABLE temp_results;
END //
DELIMITER ;
-- 计算2023年各月工作日平均GMV
SELECT
YEAR(c.date_id) AS year,
MONTH(c.date_id) AS month,
COUNT(DISTINCT c.date_id) AS working_days,
SUM(o.amount) / COUNT(DISTINCT c.date_id) AS avg_daily_gmv
FROM orders o
JOIN calendar c ON DATE(o.created_at) = c.date_id
WHERE c.is_working_day = TRUE
AND YEAR(c.date_id) = 2023
GROUP BY YEAR(c.date_id), MONTH(c.date_id)
ORDER BY year, month;
-- 找出连续3个交易日上涨的股票
WITH daily_prices AS (
SELECT
stock_code,
trade_date,
close_price,
LAG(close_price, 1) OVER (PARTITION BY stock_code ORDER BY trade_date) AS prev_price
FROM stock_trades
WHERE is_trading_day = 1 -- 交易日标记
),
rising_days AS (
SELECT
stock_code,
trade_date,
SUM(CASE WHEN close_price > prev_price THEN 1 ELSE 0 END)
OVER (PARTITION BY stock_code ORDER BY trade_date ROWS 2 PRECEDING) AS rising_streak
FROM daily_prices
)
SELECT DISTINCT stock_code
FROM rising_days
WHERE rising_streak = 3;
场景 | 推荐方案 | 优点 | 缺点 |
---|---|---|---|
简单标准工作日 | 日期函数 | 无需额外表 | 无法处理节假日 |
长期复杂业务系统 | 日历表 | 灵活可维护 | 需要初始化数据 |
临时分析 | 递归CTE | 无需准备数据 | 性能较差 |
多国家节假日 | 混合编程+数据库存储过程 | 处理最复杂场景 | 实现复杂度高 |
通过本文介绍的各种方法,您可以根据具体业务需求和技术环境,选择最适合的SQL工作日统计方案。正确的实现不仅能提高报表准确性,还能显著优化查询性能。
字数统计:约3,450字(含代码示例) “`
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。