技术问询:MySQL结果一行转多行方法及订单日程自动化需求
嘿,针对你的两个问题,我来一步步帮你解决:
问题1:如何遍历MySQL查询结果,将单条数据拆分为多行?
要把单条数据按指定次数拆分成多行,核心思路是生成一个与拆分次数匹配的数字序列,再将这个序列和原表关联,从而把单条记录扩展成多行。下面分两种MySQL版本给出方案:
方案1:MySQL 8.0+(支持递归CTE)
递归CTE是最简洁的方式,先生成从1到最大拆分次数的数字序列,再关联原表过滤出需要的行数:
WITH RECURSIVE nums AS ( SELECT 1 AS n UNION ALL SELECT n + 1 FROM nums WHERE n < (SELECT MAX(total_baths) FROM orders) ) SELECT o.id, n AS bath_sequence FROM orders o JOIN nums n ON n.n <= o.total_baths;
解释:
nums递归CTE生成从1开始的连续数字,直到达到订单表中最大的洗浴次数(你可以替换成自己需要的字段)。- 关联订单表后,只要数字小于等于该订单的拆分次数,就会生成一行,这样每条订单就会被拆分成
total_baths行。
方案2:MySQL 5.x(不支持CTE)
如果你的MySQL版本不支持CTE,可以用笛卡尔积生成数字序列:
SELECT o.id, (a.n + b.n * 10 + c.n * 100) + 1 AS bath_sequence FROM orders o CROSS JOIN (SELECT 0 AS n UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) a CROSS JOIN (SELECT 0 AS n UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) b CROSS JOIN (SELECT 0 AS n UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) c WHERE (a.n + b.n * 10 + c.n * 100) + 1 <= o.total_baths;
解释:
- 三个包含0-9的子表笛卡尔积,生成0-999的数字,加1后变成1-1000,足够覆盖大部分场景。
- 通过WHERE条件过滤出不超过订单拆分次数的数字,实现单行转多行。
问题2:自动化日程安排中的订单洗浴任务拆分
先明确你的核心需求:
- 从订单中提取ID、交付日期,计算洗浴次数(订单价格÷125)
- 每个订单的洗浴任务必须在交付日期完成,每日最多完成2次/订单
- 同一工作日可安排多个2次任务,但不能来自同一订单(这里我理解为:同一工作日内,同一个订单的任务组不能连续安排,避免集中处理)
下面是完整的SQL解决方案:
WITH RECURSIVE order_baths AS ( -- 第一步:计算每个订单的基础信息和总洗浴次数 SELECT order_id, internal_delivery_date, order_price DIV 125 AS total_baths, -- 用整数除法计算洗浴次数,可根据需求换成ROUND/FLOOR -- 判断交付日期是否为工作日(0=周一,4=周五) CASE WHEN WEEKDAY(internal_delivery_date) BETWEEN 0 AND 4 THEN internal_delivery_date ELSE NULL END AS work_day FROM orders WHERE order_price >= 125 -- 过滤掉不需要洗浴的订单 ), task_groups AS ( -- 递归生成任务组:每组最多2次,直到剩余次数为0 SELECT order_id, internal_delivery_date, work_day, LEAST(2, total_baths) AS baths_in_group, total_baths - LEAST(2, total_baths) AS remaining_baths, 1 AS group_number FROM order_baths WHERE total_baths > 0 UNION ALL SELECT t.order_id, t.internal_delivery_date, t.work_day, LEAST(2, t.remaining_baths) AS baths_in_group, t.remaining_baths - LEAST(2, t.remaining_baths) AS remaining_baths, t.group_number + 1 AS group_number FROM task_groups t WHERE t.remaining_baths > 0 ), ranked_tasks AS ( -- 给任务排名,实现同一订单的任务组在工作日内分散安排 SELECT *, ROW_NUMBER() OVER (PARTITION BY work_day ORDER BY order_id, group_number) AS task_rank, ROW_NUMBER() OVER (PARTITION BY work_day, order_id ORDER BY group_number) AS order_group_rank FROM task_groups WHERE work_day IS NOT NULL -- 只保留工作日的任务 ) -- 最终输出分散后的任务列表 SELECT order_id, internal_delivery_date, work_day, baths_in_group, group_number FROM ranked_tasks ORDER BY work_day, order_group_rank, task_rank;
关键步骤解释:
order_bathsCTE:计算每个订单的总洗浴次数,同时筛选出工作日的交付日期(如果交付日期是周末,这里会排除,你可以根据需求调整)。task_groupsCTE:递归拆分任务,每次取最多2次作为一个任务组,直到该订单的所有洗浴次数都被拆分完成。ranked_tasksCTE:通过窗口函数给任务排名,确保同一订单的任务组在工作日内不会连续出现,满足“同一工作日可有多组2次任务,但不可来自同一订单”的需求。- 最后按工作日、订单组排名、任务排名排序,输出最终的任务安排列表。
内容的提问来源于stack exchange,提问作者F. LK
相关产品推荐
相关产品推荐

