如何解决如果情节直接连续或重叠,则合并DATE行
|| 我有一张这样的桌子:ID BEGIN END
如果存在相同ID的重叠情节(例如2000-01-01
-2001-12-31
和2000-06-01
-2002-06-31
),我希望使用MIN(BEGIN)
,MAX(END)
合并行。
如果情节是直接连续的(例如2000-01-01
-2000-06-31
和2000-07-01
-2000-12-31
),则应执行相同的操作。
如果情节之间有“丢失”天(例如2000-01-01
-2000-06-15
和2000-07-01
-2000-12-31
),则不应合并它们。
如何做到这一点?
目前,我的代码如下所示:
SELECT \"ID\",MIN(\"BEGIN\"),MAX(\"END\")
FROM ...
GROUP BY \"ID\"
但是,当然,这不能满足最后一个条件(如果有“丢失”天,则不合并)。
先感谢您!
[编辑]
我正在研究一个解决方案,我将表与自身连接在一起。这是一项改进,但尚未完成。我认为其他建议更好(但更复杂)。但是,我想分享我未完成的工作:
SELECT \"ID\",LEAST(tab1.\"BEGIN\",tab2.\"BEGIN\"),GREATEST(tab1.\"END\",tab2.\"END\")
FROM <mytable> AS tab1
JOIN <mytable> AS tab2
ON tab1.\"ID\" = tab2.\"ID\"
AND (tab1.\"BEGIN\",tab1.\"END\" + INTERVAL \'2 day\') OVERLAPS (tab2.\"BEGIN\",tab2.\"END\")
ORDER BY \"ID\"
[编辑2]
谢谢您的帮助!
我试图弄清楚窗口函数和WITH-QUERY到现在已经工作了几个小时-直到我意识到我的数据库在PostGresql 8.3上运行(这两个都不支持)。有没有窗口功能和WITH-QUERY的方法吗?
再次谢谢你!
[编辑3]
样本数据:
ID BEGIN END
1;\"2000-01-01\";\"2000-03-31\"
1;\"2000-04-01\";\"2000-05-31\"
1;\"2000-04-15\";\"2000-07-31\"
1;\"2000-09-01\";\"2000-10-31\"
2;\"2000-02-01\";\"2000-03-15\"
2;\"2000-01-15\";\"2000-03-31\"
2;\"2000-04-01\";\"2000-04-15\"
3;\"2000-06-01\";\"2000-06-15\"
3;\"2000-07-01\";\"2000-07-15\"
样本输出:
ID BEGIN END
1;\"2000-01-01\";\"2000-07-31\"
1;\"2000-09-01\";\"2000-10-31\"
2;\"2000-01-15\";\"2000-04-15\"
3;\"2000-06-01\";\"2000-06-15\"
3;\"2000-07-01\";\"2000-07-15\"
[编辑4]
一种可能的解决方案:
WITH
t1 AS (
SELECT id,begin AS time
FROM \"nace-8510-test\".checkfkt
UNION ALL
SELECT id,end
FROM \"nace-8510-test\".checkfkt
),t2 AS (
SELECT Row_Number() OVER(PARTITION BY id ORDER BY time) AS num,id,time
FROM t1 AS t1_1
),t3 AS (
SELECT t2_1.num - Row_Number() OVER(PARTITION BY t2_1.id ORDER BY t2_1.time,t2_2.time) num1,t2_1.id,t2_1.time AS begin,t2_2.time AS end
FROM t2 AS t2_1
INNER JOIN t2 AS t2_2
ON t2_1.id = t2_2.id
AND t2_1.num = t2_2.num - 1
WHERE
EXISTS (
SELECT *
FROM \"nace-8510-test\".checkfkt AS s
WHERE s.id = t2_1.id
AND (s.begin < t2_2.time AND s.end > t2_1.time)
)
OR t2_1.time = t2_2.time
OR t2_1.time + INTERVAL \'1 day\' = t2_2.time
)
SELECT id,MIN(begin) AS von,MAX(end) AS bis
FROM t3
GROUP BY id,num1
ORDER BY id
非常感谢本文的作者:
http://blog.developpez.com/sqlpro/p9821/langage-sql-norme/agregation-d-intervalles-zh-sql-1/
解决方法
编辑:这是一个好消息,您的DBA同意升级到较新版本的PostgreSQL。单独的窗口功能使升级成为值得的投资。
正如您所注意到的那样,我的原始答案有一个主要缺陷:每ѭ20one只能有一行。
以下是没有这种限制的更好的解决方案。
我已经使用系统上的测试表(8.4)对其进行了测试。
如果您有时间,我想知道它如何对您的数据执行。
我还在这里写了一个解释:https://www.mechanical-meat.com/1/detail
WITH RECURSIVE t1_rec ( id,\"begin\",\"end\",n ) AS (
SELECT id,n
FROM (
SELECT
id,CASE
WHEN LEAD(\"begin\") OVER (
PARTITION BY id
ORDER BY \"begin\") <= (\"end\" + interval \'2\' day)
THEN 1 ELSE 0 END AS cl,ROW_NUMBER() OVER (
PARTITION BY id
ORDER BY \"begin\") AS n
FROM mytable
) s
WHERE s.cl = 1
UNION ALL
SELECT p1.id,p1.\"begin\",p1.\"end\",a.n
FROM t1_rec a
JOIN mytable p1 ON p1.id = a.id
AND p1.\"begin\" > a.\"begin\"
AND (a.\"begin\",a.\"end\" + interval \'2\' day) OVERLAPS
(p1.\"begin\",p1.\"end\")
)
SELECT t1.id,min(t1.\"begin\"),max(t1.\"end\")
FROM t1_rec t1
LEFT JOIN t1_rec t2 ON t1.id = t2.id
AND t2.\"end\" = t1.\"end\"
AND t2.n < t1.n
WHERE t2.n IS NULL
GROUP BY t1.id,t1.n
ORDER BY t1.id,t1.n;
原始(已弃用)答案如下;
注:每id
限制为一行。
Denis可能会使用lead()
和lag()
是正确的,但还有另一种方法!
您也可以使用所谓的递归SQL解决此问题。
重叠功能也很方便。
我已经在系统上完全测试了此解决方案(8.4)。
它运作良好。
WITH RECURSIVE rec_stmt ( id,begin,end ) AS (
/* seed statement:
start with only first start and end dates for each id
*/
SELECT id,MIN(begin),MIN(end)
FROM mytable seed_stmt
GROUP BY id
UNION ALL
/* iterative (not really recursive) statement:
append qualifying rows to resultset
*/
SELECT t1.id,t1.begin,t1.end
FROM rec_stmt r
JOIN mytable t1 ON t1.id = r.id
AND t1.begin > r.end
AND (r.begin,r.end + INTERVAL \'1\' DAY) OVERLAPS
(t1.begin - INTERVAL \'1\' DAY,t1.end)
)
SELECT MIN(begin),MAX(end)
FROM rec_stmt
GROUP BY id;
, 我并没有完全理解您的问题,但是我绝对确定您需要研究lead()/lag()
窗口函数。
例如,类似这样的东西将是放置在子查询或公共表表达式中的一个很好的起点,以便识别每个id是否行重叠:
select id,lag(start) over w as prev_start,lag(end) over w as prev_end,start,end,lead(start) over w as next_start,lead(end) over w as next_end
from yourtable
window w as (
partition by id
order by start,end
)
, 关于第二个问题,我不确定PostgreSQL,但是在SQL Server中,有一个DATEDIFF(interval,start_date,end_date),可以为您指定两个日期之间的间隔。您可以使用MIN(Begin)作为开始日期,MAX(End)作为结束日期来获取间隔差异。然后,您可能会在case语句中使用它来输出某些内容,尽管您可能需要进行子查询或与您的方案等效的内容。
, 纯SQL
对于纯SQL解决方案,请查看Adam \的文章并阅读本文(这篇文章是用法语编写的,但是您会发现它不太难阅读)。在咨询了postgresql-mailing-list之后,向我推荐了这篇文章(谢谢!)。
对于我的数据,这是不合适的,因为所有可能的解决方案都需要至少3次自行联接表。对于(非常)大量的数据,这变成了一个问题。
半SQL,半命令式语言
如果您主要关心速度,并且可以使用命令式语言,那么您可以获得更快的速度(当然,这取决于数据量)。就我而言,使用R执行任务(至少)快了1.000倍。
脚步:
(1)获取一个.csv文件。照顾排序!!!
COPY (
SELECT \"ID\",\"BEGIN\",\"END\"
<sorry,for a reason I don\'t know StackOverflow won\'t let me finish my code here...>
(2)做这样的事情(此代码是R,但是您可以用任何命令式语言做类似的事情):
data - read.csv2(\"</path/to.csv>\")
data$BEGIN - as.Date(data$BEGIN)
data$END - as.Date(data$END)
smoothingEpisodes - function (theData) {
theLength - nrow(theData)
if (theLength 2L) return(theData)
ID - as.integer(theData[[\"ID\"]])
BEGIN - as.numeric(theData[[\"BEGIN\"]])
END - as.numeric(theData[[\"END\"]])
curId - ID[[1L]]
curBEGIN - BEGIN[[1L]]
curEND - END[[1L]]
out.1 - integer(length = theLength)
out.2 - out.3 - numeric(length = theLength)
j - 1L
for(i in 2:nrow(theData)) {
nextId - ID[[i]]
nextBEGIN - BEGIN[[i]]
nextEND - END[[i]]
if (curId != nextId | (curEND + 1) nextBEGIN) {
out.1[[j]] - curId
out.2[[j]] - curBEGIN
out.3[[j]] - curEND
j - j + 1L
curId - nextId
curBEGIN - nextBEGIN
curEND - nextEND
} else {
curEND - max(curEND,nextEND,na.rm = TRUE)
}
}
out.1[[j]] - curId
out.2[[j]] - curBEGIN
out.3[[j]] - curEND
theOutput - data.frame(ID = out.1[1:j],BEGIN = as.Date(out.2[1:j],origin = \"1970-01-01\"),END = as.Date(out.3[1:j],origin = \"1970-01-01\"))
theOutput
}
data1 - smoothingEpisodes(data)
data2 - transform(data1,TAGE = (as.numeric(data1$END - data1$BEGIN) + 1))
write.csv2(data2,file = \"</path/to/output.csv>\")
您可以在此处找到有关此R代码的详细讨论:
“平滑”时间数据-可以更高效地完成吗?
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。