Как сделать курсор T-SQL быстрее? - PullRequest
7 голосов
/ 13 мая 2009

Эй, у меня есть курсор в хранимой процедуре под SQL Server 2000 (сейчас невозможно обновить), которая обновляет всю таблицу, но обычно это занимает несколько минут. Мне нужно сделать это быстрее. Вот пример таблицы, отфильтрованной по произвольному идентификатору продукта; Пример таблицы http://img231.imageshack.us/img231/9464/75187992.jpg Принимая во внимание, что GDEPO: входной склад, CDEPO: выходной склад, Adet: количество, количество E_CIKAN, которое используется.

Запись объяснений:
1: 20 единица входит в депо 01, 2: 10 блок выходит 01. 3: 5 Блок покидает 01 (E_CIKAN для 1-й записи теперь будет 15) 4:10 больше единицы входит в депо 01. 5: 3 единица оставляет 01 из 1-й записи. Обратите внимание, что для первой записи E_CIKAN установлено 18. 6: Здесь возникает проблема: 3 единицы должны покинуть депо 01. Требуется 2 единицы из 1-й записи и 1 единица из 5-й записи. Мой ИП может справиться с этим штрафом, как показано на рисунке, за исключением того, что он ДЕЙСТВИТЕЛЬНО медленный.

Вот хранимая процедура, переведенная на английский;

CREATE PROC [dbo].[UpdateProductDetails]
as
UPDATE PRODUCTDETAILS SET E_CIKAN=0;
DECLARE @ID int
DECLARE @SK varchar(50),@DP varchar(50)  --SK = STOKKODU = PRODUCTID, DP = DEPOT
DECLARE @DEMAND float     --Demand=Quantity, We'll decrease it record by record
DECLARE @SUBID int
DECLARE @SUBQTY float,@SUBCK float,@REMAINS float
DECLARE SH CURSOR FAST_FORWARD FOR
SELECT [ID],PRODUCTID,QTY,EXITDEPOT FROM PRODUCTDETAILS  WHERE (EXITDEPOT IS NOT NULL) ORDER BY [DATE] ASC
OPEN SH
FETCH NEXT FROM SH INTO @ID, @SK,@DEMAND,@DP

WHILE (@@FETCH_STATUS = 0)
BEGIN
   DECLARE SA CURSOR FAST_FORWARD FOR
   SELECT [ID],QTY,E_CIKAN FROM PRODUCTDETAILS  WHERE (QTY>E_CIKAN) AND (PRODUCTID=@SK) AND (ENTRYDEPOT=@DP) ORDER BY [DATE] ASC
   OPEN SA
   FETCH NEXT FROM SA INTO @SUBID, @SUBQTY,@SUBCK
   WHILE (@@FETCH_STATUS = 0) AND (@DEMAND>0)
   BEGIN
      SET @REMAINS=@SUBQTY-@SUBCK
      IF @DEMAND>@REMAINS  --current record isnt sufficient, use it and move on
      BEGIN
         UPDATE PRODUCTDETAILS SET E_CIKAN=QTY WHERE ID=@SUBID;
         SET @DEMAND=@DEMAND-@REMAINS
      END
      ELSE
      BEGIN
         UPDATE PRODUCTDETAILS SET E_CIKAN=E_CIKAN+@DEMAND WHERE ID=@SUBID;
         SET @DEMAND=0
      END
      FETCH NEXT FROM SA INTO @SUBID, @SUBAD,@SUBCK
   END
   CLOSE SA
   DEALLOCATE SA
   FETCH NEXT FROM SH INTO @ID, @SK,@DEMAND,@DP
END
CLOSE SH
DEALLOCATE SH

Ответы [ 6 ]

10 голосов
/ 14 мая 2009

Исходя из нашего разговора в моем другом ответе на этот вопрос, я думаю, что нашел способ ускорить вашу рутину.

У вас есть два вложенных курсора:

  • Первый выбирает каждую строку, в которой указан exitdepot. Это берет продукт, депо и количество, а затем:
  • Внутренний цикл курсора проходит по строкам для этого продукта / депо, в которых указан entrydepot. Он добавляется в E_CIKAN для каждого, пока не будет выделен весь продукт.

Таким образом, внутренний цикл курсора запускается как минимум один раз для каждой строки exitdepot. Однако вашей системе все равно, какие элементы были получены с какой транзакцией - вы только пытаетесь вычислить окончательные значения E_CIKAN.

Итак ...

Ваша внешняя петля должна получить только общее количество отправленных товаров для каждого комбо продукта / депо. Следовательно, вы можете изменить определение внешнего курсора на:

DECLARE SH CURSOR FAST_FORWARD FOR
    SELECT PRODUCTID,EXITDEPOT, Sum(Qty) as TOTALQTY
    FROM PRODUCTDETAILS  
    WHERE (EXITDEPOT IS NOT NULL) 
    GROUP BY PRODUCTID, EXITDEPOT
OPEN SH
FETCH NEXT FROM SH INTO @SK,@DP,@DEMAND

(а затем также изменить совпадающий FETCH с SH в конце кода для совпадения, очевидно)

Это означает, что у вашего внешнего курсора будет намного меньше строк для цикла, а у вашего внутреннего курсора будет примерно такое же количество строк для цикла.

Так что это должно быть быстрее.

2 голосов
/ 13 мая 2009

Курсоры должны быть худшим решением любой проблемы при использовании T-SQL.

У вас есть два варианта в зависимости от сложности того, что вы на самом деле пытаетесь выполнить:

  1. Попытка переписать весь набор кода для использования операций над множествами. Это был бы самый быстрый способ выполнения ... но иногда вы просто не можете сделать это, используя операции над множествами.

  2. Заменить курсор комбинацией табличной переменной (со столбцом идентификаторов), счетчика и цикла while. Затем вы можете просмотреть каждую строку табличной переменной. Работает лучше, чем курсор ... хотя может показаться, что не так.

1 голос
/ 13 мая 2009

Я вижу, что проблема, которую вы пытаетесь решить, довольно сложна:

  • Когда есть строка с указанным GDEPO, она представляет запас, поступающий в депо, и вы хотите использовать E_CIKAN из этой строки , чтобы отследить, какая часть запаса будет использована позже. E_CIKAN начнется с 0, а затем будет добавлен по мере того, как акции выходят, пока не достигнет ADET.

  • Таким образом, когда есть следующая строка с указанным CDEPO, она представляет запасы, выходящие на рынок, и вы хотите вернуться к E_CIKAN строки GDEPO и откорректировать E_CIKAN, добавив количество запаса к это.

  • Когда были две отдельные строки с входящим запасом (указан GDEPO), иногда возникает переполнение, когда E_CIKAN одной строки достигает максимума (ADET), а затем вы хотите добавить остаток к следующей .

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

Там может быть способом сделать это без курсора, как предлагают другие. Но я думаю, что если бы вы могли перестроить свои таблицы и сохранить данные по-другому, вы могли бы облегчить проблему.

Например, вместо того, чтобы отслеживать запасы в той же таблице, в которой записываются операции с акциями, вы могли бы иметь отдельную таблицу со столбцами «Product_id, Depo_id, amount», которая отслеживает общую сумму каждого продукта в каждом депо в одно время?

Такое изменение структуры базы данных может упростить задачу.

Или ... вместо использования E_CIKAN для отслеживания того, что используется , используйте его для отслеживания того, что остается . И сохраняйте значение E_CIKAN в каждой строке. Таким образом, всякий раз, когда запас поступает или выходит из депо, пересчитайте E_CIKAN в тот момент времени и сохраните его в этой строке транзакции (вместо того, чтобы пытаться вернуться к исходной строке «в наличии» и обновить это там). Затем, чтобы узнать текущий запас, вы просто посмотрите на самую последнюю сделку для этого продукта / депо.

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

1 голос
/ 13 мая 2009

Во-первых, если вы ДОЛЖНЫ использовать курсор и обновляете материал, то объявите курсор с помощью предложения FOR UPDATE. (См. Пример ниже. Обратите внимание, что пример НЕ основан на вашем коде.)

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

DECLARE LoopingCursor CURSOR LOCAL DYNAMIC
FOR
    select sortorder from customfielddefinition
    where context=@targetContext
FOR UPDATE OF sortorder
1 голос
/ 13 мая 2009

Удалить курсор и выполнить пакетное обновление. Мне еще не удалось найти обновление, которое нельзя сделать в пакете.

1 голос
/ 13 мая 2009

удалите курсор и перепишите, что в качестве ОБНОВЛЕНИЯ ОТ присоединения в запросе курсора, вы можете сделать IF, если вам нужно. Я слишком занят сегодня, чтобы написать ОБНОВЛЕНИЕ для тебя сегодня ...

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