Oracle SQL获取两日期间每周起止时间及时长计算问题排查
搞定ORA-01840错误,实现日期区间内每周起止时间与时长计算
为啥会报ORA-01840?
你碰到的这个错误,核心问题出在周起止时间的计算逻辑上:原SQL里用to_char(..., 'IW')拿到的是ISO周的编号(比如21、22这种数字),但你却把它当成日期字符串,用to_date(..., 'dd/mm/yyyy hh24:mi:ss')去转换——这显然不匹配啊,数字和日期格式完全对不上,数据库肯定解析失败。
另外原SQL还有些没必要的操作:反复把DATE类型转成字符串再转回DATE,既拖慢性能又容易踩格式坑,得改掉。
修正后的SQL代码
下面是调整后的完整SQL,直接基于DATE类型计算周边界,彻底避开字符串转换的坑,同时满足你的需求:
WITH date_range AS ( SELECT pdm.des_comercial serie, pdm.id_material codserie, ri.id_accion intervencion, TO_CHAR(NVL(ri.fecha_salida_rev, SYSDATE), 'dd/mm/RRRR') fecha1, TO_CHAR( CASE WHEN ri.fecha_salida_rev > TO_DATE('18/06/2019', 'dd/mm/yyyy') THEN TO_DATE('18/06/2019', 'dd/mm/yyyy') WHEN ri.fecha_salida_rev IS NULL THEN TO_DATE('18/06/2019', 'dd/mm/yyyy') ELSE ri.fecha_salida_rev END, 'dd/mm/yyyy hh24:mi:ss' ) fechasalida, TO_CHAR( CASE WHEN ri.fecha_entrada_rev < TO_DATE('01/06/2019', 'dd/mm/yyyy') THEN TO_DATE('01/06/2019', 'dd/mm/yyyy') ELSE ri.fecha_entrada_rev END, 'dd/mm/yyyy hh24:mi:ss' ) fechaentrada, ri.cod_taller_rev, ri.COD_MATRICULA, ri.fecha_entrada_rev start_date, -- 保留原生DATE类型,不转字符串 ri.fecha_salida_rev end_date -- 同上,避免格式转换错误 FROM r_intervencion ri JOIN planificador.pl_dh_material pdm ON pdm.id_material = ri.cod_serie AND pdm.hasta = 99999999 WHERE ri.id_accion = ri.amortizada_por AND ri.causa_entrada = 1 AND ri.tipo_accion = 1 AND ri.ID_ACCION = 'IM4' AND ri.fecha_salida_rev BETWEEN TO_DATE('01/06/2019', 'dd/mm/yyyy') AND TO_DATE('18/06/2019', 'dd/mm/yyyy') ), week_boundaries AS ( SELECT LEVEL AS week_num, -- 计算当前ISO周的周一00:00:00(ISO周的起始是周一) TRUNC(start_date, 'IW') + (7 * (LEVEL - 1)) AS week_start, -- 计算当前ISO周的周日23:59:59(下周一减1秒) TRUNC(start_date, 'IW') + (7 * LEVEL) - INTERVAL '1' SECOND AS week_end, serie, codserie, intervencion, cod_taller_rev, cod_matricula, fechaentrada, fechasalida, start_date, end_date FROM date_range -- 生成从start_date所在周到end_date所在周的所有周数 CONNECT BY LEVEL <= ((TRUNC(end_date, 'IW') - TRUNC(start_date, 'IW')) / 7) + 1 -- 防止多条记录时生成重复数据 AND PRIOR ri.id_accion = ri.id_accion AND PRIOR SYS_GUID() IS NOT NULL ), actual_intervals AS ( SELECT -- 实际时间段的起始:取周起始和原始start_date的较大值 GREATEST(week_start, start_date) AS interval_start, -- 实际时间段的结束:取周结束和原始end_date的较小值 LEAST(week_end, end_date) AS interval_end, -- 格式化成你要的dd/mm/yyyy hh24:mi:ss TO_CHAR(GREATEST(week_start, start_date), 'dd/mm/yyyy hh24:mi:ss') AS formatted_start, TO_CHAR(LEAST(week_end, end_date), 'dd/mm/yyyy hh24:mi:ss') AS formatted_end, -- 计算时长差(带小数的天数,比如0.3333就是8小时) LEAST(week_end, end_date) - GREATEST(week_start, start_date) AS duration_days, serie, codserie, intervencion, cod_taller_rev, cod_matricula, start_date, end_date, fechaentrada, fechasalida FROM week_boundaries ) SELECT formatted_start AS startweek, formatted_end AS endweek, duration_days AS dias, serie, codserie, intervencion, cod_taller_rev, cod_matricula, start_date, end_date, fechaentrada, fechasalida, rd.descripcion FROM actual_intervals JOIN r_depositos rd ON cod_taller_rev = rd.cod_deposito;
核心逻辑拆解
- 保留DATE类型:
date_range里直接用原生的start_date和end_date,不再做多余的字符串转换,从根源避免格式错误。 - 计算周边界:
TRUNC(date, 'IW'):把日期截断到所在ISO周的周一凌晨0点,这是ISO周的标准起始。- 用
LEVEL生成连续的周,每加7天就是下一周的起始。 - 周结束时间设为下周一的0点减1秒,也就是当前周周日的23:59:59,确保覆盖整周的最后一秒。
- 校准实际时间段:用
GREATEST和LEAST确保每个时间段不会超出你原始的start_date和end_date——比如你示例里最后一周只到05/06/2019 08:00:00,而不是周日的23:59:59。 - 时长计算:直接用DATE类型相减,得到的结果是带小数的天数,比如2.3333就代表2天8小时,完全符合时分秒的精度要求。
你的示例数据测试结果
用你给的示例:
start_date = 20/05/2019 20:00:00end_date = 05/06/2019 08:00:00
修正后的SQL会返回3条符合预期的记录:
- 第一段:
20/05/2019 20:00:00→26/05/2019 23:59:59,时长约6.1667天(6天3小时59分59秒) - 第二段:
27/05/2019 00:00:00→02/06/2019 23:59:59,时长整整7天 - 第三段:
03/06/2019 00:00:00→05/06/2019 08:00:00,时长2.3333天(2天8小时)
内容的提问来源于stack exchange,提问作者Carlota
相关产品推荐
相关产品推荐

