sql - 分组连续范围

标签 sql r range data.table plyr

我有一个包含多行的数据表,我想有条件地对两列进行分组,即开始和结束。这些列代表关联人员正在做某事的某个月份。下面是一些示例数据(您可以使用 R 读入,或者如果您不使用 R 则查找下面的纯表格):

# base:
test <- read.table(
text = "
1   A   mnb USA prim    4   12
2   A   mnb USA x   13  15
3   A   mnb USA un  16  25
4   A   mnb USA fdfds   1   2
5   B   ghf CAN sdg 3   27
6   B   ghf CAN hgh 28  29
7   B   ghf CAN y   24  31
8   B   ghf CAN ghf 38  42
",header=F)
library(data.table)
setDT(test)
names(test) <-  c("row","Person","Name","Country","add info","Begin","End")
out <- read.table(
text = "
1   A   mnb USA fdfds   1   2
2   A   mnb USA -   4   25
3   B   ghf CAN -   3   31
4   B   ghf CAN ghf 38  42
",header=F)
setDT(out)
names(out) <- c("row","Person","Name","Country","add info","Begin","End")

分组应如下进行:如果 A 人在第 4 个月到 15 个月进行徒步旅行,并从第 16 个月到第 24 个月旅行,我会将第 4 个月到第 24 个月的连续(即不间断)事件分组。如果之后人 A从25月到28月做了冲浪,我也会加上这个,整个小组事件从4到28持续。
现在有问题的情况是有重叠的时期,例如 A 可能也从 11 到 31 钓鱼,所以整个事情会变成 4 到 31。但是,如果 A 做了从 1 到 2 的某事,那将是一个单独的事件(与 1 到 3 相比,也必须添加,因为 3 连接到 4)。我希望这很清楚,如果没有,您可以在上面的代码中找到更多示例。
我正在使用数据表,因为我的数据集非常大。到目前为止,我已经开始使用 sqldf,但是如果每个人有如此多的事件(比如 8 个或更多),这将是有问题的。
这可以在数据表、plyr 或 sqldf 中完成吗?
请注意:我也在寻找 SQL 中的答案,因为我可以直接在 sqldf 中使用它或尝试将其转换为另一种语言。 sqldf 支持 (1) SQLite 后端数据库(默认情况下),(2) H2 java 数据库,(3) PostgreSQL 数据库和 (4) sqldf 0.4-0 以后也支持 MySQL。

编辑:这里是“纯”表:

在:
Person Name Country add info  Begin End
A      mnb  USA     prim      4      12
A      mnb  USA     x         13     15
A      mnb  USA     un        16     25
A      mnb  USA     fdfds     1      2
B      ghf  CAN     sdg       3      27
B      ghf  CAN     hgh       28     29
B      ghf  CAN     y         24     31
B      ghf  CAN     ghf       38     42

出去:
A      mnb  USA     fdfds     1      2
A      mnb  USA     -         4      25
B      ghf  CAN     -         3      31
B      ghf  CAN     ghf       38     42

最佳答案

如果您使用的是 SQL Server 2012 或更高版本,则可以使用 LAG 和 LEAD 函数来构建逻辑以达到最终所需的数据集。我相信,自 Oracle 8i 以来,这些功能也已在 Oracle 中可用。

以下是我在 SQL Server 2012 中创建的解决方案,应该对您有所帮助。您提供的示例值被加载到一个临时表中,以表示您所说的第一个“纯表”。使用这两个函数以及 OVER 子句,我使用以下 T-SQL 代码得出了您的最终数据集。我在代码中留下了一些注释掉的行,以展示我如何能够逐个构建整体解决方案,这说明了放置在 GapMarker 列的 CASE 语句中的各种场景,作为分组标志。

IF OBJECT_ID('tempdb..#MyTable') IS NOT NULL
	DROP TABLE #MyTable

CREATE TABLE #MyTable (
	 Person CHAR(1)
	,[Name] VARCHAR(3)
	,Country VARCHAR(10)
	,add_info VARCHAR(10)
	,[Begin] INT
	,[End] INT
)

INSERT INTO #MyTable (Person, Name, Country, add_info, [Begin], [End])
	VALUES ('A', 'mnb', 'USA', 'prim', 4, 12),
	('A', 'mnb', 'USA', 'x', 13, 15),
	('A', 'mnb', 'USA', 'un', 16, 25),
	('A', 'mnb', 'USA', 'fdfds', 1, 2),
	('B', 'ghf', 'CAN', 'sdg', 3, 27),
	('B', 'ghf', 'CAN', 'hgh', 28, 29),
	('B', 'ghf', 'CAN', 'y', 24, 31),
	('B', 'ghf', 'CAN', 'ghf', 38, 42);

WITH CTE
AS
(SELECT
		mt.Person
		,mt.Name
		,mt.Country
		,mt.add_info
		,mt.[Begin]
		,mt.[End]
		--,LEAD([Begin], 1) OVER (PARTITION BY mt.Person ORDER BY [End])
		--,CASE WHEN [End] + 1 = LEAD([Begin], 1) OVER (PARTITION BY mt.Person ORDER BY [End])
		--      --AND LEAD([Begin], 1) OVER (PARTITION BY mt.Person ORDER BY [End]) = LEAD([End], 1) OVER (PARTITION BY mt.Person ORDER BY [End])
		--	  THEN 1
		--	  ELSE 0
		--  END AS Grp
		--,MARKER = COALESCE(LEAD([Begin], 1) OVER (PARTITION BY mt.Person ORDER BY [End]), LAG([End], 1) OVER (PARTITION BY mt.Person ORDER BY [End]))
		,CASE
			WHEN mt.[End] + 1 = COALESCE(LEAD([Begin], 1) OVER (PARTITION BY mt.Person ORDER BY [End]), LAG([End], 1) OVER (PARTITION BY mt.Person ORDER BY [End])) OR
				1 + COALESCE(LEAD([Begin], 1) OVER (PARTITION BY mt.Person ORDER BY [End]), LAG([End], 1) OVER (PARTITION BY mt.Person ORDER BY [End])) = mt.[Begin] OR
				COALESCE(LEAD([Begin], 1) OVER (PARTITION BY mt.Person ORDER BY [Begin]), LAG([End], 1) OVER (PARTITION BY mt.Person ORDER BY [Begin])) BETWEEN mt.[Begin] AND mt.[End] OR
				[End] BETWEEN LAG([Begin], 1) OVER (PARTITION BY mt.Person ORDER BY [Begin]) AND LAG([End], 1) OVER (PARTITION BY mt.Person ORDER BY [Begin]) THEN 1
			ELSE 0
		END AS GapMarker
		,InBetween = COALESCE(LEAD([Begin], 1) OVER (PARTITION BY mt.Person ORDER BY [Begin]), LAG([End], 1) OVER (PARTITION BY mt.Person ORDER BY [Begin]))
		,EndInBtw = LAG([Begin], 1) OVER (PARTITION BY mt.Person ORDER BY [Begin])
		,LagEndInBtw = LAG([End], 1) OVER (PARTITION BY mt.Person ORDER BY [Begin])
	FROM #MyTable mt
--ORDER BY mt.Person, mt.[Begin]
)
SELECT DISTINCT
	X.Person
	,X.[Name]
	,X.Country
	,t.add_info
	,X.MinBegin
	,X.MaxEnd
FROM (SELECT
		c.Person
		,c.[Name]
		,c.Country
		,c.add_info
		,c.[Begin]
		,c.[End]
		,c.GapMarker
		,c.InBetween
		,c.EndInBtw
		,c.LagEndInBtw
		,MIN(c.[Begin]) OVER (PARTITION BY c.Person, c.GapMarker ORDER BY c.Person) AS MinBegin
		,MAX(c.[End]) OVER (PARTITION BY c.Person, c.GapMarker ORDER BY c.Person) AS MaxEnd
	--, CASE WHEN c.[End]+1 = c.MARKER
	--        OR c.MARKER +1 = c.[Begin] 
	--  THEN 1
	--  ELSE 0
	--  END Grp
	FROM CTE AS c) X
LEFT JOIN #MyTable AS t
	ON t.[Begin] = X.[MinBegin]
		AND t.[End] = X.[MaxEnd]
		AND t.Person = X.Person
ORDER BY X.Person, X.MinBegin
--ORDER BY Person, [Begin]


这是与您所需的最终数据集匹配的结果的屏幕截图:

enter image description here

关于sql - 分组连续范围,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39626781/

相关文章:

arrays - Delphi 检查字符是否在 'A' 范围内。 .'Z' 和 '0' 。 .'9'

mysql - 如何按手动顺序取一些行,然后按常规方式休息

sql - 如何结束一天?

r - 在函数中使用嵌套变量

重新变换线性模型。 R 案例研究

python - 如何检查当前时间是否在python的范围内?

sql - 将表格/ View 以 x 行的批处理导出到 .csv

PHP 使用条件 if/elseif/else 创建 MySQL 查询

r - 折叠相交区域

Excel VBA 工作表.名称与工作表.范围