2011-08-28 6 views
8

MSSQLまたはDB2またはOracleを使用していません。 CTEがありません。 OVERLAP述部がありません。 INTERVALデータ型がありません。 状況:修理する車両には、 まで注文したすべての部品を受け取るまで作業を開始できません。 部品は、修理の開始前に複数回注文することができます。我々は、車両が上にあったID = 1つの 部品として同定車両用だから複数の重複間隔から経過した時間を検索するSQL

「を部品保持」する時間を抽出する必要 は、(D1)を注文し、4つの異なる機会

ID  d1  d2 
    1  8/1 8/8 
    1  8/2 8/6 
    1  8/12 8/14 
    1  8/3 8/10 

8/1        8/8 
    d1        d2 
    |-------------------------------| 
     8/2    8/6     8/12  8/14     
     d1    d2      d1  d2  
      |---------------|      |----------|  
        8/3     8/10 
        d1     d2 
        |---------------------| 
8/1              8/14 
    |---------------------------------------------------------| = 13 days 
             8/10 8/12 
    |--------------------------------------| + |----------| = parts hold = 11 days 
に(D2)を受信しました

上記からわかるように、仕事を開始するまでの待ち時間(自動車が仕事に利用可能であった日の として8/1と仮定)は13日間です。 部品の待機に費やされた実際の時間は11日であり、これはデータから導き出す必要がある番号 です。 実際の日時データは、表示を簡単にするために、このサンプルデータで日付を使用した の時間を抽出するタイムスタンプとなります。 私たちは、(psmではなく、udfではなくカーソルではなく)集合ベースのソリューションを生成することに苦労しています。 TIA

+0

私は持っていますが、補助的なカレンダーテーブルの左の結合が役立つかもしれません。 –

+0

[可能性のある日付のセットのギャップを見つけるための良い方法は何ですか?](http://stackoverflow.com/questions/4765495/what-is-a-good-way-to-find-gaps-in) -a-set-of-datespans) –

+0

@Brian、この質問は大きく異なります。 OP、クエリを支援するビューを追加できますか? –

答えて

4

このSQL文は、(tはSAMPEテーブルのテーブル名です)あなたが欲しいものを得るようだ:

SELECT 
    d.id, 
    d.duration, 
    d.duration - 
    IFNULL(
     (SELECT Sum(timestampdiff(SQL_TSI_DAY, 
            no_hold.d2, 
            (SELECT min(d1) FROM t t4 
            WHERE t4.id = no_hold.id and t4.d1 > no_hold.d2))) 
     FROM (SELECT DISTINCT id, d2 FROM t t1 
       WHERE (SELECT sum(IIF(t1.d2 between t2.d1 and t2.d2, 1, 0)) 
         FROM t t2 WHERE t2.id = t1.id and t2.d2 <> t1.d2) = 0 
      And d2 <> (select max(d2) from t t3 where t3.id = t1.id)) no_hold 
     WHERE no_hold.id = d.id), 
     0) "parts hold" 
FROM 
    (SELECT id, timestampdiff(SQL_TSI_DAY, min(d1), max(d2)) duration 
    FROM t GROUP BY id) d 

外部クエリを取得します修復作業の継続時間。複合サブクエリは、部品を待機していない合計日数を計算します。これは、車両は部品を待っていない開始日を配置することによって行われ、それが再び部品を待つ始めるまでの日数をカウントします

// 1) The query for finding the starting dates when the vehicle is not waiting for parts, 
// i.e. finding all d2 that is not within any date range where the vehicle is waiting for part. 
// The DISTINCT is needed to removed duplicate starting "no hold" period. 

SELECT DISTINCT id, d2 
FROM t t1 
WHERE (SELECT sum(IIF(t1.d2 between t2.d1 and t2.d2, 1, 0)) from t t2 
     WHERE t2.id = t1.id and t2.d2 <> t1.d2) = 0 AND 
     d2 <> (SELECT max(d2) FROM t t3 WHERE t3.id = t1.id)) 

// 2)車両がある日一部ではないために待っている車まで上記のクエリからの日付が//

timestampdiff(SQL_TSI_DAY, no_hold.d2, (SELECT min(d1) FROM t t4 WHERE t4.id = no_hold.id and t4.d1 > no_hold.d2)) 

再び一部を待って上記の2を組み合わせると、すべてのこのような期間を集約されている車両は部品を待っていないことの日数を与えます。最後のクエリでは、外側のクエリから各idの結果を計算するための追加の条件が追加されます。

これはおそらく、多数のIDを持つ非常に大きなテーブルではあまり効率的ではありません。 IDが1つまたは数個に制限されているとうまくいくはずです。

+0

うわー、それは美しいです! – jon

+0

重複開始日の「ホールド」期間の問題を修正するために編集されました。 –

+0

Alex:上記のメインコードブロックで、 – jon

5

@Alex Wのクエリが機能しませんでした。それは標準のSQLではないので、SQL Serverと互換性を持たせるためには多くの書き換えが必要でした(これは私がテストすることができます)。しかし、それは私にいくつかのインスピレーションを与えてくれました。


途切れない待機のすべての期間のすべての開始点を探す:

SELECT DISTINCT 
    t1.ID, 
    t1.d1 AS date, 
    -DATEDIFF(DAY, (SELECT MIN(d1) FROM Orders), t1.d1) AS n 
FROM Orders t1 
LEFT JOIN Orders t2     -- Join for any events occurring while this 
    ON t2.ID = t1.ID     -- is starting. If this is a start point, 
    AND t2.d1 <> t1.d1    -- it won't match anything, which is what 
    AND t1.d1 BETWEEN t2.d1 AND t2.d2 -- we want. 
GROUP BY t1.ID, t1.d1, t1.d2 
HAVING COUNT(t2.ID) = 0 

とエンドポイントの等価:

SELECT DISTINCT 
    t1.ID, 
    t1.d2 AS date, 
    DATEDIFF(DAY, (SELECT MIN(d1) FROM Orders), t1.d2) AS n 
FROM Orders t1 
LEFT JOIN Orders t2 
    ON t2.ID = t1.ID 
    AND t2.d2 <> t1.d2 
    AND t1.d2 BETWEEN t2.d1 AND t2.d2 
GROUP BY t1.ID, t1.d1, t1.d2 
HAVING COUNT(t2.ID) = 0 

nは、いくつかの共通からの日数ですある時点。開始点は負の値を持ち、終点は正の値を持ちます。これは、それらを追加して、その間の日数を取得できるようにするためです。

span = end - start 
span = end + (-start) 
span1 + span2 = end1 + (-start1) + end2 + (-start2) 

最後に、私たちは物事を追加する必要があります。

SELECT ID, SUM(n) AS hold_days 
FROM (
    SELECT DISTINCT 
     t1.id, 
     t1.d1 AS date, 
     -DATEDIFF(DAY, (SELECT MIN(d1) FROM Orders), t1.d1) AS n 
    FROM Orders t1 
    LEFT JOIN Orders t2 
     ON t2.ID = t1.ID 
     AND t2.d1 <> t1.d1 
     AND t1.d1 BETWEEN t2.d1 AND t2.d2 
    GROUP BY t1.ID, t1.d1, t1.d2 
    HAVING COUNT(t2.ID) = 0 
    UNION ALL 
    SELECT DISTINCT 
     t1.id, 
     t1.d2 AS date, 
     DATEDIFF(DAY, (SELECT MIN(d1) FROM Orders), t1.d2) AS n 
    FROM Orders t1 
    LEFT JOIN Orders t2 
     ON t2.ID = t1.ID 
     AND t2.d2 <> t1.d2 
     AND t1.d2 BETWEEN t2.d1 AND t2.d2 
    GROUP BY t1.ID, t1.d1, t1.d2 
    HAVING COUNT(t2.ID) = 0 
    ORDER BY ID, date 
) s 
GROUP BY ID; 

入力テーブル(受注):

ID d1   d2 
1 2011-08-01 2011-08-08 
1 2011-08-02 2011-08-06 
1 2011-08-03 2011-08-10 
1 2011-08-12 2011-08-14 
2 2011-08-01 2011-08-03 
2 2011-08-02 2011-08-06 
2 2011-08-05 2011-08-09 

が出力:

ID hold_days 
1   11 
2   8 

または、ストアドプロシージャでこれを行うこともできます。

CREATE PROCEDURE CalculateHoldTimes 
    @ID int = 0 
AS 
BEGIN 
    DECLARE Events CURSOR FOR 
    SELECT * 
    FROM (
     SELECT d1 AS date, 1 AS diff 
     FROM Orders 
     WHERE ID = @ID 
     UNION ALL 
     SELECT d2 AS date, -1 AS diff 
     FROM Orders 
     WHERE ID = @ID 
    ) s 
    ORDER BY date; 

    DECLARE @Events_date date, 
      @Events_diff int, 
      @Period_start date, 
      @Period_accum int, 
      @Total_start date, 
      @Total_count int; 

    OPEN Events; 

    FETCH NEXT FROM Events 
    INTO @Events_date, @Events_diff; 

    SET @Period_start = @Events_date; 
    SET @Period_accum = 0; 
    SET @Total_start = @Events_date; 
    SET @Total_count = 0; 

    WHILE @@FETCH_STATUS = 0 
    BEGIN 
     SET @Period_accum = @Period_accum + @Events_diff; 

     IF @Period_accum = 1 AND @Events_diff = 1 
      -- Start of period 
      SET @Period_start = @Events_date; 
     ELSE IF @Period_accum = 0 AND @Events_diff = -1 
      -- End of period 
      SET @Total_count = @Total_count + 
       DATEDIFF(day, @Period_start, @Events_date); 

     FETCH NEXT FROM Events 
     INTO @Events_date, @Events_diff; 
    END; 

    SELECT 
     @Total_start AS d1, 
     @Events_date AS d2, 
     @Total_count AS hold_time; 
END; 

でそれを呼び出します

EXEC CalculateHoldTimes 1; 
+0

ありがとうMizardX、これは私たちが探していたものです – jon

+0

申し訳ありませんが、あなたの答えをクリックするのに十分なポイントがありません – jon

+0

今できます。 :)とにかく;あなたの質問に答えると思われる場合は、依然として回答を受け入れることができます。投票矢印の下にあるチェックマークをクリックします。 –

0
USE [DnnMasterShoraSystem] 
GO 
/****** Object: StoredProcedure [dbo].[CalculateHoldTimes] Script Date: 12/8/2014 1:36:12 PM ******/ 
SET ANSI_NULLS ON 
GO 
SET QUOTED_IDENTIFIER ON 
GO 

ALTER PROCEDURE [dbo].[CalculateHoldTimes] 
    @PID int  
AS 
BEGIN  
CREATE TABLE #tblTemp(
    [ID] [int] NOT NULL, 
    [PID] [int] NOT NULL, 
    [BID] [int] NOT NULL, 
    [Active] [bit] NULL, 
    [WorkStartDate] [nvarchar](10) NULL, 
    [WorkEndDate] [nvarchar](10) NULL, 
    [jobStateID] [int] NULL, 
    [RegisterType] [int] NULL, 
    [RegisterState] [int] NULL, 
    [En_time] [datetime] NULL, 
    [Fa_time] [nvarchar](40) NULL, 
    [Status] [nvarchar](100) NULL, 
    [PortalId] [int] NULL, 
    [ModuleId] [int] NULL, 
    [UserId] [int] NULL, 
    [BrName] [nvarchar](150) NULL, 
    [BrCode] [nvarchar](20) NULL, 
    [WorkEndDate_New] [nvarchar](10) NULL 
) ON [PRIMARY] 

insert into #tblTemp 
select * from [dbo].[Shora.Personel_Branch_Copy] 
     where WorkStartDate is not null 
     --and [dbo].[ShamsiToMiladi](WorkStartDate) <GETDATE() 
     --and [dbo].[ShamsiToMiladi](WorkEndDate) <GETDATE() 
     and [email protected] 
     --and [dbo].[ShamsiToMiladi](WorkEndDate)<[dbo].[ShamsiToMiladi](@NewDate) 
     order by WorkStartDate 

DECLARE Events CURSOR FOR 
    SELECT [dbo].[ShamsiToMiladi](WorkStartDate) AS StartDate,[dbo].[ShamsiToMiladi](WorkEndDate) AS EndDate 
     FROM #tblTemp   
    ORDER BY StartDate; 

--drop table #tblTemp 

    DECLARE @SDate date, 
      @EDate date, 
      @Period_Start date, 
      @Period_End date, 
      @Total int, 
      @OldSDate date, 
      @OldEDate date 


    OPEN Events; 

    FETCH NEXT FROM Events 
    INTO @SDate, @EDate; 

    set @Total=0 
    SET @Period_Start [email protected] 
    set @[email protected] 

    WHILE @@FETCH_STATUS = 0 
    BEGIN  
    if @OldSDate>@Period_End 
     begin 
      set @[email protected]    

      if @Period_End>[email protected]_Start 
      set @Total+=DATEDIFF(DAY,@Period_Start,@Period_End) 
     end 
    else if @SDate<@Period_End 
     begin  
     set @[email protected]_Start  
      set @Total=DATEDIFF(DAY,@Period_Start,@Period_End) 
     end 

     set @[email protected] 
     set @[email protected] 

     FETCH NEXT FROM Events 
     INTO @SDate, @EDate; 

     if @Period_End<@EDate 
     set @[email protected] 

    END; 

INSERT INTO [dbo].[PersonelDays] 
      (PID 
      ,[Total_Start] 
      ,[Total_End] 
      ,[Total_count]) 
    VALUES 
      (@PID,   
      @Period_Start, 
      @Period_End, 
      @Total 
      ) 

drop table #tblTemp 
CLOSE Events 
DEALLOCATE Events 
END; 
関連する問題