2017-04-13 75 views
1

我想問你如何替換我插入到存儲過程中的遊標。我該如何替換T-SQL遊標?

實際上,我們發現光標是管理我的場景的唯一出路,但正如我讀過的,這不是最佳實踐。

這是我的場景:我必須逐行計算股票行數,並根據之前的行中計算的內容設置季節。

我可以設置傳輸類型爲「購買」的季節。其他傳輸應通過T-SQL查詢設置正確的季節。

,我應該算本賽季該表具有下面的模板和僞造數據,但它們反映了真實情況:

Transfer Table Example

enter image description here

具有「FlgSeason」中設定的行爲空,計算方法如下:按升序排列,光標從第3行開始,返回前一行並計算每個季節的庫存量,然後用庫存最小季節更新列季節。

這是我使用的代碼:

CREATE TABLE [dbo].[transfers] 
(
    [rowId] [int] NULL, 
    [area] [int] NULL, 
    [store] [int] NULL, 
    [item] [int] NULL, 
    [date] [date] NULL, 
    [type] [nvarchar](50) NULL, 
    [qty] [int] NULL, 
    [season] [nvarchar](50) NULL, 
    [FlagSeason] [int] NULL 
) ON [PRIMARY] 

INSERT INTO [dbo].[transfers] 
      ([rowId] 
      ,[area] 
      ,[store] 
      ,[item] 
      ,[date] 
      ,[type] 
      ,[qty] 
      ,[season] 
      ,[FlagSeason]) 
     VALUES (1,1,20,300,'2015-01-01','Purchase',3,'2015-FallWinter',1) 
    , (2,1,20,300,'2015-01-01','Purchase',4,'2016-SpringSummer',1) 
    , (3,1,20,300,'2015-01-01','Sales',-1,null,null) 
    , (4,1,20,300,'2015-01-01','Sales',-2,null,null) 
    , (5,1,20,300,'2015-01-01','Sales',-1,null,null) 
    , (6,1,20,300,'2015-01-01','Sales',-1,null,null) 
    , (7,1,20,300,'2015-01-01','Purchase',4,'2016-FallWinter',1) 
    , (8,1,20,300,'2015-01-01','Sales',-1,null,null) 

DECLARE @RowId as int 
DECLARE db_cursor CURSOR FOR 
Select RowID 
from Transfers 
where [FlagSeason] is null 
order by RowID 

OPEN db_cursor 
FETCH NEXT FROM db_cursor INTO @RowId 

WHILE @@FETCH_STATUS = 0 
BEGIN 


Update Transfers 
set Season = (Select min (Season) as Season 
         from (
          Select 
          Season 
          , SUM(QTY) as Qty 
          from Transfers 
          where RowID < @RowId 
          and [FlagSeason] = 1 
          group by Season 
          having Sum(QTY) > 0 
         )S 
          where s.QTY >= 0 
        ) 
, [FlagSeason] = 1 

where rowId = @RowId 

     FETCH NEXT FROM db_cursor INTO @RowId 

    end 

在這種情況下,查詢將提取:

  • 3數量的季節2015年FW
  • 4 2016年SS。

比更新統計將設置2015年FW(兩個季度與數量的最小值)。

然後勒沃庫森往前走行4,然後再次運行查詢,提取更新的考慮,在第3行計算股票那麼結果應該是

  • 數量2對於2015年FW
  • 數量4 FOr 2016 SS

然後更新會設置2015 FW。 依此類推。

最終的輸出應該是這樣的:

Output

enter image description here

其實,唯一的出路是實現一個光標,現在它需要上面30/40分鐘掃描並更新大約250萬行。有沒有人知道一個解決方案而不重複遊標?

在此先感謝!

+2

提供您的表結構和示例數據作爲可運行腳本,您將很快得到答案。 –

+0

謝謝韋斯你是對的。我已經包含了整個腳本。 – Thenoisemaker

回答

1

更新於2008年

IF OBJECT_ID('tempdb..#transfer') IS NOT NULL 
    DROP TABLE #transfer; 
GO 

CREATE TABLE #transfer (
         RowID INT IDENTITY(1, 1) PRIMARY KEY NOT NULL, 
         Area INT, 
         Store INT, 
         Item INT, 
         Date DATE, 
         Type VARCHAR(50), 
         Qty INT, 
         Season VARCHAR(50), 
         FlagSeason INT 
         ); 

INSERT INTO #transfer (Area, 
         Store, 
         Item, 
         Date, 
         Type, 
         Qty, 
         Season, 
         FlagSeason 
        ) 
VALUES (1, 20, 300, '20150101', 'Purchase', 3, '2015-SpringSummer', 1), 
(1, 20, 300, '20150601', 'Purchase', 4, '2016-SpringSummer', 1), 
(1, 20, 300, '20150701', 'Sales', -1, NULL, NULL), 
(1, 20, 300, '20150721', 'Sales', -2, NULL, NULL), 
(1, 20, 300, '20150901', 'Sales', -1, NULL, NULL), 
(1, 20, 300, '20160101', 'Sales', -1, NULL, NULL), 
(1, 20, 300, '20170101', 'Purchase', 4, '2017-SpringSummer', 1), 
(1, 20, 300, '20170125', 'Sales', -1, NULL, NULL), 
(1, 20, 300, '20170201', 'Sales', -1, NULL, NULL), 
(1, 20, 300, '20170225', 'Sales', -1, NULL, NULL), 
(1, 21, 301, '20150801', 'Purchase', 4, '2017-SpringSummer', 1), 
(1, 21, 301, '20150901', 'Sales', -1, NULL, NULL), 
(1, 21, 301, '20151221', 'Sales', -2, NULL, NULL), 
(1, 21, 302, '20150801', 'Purchase', 1, '2016-SpringSummer', 1), 
(1, 21, 302, '20150901', 'Purchase', 1, '2017-SpringSummer', 1), 
(1, 21, 302, '20151101', 'Sales', -1, NULL, NULL), 
(1, 21, 302, '20151221', 'Sales', -1, NULL, NULL), 
(1, 20, 302, '20150801', 'Purchase', 1, '2016-SpringSummer', 1), 
(1, 20, 302, '20150901', 'Purchase', 1, '2017-SpringSummer', 1), 
(1, 20, 302, '20151101', 'Sales', -1, NULL, NULL), 
(1, 20, 302, '20151221', 'Sales', -1, NULL, NULL); 
WITH Purchases 
AS (SELECT t1.RowID, 
      t1.Area, 
      t1.Store, 
      t1.Item, 
      t1.Date, 
      t1.Type, 
      t1.Qty, 
      t1.Season, 
      RunningInventory = (SELECT SUM(t2.Qty) 
           FROM #transfer AS t2 
           WHERE t1.Type = t2.Type 
             AND t1.Area = t2.Area 
             AND t1.Store = t2.Store 
             AND t1.Item = t2.Item 
             AND t2.Date <= t1.Date 
           ) 
    FROM #transfer AS t1 
    WHERE t1.Type = 'Purchase' 
    ), 
    Sales 
AS (SELECT t1.RowID, 
      t1.Area, 
      t1.Store, 
      t1.Item, 
      t1.Date, 
      t1.Type, 
      t1.Qty, 
      t1.Season, 
      RunningSales = (SELECT SUM(ABS(t2.Qty)) 
          FROM #transfer AS t2 
          WHERE t1.Type = t2.Type 
            AND t1.Area = t2.Area 
            AND t1.Store = t2.Store 
            AND t1.Item = t2.Item 
            AND t2.Date <= t1.Date 
          ) 
    FROM #transfer AS t1 
    WHERE t1.Type = 'Sales' 
    ) 
SELECT Sales.RowID, 
     Sales.Area, 
     Sales.Store, 
     Sales.Item, 
     Sales.Date, 
     Sales.Type, 
     Sales.Qty, 
     Season = (SELECT TOP 1 
         Purchases.Season 
        FROM Purchases 
        WHERE Purchases.Area = Sales.Area 
         AND Purchases.Store = Sales.Store 
         AND Purchases.Item = Sales.Item 
         AND Purchases.RunningInventory >= Sales.RunningSales 
        ORDER BY Purchases.Date, Purchases.Season 
       ) 
FROM Sales 
UNION ALL 
SELECT Purchases.RowID , 
     Purchases.Area , 
     Purchases.Store , 
     Purchases.Item , 
     Purchases.Date , 
     Purchases.Type , 
     Purchases.Qty , 
     Purchases.Season 
FROM Purchases 
ORDER BY Sales.Area, Sales.Store, item, Sales.Date 

*原來的答覆如下**

我不明白flagseason專欄的目的,所以我不包括運行。本質上,它會計算採購和銷售的運行總額,然後查找每個銷售交易具有至少sales_to_date流出的purchase_to_date庫存的季節。

IF OBJECT_ID('tempdb..#transfer') IS NOT NULL 
    DROP TABLE #transfer; 
GO 

CREATE TABLE #transfer (
         RowID INT IDENTITY(1, 1) PRIMARY KEY NOT NULL, 
         Area INT, 
         Store INT, 
         Item INT, 
         Date DATE, 
         Type VARCHAR(50), 
         Qty INT, 
         Season VARCHAR(50), 
         FlagSeason INT 
         ); 

INSERT INTO #transfer (Area, 
         Store, 
         Item, 
         Date, 
         Type, 
         Qty, 
         Season, 
         FlagSeason 
        ) 
VALUES (1, 20, 300, '20150101', 'Purchase', 3, '2015-FallWinter', 1), 
(1, 20, 300, '20150601', 'Purchase', 4, '2016-SpringSummer', 1), 
(1, 20, 300, '20150701', 'Sales', -1, NULL, NULL), 
(1, 20, 300, '20150721', 'Sales', -2, NULL, NULL), 
(1, 20, 300, '20150901', 'Sales', -1, NULL, NULL), 
(1, 20, 300, '20160101', 'Sales', -1, NULL, NULL), 
(1, 20, 300, '20170101', 'Purchase', 4, '2016-FallWinter', 1), 
(1, 20, 300, '20170201', 'Sales', -1, NULL, NULL); 

WITH Inventory 
AS (SELECT *, 
      PurchaseToDate = SUM(CASE WHEN Type = 'Purchase' THEN Qty ELSE 0 END) OVER (ORDER BY Date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW), 
      SalesToDate = ABS(SUM(CASE WHEN Type = 'Sales' THEN Qty ELSE 0 END) OVER (ORDER BY Date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)) 
    FROM #transfer 
    ) 
SELECT Inventory.RowID, 
     Inventory.Area, 
     Inventory.Store, 
     Inventory.Item, 
     Inventory.Date, 
     Inventory.Type, 
     Inventory.Qty, 
     Season = CASE 
        WHEN Inventory.Season IS NULL 
        THEN (SELECT TOP 1 
           PurchaseToSales.Season 
          FROM Inventory AS PurchaseToSales 
          WHERE PurchaseToSales.PurchaseToDate >= Inventory.SalesToDate 
          ORDER BY Inventory.Date 
         ) 
        ELSE 
        Inventory.Season 
       END, 
     Inventory.PurchaseToDate, 
     Inventory.SalesToDate 
FROM Inventory; 

*更新*******************************

你需要一個索引在您的數據,以幫助排序,使其執行。

可能:

CREATE NONCLUSTERED INDEX IX_Transfer ON #transfer(Store, Item, Date) INCLUDE(Area,Qty,Season,Type) 

您應該看到指定索引的索引掃描。它不會被查找,因爲示例查詢不會過濾任何數據,並且包含所有數據。

此外,您需要從SalesToDate的Partition By子句中刪除季節。重置每個季節的銷售數據會導致您的比較關閉,因爲需要將滾動銷售額與滾動庫存進行比較,以便確定銷售庫存來源。其他

兩個提示分區子句:

  1. 不要重複字段通過分區之間和秩序的。由於每個分區的聚合被重置,分區字段的順序並不重要。最好的情況是,有序分區字段將被忽略,最壞的情況是它可能會導致優化器以特定順序聚合字段。這對結果沒有任何影響,但可能會增加不必要的開銷。

  2. 確保您的索引與/ order by子句匹配分區的定義。

該索引應該是[分割字段,順序無關緊要] + [順序字段,順序需要按順序排列順序]。 在您的方案中,索引列應該位於商店,商品和日期。如果日期在商店或商品之前,則不會使用索引,因爲優化程序需要先按商店&項目處理分區,然後再按日期排序。

如果你可以在你的數據的多個區域,指數和分區的條款將需要

指標:區域,存儲,項目,日期

分區方式:區,商店,項目以便通過日期

+0

嗨Wes,謝謝你的回覆。我要測試它,我會讓你知道。參考您關於「FlagSeason」列的問題,它在光標內部用於標識已分配季節的行。通過這種方式,光標可以計算爲每個項目更新的庫存 - 季節和何時必須計算新的行,彙總flg季節= 1的數量。 – Thenoisemaker

+0

我更新了我的答案。至於FlagSeason,爲什麼你不能檢查一個賽季是否被分配?爲什麼它需要成爲一個不同的專欄? –

+0

嗨Wes,我剛剛測試過,它似乎比以前更快,但實際上提取整個輸出將花費很多。可能是因爲我已經將查詢運行到本地機器中了)。我必須使用功能強大的服務器來測試它,但實際上我無法實現它,因爲我們仍然安裝了SQL SERVER 2008 R2,並且無法識別「ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW」。你知道這個部分的查詢有什麼結果嗎?參考flgSeason,你是對的。這不是必要的,它可以被刪除。 – Thenoisemaker

0

提到Wes的回答,提出的解決方案几乎沒有問題。它工作的很好,但我注意到本季的分配不能正常工作,在我的情況下,庫存應該由商店和物品本身進行計算和更新。我已經更新了腳本添加一些adjstments。此外,我添加了一些新的「假」數據,以更好地理解我的情況以及它應該如何工作。

IF OBJECT_ID('tempdb..#transfer') IS NOT NULL 
    DROP TABLE #transfer; 
GO 

CREATE TABLE #transfer (
         RowID INT IDENTITY(1, 1) PRIMARY KEY NOT NULL, 
         Area INT, 
         Store INT, 
         Item INT, 
         Date DATE, 
         Type VARCHAR(50), 
         Qty INT, 
         Season VARCHAR(50), 
         FlagSeason INT 
         ); 

INSERT INTO #transfer (Area, 
         Store, 
         Item, 
         Date, 
         Type, 
         Qty, 
         Season, 
         FlagSeason 
        ) 
VALUES (1, 20, 300, '20150101', 'Purchase', 3, '2015-SpringSummer', 1), 
(1, 20, 300, '20150601', 'Purchase', 4, '2016-SpringSummer', 1), 
(1, 20, 300, '20150701', 'Sales', -1, NULL, NULL), 
(1, 20, 300, '20150721', 'Sales', -2, NULL, NULL), 
(1, 20, 300, '20150901', 'Sales', -1, NULL, NULL), 
(1, 20, 300, '20160101', 'Sales', -1, NULL, NULL), 
(1, 20, 300, '20170101', 'Purchase', 4, '2017-SpringSummer', 1), 
(1, 20, 300, '20170125', 'Sales', -1, NULL, NULL), 
(1, 20, 300, '20170201', 'Sales', -1, NULL, NULL), 
(1, 20, 300, '20170225', 'Sales', -1, NULL, NULL), 
(1, 21, 301, '20150801', 'Purchase', 4, '2017-SpringSummer', 1), 
(1, 21, 301, '20150901', 'Sales', -1, NULL, NULL), 
(1, 21, 301, '20151221', 'Sales', -2, NULL, NULL), 
(1, 21, 302, '20150801', 'Purchase', 1, '2016-SpringSummer', 1), 
(1, 21, 302, '20150901', 'Purchase', 1, '2017-SpringSummer', 1), 
(1, 21, 302, '20151101', 'Sales', -1, NULL, NULL), 
(1, 21, 302, '20151221', 'Sales', -1, NULL, NULL), 
(1, 20, 302, '20150801', 'Purchase', 1, '2016-SpringSummer', 1), 
(1, 20, 302, '20150901', 'Purchase', 1, '2017-SpringSummer', 1), 
(1, 20, 302, '20151101', 'Sales', -1, NULL, NULL), 
(1, 20, 302, '20151221', 'Sales', -1, NULL, NULL) 




; 

WITH Inventory 
AS (SELECT *, 
      PurchaseToDate = SUM(CASE WHEN Type = 'Purchase' THEN Qty ELSE 0 END) OVER (partition by store, item ORDER BY store, item,Date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW), 
      SalesToDate = ABS(SUM(CASE WHEN Type = 'Sales' THEN Qty ELSE 0 END) OVER (partition by store, item,season ORDER BY store, item, Date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)) 
    FROM #transfer 
    ) 
SELECT Inventory.RowID, 
     Inventory.Area, 
     Inventory.Store, 
     Inventory.Item, 
     Inventory.Date, 
     Inventory.Type, 
     Inventory.Qty, 
     Season = CASE 
        WHEN Inventory.Season IS NULL 
        THEN (SELECT TOP 1 
           PurchaseToSales.Season 
          FROM Inventory AS PurchaseToSales 
          WHERE PurchaseToSales.PurchaseToDate >= Inventory.SalesToDate 
            and PurchaseToSales.Item = inventory.item --//Added 
            and PurchaseToSales.store = inventory.store --//Added 
            and PurchaseToSales.Area = Inventory.area --//Added 

          ORDER BY Inventory.Date 
         ) 
        ELSE 
        Inventory.Season 
       END, 
     Inventory.PurchaseToDate, 
     Inventory.SalesToDate 
FROM Inventory 

這裏輸出:

enter image description here

經過上述調整後,它工作正常,但如果我轉了假數據與採用6個milions行的數據表中,真正的數據,查詢變得很慢(每分鐘萃取〜400行),因爲這些校驗子查詢的where子句內的插入的:

WHERE PurchaseToSales.PurchaseToDate >= Inventory.SalesToDate 
            and PurchaseToSales.Item = inventory.item --//Added 
            and PurchaseToSales.store = inventory.store --//Added 
            and PurchaseToSales.Area = Inventory.area --//Added 

我嘗試用「交叉應用」函數替換子查詢,但沒有任何更改。我錯過了什麼?

在此先感謝