4

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

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

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

4

4 に答える 4

4

RT Snodgrass著「Developing Time-Oriented Database Applications in SQL」(その PDF は、彼の Web サイトの出版物から入手できます) を読んで、p165-166 の図 6.25 までたどり着くと、 -現在の例で使用できる簡単な SQL。同じ ID 値と連続した時間間隔を持つさまざまな行をグループ化します。

以下のクエリの作成はほぼ正しいですが、最初の 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 では問題なく動作するはずです。

于 2008-10-25T05:31:23.223 に答える
1

何らかの反復が必要になるのではないかと思いますが、その道をたどりたくありません。

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

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

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

于 2008-10-25T02:19:22.643 に答える
1

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

于 2008-10-24T22:11:47.510 に答える
0

さて、結合とカーソルを組み合わせて使用​​する反復ルートをたどることにしました。データ テーブルをそれ自体に対して JOIN することで、連続するレコードのみのリンク リストを作成できます。

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 つのレコードに対して 1 つの更新しか実行できず、(データを追加し続けるために) 複数の更新を行うと、レコードの元の内容の読み取りに戻るようです。

于 2008-10-27T16:45:35.333 に答える