【问题标题】:SQL Server Group by date and by time of day over a date rangeSQL Server 按日期和日期范围内的时间分组
【发布时间】:2018-04-13 18:09:15
【问题描述】:

我什至不确定这是否可以/应该通过 SQL 来完成,但可以。

我有一个像这样存储开始日期和结束日期的表格

userPingId    createdAt                    lastUpdatedAt
1             2017-10-17 11:31:52.160      2017-10-18 14:31:52.160

我想返回一个按日期对结果进行分组的结果集,以及它们是否在两个日期之间的不同点之间处于活动状态。

不同点是

  • 早上 - 中午 12 点之前
  • 下午 - 中午 12 点到下午 5 点之间
  • 晚上 - 下午 5 点之后

所以例如我会得到以下结果

sessionDate    morning    afternoon    evening
2017-10-17     1          1            1
2017-10-18     1          1            0

这是我到目前为止所拥有的,我相信它非常接近,但我无法获得我需要的结果这一事实让我认为这在 SQL 中可能是不可能的(顺便说一句,我正在使用数字查找表在我在另一个教程中看到的查询中)

DECLARE @s DATE = '2017-01-01', @e DATE = '2018-01-01';
;WITH d(sessionDate) AS
(
  SELECT TOP (DATEDIFF(DAY, @s, @e) + 1) DATEADD(DAY, n-1, @s) 
  FROM dbo.Numbers ORDER BY n
)
SELECT 
d.sessionDate,
sum(case when 
(CONVERT(DATE, createdAt) = d.sessionDate AND datepart(hour, createdAt) < 12) 
OR (CONVERT(DATE, lastUpdatedAt) = d.sessionDate AND datepart(hour, lastUpdatedAt) < 12) 
then 1 else 0 end) as Morning,
sum(case when 
(datepart(hour, createdAt) >= 12 and datepart(hour, createdAt) < 17)
OR (datepart(hour, lastUpdatedAt) >= 12 and datepart(hour, lastUpdatedAt) < 17) 
OR (datepart(hour, createdAt) < 12 and datepart(hour, lastUpdatedAt) >= 17)
then 1 else 0 end) as Afternoon,
sum(case when datepart(hour, createdAt) >= 17 OR datepart(hour, lastUpdatedAt) >= 17 then 1 else 0 end) as Evening
FROM d
LEFT OUTER JOIN MYTABLE AS s
ON s.createdAt >= @s AND s.lastUpdatedAt <= @e
AND (CONVERT(DATE, s.createdAt) = d.sessionDate OR CONVERT(DATE, s.lastUpdatedAt) = d.sessionDate)
WHERE d.sessionDate >= @s AND d.sessionDate <= @e
AND userPingId = 49
GROUP BY d.sessionDate
ORDER BY d.sessionDate;

【问题讨论】:

  • 你使用的是什么版本的sql server?时间数据类型 (stackoverflow.com/a/3656549/359135) 和数据透视命令的组合可以让这变得非常简单 (data.stackexchange.com/stackoverflow/revision/749735/930554/…)
  • createdAtlastUpdatedAt 可以只跨越一天或两天(午夜之前到午夜之后)甚至更多天吗?
  • 我相信我们使用的是最新版本的 SQL Server,并且 createdAt 到 lastUpdatedAt 可以跨越任意天数
  • 给你:Microsoft SQL Server 2016 (RTM-CU2) (KB3182270) - 13.0.2164.0 (X64)
  • @gordatron 在这种情况下,我希望它出现在第一天的上午、下午和晚上以及第二天的上午和下午。所以我猜这两个日期之间的所有连续时间段(早上、下午和晚上)

标签: sql sql-server datetime


【解决方案1】:

在您开始使用数字表的基础上,您可以使用另一个 common table expression 使用 cross apply() 将时间范围添加到您的临时日历表中 和table value constructor (values (...),(...))

从那里,您可以使用基于重叠日期范围的 inner join 以及条件聚合来透视结果:

declare @s datetime = '2017-01-01', @e datetime = '2018-01-01';

;with n as (select n from (values(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) t(n))
, d as (  /* adhoc date/numbers table */
  select top (datediff(day, @s, @e)+1) 
      SessionDate=convert(datetime,dateadd(day,row_number() over(order by (select 1))-1,@s))
  from n as deka cross join n as hecto cross join n as kilo
                 cross join n as tenK cross join n as hundredK
   order by SessionDate
)
, h as ( /* add time ranges to date table */
  select 
      SessionDate
    , StartDateTime = dateadd(hour,v.s,SessionDate)
    , EndDateTime   = dateadd(hour,v.e,SessionDate)
    , v.point
  from d
    cross apply (values 
        (0,12,'morning')
       ,(12,17,'afternoon')
       ,(17,24,'evening')
      ) v (s,e,point)
)

select
    t.userPingId
  , h.SessionDate
  , morning = count(case when point = 'morning' then 1 end)
  , afternoon = count(case when point = 'afternoon' then 1 end)
  , evening = count(case when point = 'evening' then 1 end)
from t
  inner join h
    on t.lastupdatedat >= h.startdatetime
   and h.enddatetime   > t.createdat 
group by t.userPingId, h.SessionDate

rextester 演示:http://rextester.com/MVB77123

返回:

+------------+-------------+---------+-----------+---------+
| userPingId | SessionDate | morning | afternoon | evening |
+------------+-------------+---------+-----------+---------+
|          1 | 2017-10-17  |       1 |         1 |       1 |
|          1 | 2017-10-18  |       1 |         1 |       0 |
+------------+-------------+---------+-----------+---------+

或者,您可以在最终的select 中使用pivot() 而不是条件聚合:

select UserPingId, SessionDate, Morning, Afternoon, Evening
from (
  select
      t.userPingId
    , h.SessionDate
    , h.point
  from t
    inner join h
      on t.lastupdatedat >= h.startdatetime
     and h.enddatetime   > t.createdat 
  ) t
  pivot (count(point) for point in ([Morning], [Afternoon], [Evening])) p

rextester 演示:http://rextester.com/SKLRG63092

【讨论】:

  • 这真是太棒了,我会花下一个小时左右的时间来理解它。
  • @user2634794 如果有什么需要我详细解释的,请告诉我!
  • @user2634794 这是一个分解每个部分的演示:dbfiddle.uk demo 并使用像您一样的数字表而不是临时数字表。
【解决方案2】:

您可以在 CTE 上使用 PIVOT 来得出此问题的解决方案。

下面是测试表

从 ping 中选择 *

下面是sql查询

;with details as 
(
select userPingId, createdAt as presenceDate  , convert(date, createdAt) as 
onlyDate,
datepart(hour, createdAt) as onlyHour
from ping

union all

select userPingId, lastUpdatedAt as presenceDate , convert(date, 
lastUpdatedAt) as onlyDate,
datepart(hour, lastUpdatedAt) as onlyHour
from ping
) 
, cte as 
(
select onlyDate,count(*) as count,
case 
  when onlyHour between 0 and 12 then 'morning' 
  when onlyHour between 12 and 17 then 'afternoon' 
  when onlyHour>17 then 'evening' 


end as 'period'

from details
group by onlyDate,onlyHour
)

select onlyDate,  coalesce(morning,0) as morning, 
coalesce(afternoon,0) as afternoon , coalesce(evening,0) as evening from 
(
 select onlyDate, count,period  
 from cte ) src
 pivot
 (
  sum(count)
  for period in ([morning],[afternoon],[evening])

 ) p

下面是最终结果

【讨论】:

    【解决方案3】:

    这与已经发布的答案非常相似,我只是想要 PIVOT 的练习:)

    我使用一个单独的表格,其中包含时间部分。然后将其与数字表交叉连接以创建分桶的日期和时间范围。我将它加入到数据中,然后旋转它(例如:https://data.stackexchange.com/stackoverflow/query/750496/bucketing-data-into-date-am-pm-evening-and-pivoting-results

    SELECT
      *
    FROM (
        SELECT
          [userPingId],
          dt,
          [desc]
        FROM (
            SELECT
              DATEADD(D, number, @s) AS dt,
              CAST(DATEADD(D, number, @s) AS datetime) + CAST(s AS datetime) AS s,
              CAST(DATEADD(D, number, @s) AS datetime) + CAST(e AS datetime) AS e,
              [desc]
            FROM #numbers
            CROSS JOIN #times
            WHERE number < DATEDIFF(D, @s, @e)
            ) ts
        INNER JOIN #mytable AS m
          ON m.createdat < ts.e
          AND m.[lastUpdatedAt] >= ts.s
      ) src
    PIVOT
    (
    COUNT([userPingId])
    
    FOR [desc] IN ([am], [pm], [ev])
    ) piv;
    

    #times 表只是:

    s                   e                   desc
    00:00:00.0000000    12:00:00.0000000    am
    12:00:00.0000000    17:00:00.0000000    pm
    17:00:00.0000000    23:59:59.0000000    ev
    

    【讨论】:

      猜你喜欢
      • 2013-12-18
      • 1970-01-01
      • 1970-01-01
      • 2014-10-11
      • 1970-01-01
      • 1970-01-01
      • 2012-01-28
      • 1970-01-01
      • 2013-01-07
      相关资源
      最近更新 更多