【问题标题】:How to handle an arbitrary number of intervals in SQL?如何在 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' 列的含义是我们要添加的新列。抱歉,不清楚。

标签: sql snowflake-cloud-data-platform ansi-sql


【解决方案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
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2021-08-18
          • 1970-01-01
          • 1970-01-01
          • 2017-03-22
          • 2021-06-29
          • 1970-01-01
          • 2017-02-28
          • 1970-01-01
          相关资源
          最近更新 更多