SQL Server组按日期和日期范围内的时间

时间:2017-11-01 12:16:04

标签: sql sql-server datetime

我甚至不确定是否可以/应该这样做是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中可能是不可能的(顺便说一下我&# 39;我在我的查询中使用数字查找表,我在另一个教程中看到了)

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;

3 个答案:

答案 0 :(得分: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 |
+------------+-------------+---------+-----------+---------+

或者,您可以在最终pivot()中使用select代替条件聚合:

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

答案 1 :(得分:0)

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

以下是测试表

  

从ping中选择*

enter image description here

以下是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

以下是最终结果

enter image description here

答案 2 :(得分:0)

这是一个与已发布的答案非常相似的答案,我只想用PIVOT进行练习:)

我使用一个单独的表格,其中包含时间部分。然后将其与数字表交叉连接,以创建分组的日期和时间范围。我将其加入数据然后转动它(例如:http://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