如何在 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

如果您想要 01 作为雪花中的 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 EXISTSSELECT 语句的组合:

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 中处理任意数量的间隔?的主要内容,如果未能解决你的问题,请参考以下文章

创建 SAS 宏来处理任意数量的变量

创建SAS宏以处理任意数量的变量

如何使用任意数量的通道创建图像? (蟒蛇)

如何在 oracle-sql 中计算周一待处理票的总数?

时间间隔内的 SQL 计数

如何在 CSV 文件的一行中写入任意数量的列表元素?