Генерация последовательности в SQL Server, низкая производительность при перекрестном применении - PullRequest
3 голосов
/ 12 декабря 2011

Я получил следующий код из Интернета много лет назад, и он мне очень помог.Это просто функция, которая генерирует последовательность чисел от 1 до того, что вы передаете.

По сути, это способ выполнения цикла for в инструкции SQL.

CREATE FUNCTION [SequenceCreate]
(@MaxValue INT)
RETURNS TABLE
AS

RETURN
WITH
Num1 (n) AS (SELECT 1 UNION ALL SELECT 1),
Num2 (n) AS (SELECT 1 FROM Num1 AS X, Num1 AS Y),
Num3 (n) AS (SELECT 1 FROM Num2 AS X, Num2 AS Y),
Num4 (n) AS (SELECT 1 FROM Num3 AS X, Num3 AS Y),
Num5 (n) AS (SELECT 1 FROM Num4 AS X, Num4 AS Y),
Num6 (n) AS (SELECT 1 FROM Num5 AS X, Num5 AS Y),
Nums (n) AS
(SELECT ROW_NUMBER() OVER(ORDER BY n)
FROM Num6)
SELECT n AS [Value] FROM Nums
WHERE n BETWEEN 1 AND @MaxValue;

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

DECLARE @T TABLE(StartNum INT, ItemCount INT)
INSERT INTO @T VALUES (100, 5)
INSERT INTO @T VALUES (110, 7)
INSERT INTO @T VALUES (55, 3)

SELECT Seq.Value + StartNum FROM @T
CROSS APPLY he.SequenceCreate(ItemCount) AS Seq

Это очень медленно на моей машине.Кто-нибудь знает, почему он работает хорошо, когда выполняется один раз, но работает очень плохо, когда выполняется 3 раза через кросс-применить?Даже если таблица @T содержит только 1 строку, производительность по-прежнему ужасна.Есть ли лучший способ написать это?

Заранее спасибо, Майкл

Ответы [ 2 ]

3 голосов
/ 12 декабря 2011

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

CREATE FUNCTION [SequenceCreate]
(@MaxValue INT)
RETURNS @T TABLE ([Value] INT NOT NULL PRIMARY KEY)
AS
BEGIN

  WITH
  Num1 (n) AS (SELECT 1 UNION ALL SELECT 1),
  Num2 (n) AS (SELECT 1 FROM Num1 AS X, Num1 AS Y),
  Num3 (n) AS (SELECT 1 FROM Num2 AS X, Num2 AS Y),
  Num4 (n) AS (SELECT 1 FROM Num3 AS X, Num3 AS Y),
  Num5 (n) AS (SELECT 1 FROM Num4 AS X, Num4 AS Y),
  Num6 (n) AS (SELECT 1 FROM Num5 AS X, Num5 AS Y),
  Nums (n) AS
  (SELECT ROW_NUMBER() OVER(ORDER BY n)
  FROM Num6)
  INSERT INTO @T
  SELECT n AS [Value] FROM Nums
  WHERE n BETWEEN 1 AND @MaxValue;

  RETURN
END
2 голосов
/ 12 декабря 2011

Если вы посмотрите на оценочные планы выполнения обоих ваших запросов, вы увидите множество Constant Scan s, к которым присоединяются выходные данные Nested Loops.

В случае

select * from dbo.SequenceCreate (100)

Расчетное количество строк для каждого Constant Scan равно 1

В случае

SELECT N.N + StartNum 
FROM @T t
LEFT JOIN Numbers AS N ON N.N <= T.ItemCount

Расчетное количество строк для каждого Constant Scan равно 2. Так что это хороший пример геометрическогопрогрессия.Последний Neste Loops возвращает 4294970000 строк - 36 ГБ.

Я не могу сказать, почему оптимизатор выбрал этот план, но он выбрал его.

Вместо этого можно использовать следующий подход.

Сначала создайте таблицу с последовательными числами

CREATE TABLE Numbers(N INT PRIMARY KEY NOT NULL IDENTITY(1,1));
GO

INSERT INTO Numbers DEFAULT VALUES;
GO 1000 -- it takes about 2 minutes for 1000 but you need to execut it just once 

Используйте следующий скрипт:

DECLARE @T TABLE(StartNum INT, ItemCount INT)

INSERT INTO @T VALUES (100, 5)
INSERT INTO @T VALUES (110, 7)
INSERT INTO @T VALUES (55, 3)

SELECT N.N + StartNum 
FROM @T t
LEFT JOIN Numbers AS N ON N.N <= T.ItemCount
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...