如何在 SQL 中处理任意数量的间隔?
Posted
技术标签:
【中文标题】如何在 SQL 中处理任意数量的间隔?【英文标题】:How to handle an arbitrary number of intervals in SQL? 【发布时间】:2021-02-07 17:38:03 【问题描述】:我在 SQL 数据库中有两个表。第一个,path
,保存路径(或轨迹)上的点。每个点都有一行。
第二个表“间隔”列出了第一个表上表示的路径上的间隔。这些已在某些方面被识别为特殊的,例如对象移动不多的路径的一部分。
我们希望在路径上识别多个区间。
我想在path
表中添加一个新列,其值表示路径的该部分是否在这些间隔之一内。我在下面的示例中给出了这个附加列的示例。
如果只有一个间隔我会使用
CASE WHEN p.time BETWEEN i.Start_Time AND i.End_Time THEN True ELSE False END
我可以做些什么来处理任意数量的间隔?
间隔:
| Interval ID | Start_Time | End_Time |
|-------------|------------|----------|
| 1 | 5 | 36 |
| 2 | 71 | 78 |
| 3 | 206 | 308 |
| ... | | |
这里是path
表的示例,其中成功添加了所需的“at_rest”列。
| time | x | y | at_rest |
|------|---|----|---------|
| 0 | 5 | 9 | 0 |
| 1 | 6 | 10 | 0 |
| 2 | 7 | 31 | 1 |
| 3 | 9 | 49 | 1 |
| ... | | | |
【问题讨论】:
path.at_rest 是旧的现有列吗?或者它是您的新专栏的名称?或者您想为新列命名什么? 用您正在使用的数据库标记您的问题。同时显示您想要的结果。 @donPablo 我在文本中添加了一些说明。 'at_rest' 列的含义是我们要添加的新列。抱歉,不清楚。 【参考方案1】:正如 donPablo 所说,这可以通过连接来解决(如您所描述的):
WITH intervals AS (
SELECT *
FROM VALUES
(1,1,5,36)
,(1,2,71,78)
,(1,3,206,308)
v(unit_id, interval_id, start_time, end_time)
), paths AS (
SELECT *
FROM VALUES
(1,0,5,9 )
,(1,1,6,10)
,(1,2,7,31)
,(1,3,9,49)
,(1,4,9,48)
,(1,5,9,47)
,(1,6,9,46)
p(unit_id, time, x, y)
)
SELECT p.unit_id
,p.time
,p.x
,p.y
,i.unit_id is not null as atRest
FROM paths p
LEFT JOIN intervals i
ON p.unit_id = i.unit_id and p.time between i.start_time and i.end_time
order by 1,2;
给予:
UNIT_ID TIME X Y ATREST
1 0 5 9 FALSE
1 1 6 10 FALSE
1 2 7 31 FALSE
1 3 9 49 FALSE
1 4 9 48 FALSE
1 5 9 47 TRUE
1 6 9 46 TRUE
请注意,您给出的示例区间并不意味着您给出的第一点“静止”,因为这些不在区间内。
我还包括一个“unit_id”,因为只有一个实体的大量数据不太可能。
现在,如果您的区间数据重叠,那么您需要更改计数:
WITH intervals AS (
SELECT *
FROM VALUES
(1,1,2,4)
,(1,2,4,6)
,(1,3,206,308)
v(unit_id, interval_id, start_time, end_time)
), paths AS (
SELECT *
FROM VALUES
(1,0,5,9 )
,(1,1,6,10)
,(1,2,7,31)
,(1,3,9,49)
,(1,4,9,48)
,(1,5,9,47)
,(1,6,9,46)
p(unit_id, time, x, y)
)
SELECT p.unit_id
,p.time
,p.x
,p.y
,count(i.unit_id) > 0 as atRest
FROM paths p
LEFT JOIN intervals i
ON p.unit_id = i.unit_id and p.time between i.start_time and i.end_time
group by 1,2,3,4
order by 1,2;
给予:
UNIT_ID TIME X Y ATREST
1 0 5 9 FALSE
1 1 6 10 FALSE
1 2 7 31 TRUE
1 3 9 49 TRUE
1 4 9 48 TRUE
1 5 9 47 TRUE
1 6 9 46 TRUE
如果您想要 0
或 1
作为雪花中的 at_rest 值,则交换到内联 IFF 是最紧凑的。
,iff(count(i.unit_id) > 0,1,0) as atRest
【讨论】:
【参考方案2】:我的同事和我想出的解决方案使用了可怕的CROSS JOIN
,但我们可以预先将表过滤到只有几千行,这样就不会出现这样的问题。
WITH interval_path AS (SELECT path.*, intervals.*
FROM path
CROSS JOIN intervals
WHERE path.time BETWEEN intervals.Start_Time AND intervals.End_Time)
SELECT path.time, path.x, path.y,
COALESCE(interval_path.in_cluster, 0) AS in_cluster
FROM path
LEFT JOIN interval_path ON interval_path.time = path.time
ORDER BY path.time
【讨论】:
【参考方案3】:我认为拥有 Interval_ID 比仅是/否指示符更有意义。 atRest 列保留了您最初的 Yes/No 概念。另外,我在路径表中添加了更多数据以获得我的结果--
SELECT
[Path_time]
,[x]
,[y]
,[at_rest]
,ki.[Interval ID]
,Case When ki.[Interval ID] IS NULL Then 0 Else 1 End as atRest
FROM [StackOver].[dbo].[Keith_path] as kp
Left Join [StackOver].[dbo].[Keith_interval] as ki
On kp.Path_time BETWEEN ki.Start_Time AND ki.End_Time
Order By kp.Path_time
这需要转换为更新 sql 以替换现有值。结果是——
Path_time x y at_rest IntervalID atRest
0 5 9 0 NULL 0
1 6 10 0 NULL 0
2 7 31 1 NULL 0
3 7 33 0 NULL 0
4 7 32 0 NULL 0
5 8 31 0 1 1
6 9 30 0 1 1
【讨论】:
【参考方案4】:如果您只想要true
/false
的一行,请使用exists
。许多数据库直接支持布尔类型,因此您可以使用:
select . . .,
(exists (select 1
from intervals i
where p.time bertween i.start_time and i.end_time
)
) as flag
在其他数据库中,你需要一个case
表达式:
select . . .,
(case when exists (select 1
from intervals i
where p.time bertween i.start_time and i.end_time
)
then 1 else 0
) as flag
【讨论】:
不幸的是,单个区间内可能有大量行。 @keith 。 . .所以呢?这提供了一个标志,表明给定行是否在 any 间隔内。【参考方案5】:答案在不同的 DBMS 中可能会有所不同。 我认为这个逻辑有效,但实施取决于您的环境。 我假设间隔没有重叠。
您可以在CASE
中使用WHEN EXISTS
和SELECT
语句的组合:
CASE WHEN EXISTS(
SELECT Interval_ID
FROM intervals
WHERE p.time BETWEEN Start_Time AND End_Time
) THEN (
SELECT Interval_ID
FROM intervals
WHERE p.time BETWEEN Start_Time AND End_Time
)
ELSE 0
【讨论】:
以上是关于如何在 SQL 中处理任意数量的间隔?的主要内容,如果未能解决你的问题,请参考以下文章