SQL Server: улучшение ПРОЦЕДУРЫ без использования CURSOR - PullRequest
2 голосов
/ 05 февраля 2010

Я ищу способ написать приведенную ниже процедуру без использования курсора или просто найти более эффективный запрос.

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;

Ответы [ 5 ]

3 голосов
/ 05 февраля 2010

Начните с таблицы чисел, которая достаточно велика для обработки максимальной суммы заказа:

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

Это должно сделать это!

Если по какой-то причине у вас возникли сомнения по поводу размещения таблицы постоянных номеров в вашей базе данных (что я не понимаю, поскольку это прекрасный инструмент), то вы можете просто присоединиться к данной CTE вместо самой таблицы. В SQL 2000 вы можете создать временную таблицу и использовать цикл, но я бы настоятельно не рекомендовал это делать.

Настоятельно рекомендуется таблица чисел. Не стоит беспокоиться о том, что какое-то будущее изменение нарушит его (набор целых чисел не изменится в ближайшее время). Некоторые люди используют таблицу Numbers с миллионом номеров, которые занимают всего около 4 МБ.

Чтобы ответить на критику таблицы чисел: если дизайн базы данных использует таблицу чисел, то эту таблицу не нужно будет менять. Это как любая другая таблица в базе данных, и на нее можно положиться. Вы не слишком беспокоитесь о сбоях запросов к таблице Orders, потому что однажды таблица может не существовать, поэтому я не понимаю, почему возникла бы аналогичная проблема с другой таблицей, которая требуется и зависит от нее.

UPDATE

За время, прошедшее с момента написания этого ответа, я узнал о таблице master.dbo.spt_values, в которой есть столбец number. При запросе where type='P' вы получаете 0 - 255 в SQL 2000 и 0 - 8191 в SQL 2005 и выше. (Также есть потенциально полезные столбцы low и high.) Вы можете несколько раз соединить эту таблицу с самим собой, если необходимо, даже в SQL 2000, очень быстро получить кучу строк.

1 голос
/ 06 февраля 2010

Здесь есть небольшое отклонение от предыдущих ответов, которое позволяет избежать таблицы постоянных чисел (хотя я не уверен, почему люди так боятся этой конструкции), и позволяет вам создать CTE во время выполнения, который содержит точно набор из числа вам нужно выполнить правильное количество вставок (проверяя на наибольшее количество). Я закомментировал CROSS JOIN в первоначальном CTE, но вы можете использовать его, если ваше количество для любого данного заказа может превышать количество строк в 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;
1 голос
/ 05 февраля 2010

Хитрость заключается в том, чтобы ввести таблицу значений (с именем, в приведенном ниже примере, 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.

Я не проверял остальную логику с временными таблицами и всеми (я предполагаю, что это временные таблицы с целью проверки логики, а не как части самого процесса), но кажется, что Выше приведен тип конструкции, необходимый для выражения необходимой логики только декларативным способом, без курсора и даже без цикла.

0 голосов
/ 05 февраля 2010

Это все еще использует цикл, но он избавился от курсора. Если не считать таблицы чисел, к которой можно присоединиться, я думаю, что это лучший ответ.

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 голосов
/ 05 февраля 2010
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

Я чувствую, что здесь неправильно истолковываю логику, но разве это не эквивилант?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...