Оптимизация SQL-запросов - PullRequest
       7

Оптимизация SQL-запросов

0 голосов
/ 07 февраля 2011

Этот отчет занимал около 16 секунд, когда для обработки было 8000 строк. Теперь 50000 строк и отчет занимает 2:30 минуты.

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

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

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

Я думаю, что могу сжать ВСЕ это в один большой SELECT либо (a) 4 соединениями в одну и ту же таблицу, чтобы получить 4 статуса, но тогда я не уверен, как получить ТОП 1, или я могу попробуйте (b) использовать вложенные подзапросы, но по сравнению с текущим кодом они кажутся очень грязными.

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

PS: предположим, что эта БД по большей части нормализована, но плохо спроектирована, и что я не могу добавлять индексы. Я в основном должен работать с этим, как есть.

Там, где код говорит (меньше чем), мне пришлось заменить символ «меньше чем», потому что он обрезал часть моего кода.

Спасибо!

CREATE PROCEDURE RptCollectionAccountStatusReport AS

SET NOCOUNT ON;

DECLARE @Accounts TABLE
(
  [AccountKey] INT IDENTITY(1,1) NOT NULL,
  [ManagementCompany] NVARCHAR(50),
  [Association] NVARCHAR(100),
  [AccountNo] INT UNIQUE,
  [StreetAddress] NVARCHAR(65),
  [State] NVARCHAR(50),
  [PrimaryStatus] NVARCHAR(100),
  [PrimaryStatusDate] SMALLDATETIME,
  [PrimaryDaysRemaining] INT,
  [SecondaryStatus] NVARCHAR(100),
  [SecondaryStatusDate] SMALLDATETIME,
  [SecondaryDaysRemaining] INT,
  [TertiaryStatus] NVARCHAR(100),
  [TertiaryStatusDate] SMALLDATETIME,
  [TertiaryDaysRemaining] INT,
  [ExternalStatus] NVARCHAR(100),
  [ExternalStatusDate] SMALLDATETIME,
  [ExternalDaysRemaining] INT
);

INSERT INTO
  @Accounts (
    [ManagementCompany],
    [Association],
    [AccountNo],
    [StreetAddress],
    [State])
SELECT
  mc.Name AS [ManagementCompany],
  a.LegalName AS [Association],
  c.CollectionKey AS [AccountNo],
  u.StreetNumber + ' ' + u.StreetName AS [StreetAddress],
  CASE WHEN c.InheritedAccount = 1 THEN 'ZZ' ELSE u.State END AS [State]
FROM
  ManagementCompany mc WITH (NOLOCK)
JOIN
  Association a WITH (NOLOCK) ON a.ManagementCompanyKey = mc.ManagementCompanyKey
JOIN
  Unit u WITH (NOLOCK) ON u.AssociationKey = a.AssociationKey
JOIN
  Collection c WITH (NOLOCK) ON c.UnitKey = u.UnitKey
WHERE
  c.Closed IS NULL;

DECLARE @MaxAccountKey INT;
SELECT @MaxAccountKey = MAX([AccountKey]) FROM @Accounts;

DECLARE @index INT;
SET @index = 1;

WHILE @index (less than) @MaxAccountKey BEGIN

DECLARE @CollectionKey INT;
SELECT @CollectionKey = [AccountNo] FROM @Accounts WHERE [AccountKey] = @index;

DECLARE @PrimaryStatus NVARCHAR(100) = NULL;
DECLARE @PrimaryStatusDate SMALLDATETIME = NULL;
DECLARE @PrimaryDaysRemaining INT = NULL;
DECLARE @SecondaryStatus NVARCHAR(100) = NULL;
DECLARE @SecondaryStatusDate SMALLDATETIME = NULL;
DECLARE @SecondaryDaysRemaining INT = NULL;
DECLARE @TertiaryStatus NVARCHAR(100) = NULL;
DECLARE @TertiaryStatusDate SMALLDATETIME = NULL;
DECLARE @TertiaryDaysRemaining INT = NULL;
DECLARE @ExternalStatus NVARCHAR(100) = NULL;
DECLARE @ExternalStatusDate SMALLDATETIME = NULL;
DECLARE @ExternalDaysRemaining INT = NULL;

SELECT TOP 1
@PrimaryStatus = a.StatusName, @PrimaryStatusDate = c.StatusDate, @PrimaryDaysRemaining = c.DaysRemaining
FROM CollectionAccountStatus c WITH (NOLOCK) JOIN AccountStatus a WITH (NOLOCK) ON c.AccountStatusKey = a.AccountStatusKey
WHERE c.CollectionKey = @CollectionKey AND a.StatusType = 'Primary Status' AND a.StatusName  'Cleared'
ORDER BY c.sysCreated DESC;

SELECT TOP 1
@SecondaryStatus = a.StatusName, @SecondaryStatusDate = c.StatusDate, @SecondaryDaysRemaining = c.DaysRemaining
FROM CollectionAccountStatus c WITH (NOLOCK) JOIN AccountStatus a WITH (NOLOCK) ON c.AccountStatusKey = a.AccountStatusKey
WHERE c.CollectionKey = @CollectionKey AND a.StatusType = 'Secondary Status' AND a.StatusName  'Cleared'
ORDER BY c.sysCreated DESC;

SELECT TOP 1
@TertiaryStatus = a.StatusName, @TertiaryStatusDate = c.StatusDate, @TertiaryDaysRemaining = c.DaysRemaining
FROM CollectionAccountStatus c WITH (NOLOCK) JOIN AccountStatus a WITH (NOLOCK) ON c.AccountStatusKey = a.AccountStatusKey
WHERE c.CollectionKey = @CollectionKey AND a.StatusType = 'Tertiary Status' AND a.StatusName  'Cleared'
ORDER BY c.sysCreated DESC;

SELECT TOP 1
@ExternalStatus = a.StatusName, @ExternalStatusDate = c.StatusDate, @ExternalDaysRemaining = c.DaysRemaining
FROM CollectionAccountStatus c WITH (NOLOCK) JOIN AccountStatus a WITH (NOLOCK) ON c.AccountStatusKey = a.AccountStatusKey
WHERE c.CollectionKey = @CollectionKey AND a.StatusType = 'External Status' AND a.StatusName  'Cleared'
ORDER BY c.sysCreated DESC;

UPDATE
  @Accounts
SET
  [PrimaryStatus] = @PrimaryStatus,
  [PrimaryStatusDate] = @PrimaryStatusDate,
  [PrimaryDaysRemaining] = @PrimaryDaysRemaining,
  [SecondaryStatus] = @SecondaryStatus,
  [SecondaryStatusDate] = @SecondaryStatusDate,
  [SecondaryDaysRemaining] = @SecondaryDaysRemaining,
  [TertiaryStatus] = @TertiaryStatus,
  [TertiaryStatusDate] = @TertiaryStatusDate,
  [TertiaryDaysRemaining] = @TertiaryDaysRemaining,
  [ExternalStatus] = @ExternalStatus,
  [ExternalStatusDate] = @ExternalStatusDate,
  [ExternalDaysRemaining] = @ExternalDaysRemaining
WHERE
  [AccountNo] = @CollectionKey;

SET @index = @index + 1;

END;

SELECT
  [ManagementCompany],
  [Association],
  [AccountNo],
  [StreetAddress],
  [State],
  [PrimaryStatus],
  CONVERT(VARCHAR, [PrimaryStatusDate], 101) AS [PrimaryStatusDate],
  [PrimaryDaysRemaining],
  [SecondaryStatus],
  CONVERT(VARCHAR, [SecondaryStatusDate], 101) AS [SecondaryStatusDate],
  [SecondaryDaysRemaining],
  [TertiaryStatus],
  CONVERT(VARCHAR, [TertiaryStatusDate], 101) AS [TertiaryStatusDate],
  [TertiaryDaysRemaining],
  [ExternalStatus],
  CONVERT(VARCHAR, [ExternalStatusDate], 101) AS [ExternalStatusDate],
  [ExternalDaysRemaining]
FROM
 @Accounts
ORDER BY
  [ManagementCompany],
  [Association],
  [StreetAddress]
ASC;

Ответы [ 3 ]

4 голосов
/ 07 февраля 2011
  1. Не пытайтесь угадать, где запрос идет неправильно - посмотрите на план выполнения.Он скажет вам, что жует ваши ресурсы.

  2. Можно выполнить обновление непосредственно из другой таблицы, даже из табличной переменной: Обновление SQL из одной таблицы в другую на основе совпадения идентификатора

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

JOIN AccountStatus As TertiaryAccountStatus...AND a.StatusType = 'Tertiary Status'
JOIN AccountStatus AS SecondaryAccountStatus...AND a.StatusType = 'Secondary Status'
Держу пари, у вас нет индекса в поле AccountStatus.StatusType.Вместо этого вы можете попробовать использовать PK этой таблицы.

HTH.

3 голосов
/ 07 февраля 2011

Сначала используйте временную таблицу вместо таблицы. Они могут быть проиндексированы.

Далее не зацикливайтесь! Циклы вредны для производительности практически в каждом случае. Этот цикл выполняется 50000 раз, а не один раз для 50000 записей, это будет ужасно, когда у вас будет миллион записей! Вот ссылка, которая поможет вам понять, как выполнять обработку на основе множеств. Это написано, чтобы избежать курсоров, но циклы похожи на курсоры, поэтому это должно помочь. http://wiki.lessthandot.com/index.php/Cursors_and_How_to_Avoid_Them

И (nolock) будет давать грязные чтения данных, которые могут быть очень плохими для отчетов. Если у вас версия SQl Server выше 2000, есть лучший выбор.

2 голосов
/ 07 февраля 2011
SELECT @CollectionKey = [AccountNo] FROM @Accounts WHERE [AccountKey] = @index;

Для этого запроса будет полезно объявление PRIMARY KEY для вашей табличной переменной.

  • Когда вы говорите IDENTITY, вы просите базу данных автоматически заполнить столбец.
  • Когда вы говорите PRIMARY KEY, вы просите базу данных организовать данные в кластеризованный индекс.

Эти два понятия очень разные. Как правило, вы должны использовать оба из них.

DECLARE @Accounts TABLE
(
  [AccountKey] INT IDENTITY(1,1) PRIMARY KEY,

Я не могу добавить индексы.

В этом случае скопируйте данные в базу данных, где вы можете добавить индексы. И использовать: SET STATISTICS IO ON

...