2010-02-05 35 views
2

我正在寻找一种方式来写如下的过程不使用光标或只是为了找到一个更好的执行查询。SQL服务器:改善过程,而不使用CURSOR

CREATE TABLE #OrderTransaction (OrderTransactionId int, ProductId int, Quantity int); 
CREATE TABLE #Product (ProductId int, MediaTypeId int); 
CREATE TABLE #OrderDelivery (OrderTransactionId int, MediaTypeId int); 

INSERT INTO #Product (ProductId, MediaTypeId) VALUES (1,1); 
INSERT INTO #Product (ProductId, MediaTypeId) VALUES (2,2); 
INSERT INTO #OrderTransaction(OrderTransactionId, ProductId, Quantity) VALUES (1,1,1); 
INSERT INTO #OrderTransaction(OrderTransactionId, ProductId, Quantity) VALUES (2,2,6); 

DECLARE @OrderTransactionId int, @MediaTypeId int, @Quantity int; 

DECLARE ordertran CURSOR FAST_FORWARD FOR 
    SELECT OT.OrderTransactionId, P.MediaTypeId, OT.Quantity 
    FROM #OrderTransaction OT WITH (NOLOCK) 
     INNER JOIN #Product P WITH (NOLOCK) 
      ON OT.ProductId = P.ProductId 


OPEN ordertran; 
FETCH NEXT FROM ordertran INTO @OrderTransactionId, @MediaTypeId, @Quantity; 

WHILE @@FETCH_STATUS = 0 
BEGIN 
    WHILE @Quantity > 0 
    BEGIN 
     INSERT INTO #OrderDelivery ([OrderTransactionId], [MediaTypeId]) 
     VALUES (@OrderTransactionId, @MediaTypeId) 

     SELECT @Quantity = @Quantity - 1; 
    END 

    FETCH NEXT FROM ordertran INTO @OrderTransactionId, @MediaTypeId, @Quantity; 
END 

CLOSE ordertran; 
DEALLOCATE ordertran; 


SELECT * FROM #OrderTransaction 
SELECT * FROM #Product 
SELECT * FROM #OrderDelivery 

DROP TABLE #OrderTransaction; 
DROP TABLE #Product; 
DROP TABLE #OrderDelivery; 

回答

3

开始Numbers表格足够大,以处理最大订单金额:

CREATE TABLE Numbers (
    Num int NOT NULL PRIMARY KEY CLUSTERED 
) 

-- SQL 2000 version 
INSERT Numbers VALUES (1) 
SET NOCOUNT ON 
GO 
INSERT Numbers (Num) SELECT Num + (SELECT Max(Num) FROM Numbers) FROM Numbers 
GO 15 

-- SQL 2005 and up version 
WITH 
    L0 AS (SELECT c = 1 UNION ALL SELECT 1), 
    L1 AS (SELECT c = 1 FROM L0 A, L0 B), 
    L2 AS (SELECT c = 1 FROM L1 A, L1 B), 
    L3 AS (SELECT c = 1 FROM L2 A, L2 B), 
    L4 AS (SELECT c = 1 FROM L3 A, L3 B), 
    L5 AS (SELECT c = 1 FROM L4 A, L4 B), 
    N AS (SELECT Num = ROW_NUMBER() OVER (ORDER BY c) FROM L5) 
INSERT Numbers(Num) 
SELECT Num FROM N 
WHERE Num <= 32768; 

然后,立即后您的INSERT语句:

INSERT #OrderDelivery (OrderTransactionId, MediaTypeId) 
SELECT 
    OT.OrderTransactionId, 
    P.MediaTypeId 
FROM 
    #OrderTransaction OT 
    INNER JOIN #Product P ON OT.ProductId = P.ProductId 
    INNER JOIN Numbers N ON N.Num BETWEEN 1 AND OT.Quantity 

应该这样做!

如果由于某种原因,你对于在数据库中放置一个永久的Numbers表(我不明白它是一个很棒的工具)有疑虑,那么你可以简单地加入给定的CTE而不是表本身。在SQL 2000中,你可以创建一个临时表并使用一个循环,但我会强烈建议。

强烈建议使用数字表。没有人担心未来会发生什么变化(整组数字不会很快改变)。有些人使用数字表,其中有数百万个数字,只有大约4MB的存储空间。

要回答Numbers表的批评:如果数据库设计使用数字表,那么该表就不需要改变。它就像数据库中的任何其他表一样,可以依赖它。您不必担心Orders表的查询失败,因为有一天表格可能不存在,所以我不明白为什么会有任何类似的关于另一个需要和依赖的表的问题。

UPDATE

在时间,因为写这个答案我已经了解其中有一个numbermaster.dbo.spt_values表。当用where type='P'查询时,您在SQL 2000中获得0 - 255,在SQL 2005中获得0 - 8191。(还有可能有用的lowhigh列。)如果需要,您可以将该表跨多次连接到自身,即使在SQL 2000中,也可以非常快速地获得一堆行。

0
INSERT INTO #OrderDelivery ([OrderTransactionId], [MediaTypeId]) 
SELECT OT.OrderTransactionId, P.MediaTypeId, 
FROM #OrderTransaction OT 
INNER JOIN #Product P 
ON OT.ProductId = P.ProductId 
WHERE OT.Quantity > 0 

我觉得我在这里误读的逻辑,但是这不就是equivelant?

+0

不,只需运行问题查询中提供的即可。基本上Quantity = 6在#OrderDelivery表中生成六个插入 – Ender 2010-02-05 19:33:14

+0

我知道我在误读某些东西! – 2010-02-05 19:40:44

0

这仍然使用一个循环,但它已经摆脱了光标。在创建一个数字表来加入,我认为这是最好的答案。

DECLARE @Count AS INTEGER 

SET @Count = 1 

WHILE (1 = 1) 
BEGIN 

    INSERT INTO #OrderDelivery ([OrderTransactionId], [MediaTypeId]) 
    SELECT OT.OrderTransactionId, P.MediaTypeId, OT.Quantity 
    FROM #OrderTransaction OT WITH (NOLOCK) 
     INNER JOIN #Product P WITH (NOLOCK) 
      ON OT.ProductId = P.ProductId 
    WHERE OT.Quantity > @Count 

    IF @@ROWCOUNT = 0 
     BREAK 

    SET @COUNT = @COUNT + 1 
END 
+0

为什么您认为WHILE循环的操作与fast_forward光标不同?这只是一个具有不同语法的游标。 – 2010-02-05 20:29:30

+0

@Aaron:原始解决方案使用游标从原始表中获取每条记录,然后根据数量字段的值执行一些INSERT语句。我的解决方案只执行MAX(数量)INSERT语句。另外,使用游标会有一些开销(尽管我已经测试了多年)。总的来说,我相信这是保证工作的最佳解决方案。 – 2010-02-05 20:53:22

+2

Uhm no。在这个过程中不需要*任何* WHILE循环或游标。 – RBarryYoung 2010-02-05 22:30:18

1

诀窍是引入一种表值的(命名的,在下面的例子中,MyTableOfIntegers),它包含所有的整数值1和(至少)一些值之间(在的情况下在一方面,这将是来自OrderTransaction表的最大可能数量值)。

INSERT INTO #OrderDelivery ([OrderTransactionId], [MediaTypeId]) 
    SELECT OT.OrderTransactionId, P.MediaTypeId 
    FROM #OrderTransaction OT WITH (NOLOCK) 
    INNER JOIN #Product P WITH (NOLOCK) 
    ON OT.ProductId = P.ProductId 
    JOIN MyTableOfIntegers I ON I.Num <= OT.Quantity 
    --WHERE some optional conditions 

本质上额外的JOIN上MyTableOfIntegers,会产生许多重复的行为OT.Quantity,这似乎是什么光标的目的是:以插入在OrderDelivery表中的许多重复的行。

我没有用临时表检查逻辑的其余部分,所有的(我假设这些都是检查的逻辑,而不是过程中适当的一部分,目的临时表),但似乎以上是仅以声明方式表示所需逻辑所需的构造类型,没有任何游标或甚至任何循环。

1

这是在以前的答案稍有差异,避免永久性的数字表(虽然我不知道为什么人们如此害怕这个结构的),并允许你建立一个运行时CTE包含完全您需要执行正确数量的插入(通过检查最高数量)的一组数字。我在最初的CTE中注释了CROSS JOIN,但是如果您的任何给定顺序的数量可能超过sys.columns中的行数,则可以使用它。希望这不太可能发生。请注意,这是针对SQL Server 2005的......并且让我们知道您的目标是哪个特定版本总是有用的。

DECLARE @numsNeeded INT; 

SELECT @numsNeeded = MAX(Quantity) FROM #OrderTransaction; 

WITH n AS 
(
    SELECT TOP (@numsNeeded) i = ROW_NUMBER() 
    OVER (ORDER BY c.[object_id]) 
    FROM sys.columns AS c --CROSS JOIN sys.columns AS c2 
) 
INSERT #OrderDelivery 
(
    OrderTransactionID, 
    MediaTypeID 
) 
SELECT t.OrderTransactionID, p.MediaTypeID 
    FROM #OrderTransaction AS t 
    INNER JOIN #Product AS p 
    ON t.ProductID = p.ProductID 
    INNER JOIN n 
    ON n.i <= t.Quantity;