2

1 つのテーブルを持つリレーション データベースがあるとします。

{datetime, tapeID, backupStatus}

2012-07-09 3:00, ID33, Start
2012-07-09 3:05, ID34, Start
2012-07-09 3:10, ID35, Start
2012-07-09 4:05, ID34, End
2012-07-09 4:10, ID33, Start
2012-07-09 5:05, ID33, End
2012-07-09 5:10, ID34, Start
2012-07-09 6:00, ID34, End
2012-07-10 4:00, ID35, Start
2012-07-11 5:00, ID35, End

tapeID = それぞれ固有の ID を持つ 100 個の異なるテープのいずれか。

backupStatus = Start または End の 2 つの割り当てのうちの 1 つ。

5 つのフィールドを返す SQL クエリを書きたい

{startTime,endTime,tapeID,totalBackupDuration,numberOfRestarts}
2012-07-09 3:00,2012-07-09 5:05, ID33, 0days2hours5min,1
2012-07-09 3:05,2012-07-09 4:05, ID34, 0days1hours0min,0
2012-07-09 3:10,2012-07-10 5:00, ID35, 0days0hours50min,1
2012-07-09 5:10,2012-07-09 6:00, ID34, 0days0hours50min,0

開始日と終了日をペアにして、各バックアップセットがいつ完全に完了したかを特定しようとしています。ここでの注意点は、単一のバックアップ セットのバックアップが再開される可能性があるため、次の終了イベントまで完了と見なされない複数の開始時刻が存在する可能性があることです。1 つのバックアップセットを 1 日に複数回バックアップする場合があるため、開始時刻と終了時刻を別々に指定する必要があります。

事前にご協力いただきありがとうございます。B

4

4 に答える 4

2

必要なことは、次の終了日をすべての開始に割り当てることです。次に、その間の開始回数を数えます。

select tstart.datetime as starttime, min(tend.datetime) as endtime, tstart.tapeid
from (select *
      from t
      where BackupStatus = 'Start'
     ) tstart join
     (select *
      from t
      where BackupStatus = 'End'
     ) tend
     on tstart.tapeid = tend.tapeid and
        tend.datetime >= tstart.datetime

これは近いですが、終了時刻ごとに複数の行があります (開始回数によって異なります)。これを処理するには、tapeid と終了時間でグループ化する必要があります。

select min(a.starttime) as starttime, a.endtime, a.tapeid,
       datediff(s, min(a.starttime), endtime), -- NOT CORRECT, DATABASE SPECIFIC
       count(*) - 1 as NumRestarts
from (select tstart.dt as starttime, min(tend.dt) as endtime, tstart.tapeid 
      from (select *
            from #t
            where BackupStatus = 'Start'
           ) tstart join
           (select *
            from #t
            where BackupStatus = 'End'
           ) tend
           on tstart.tapeid = tend.tapeid and
              tend.dt >= tstart.dt
     group by tstart.dt, tstart.tapeid
    ) a
group by a.endtime, a.tapeid 

このバージョンは、SQL Server 構文を使用して作成しました。テスト テーブルを作成するには、以下を使用できます。

create table #t (
    dt datetime,
    tapeID varchar(255),
    BackupStatus varchar(255)
)

insert into #t (dt, tapeID, BackupStatus) values ('2012-07-09 3:00', 'ID33', 'Start')
insert into #t (dt, tapeID, BackupStatus) values ('2012-07-09 3:05', 'ID34', 'Start')
insert into #t (dt, tapeID, BackupStatus) values ('2012-07-09 3:10', 'ID35', 'Start')
insert into #t (dt, tapeID, BackupStatus) values ('2012-07-09 4:05', 'ID34', 'End')
insert into #t (dt, tapeID, BackupStatus) values ('2012-07-09 4:10', 'ID33', 'Start')
insert into #t (dt, tapeID, BackupStatus) values ('2012-07-09 5:05', 'ID33', 'End')
insert into #t (dt, tapeID, BackupStatus) values ('2012-07-09 5:10', 'ID34', 'Start')
insert into #t (dt, tapeID, BackupStatus) values ('2012-07-09 6:00', 'ID34', 'End')
insert into #t (dt, tapeID, BackupStatus) values ('2012-07-10 4:00', 'ID35', 'Start')
insert into #t (dt, tapeID, BackupStatus) values ('2012-07-11 5:00', 'ID35', 'End')
于 2012-07-09T21:55:31.250 に答える
2

これが私のバージョンです。テーブルに追加INSERT #T SELECT '2012-07-11 12:00', 'ID35', 'Start'すると、このクエリにも未完了のバックアップが表示されます。OUTER APPLY問題を解決する自然な方法です。

SELECT
   Min(T.dt) StartTime,
   Max(E.dt) EndTime,
   T.tapeID,
   Datediff(Minute, Min(T.dt), Max(E.dt)) TotalBackupDuration,
   Count(*) - 1 NumberOfRestarts
FROM
   #T T
   OUTER APPLY (
      SELECT TOP 1 E.dt
      FROM #T E
      WHERE
         T.tapeID = E.tapeID
         AND E.BackupStatus = 'End'
         AND E.dt > T.dt
      ORDER BY E.dt
   ) E
WHERE
   T.BackupStatus = 'Start'
GROUP BY
   T.tapeID,
   IsNull(E.dt, T.dt)

CROSS APPLY についての 1 つのことは、1 つの行のみを返し、外部参照がすべて実際のテーブルである場合、それを派生テーブルの WHERE 句に移動することにより、SQL 2000 で同等になることです。

SELECT
   Min(T.dt) StartTime,
   Max(T.EndTime) EndTime,
   T.tapeID,
   Datediff(Minute, Min(T.dt), Max(T.EndTime)) TotalBackupDuration,
   Count(*) - 1 NumberOfRestarts
FROM (
      SELECT
         T.*,
         (SELECT TOP 1 E.dt
            FROM #T E
            WHERE
               T.tapeID = E.tapeID
               AND E.BackupStatus = 'End'
               AND E.dt > T.dt
            ORDER BY E.dt
         ) EndTime
      FROM #T T
      WHERE T.BackupStatus = 'Start'
   ) T
GROUP BY
   T.tapeID,
   IsNull(T.EndTime, T.dt)

すべてが実際のテーブルではない外部参照 (別のサブクエリの外部参照から計算された値が必要な場合) の場合、これを実現するには、ネストされた派生テーブルを追加する必要があります。

私はついに弾丸を噛み、いくつかの実際のテストを行いました. SPFiredrake のテーブル作成スクリプトを使用して、大量のデータで実際のパフォーマンスを確認しました。プログラムで行ったので、入力エラーはありません。それぞれ 10 回の実行を行い、各列の最悪値と最良値を除外し、その統計の残りの 8 つの列値を平均しました。

インデックスは、100% の FILL FACTOR でテーブルにデータを入力した後に作成されました。クラスター化インデックスのみが存在する場合、[インデックス] 列には 1 が表示されます。BackupStatus の非クラスター化インデックスが追加されている場合は 2 を示します。

テストからクライアント ネットワーク データ転送を除外するために、次のように各クエリを変数に選択しました。

DECLARE
   @StartTime datetime,
   @EndTime datetime,
   @TapeID varchar(5),
   @Duration int,
   @Restarts int;


WITH A AS (
-- The query here
)
SELECT
   @StartTime = StartTime,
   @EndTime = EndTime,
   @TapeID = TapeID,
   @Duration = TotalBackupDuration,
   @Restarts = NumberOfRestarts
FROM A;

また、テーブルの列の長さをより合理的な値 (tapeID varchar(5)、BackupStatus varchar(5)) に調整しました。実際、BackupStatus はビット列で、tapeID は整数である必要があります。しかし、当面は varchar を使用します。

   Server  Indexes       UserName   Reads  Writes    CPU  Duration
---------  -------  -------------  ------  ------  -----  --------
   x86 VM        1          ErikE   97219       0    599       325
   x86 VM        1  Gordon Linoff     606       0  63980     54638
   x86 VM        1    SPFiredrake  344927     260  23621     13105

   x86 VM        2          ErikE   96388       0    579       324
   x86 VM        2  Gordon Linoff  251443       0  22775     11830
   x86 VM        2    SPFiredrake  197845       0  11602      5986

x64 Beefy        1          ErikE   96745       0    919        61
x64 Beefy        1  Gordon Linoff  320012      70  62372     13400
x64 Beefy        1    SPFiredrake  362545     288  20154      1686

x64 Beefy        2          ErikE   96545       0    685       164
x64 Beefy        2  Gordon Linoff  343952      72  65092     17391
x64 Beefy        2    SPFiredrake  198288       0  10477       924

ノート:

  • x86 VM: ほとんどアイドル状態の仮想マシン、Microsoft SQL Server 2008 (RTM) - 10.0.1600.22 (Intel X86)
  • x64 Beefy: 非常に頑丈で、おそらく非常にビジーな Microsoft SQL Server 2008 R2 (RTM) - 10.50.1765.0 (X64)

2 番目のインデックスは、すべてのクエリに役立ちました。

興味深いことに、Gordon が 1 つのサーバーで最初に少ない読み取り数を 2 番目に高くしたのは興味深いことです。より強力なサーバー)。しかし、その計画によって CPU コストが大幅に削減され、オプティマイザでのコストが削減されたため、インデックスによって読み取り数が増加しました。

于 2012-07-10T20:14:58.257 に答える
1

刺してみようと思いました。Gordon Linoff のソリューションをテストしましたが、彼自身の例では、tapeID 33 を正しく計算していません (対応する終了ではなく、次の開始に一致します)。

私の試みは、CROSS/OUTER APPLY を利用しているため、SQL サーバー 2005 以降を使用していることを前提としています。サーバー2000で必要な場合は、おそらくスイングできますが、これが最もクリーンなソリューションのように思えました(すべての終了要素から開始し、最初の開始要素を一致させて結果を取得するため)。私が何をしているのかを理解できるように、注釈も付けます。

SELECT 
    startTime, endT.dt endTime, endT.tapeID, DATEDIFF(s, startTime, endT.dt), restarts
FROM 
    #t endT -- Main source, getting all 'End' records so we can match.
    OUTER APPLY ( -- Match possible previous 'End' records for the tapeID
        SELECT TOP 1 dt 
        FROM #t 
        WHERE dt < endT.dt AND tapeID = endT.tapeID 
        AND BackupStatus = 'End') g
    CROSS APPLY (SELECT ISNULL(g.dt, CAST(0 AS DATETIME)) dt) t 
    CROSS APPLY ( 
        -- Match 'Start' records between our 'End' record
        -- and our possible previous 'End' record.
        SELECT MIN(dt) startTime, 
            COUNT(*) - 1 restarts -- Restarts, so -1 for the first 'Start'
        FROM #t 
        WHERE tapeID = endT.tapeID AND BackupStatus = 'Start' 
                -- This is where our previous possible 'End' record is considered
            AND dt > t.dt AND dt < endt.dt) starts
WHERE 
    endT.BackupStatus = 'End'

編集:このリンクにあるテスト データ生成スクリプト。

そこで、3 つの方法に対していくつかのデータを実行することに決め、ErikE のソリューションが最速であり、私のソリューションは非常に僅差であり、Gordon のソリューションはかなりのサイズのセットでは非効率的であることがわかりました (1000 レコードを処理した場合でも、速度が低下し始めました)。小規模なセット (約 5,000 レコード) の場合、私の方法は Erik の方法よりも優れていますが、それほどではありません。正直なところ、データを取得するために追加の集計関数を必要としないため、この方法が気に入っていますが、効率/速度の戦いでは ErikE のほうが勝っています。

編集 2: テーブル内の 55,000 レコード (および 12,000 の一致する開始/終了ペア) の場合、エリックは ~0.307 秒かかり、私のものは ~0.157 秒かかります (50 回以上の試行の平均)。個々の実行が全体に変換されると想定していたので、これには少し驚きましたが、クエリによってインデックス キャッシュがより適切に利用されているため、後続のヒットのコストが低くなると思います。実行計画を見ると、ErikE にはメイン パスからの分岐が 1 つしかないため、最終的にはほとんどのクエリに対してより大きなセットで作業しています。出力の近くで結合する 3 つの分岐があるため、特定の瞬間に少ないデータをかき回し、最後に結合します。

于 2012-07-10T19:46:26.010 に答える
0

非常にシンプルにします。開始イベント用に 1 つのサブクエリを作成し、終了イベント用に別のサブクエリを作成します。開始と終了を持つ各行の各セットのランク関数。次に、2 つのサブクエリに左結合を使用します。

-- QUERY
WITH CTE as 
(
SELECT  dt
        , ID
        , status  
        --, RANK () OVER (PARTITION BY ID ORDER BY DT) as rnk1
        --,  RANK () OVER (PARTITION BY status ORDER BY DT) as rnk2
FROM INT_backup
)
SELECT * 
FROM CTE 
ORDER BY id, rnk2


select * FROM INT_backup order by id, dt

SELECT * 
FROM 
(
    SELECT  dt
            , ID
            , status  
            , rank () over (PARTITION by ID ORDER BY dt) as rnk
    FROM INT_backup
    WHERE status = 'start' 
) START_SET
LEFT JOIN 
(
    SELECT  dt
            , ID
            , status  
            , rank () over (PARTITION by ID ORDER BY dt) as rnk
    FROM INT_backup
    where status = 'END'
) END_SET
ON Start_Set.ID = End_SET.ID 
AND Start_Set.Rnk = End_Set.rnk
于 2014-01-28T00:02:13.310 に答える