3

在庫トランザクションのテーブルがあります。簡単な例:

--Inventory Transactions            
Date         Sold    Purchased  Balance(not in table)
Today        1                  -5
Yesterday    6                  -4
5 days ago           5          +2
10 days ago  103                -3 
20 days ago          100        +100

要件は、記事の残高がマイナス(在庫切れ)であった日をレポートに含める必要があることを示しています。上記の例では、答えとして昨日を意味します。

これをSQLに変換しようとしていますが、問題が発生しています。私はCTEを使用してみました:

with Stockouts as (
select getdate() as [Date],
       (calculation) as Balance
from [Inventory Transactions]
--some constraints to get the correct article are omitted
union all
select dateadd(dd, -1, Stockouts.[Date]) as [Date], 
       Stockouts.Balance - (calculation) as Balance
from [Inventory Transactions]
inner join Stockouts    
)

しかし、再帰部分でサブクエリを使用できず(現在のトランザクションの前の最後のトランザクションを見つけるため)、特定の日付にトランザクションがない場合、内部結合がループを停止するという問題があります(したがって、dateadd部分も失敗します) )。

この問題を解決するための最良のアプローチは何でしょうか?

4

2 に答える 2

2

最善のアプローチは次のように使用することだと思いますOUTER APPLY

DECLARE @InventoryTransactions TABLE ([Date] DATE, Sold INT, Purchased INT)
INSERT @InventoryTransactions VALUES 
    ('20120504', 1, 0),
    ('20120503', 6, 0),
    ('20120501', 0, 5),
    ('20120425', 103, 0),
    ('20120415', 0, 100)

SELECT  trans.Date, 
        trans.Sold, 
        trans.Purchased, 
        ISNULL(Balance, 0) [BalanceIn],
        ISNULL(Balance, 0) + (Purchased - Sold) [BalanceOut]
FROM    @InventoryTransactions trans
        OUTER APPLY
        (   SELECT  SUM(Purchased - Sold) [Balance]
            FROM    @InventoryTransactions bal
            WHERE   Bal.Date < trans.Date
        ) bal

あなたのアプローチは再帰にはあまり適していません。すべての日付が必要な場合は、日付テーブルを作成しLEFT JOIN、上記の結果をすべての日付を含むテーブルに作成することをお勧めします。日付の永続的なテーブル (dbo.Calendar のようなもの) はさまざまな状況で使用できるため、おそらく最適ですが、ループ、CTE、またはシステム ビューのいずれかを使用していつでも一時テーブルを作成して操作することができます。増分日付のリストを生成する方法に関する質問は、以前に回答されています

編集

要件を読み直してください。これは、実際に必要なものを取得するためのより良いアプローチだと思います(同じサンプルデータを使用します)。

;WITH Transactions AS
(   SELECT  trans.Date, 
            trans.Sold, 
            trans.Purchased, 
            ISNULL(Balance, 0) [BalanceIn],
            ISNULL(Balance, 0) + (Purchased - Sold) [BalanceOut]
    FROM    @InventoryTransactions trans
            OUTER APPLY
            (   SELECT  SUM(Purchased - Sold) [Balance]
                FROM    @InventoryTransactions bal
                WHERE   Bal.Date < trans.Date
            ) bal
) 
SELECT  DATEDIFF(DAY, MAX(Date), CURRENT_TIMESTAMP) [Days Since Negative Balance]
FROM    Transactions
WHERE   BalanceIn > 0

編集2

と再帰の間のクエリプランの違いを示すために、 SQL Fiddleを作成しました。OUTER APPLYCTE の方が大量の作業であることがわかります。ローカル マシンで同じデータを実行すると、同じバッチで 2 つを実行すると、外側の適用メソッドの相対的なバッチ コストが 17% になり、 Recursive CTE メソッドが 83% を占めています。

于 2012-05-04T09:47:52.743 に答える
1

再帰的なcteでそれをしたい場合。これは提案かもしれません:

テストデータ:

DECLARE @T TABLE(Date DATETIME,Sold INT, Purchased INT)

INSERT INTO @T
VALUES
    (GETDATE(),1,NULL),
    (GETDATE()-1,6,NULL),
    (GETDATE()-5,NULL,5),
    (GETDATE()-10,103,NULL),
    (GETDATE()-20,NULL,100)

クエリ

;WITH CTE
AS
(
    SELECT ROW_NUMBER() OVER(ORDER BY Date ASC) AS RowNbr, t.* FROM @T AS T
)
, CTE2
AS
(
    SELECT
        CTE.RowNbr,
        CTE.Date,
        CTE.Sold,
        CTE.Purchased,
        (ISNULL(CTE.Purchased,0)-ISNULL(CTE.Sold,0)) AS Balance
    FROM
        CTE
    WHERE
         CTE.RowNbr=1
    UNION ALL
    SELECT
        CTE.RowNbr,
        CTE.Date,
        CTE.Sold,
        CTE.Purchased,
        CTE2.Balance+ISNULL(CTE.Purchased,0)-ISNULL(CTE.Sold,0) AS Balance
    FROM
        CTE
        JOIN CTE2
            ON CTE.RowNbr=CTE2.RowNbr+1
)
SELECT * FROM CTE2 ORDER BY CTE2.RowNbr DESC

出力

5   2012-05-04 11:49:45.497 1       NULL    -5
4   2012-05-03 11:49:45.497 6       NULL    -4
3   2012-04-29 11:49:45.497 NULL    5        2
2   2012-04-24 11:49:45.497 103     NULL    -3
1   2012-04-14 11:49:45.497 NULL    100     100
于 2012-05-04T09:53:07.683 に答える