2017-04-13 80 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 

我尝试用“交叉应用”函数替换子查询,但没有任何更改。我错过了什么?

在此先感谢