【问题标题】:How to calculate the total time while excluding the overlapped time in Microsoft SQL?Microsoft SQL 中如何在排除重叠时间的情况下计算总时间?
【发布时间】:2018-04-20 04:50:37
【问题描述】:

在这种情况下,我在处理间隙和孤岛类型时遇到了问题。 我想计算 Microsoft SQL 的总停机时间。有没有我可以产生以下输出?谢谢!

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

在这种情况下:

机器 A:14 小时

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

机器 C:1 小时

机器 D:2 小时

机器 E:重叠 1 小时

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

总共是19小时作为实际停机时间'

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

【问题讨论】:

  • select 19 as [Downtime (hours)] 浮现在我的脑海中,因为您没有为计算提供任何解释,这是获得结果的最简单方法。
  • @GordonLinoff 我只是编辑以便对计算进行解释

标签: sql sql-server


【解决方案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;

【讨论】:

  • 感谢您的回复!如果我的表是一个查询,我应该把我的查询放到哪里?
  • @Felix:请查看编辑。只需将 Production AS 公用表表达式中的示例查询替换为您的查询即可。
  • 嗨,我有一个简单的问题:为什么我必须“将生产日期声明为 DATE = '2018-01-16'”?因为我的桌子上会有很多不同的日期。
  • 另外,当我按照您的指示执行您的查询时。我收到一个错误:“SQL 错误 (102): 'GO' 附近的语法不正确。我该如何解决这个问题?
  • 删除代码中的 GO 后,出现另一个错误“SQL 错误 (262): CREATE FUNCTION permission denied in database 'tempdb'”
【解决方案2】:

这很丑陋,但这是我所做的:

  1. 合并所有记录
  2. 检查重叠
  3. 获取重叠的最小开始和最大停止
  4. 从原始集合中删除重叠
  5. 对重叠和非重叠的开始/停止增量求和

我使用您上面的示例数据创建了一个表格并得到了您的答案: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;

【讨论】:

  • 你介意我是否可以让你的表代码在 SQL Fiddle 中创建一个表
  • 如果我有一个表作为查询,我应该把我的查询放在哪里?
  • @Felix 将您的表名替换为 [CEA_DBA].[dbo].[LineInteruptions]。表代码为 CREATE TABLE [dbo].[LineInteruptions]( [Machine] [char](1) NOT NULL, [Date] [date] NOT NULL, [TimeStarted] [time](7) NOT NULL, [TimeCompleted] [time](7) NOT NULL, [id] [int] IDENTITY(1,1) NOT NULL) ON [PRIMARY] GO
猜你喜欢
  • 1970-01-01
  • 2017-07-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-23
  • 2015-08-17
  • 2017-11-09
  • 1970-01-01
相关资源
最近更新 更多