Sql 如何编写Oracle查询以查找从到日期可能重叠的总长度
我正在努力找到以下任务的查询 我有以下数据,希望找到每个唯一ID的总网络日数Sql 如何编写Oracle查询以查找从到日期可能重叠的总长度,sql,oracle,Sql,Oracle,我正在努力找到以下任务的查询 我有以下数据,希望找到每个唯一ID的总网络日数 ID From To NetworkDay 1 03-Sep-12 07-Sep-12 5 1 03-Sep-12 04-Sep-12 2 1 05-Sep-12 06-Sep-12 2 1 06-Sep-12 12-Sep-12 5 1 31-Aug-12 04-Sep-12 3 2 04-Sep-12 06-Sep
ID From To NetworkDay
1 03-Sep-12 07-Sep-12 5
1 03-Sep-12 04-Sep-12 2
1 05-Sep-12 06-Sep-12 2
1 06-Sep-12 12-Sep-12 5
1 31-Aug-12 04-Sep-12 3
2 04-Sep-12 06-Sep-12 3
2 11-Sep-12 13-Sep-12 3
2 05-Sep-12 08-Sep-12 3
问题是日期范围可能会重叠,并且我无法生成SQL来提供以下结果
ID From To NetworkDay
1 31-Aug-12 12-Sep-12 9
2 04-Sep-12 08-Sep-12 4
2 11-Sep-12 13-Sep-12 3
然后
ID Total Network Day
1 9
2 7
如果无法计算网络日,只需进入第二个表即可
希望我的问题很清楚如何构造一个SQL,通过删除漏洞并只考虑最大间隔来合并间隔。它是这样的,没有经过测试:
SELECT DISTINCT F.ID, F.From, L.To
FROM Temp AS F, Temp AS L
WHERE F.From < L.To AND F.ID = L.ID
AND NOT EXISTS (SELECT *
FROM Temp AS T
WHERE T.ID = F.ID
AND F.From < T.From AND T.From < L.To
AND NOT EXISTS ( SELECT *
FROM Temp AS T1
WHERE T1.ID = F.ID
AND T1.From < T.From
AND T.From <= T1.To)
)
AND NOT EXISTS (SELECT *
FROM Temp AS T2
WHERE T2.ID = F.ID
AND (
(T2.From < F.From AND F.From <= T2.To)
OR (T2.From < L.To AND L.To < T2.To)
)
)
我们可以使用Oracle分析,即。。。PARTITION BY子句,在Oracle中执行此操作。PARTITION BY子句类似于GROUP BY,但没有聚合部分。这意味着我们可以将行分组在一起,即对它们进行分区,并将它们作为单独的组对其执行操作。当我们对每一行进行操作时,我们就可以访问上面前一行的列。这是功能分区给我们的。分区依据与表的分区性能无关 那么,我们如何输出不重叠的日期呢?我们首先根据ID、DFROM字段对查询进行排序,然后使用ID字段将分区设为行组。然后,我们使用类似于:in伪代码的表达式测试前一行的TO值和当前行FROM值是否重叠
max(previous.DTO, current.DFROM) as DFROM
如果这个基本表达式没有重叠,它将返回原始的DFROM值,但是如果有重叠,它将返回上一个TO值。因为我们的行是有序的,所以我们只需要关注最后一行。如果前一行与当前行完全重叠,我们希望该行的日期范围为“零”。因此,我们对DTO字段执行相同的操作,以获得:
max(previous.DTO, current.DFROM) as DFROM, max(previous.DTO, current.DTO) as DTO
一旦我们用调整后的DFROM和DTO值生成了新的结果集,我们就可以将它们聚合起来,并计算DFROM和DTO的范围间隔
请注意,数据库中的大多数日期计算都不包括在内,例如您的数据。所以像DATEDIFFdto,dfrom这样的东西不包括dto实际指的日期,所以我们首先要将dto调整为一天
我不再有访问Oracle服务器的权限,但我知道这在Oracle Analytics中是可能的。查询应该是这样的:
请更新我的帖子,如果你得到这个工作
SELECT id,
max(dfrom, LAST_VALUE(dto) OVER (PARTITION BY id ORDER BY dfrom) ) as dfrom,
max(dto, LAST_VALUE(dto) OVER (PARTITION BY id ORDER BY dfrom) ) as dto
from (
select id, dfrom, dto+1 as dto from my_sample -- adjust the table so that dto becomes non-inclusive
order by id, dfrom
) sample;
这里的秘密是通过返回当前行之前的值的表达式按id顺序进行分区的最后一个_值。
因此,这个查询应该输出不重叠的新dfrom/dto值。然后,只需从数据到数据进行子查询,并求和总数
使用MySQL
我确实有权访问mysql服务器,所以我确实让它在那里工作。MySQL没有像Oracle那样的结果分区分析,所以我们必须使用结果集变量。这意味着我们使用@var:=xxx类型的表达式来记住最后的日期值,并根据需要调整dfrom/dto。相同的算法只是语法稍微长一点,更复杂一点。我们还必须在ID字段更改时忘记最后日期值
因此,以下是与您的值相同的示例表:
create table sample(id int, dfrom date, dto date, networkDay int);
insert into sample values
(1,'2012-09-03','2012-09-07',5),
(1,'2012-09-03','2012-09-04',2),
(1,'2012-09-05','2012-09-06',2),
(1,'2012-09-06','2012-09-12',5),
(1,'2012-08-31','2012-09-04',3),
(2,'2012-09-04','2012-09-06',3),
(2,'2012-09-11','2012-09-13',3),
(2,'2012-09-05','2012-09-08',3);
在查询中,我们输出未分组的结果集,如下所示:
变量@ld是最后一个日期,变量@lid是最后一个id。只要@lid发生变化,我们就会将@ld重置为null。FYI在mysql中:=运算符是赋值发生的地方,an=运算符正好等于
这是一个3级查询,但可以简化为2级。我使用了一个额外的外部查询,以使内容更具可读性。最内部的查询很简单,它将dto列调整为非包容性,并执行正确的行排序。中间查询调整dfrom/dto值,使其不重叠。外部查询简单删除未使用的字段,并计算区间范围
set @ldt=null, @lid=null;
select id, no_dfrom as dfrom, no_dto as dto, datediff(no_dto, no_dfrom) as days from (
select if(@lid=id,@ldt,@ldt:=null) as last, dfrom, dto, if(@ldt>=dfrom,@ldt,dfrom) as no_dfrom, if(@ldt>=dto,@ldt,dto) as no_dto, @ldt:=if(@ldt>=dto,@ldt,dto), @lid:=id as id,
datediff(dto, dfrom) as overlapped_days
from (select id, dfrom, dto + INTERVAL 1 DAY as dto from sample order by id, dfrom) as sample
) as nonoverlapped
order by id, dfrom;
上面的查询给出的结果通知dfrom/dto在此不重叠:
+------+------------+------------+------+
| id | dfrom | dto | days |
+------+------------+------------+------+
| 1 | 2012-08-31 | 2012-09-05 | 5 |
| 1 | 2012-09-05 | 2012-09-08 | 3 |
| 1 | 2012-09-08 | 2012-09-08 | 0 |
| 1 | 2012-09-08 | 2012-09-08 | 0 |
| 1 | 2012-09-08 | 2012-09-13 | 5 |
| 2 | 2012-09-04 | 2012-09-07 | 3 |
| 2 | 2012-09-07 | 2012-09-09 | 2 |
| 2 | 2012-09-11 | 2012-09-14 | 3 |
+------+------------+------------+------+
t_数据-您的初始数据
t_假日-包含假日列表
t_data_rn-只需将唯一键rownum添加到t_数据的每一行
t_模型-将t_数据日期范围扩展为日期的平面列表
t_network_days-根据星期六、星期日和假日列表,将t_模型中的每个日期标记为工作日或周末
最终查询-计算每个组的网络日数。
合并范围的规则是什么?例如,您如何知道2012年9月4日到2012年9月8日应该是4天而不是5天?我猜这和周末不算有什么关系?嗨,安德鲁斯,你说得对,这个想法是把周末排除在外。但是,这一部分并不是很重要,因为我可以在以后得到第二个表时计算它。所以我们可以在得到第一个表的结果时忽略NetworkDay列的值?第二个结果表很简单:从第一个表中按id分组选择id,sumnetworkday,不是吗?不是,因为我不想重复计算重叠部分。查询ab
ove将获得ID 2的9个网络日,因为它将两次计算9月5日和9月6日。9月5日和6日在ID的第一行和最后一行重叠2@Roby:您可以使用此相关问题中描述的方法来构建连续的日期范围:
with t_data as (
select 1 as id,
to_date('03-sep-12','dd-mon-yy') as start_date,
to_date('07-sep-12','dd-mon-yy') as end_date from dual
union all
select 1,
to_date('03-sep-12','dd-mon-yy'),
to_date('04-sep-12','dd-mon-yy') from dual
union all
select 1,
to_date('05-sep-12','dd-mon-yy'),
to_date('06-sep-12','dd-mon-yy') from dual
union all
select 1,
to_date('06-sep-12','dd-mon-yy'),
to_date('12-sep-12','dd-mon-yy') from dual
union all
select 1,
to_date('31-aug-12','dd-mon-yy'),
to_date('04-sep-12','dd-mon-yy') from dual
union all
select 2,
to_date('04-sep-12','dd-mon-yy'),
to_date('06-sep-12','dd-mon-yy') from dual
union all
select 2,
to_date('11-sep-12','dd-mon-yy'),
to_date('13-sep-12','dd-mon-yy') from dual
union all
select 2,
to_date('05-sep-12','dd-mon-yy'),
to_date('08-sep-12','dd-mon-yy') from dual
),
t_holidays as (
select to_date('01-jan-12','dd-mon-yy') as holiday
from dual
),
t_data_rn as (
select rownum as rn, t_data.* from t_data
),
t_model as (
select distinct id,
start_date
from t_data_rn
model
partition by (rn, id)
dimension by (0 as i)
measures(start_date, end_date)
rules
( start_date[for i
from 1
to end_date[0]-start_date[0]
increment 1] = start_date[0] + cv(i),
end_date[any] = start_date[cv()] + 1
)
order by 1,2
),
t_network_days as (
select t_model.*,
case when
mod(to_char(start_date, 'j'), 7) + 1 in (6, 7)
or t_holidays.holiday is not null
then 0 else 1
end as working_day
from t_model
left outer join t_holidays
on t_holidays.holiday = t_model.start_date
)
select id,
sum(working_day) as network_days
from t_network_days
group by id;