【问题标题】:Need to split datetime ranges by each day需要每天分割日期时间范围
【发布时间】:2019-07-22 08:52:34
【问题描述】:

我有一个需要根据日期时间拆分的表

输入表

ID|    Start              |    End
--------------------------------------------
A |    2019-03-04 23:18:04|    2019-03-04 23:21:25
--------------------------------------------
A |    2019-03-04 23:45:05|    2019-03-05 00:15:14
--------------------------------------------

需要的输出

ID|    Start              |    End
--------------------------------------------
A |    2019-03-04 23:18:04|    2019-03-04 23:21:25
--------------------------------------------
A |    2019-03-04 23:45:05|    2019-03-04 23:59:59
--------------------------------------------
A |    2019-03-05 00:00:00|    2019-03-05 00:15:14
--------------------------------------------

谢谢!!

【问题讨论】:

  • 23:59:59.001 到 23:59:59.999 的时间呢?
  • 数据格式只有秒级的分辨率。
  • 范围是否可能超过 2 个日期?也许是 2019-03-05 - 2019-03-08?
  • 如果间隔超过一天怎么办?例如(start, end) = (2019-01-01 00:00:00, 2019-01-05 00:00:00)?
  • 我检查了表格似乎没有超过两天的范围。您说得很好,如果将来确实出现此类事件,它应该处理它们。

标签: sql datetime timestamp amazon-redshift


【解决方案1】:

试试下面的代码。仅当开始日期和结束日期在连续两天内时才有效。如果开始日期和结束日期的差异超过 1 天,则不会。

MSSQL:

SELECT ID,[Start],[End]
FROM Input_Table A
WHERE DATEDIFF(DD,[Start],[End]) = 0

UNION ALL


SELECT ID,[Start], CAST(CAST(CAST([Start] AS DATE)  AS VARCHAR(MAX)) +' 23:59:59' AS DATETIME)
FROM Input_Table A
WHERE DATEDIFF(DD,[Start],[End]) > 0

UNION ALL

SELECT ID,CAST(CAST([End] AS DATE) AS DATETIME),[End]
FROM Input_Table A
WHERE DATEDIFF(DD,[Start],[End]) > 0

ORDER BY 1,2,3

PostgreSQL:

SELECT ID,
TO_TIMESTAMP(startDate,'YYYY-MM-DD HH24:MI:SS'),
TO_TIMESTAMP(endDate, 'YYYY-MM-DD HH24:MI:SS')
FROM mytemp A
WHERE DATE_PART('day', endDate::date) - 
    DATE_PART('day',startDate::date) = 0

UNION ALL


SELECT ID,
TO_TIMESTAMP(startDate,'YYYY-MM-DD HH24:MI:SS'), 
TO_TIMESTAMP(CONCAT(CAST(CAST (startDate AS DATE) AS VARCHAR) , 
    ' 23:59:59') , 'YYYY-MM-DD HH24:MI:SS')
FROM mytemp A
WHERE DATE_PART('day', endDate::date) - 
    DATE_PART('day',startDate::date) > 0

UNION ALL


SELECT ID,
TO_TIMESTAMP(CAST(CAST (endDate AS DATE) AS VARCHAR) ,
    'YYYY-MM-DD HH24:MI:SS')    ,
TO_TIMESTAMP(endDate,'YYYY-MM-DD HH24:MI:SS')
FROM mytemp A
WHERE DATE_PART('day', endDate::date) - 
    DATE_PART('day',startDate::date) > 0;

PostgreSQL 演示Here

【讨论】:

  • 都是指Input_Table A,是不是缺少了什么?
  • 不,都是 Input_Table A 的输入。
  • 不客气@JASONMENDES。如果真的有帮助,请接受答案:)
  • Postgres 版本已添加到答案中。
  • 如果这两列被定义为 timestamp (并且他们的正常思维会将时间戳存储在 varchar 列中),那么所有这些强制转换和 to_timestamp() 调用似乎都毫无用处。 DATE_PART('day', endDate::date) - DATE_PART('day',startDate::date) = 0 也可以简化为 enddate::date - starddate::date) = 0 或简单的 enddate::date = startdate::date
【解决方案2】:

demo:db<>fiddle

即使范围超过一天也有效

WITH cte AS (
    SELECT
        id,
        start_time,
        end_time,
        gs,
        lag(gs) over (PARTITION BY id ORDER BY gs)                                 -- 2
    FROM
        a
    LEFT JOIN LATERAL
        generate_series(start_time::date + 1, end_time::date, interval '1 day') gs  --1
    ON TRUE
)
SELECT                                                                             -- 3
    id,
    COALESCE(lag, start_time) AS start_time,
    gs - interval '1 second'
FROM
    cte
WHERE gs IS NOT NULL

UNION

SELECT DISTINCT ON (id)                                                           -- 4
    id,
    CASE WHEN start_time::date = end_time::date THEN start_time ELSE end_time::date END,  -- 5
    end_time
FROM
    cte
  1. CTE:generate_series 函数每天生成一行新的一天。因此,如果没有日期更改,则没有任何价值
  2. CTE:lag() window function 允许将当前日期值移动到下一行(当前结束是下一个开始)
  3. 使用此数据集,您可以计算新的开始值和结束值。如果没有gs 值:没有日期更改。在这一点上忽略了这一点。对于日期更改的所有情况:如果没有 lag 值,则它是开始(因此它无法获得先前的值)。在这种情况下,采用正常的start_time,否则采用日期中断时间的新一天。 end_time 是在当天的最后一秒拍摄的 (interval - '1 second')
  4. 第二部分:由于日期中断,总是有一个额外的记录需要合并。最后一条记录是从end_time 的开头(所以转换为date)。 CASE 子句将此步骤与迄今为止已被忽略的没有日期更改的情况相结合。所以如果start_timeend_time 是同一天,这里取的是原来的start_time

【讨论】:

  • 嗨,CTE 似乎不适用于我的 IDE,即 Postgres。
  • 你用的是什么版本?
  • 我正在使用 workbench/j(build 124) 并通过 AWS 连接到 Postgres DB。
  • @JASONMENDES 由于所有答案都解决了您在问题中提到的问题(不包括一开始的 Redshift 问题),如果您对所有答案都投赞成票,那就太好了! Upvotes 尊重回复者为您的问题投入的时间和工作。如果其中一个完全解决了您的问题,请不要忘记接受这一点!
  • @JASONMENDES:您应该从一开始就提到您使用的是 Amazon Redshift,而不是 Postgres。虽然他们有一些古老的根源,但他们是quite different
【解决方案3】:

很遗憾,Redshift 没有一种方便的方法来生成一系列数字。如果你的表足够大,你可以用它来生成数字。 “足够大”意味着行数大于最长跨度。如果不是这个,也许另一个表也可以工作。

一旦你有了它,你就可以使用这个逻辑:

with n as (
      select row_number() over () - 1 as n
      from t
     )
select t.id,
       greatest(t.s, date_trunc('day', t.s) + n.n * interval '1 day') as s,
       least(t.e, date_trunc('day', t.s) + (n.n + 1) * interval '1 day' - interval '1 second') as e
from t join
     n
     on t.e >= date_trunc('day', t.s) + n.n * interval '1 day';

Here 是一个 dbfiddle。它使用旧版本的 Postgres,但对于 Redshift 来说还不够老。

【讨论】:

  • 你能解释一下这个想法吗?该查询对我来说似乎不正确,在dbfiddle.uk/… 中使用 id=C 扩展范围会产生错误的结果@(我的回答中的示例)。这就是我以递归 CTE 结束的原因,它也是符合标准的解决方案。
  • @TomášZáluský 。 . .表中没有足够的行来为该示例生成足够的数字。这就是第一段所解释的。递归 CTE 很好,但 Redshift 不支持该功能。
【解决方案4】:

使用递归 CTE 模拟循环以生成间隔,即在种子行中从开始到午夜取范围,在后续行中取另一天等。

with recursive input as (
  select 'A' as id, timestamp '2019-03-04 23:18:04' as s, timestamp '2019-03-04 23:21:25' as e union
  select 'A' as id, timestamp '2019-03-04 23:45:05' as s, timestamp '2019-03-05 00:15:14' as e union
  select 'B' as id, timestamp '2019-03-06 23:45:05' as s, timestamp '2019-03-08 00:15:14' as e union
  select 'C' as id, timestamp '2019-03-10 23:45:05' as s, timestamp '2019-03-15 00:15:14' as e
), generate_id as (
  select row_number() over () as unique_id, * from input
), rec (unique_id, id, s, e) as (
  select unique_id, id, s, least(e, s::date::timestamp + interval '1 day')
  from generate_id seed
  union
  select remaining.unique_id, remaining.id, previous.e, least(remaining.e, previous.e::date::timestamp + interval '1 day')
  from rec as previous
  join generate_id remaining on previous.unique_id = remaining.unique_id and previous.e < remaining.e
)
select id, s, e from rec
order by id,s,e

注意:

  • 您的id 列似乎不是唯一的,因此我添加了自定义unique_id 列。如果 id 是唯一的,则 CTE generate_id 是不必要的。唯一性对于递归查询来说是不可避免的。
  • close-open range 更适合表示此类数据,而不是 close-close range。所以我的查询中的结束时间返回 00:00:00,而不是 23:59:59。如果它不适合您,请将查询修改为练习。

更新:查询适用于 Postgres。 OP最初标记了问题postgres,然后将标记更改为redshift。

【讨论】:

    猜你喜欢
    • 2020-06-29
    • 1970-01-01
    • 2017-05-26
    • 1970-01-01
    • 2013-09-12
    • 1970-01-01
    • 1970-01-01
    • 2021-09-17
    • 1970-01-01
    相关资源
    最近更新 更多