如何计算在Microsoft SQL中排除重叠时间的总时间?

时间:2018-04-18 23:27:50

标签: sql sql-server

在这种情况下我遇到了解决Gaps和Islands类型的问题。 我想计算Microsoft SQL中的总停机时间。有没有我可以产生以下输出?谢谢!

实际停机时间=总停机时间 - 重叠时间

Original Data

Desired Output

在这种情况下:

机器A:14小时

机器B:5小时但重叠4小时

机器C:1小时

机器D:2小时

机器E:重叠1小时

机器F:重叠2小时但1小时

实际停机时间总共为19小时

我的表格是一个查询。请告诉我如何查询。谢谢!

2 个答案:

答案 0 :(得分:1)

这是基于Itzik Ben-Gan技术的解决方案(在下面的来源中注明)。该解决方案使用DENSE_RANK函数。代码已完成 - 可以将其复制到SSMS查询窗口并执行。

USE tempdb
GO

IF OBJECT_ID('dbo.GetNums', 'IF') IS NOT NULL
  DROP FUNCTION dbo.GetNums;
GO

/* dbo.GetNums function is from Itzik Ben-Gan's article on packing intervals:
   (http://blogs.solidq.com/en/sqlserver/packing-intervals/). */

CREATE FUNCTION dbo.GetNums(@n AS BIGINT)
  RETURNS TABLE
AS
  RETURN
    WITH
      L0 AS (SELECT 1 AS c UNION ALL SELECT 1),
      L1 AS (SELECT 1 AS c FROM L0 AS A CROSS JOIN L0 AS B),
      L2 AS (SELECT 1 AS c FROM L1 AS A CROSS JOIN L1 AS B),
      L3 AS (SELECT 1 AS c FROM L2 AS A CROSS JOIN L2 AS B),
      L4 AS (SELECT 1 AS c FROM L3 AS A CROSS JOIN L3 AS B),
      L5 AS (SELECT 1 AS c FROM L4 AS A CROSS JOIN L4 AS B),
      Nums AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS n FROM L5)
    SELECT TOP (@n) n FROM Nums ORDER BY n;
GO

IF OBJECT_ID('dbo.Production', 'U') IS NOT NULL
  DROP TABLE dbo.Production;
GO

CREATE TABLE dbo.Production
  (
    production_line INT NOT NULL,
    machine CHAR(1) NOT NULL,
    [date] DATE NOT NULL,
    time_started TIME NOT NULL,
    time_completed TIME NOT NULL,
    CONSTRAINT PK_Production PRIMARY KEY(production_line, machine)
  );

INSERT INTO dbo.Production
    (production_line, machine, [date], time_started, time_completed)
  VALUES
    (1, 'A', '2018-01-16', '00:00:00', '14:00:00'),
    (1, 'B', '2018-01-16', '10:00:00', '15:00:00'),
    (1, 'C', '2018-01-16', '17:00:00', '18:00:00'),
    (1, 'D', '2018-01-16', '21:00:00', '23:00:00'),
    (1, 'E', '2018-01-16', '21:30:00', '22:30:00'),
    (1, 'F', '2018-01-16', '17:00:00', '19:00:00');

/* Algorithm adapted from "Microsoft SQL Server 2012
   High-Performance T-SQL Using Window Functions" by
   Itzik Ben-Gan (p. 198). */

DECLARE @production_date AS DATE = '2018-01-16';
DECLARE @from AS TIME = '00:00:00';
DECLARE @to AS TIME = '23:59:59';

WITH Hours AS
(
  SELECT
      DATEADD(hour, (nums.n - 1), @from) AS hr
    FROM
      dbo.GetNums(24 /* Hours in a day. */) AS nums
),
Groups AS
(
  SELECT
      H.hr,
      DATEADD(hour, -1 * DENSE_RANK() OVER (ORDER BY H.hr), H.hr) AS grp
    FROM
      dbo.Production AS P
      INNER JOIN Hours AS H ON H.hr BETWEEN P.time_started AND P.time_completed
    WHERE
      p.[date] = @production_date
),
Ranges AS
(
  SELECT
      MIN(hr) AS range_start,
      MAX(hr) AS range_end
    FROM
      Groups
    GROUP BY
      grp
)
SELECT
    SUM(DATEDIFF(hour, range_start, range_end)) AS hours_of_downtime
  FROM
    Ranges

DROP FUNCTION dbo.GetNums;
DROP TABLE dbo.Production;

编辑:回答OP关于他们的数据是否来自查询的问题。此修改示例删除临时dbo.Production表,并添加Production公用表表达式。

USE tempdb
GO

IF OBJECT_ID('dbo.GetNums', 'IF') IS NOT NULL
  DROP FUNCTION dbo.GetNums;
GO

/* dbo.GetNums function is from Itzik Ben-Gan's article on packing intervals:
   (http://blogs.solidq.com/en/sqlserver/packing-intervals/). */

CREATE FUNCTION dbo.GetNums(@n AS BIGINT)
  RETURNS TABLE
AS
  RETURN
    WITH
      L0 AS (SELECT 1 AS c UNION ALL SELECT 1),
      L1 AS (SELECT 1 AS c FROM L0 AS A CROSS JOIN L0 AS B),
      L2 AS (SELECT 1 AS c FROM L1 AS A CROSS JOIN L1 AS B),
      L3 AS (SELECT 1 AS c FROM L2 AS A CROSS JOIN L2 AS B),
      L4 AS (SELECT 1 AS c FROM L3 AS A CROSS JOIN L3 AS B),
      L5 AS (SELECT 1 AS c FROM L4 AS A CROSS JOIN L4 AS B),
      Nums AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS n FROM L5)
    SELECT TOP (@n) n FROM Nums ORDER BY n;
GO

/* Algorithm adapted from "Microsoft SQL Server 2012
   High-Performance T-SQL Using Window Functions" by
   Itzik Ben-Gan (p. 198). */

DECLARE @production_date AS DATE = '2018-01-16';
DECLARE @from AS TIME = '00:00:00';
DECLARE @to AS TIME = '23:59:59';

WITH Hours AS
(
  SELECT
      DATEADD(hour, (nums.n - 1), @from) AS hr
    FROM
      dbo.GetNums(24 /* Hours in a day. */) AS nums
),
Production AS
(
  SELECT
      production_line,
      machine,
      [date],
      time_started,
      time_completed 
    FROM
      production_table
    WHERE
      [date] = @production_date
),
Groups AS
(
  SELECT
      H.hr,
      DATEADD(hour, -1 * DENSE_RANK() OVER (ORDER BY H.hr), H.hr) AS grp
    FROM
      Production AS P
      INNER JOIN Hours AS H ON H.hr BETWEEN P.time_started AND P.time_completed
),
Ranges AS
(
  SELECT
      MIN(hr) AS range_start,
      MAX(hr) AS range_end
    FROM
      Groups
    GROUP BY
      grp
)
SELECT
    SUM(DATEDIFF(hour, range_start, range_end)) AS hours_of_downtime
  FROM
    Ranges

DROP FUNCTION dbo.GetNums;

答案 1 :(得分:0)

这非常难看,但这就是我所做的:

  1. 合并所有记录
  2. 检查重叠
  3. 获取重叠的最小开始和最大停止
  4. 删除原始集合中的重叠
  5. 对重叠和非重叠的开始/停止增量求和
  6. 我使用上面的示例数据创建了一个表格并得到了答案:19。

    代码是:

     WITH aset
     AS (
     SELECT [Machine]
          , [Date]
          , [TimeStarted]
          , TimeCompleted
     FROM   [CEA_DBA].[dbo].[LineInteruptions]
     WHERE  date = '2018-01-16'),
     overlaps
     AS (
     SELECT a.machine
            AS a_machine
          , b.machine
            AS b_machine
          , CASE
                WHEN a.TimeStarted <= b.TimeStarted
                THEN a.TimeStarted
                ELSE b.TimeStarted
            END
            AS timeStarted
          , CASE
                WHEN a.TimeCompleted >= b.TimeCompleted
                THEN a.TimeCompleted
                ELSE b.TimeCompleted
            END
            AS timeCompleted
     FROM   aset
          AS a
            CROSS JOIN aset
          AS b
     WHERE  b.TimeStarted <= a.timeCompleted
            AND b.timecompleted >= a.timecompleted
            AND a.Machine <> b.Machine),
     nonoverlaps
     AS (
     SELECT aset.timeStarted
          , aset.timeCompleted
     FROM   aset
            LEFT OUTER JOIN overlaps
          AS oa ON aset.Machine = oa.a_machine
            LEFT OUTER JOIN overlaps
          AS ob ON aset.Machine = ob.b_machine
     WHERE  oa.a_machine IS NULL
            AND ob.b_machine IS NULL),
     gset
     AS (
     SELECT TimeStarted
          , TimeCompleted
     FROM     overlaps
     UNION ALL
     SELECT timestarted
          , timecompleted
     FROM   nonoverlaps)
     SELECT SUM(DATEDIFF(hour, TimeStarted, timeCompleted))
            AS downtime
     FROM   gset;