質問

次のデータを含むテーブルがあります(例では簡略化されています)

Row Start       Finish       ID  Amount
--- ---------   ----------   --  ------
  1 2008-10-01  2008-10-02   01      10
  2 2008-10-02  2008-10-03   02      20
  3 2008-10-03  2008-10-04   01      38
  4 2008-10-04  2008-10-05   01      23
  5 2008-10-05  2008-10-06   03      14
  6 2008-10-06  2008-10-07   02       3
  7 2008-10-07  2008-10-08   02       8
  8 2008-10-08  2008-11-08   03      19

日付は期間を表し、IDはその期間中にシステムがあった状態であり、金額はその状態に関連する値です。

私がしたいのは、同じ ID番号を持つ隣接行の金額を集計することですが、連続した実行を結合できるように同じ全体シーケンスを保持します。したがって、次のようなデータになりたいと思います。

Row Start       Finish       ID  Amount
--- ---------   ----------   --  ------
  1 2008-10-01  2008-10-02   01      10
  2 2008-10-02  2008-10-03   02      20
  3 2008-10-03  2008-10-05   01      61
  4 2008-10-05  2008-10-06   03      14
  5 2008-10-06  2008-10-08   02      11
  6 2008-10-08  2008-11-08   03      19

SPに配置できるT-SQLソリューションを探していますが、単純なクエリでそれを行う方法がわかりません。何らかの反復を必要とするかもしれませんが、その道をたどりたくありません。

この集計を行う理由は、プロセスの次のステップが、シーケンス内で発生する一意のIDでグループ化されたSUM()およびCount()を実行することであるため、最終データは次のようになります:

ID  Counts Total
--  ------ -----
01       2    71
02       2    31
03       2    33

ただし、単純に行う場合

SELECT COUNT(ID), SUM(Amount) FROM data GROUP BY ID

元のテーブルには次のようなものが表示されます

ID  Counts Total
--  ------ -----
01       3    71
02       3    31
03       2    33

これは私が望むものではありません。

正しい解決策はありません

他のヒント

「SQLでの時間指向データベースアプリケーションの開発」という本を読んだ場合、 RT Snodgrass (そのpdfは出版物の下で彼のWebサイトから入手可能)、 p165-166の図6.25を見ると、現在の例で同じID値と連続時間間隔を持つさまざまな行をグループ化するために使用できる重要なSQLが見つかります。

以下のクエリ開発はほぼ修正済みですが、最後に最初のSELECTステートメントに原因がある問題があります。間違った答えが与えられる理由をまだ追跡していません。 [誰かがDBMSでSQLをテストし、そこで最初のクエリが正しく機能するかどうかを教えていただければ、とても助かります!]

次のようになります:

-- Derived from Figure 6.25 from Snodgrass "Developing Time-Oriented
-- Database Applications in SQL"
CREATE TABLE Data
(
    Start   DATE,
    Finish  DATE,
    ID      CHAR(2),
    Amount  INT
);

INSERT INTO Data VALUES('2008-10-01', '2008-10-02', '01', 10);
INSERT INTO Data VALUES('2008-10-02', '2008-10-03', '02', 20);
INSERT INTO Data VALUES('2008-10-03', '2008-10-04', '01', 38);
INSERT INTO Data VALUES('2008-10-04', '2008-10-05', '01', 23);
INSERT INTO Data VALUES('2008-10-05', '2008-10-06', '03', 14);
INSERT INTO Data VALUES('2008-10-06', '2008-10-07', '02',  3);
INSERT INTO Data VALUES('2008-10-07', '2008-10-08', '02',  8);
INSERT INTO Data VALUES('2008-10-08', '2008-11-08', '03', 19);

SELECT DISTINCT F.ID, F.Start, L.Finish
    FROM Data AS F, Data AS L
    WHERE F.Start < L.Finish
      AND F.ID = L.ID
      -- There are no gaps between F.Finish and L.Start
      AND NOT EXISTS (SELECT *
                        FROM Data AS M
                        WHERE M.ID = F.ID
                        AND F.Finish < M.Start
                        AND M.Start < L.Start
                        AND NOT EXISTS (SELECT *
                                            FROM Data AS T1
                                            WHERE T1.ID = F.ID
                                              AND T1.Start <  M.Start
                                              AND M.Start  <= T1.Finish))
      -- Cannot be extended further
      AND NOT EXISTS (SELECT *
                          FROM Data AS T2
                          WHERE T2.ID = F.ID
                            AND ((T2.Start <  F.Start  AND F.Start  <= T2.Finish)
                              OR (T2.Start <= L.Finish AND L.Finish <  T2.Finish)));

そのクエリからの出力は次のとおりです。

01  2008-10-01      2008-10-02
01  2008-10-03      2008-10-05
02  2008-10-02      2008-10-03
02  2008-10-06      2008-10-08
03  2008-10-05      2008-10-06
03  2008-10-05      2008-11-08
03  2008-10-08      2008-11-08

編集済み:最後から2番目の行に問題があります-存在しないはずです。そして、それがどこから来たのか(まだ)わかりません。

ここで、その複雑な式を別のSELECTステートメントのFROM句のクエリ式として扱う必要があります。これにより、上記の最大範囲と重複するエントリで特定のIDの金額値が合計されます。

SELECT M.ID, M.Start, M.Finish, SUM(D.Amount)
    FROM Data AS D,
         (SELECT DISTINCT F.ID, F.Start, L.Finish
              FROM Data AS F, Data AS L
              WHERE F.Start < L.Finish
                AND F.ID = L.ID
                -- There are no gaps between F.Finish and L.Start
                AND NOT EXISTS (SELECT *
                                    FROM Data AS M
                                    WHERE M.ID = F.ID
                                    AND F.Finish < M.Start
                                    AND M.Start < L.Start
                                    AND NOT EXISTS (SELECT *
                                                        FROM Data AS T1
                                                        WHERE T1.ID = F.ID
                                                          AND T1.Start <  M.Start
                                                          AND M.Start  <= T1.Finish))
                  -- Cannot be extended further
                AND NOT EXISTS (SELECT *
                                    FROM Data AS T2
                                    WHERE T2.ID = F.ID
                                      AND ((T2.Start <  F.Start  AND F.Start  <= T2.Finish)
                                        OR (T2.Start <= L.Finish AND L.Finish <  T2.Finish)))) AS M
    WHERE D.ID = M.ID
      AND M.Start  <= D.Start
      AND M.Finish >= D.Finish
    GROUP BY M.ID, M.Start, M.Finish
    ORDER BY M.ID, M.Start;

これにより、以下が得られます。

ID  Start        Finish       Amount
01  2008-10-01   2008-10-02   10
01  2008-10-03   2008-10-05   61
02  2008-10-02   2008-10-03   20
02  2008-10-06   2008-10-08   11
03  2008-10-05   2008-10-06   14
03  2008-10-05   2008-11-08   33              -- Here be trouble!
03  2008-10-08   2008-11-08   19

編集済み:これは、元の質問で要求されたCOUNTおよびSUM集計を実行するための正しいデータセットであるため、最終的な答えは次のとおりです。

>
SELECT I.ID, COUNT(*) AS Number, SUM(I.Amount) AS Amount
    FROM (SELECT M.ID, M.Start, M.Finish, SUM(D.Amount) AS Amount
            FROM Data AS D,
                 (SELECT DISTINCT F.ID, F.Start, L.Finish
                      FROM  Data AS F, Data AS L
                      WHERE F.Start < L.Finish
                        AND F.ID = L.ID
                        -- There are no gaps between F.Finish and L.Start
                        AND NOT EXISTS
                            (SELECT *
                                FROM  Data AS M
                                WHERE M.ID = F.ID
                                  AND F.Finish < M.Start
                                  AND M.Start < L.Start
                                  AND NOT EXISTS
                                      (SELECT *
                                          FROM Data AS T1
                                          WHERE T1.ID = F.ID
                                            AND T1.Start <  M.Start
                                            AND M.Start  <= T1.Finish))
                          -- Cannot be extended further
                        AND NOT EXISTS
                            (SELECT *
                                FROM  Data AS T2
                                WHERE T2.ID = F.ID
                                  AND ((T2.Start <  F.Start  AND F.Start  <= T2.Finish) OR
                                       (T2.Start <= L.Finish AND L.Finish <  T2.Finish)))
                 ) AS M
            WHERE D.ID = M.ID
              AND M.Start  <= D.Start
              AND M.Finish >= D.Finish
            GROUP BY M.ID, M.Start, M.Finish
          ) AS I
        GROUP BY I.ID
        ORDER BY I.ID;

id     number  amount
01      2      71
02      2      31
03      3      66

レビュー: ああ! Drat ... 3のエントリには、必要な「量」の2倍があります。以前の「編集された」部分は、物事がうまくいかなくなった場所を示します。最初のクエリが微妙に間違っているか(別の質問を意図しているのかもしれません)、または私が使用しているオプティマイザが誤動作しているように見えます。それでも、これに密接に関連した答えがあり、正しい値が得られるはずです。

記録用:Solaris 10上のIBM Informix Dynamic Server 11.50でテスト済み。ただし、その他の標準に適度に準拠したSQL DBMSでも正常に動作するはずです。

おそらく、カーソルを作成し、結果をループ処理して、使用しているIDを追跡し、途中でデータを蓄積する必要があります。 IDが変更されると、蓄積されたデータを一時テーブルに挿入し、プロシージャの最後にテーブルを返すことができます(そこからすべてを選択します)。テーブルベースの関数の方が良いかもしれません。その場合は、戻りのテーブルに挿入するだけです。

  

何らかの反復を必要とする可能性があると思うが、その道をたどりたくない。

これがあなたがとらなければならないルートだと思います。カーソルを使ってテーブル変数に入力します。多数のレコードがある場合は、永続テーブルを使用して結果を保存できます。データを取得する必要がある場合は、新しいデータのみを処理できます。

デフォルトの0のビットフィールドをソーステーブルに追加して、処理されたレコードを追跡します。テーブルでselect *を使用しているユーザーがいないと仮定すると、デフォルト値の列を追加してもアプリケーションの残りの部分には影響しません。

ソリューションのコーディングの支援が必要な場合は、この投稿にコメントを追加してください。

まあ、結合とカーソルの混合を使用して反復ルートを下ることに決めました。データテーブルをそれ自体に結合することにより、連続したレコードのみのリンクリストを作成できます。

INSERT INTO #CONSEC
  SELECT a.ID, a.Start, b.Finish, b.Amount 
  FROM Data a JOIN Data b 
  ON (a.Finish = b.Start) AND (a.ID = b.ID)

その後、カーソルでリストを繰り返し処理し、データテーブルを更新して調整します(そして、データテーブルから不要なレコードを削除します)

DECLARE CCursor  CURSOR FOR
  SELECT ID, Start, Finish, Amount FROM #CONSEC ORDER BY Start DESC

@Total = 0
OPEN CCursor
FETCH NEXT FROM CCursor INTO @ID, @START, @FINISH, @AMOUNT
WHILE @FETCH_STATUS = 0
BEGIN
  @Total = @Total + @Amount
  @Start_Last = @Start
  @Finish_Last = @Finish
  @ID_Last = @ID

  DELETE FROM Data WHERE Start = @Finish
  FETCH NEXT FROM CCursor INTO @ID, @START, @FINISH, @AMOUNT
  IF (@ID_Last<> @ID) OR (@Finish<>@Start_Last)
    BEGIN
      UPDATE Data
        SET Amount = Amount + @Total
        WHERE Start = @Start_Last
      @Total = 0
    END  
END

CLOSE CCursor
DEALLOCATE CCursor

これはすべて機能し、使用している一般的なデータに対して許容可能なパフォーマンスを備えています。

上記のコードに小さな問題が1つ見つかりました。もともと、カーソルを介して各ループでデータテーブルを更新していました。しかし、これはうまくいきませんでした。レコードに対して1つの更新しか実行できず、(データを追加し続けるために)複数の更新がレコードの元の内容の読み取りに戻るようです。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top