需要SQL Server查询帮助,新手

时间:2011-12-16 14:41:37

标签: sql sql-server

我有一个包含以下列的表。

id, 
compid(used to identify a piece of equipment), 
startalarmdate, 
endalarmdate

当一台设备进入报警状态时,我将compid和startalarmdate(带有当前时间)插入表中,当设备报警时,我在endalarmdate列的同一行填写空值当前时间。

所以我在表格中得到了类似的东西

417,6,Sun Oct 30 18:48:17 CDT 2011,Mon Oct 31 09:49:21 CDT 2011
422,6,Mon Oct 31 10:19:19 CDT 2011,Mon Oct 31 12:19:22 CDT 2011
427,6,Mon Oct 31 20:19:56 CDT 2011,Tue Nov 01 09:50:59 CDT 2011
429,6,Tue Nov 01 21:51:41 CDT 2011,Wed Nov 02 09:52:37 CDT 2011
432,6,Wed Nov 02 21:23:23 CDT 2011,Fri Nov 04 16:26:29 CDT 2011

我能够构建一个查询,为每个事件提供一个完整的停机时间(以小时为单位),但我现在要做的是构建一个查询,让我每个月的每一天都有一个停机时间。我喜欢它一直到左边的compid,然后在同一行的一列中的compid右边的月份的每一天。我喜欢没有停机的日子是空的。是否可以按照此表的设置方式执行此操作?

3 个答案:

答案 0 :(得分:1)

步骤1:设置一个临时表,其中包含您想要总计的所需“时间块”。这些块可以用于任何时间范围;在你的例子中,它将是一个月中每天(24小时)的一个条目。

CREATE TABLE #TimeRanges
 (
   RangeStart  datetime  not null
  ,RangeEnd    datetime  not null
 )

在数据的左外连接上确保每个时间段(日)至少有一行,即使当天没有发生警报:

SELECT
   tr.RangeStart  --  Use start of each time block to identify the block
  ,md.CompId      --  With left outer join, will be 0 or more rows for each block
  ,sum(datediff(hh
                ,case
                   when tr.RangeStart > md.StartAlarmDate then tr.RangeStart
                   else md.StartAlarmDate
                 end
                ,case
                   when tr.RangeEnd > md.EndAlarmDate then tr.RangeEnd
                   else md.EndAlarmDate
                 end))  HoursInRange
 from #TimeRanges tr
  left outer join MyData md
   on md.StartAlarmDate < tr.RangeEnd
    and md.EndAlarmDate > tr.From
 group by
   tr.RangeStart
 ,md.CompId

(我无法测试此代码,可能需要进行一些调试 - 但概念是可靠的。我会让您担心舍入部分小时数,以及是否需要&gt;和&lt ;,或&gt; =和&lt; =(如果警报开始和/或在与块边界完全相同的时间点结束,事情可能会变得棘手)。


修改/附加物

这是设置例程中使用的临时表的一种相当基本的方法(这段代码,我测试过):

--  Set up and initialize some variables
DECLARE
  @FirstDay      datetime
 ,@NumberOfDays  int

SET @FirstDay = 'Oct 1, 2011'  --  Without time, this makes it "midnight the morning of" that day
SET @NumberOfDays = 91  --  Through Dec 31


--  Creates a temporary table that will persist until it is dropped or the connection is closed
CREATE TABLE #TimeRanges
  (
    RangeStart  datetime  not null
   ,RangeEnd    datetime  not null
  ) 

--  The order in which you add rows to the table is irrelevant. By adding from last to first, I
--  only have to fuss around with one variable, instead of two (the counter and the end-point)

WHILE @NumberOfDays >= 0
 BEGIN
    INSERT #TimeRanges (RangeStart, RangeEnd)
     values ( dateadd(dd, @NumberOfDays, @FirstDay)       --  Start of day
             ,dateadd(dd, @NumberOfDays + 1, @FirstDay))  --  Start of the next day


    SET @NumberOfDays = @NumberOfDays - 1
 END


--  Review results
SELECT *
 from #TimeRanges
 order by RangeStart


--  Not necessary, but doesn't hurt, especially when testing code
DROP TABLE #TimeRanges

请注意,通过使RangeEnd成为第二天的开始,你必须小心你的greaterthans和lessthans。细节在那里会非常挑剔和挑剔,你会想做很多边缘情况测试(如果警报开始或结束,恰好在2011年12月16日00:00.000)。我会继续使用它,因为整体来说它比编码更简单,比如“Dec 16,2011 23:59.997”

答案 1 :(得分:1)

正如@paulbailey所提到的,您希望使用DATEDIFF函数来获取停机时间。

提取日期和停机时间(我正在添加您可能需要的更多列)..

SELECT compid,
       YEAR(startalarmdate) AS [Year],
       MONTH(startalarmdate) AS [Month],
       DAY(startalarmdate) AS [Day],
       DATEDIFF(ss, startalarmdate, endalarmdate) AS DowntimeInSeconds --You will need to convert thid later to the format you wish to use
FROM YourTable
/* WHERE CLAUSE - Most probably a date range */

现在,这可以为您提供停机时间的每秒停机时间。

通过按天分组并减少停机时间(再次添加您可能需要的更多列),可以轻松获得每天的停机时间。

SELECT compid, 
       [Year],
       [Month],
       [Day],
       SUM(DowntimeInSeconds) AS TotalDowntimeInSeconds
FROM (SELECT compid,
             YEAR(startalarmdate) AS [Year],
             MONTH(startalarmdate) AS [Month],
             DAY(startalarmdate) AS [Day],
             DATEDIFF(ss, startalarmdate, endalarmdate) AS DowntimeInSeconds --You will need to convert thid later to the format you wish to use
      FROM YourTable
      /* WHERE CLAUSE - Most probably a date range */) AS GetDowntimes
GROUP BY compid,  [Year], [Month], [Day]
ORDER BY [Year], [Month], [Day], compid

我相信这可以帮助你达到你想要的目的。

编辑: 要使此结果中包含没有停机时间的日期,您需要首先列出一个月内存在的所有日期。你拿这个列表然后你左上外连接上面的查询结果(你必须先删除ORDER BY)。

答案 2 :(得分:1)

Philip Kelley的回答中的案例陈述不起作用,尽管填充临时表的日期和左加入的主要原则是正确的。对于我的版本,我使用相同的变量开始 - 输入日期和报告的天数。

DECLARE @StartDate DATETIME, @Days INT
SELECT  @StartDate = GETDATE(),
        @Days = 5

-- REMOVE ANY TIME FROM THE STARTDATE
SET @StartDate = DATEADD(DAY, 0, DATEDIFF(DAY, 0, @StartDate))

-- CREATE THE TEMP TABLE OF DATES USING THE SAME METHODOLOGY
DECLARE @Dates TABLE (AlarmDate SMALLDATETIME NOT NULL PRIMARY KEY)
WHILE (@Days > 0)
BEGIN
    INSERT @Dates VALUES (DATEADD(DAY, @Days, @StartDate))
    SET @Days = @Days - 1
END

-- NOW SELECT THE DATA
SELECT  AlarmDate,
        CompID,
        CONVERT(DECIMAL(10, 2), ISNULL(SUM(DownTime), 0) / 3600.0) [DownTime]
FROM    @Dates
        LEFT JOIN
        (   SELECT  DATEADD(DAY, 0, DATEDIFF(DAY, 0, StartAlarmDate)) [StartAlarmDate],
                    CompID,
                    DATEDIFF(SECOND, StartAlarmDate, CASE WHEN EndAlarmDate >= DATEADD(DAY, 1, DATEDIFF(DAY, 0, StartAlarmDate)) THEN DATEADD(DAY, 1, DATEDIFF(DAY, 0, StartAlarmDate)) ELSE EndAlarmDate END) [DownTime]
            FROM    [yourTable]
            UNION ALL
            SELECT  DATEADD(DAY, 0, DATEDIFF(DAY, 0, EndAlarmDate)) [Date],
                    CompID,
                    DATEDIFF(SECOND, DATEADD(DAY, 1, DATEDIFF(DAY, 0, StartAlarmDate)), EndAlarmDate) [DownTime]
            FROM    [yourTable]
            WHERE   EndAlarmDate >= DATEADD(DAY, 1, DATEDIFF(DAY, 0, StartAlarmDate))
        ) data
            ON StartAlarmDate = AlarmDate
GROUP BY AlarmDate, CompID

我已经将日期差异的秒数除以3600.0,在秒数总计为60行后,每分钟差异为1分钟,当使用小时数时,总和为0。