我有一个数据库表,其中有三列对此问题至关重要:
我想从此表中创建一个视图,以便具有相同分组ID的重叠日期间隔被展平。
不重叠的日期间隔不得展平。
示例:
Group ID Start End
1 2016-01-01 2017-12-31
1 2016-06-01 2020-01-01
1 2022-08-31 2030-12-31
2 2010-03-01 2017-01-01
2 2012-01-01 2013-12-31
3 2001-01-01 9999-13-31
... ...变为
Group ID Start End
1 2016-01-01 2020-01-01
1 2022-08-31 2030-12-31
2 2010-03-01 2017-01-01
3 2001-01-01 9999-12-31
重叠的间隔可能以任何方式完成,完全被其他间隔包围,或者它们可能交错,或者它们甚至可能具有相同的开始和/或结束日期。
很少类似的ID。通常(> 95%)只有一行具有特定的组ID。大约有一千个ID出现在两行中;少数几个存在于三行中的ID;没有四行或更多行。
但我需要做好准备,可能会显示存在于四行或更多行中的组ID。
如何编写一个SQL语句来创建一个显示以这种方式展平的表的视图?
请注意,每一行也都有唯一的ID。这不需要以任何方式保留,但如果它在编写SQL时有帮助,我会通知您。
答案 0 :(得分:2)
首先,找出不是重叠序列延续的区间:
select *
from dateclap d1
where not exists(
select *
from dateclap d2
where d2.group_id=d1.group_id and
d2.end_date >= d1.start_date and
(d2.start_date < d1.start_date or
(d1.start_date=d2.start_date and d2.r_id<d1.r_id)))
最后一行区分从相同日期/时间开始的间隔,按唯一记录ID(r_id)排序。
然后,对于每个这样的记录,我们可以通过connect_by_root r_id区分钳位组来获得记录的分层选择。之后,我们需要的是获得钳位组的最小值/最大值(connect_by_root r_id是组中父记录的id):
select group_id, min(start_date) as start_date, max(end_date) as end_date
from dateclap d1
start with not exists(
select *
from dateclap d2
where d2.group_id=d1.group_id and
d2.end_date >= d1.start_date and
(d2.start_date < d1.start_date or
(d1.start_date=d2.start_date and d2.r_id<d1.r_id)))
connect by nocycle
prior group_id=group_id and
start_date between prior start_date and prior end_date
group by group_id, connect_by_root r_id
注意这里的nocycle - 这是一个避免异常的肮脏技巧,因为连接很弱并且实际上试图将记录连接到自身。你可以在“连接”之后改进条件,类似于“存在”条件,以避免使用nocycle。
P.S。表是为这样的测试创建的:
CREATE TABLE "ANIKIN"."DATECLAP"
(
"R_ID" NUMBER,
"GROUP_ID" NUMBER,
"START_DATE" DATE,
"END_DATE" DATE
) PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 NOCOMPRESS LOGGING
STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT)
TABLESPACE "ANIKIN" ;
r_id和相应的seuqence / triggers的唯一键(或可能是主键)不是特定于测试的东西,只需用唯一值填充r_id。
答案 1 :(得分:1)
创建2个函数,返回特定元素的展平开始日期和结束日期:
CREATE OR REPLACE FUNCTION getMinStartDate
(
p_group_id IN NUMBER,
p_start IN DATE
)
RETURN DATE AS
v_result DATE;
BEGIN
SELECT MIN(start_date)
INTO v_result
FROM my_data
WHERE group_id = p_group_id
AND start_date <= p_start
AND end_date >= p_start;
RETURN v_result;
END getMinStartDate;
CREATE OR REPLACE FUNCTION getMaxEndDate
(
p_group_id IN NUMBER,
p_end IN DATE
)
RETURN DATE AS
v_result DATE;
BEGIN
SELECT MAX(end_date)
INTO v_result
FROM my_data
WHERE group_id = p_group_id
AND start_date <= p_end
AND end_date >= p_end;
RETURN v_result;
END getMaxEndDate;
然后,对于每个元素,您的视图应返回这些扁平日期
当然,DISTINCT
因为各种元素可能导致相同的日期:
SELECT DISTINCT
group_id,
getMinStartDate(group_id, start_date) AS start_date,
getMaxEndDate(group_id, end_date) AS end_date
FROM my_data;
答案 2 :(得分:1)
select t1.group_id, least(min(t1.start_date), min(t2.start_date)), greatest(max(t1.start_date), max(t2.end_date)) from test_interval t1, test_interval t2
where (t1.start_date, t1.end_date) overlaps (t2.start_date, t2.end_date)
and t1.rowid <> t2.rowid
and t1.group_id = t2.group_id group by t1.group_id;
此类查询为我生成重叠间隔列表。 OVERLAPS是一个无证的运营商。我只是想知道当我们得到两对重叠而不是彼此重叠的区间时,这是否会返回错误的结果。 在我使用rowid的地方,你可以使用你唯一的行标识符
答案 3 :(得分:1)
输入数据显示最后一行的结束日期为9999-13-31。这应该得到纠正。
话虽如此,最好选择一个不完全是9999-12-31的结束日期。在许多问题中,人们需要在表中的所有日期添加一天,或几周或其他任何东西;但如果试图添加到9999-12-31,那将失败。我更喜欢8999-12-31;对于大多数计算而言,一千年应该足够了。 {:-)在我为查询创建的测试数据中,我使用了这个约定。 (虽然解决方案很容易适应9999-12-31。)
使用日期时间间隔时,请记住纯日期表示一天开始时的午夜。所以2016年的“结束日期”为2017-01-01(当天午夜),而2017年的“开始日期”也是2017-01-01。因此,表格应该具有相同的结束日期和开始日期,以便紧接着彼此相继 - 并且它们应该融合在一起形成一个区间。但是,2016-08-31结束的时间间隔和2016-09-01开始的时间间隔不应融合在一起;它们相隔一整天(特别是2016-08-31的那一天不包括在任何一个区间内)。
OP没有具体说明如何解释结束日期。我假设它们如最后一段所述;否则解决方案可以很容易地进行调整(但是需要先添加1到结束日期,然后在结尾处减去1 - 这正是9999-12-31不是“未知”的好占位符的情况之一。 )
<强>解决方案强>:
with m as
(
select group_id, start_date,
max(end_date) over (partition by group_id order by start_date
rows between unbounded preceding and 1 preceding) as m_time
from inputs -- "inputs" is the name of the base table
union all
select group_id, NULL, max(end_date) from inputs group by group_id
),
n as
(
select group_id, start_date, m_time
from m
where start_date > m_time or start_date is null or m_time is null
),
f as
(
select group_id, start_date,
lead(m_time) over (partition by group_id order by start_date) as end_date
from n
)
select * from f where start_date is not null
;
输出(提供数据):
GROUP_ID START_DATE END_DATE
---------- ---------- ----------
1 2016-01-01 2020-01-01
1 2022-08-31 2030-12-31
2 2010-03-01 2017-01-01
3 2001-01-01 8999-12-31