2016-07-01 3 views
2

У меня есть семь больших таблиц, которые могут хранить от 100 до 1 миллиона строк в любое время. Я назову их LargeTable1, LargeTable2, LargeTable3, LargeTable4 ... LargeTable7. Эти таблицы в основном статичны: нет обновлений и новых вставок. Они меняются только один раз каждые две недели или один раз в месяц, когда они усекаются, и каждая партия регистра вводится в каждую.Проблемы с производительностью с UNION больших таблиц

Все эти таблицы имеют три поля: Headquarter, Country и File. Headquarter и Country - цифры в формате «000», хотя в двух из этих таблиц они обрабатываются как int из-за некоторых других потребностей в системе.

У меня есть еще одна, гораздо меньшая таблица под названием Headquarters с информацией о каждом головном офисе. В этой таблице очень мало записей. На самом деле не более 1000.

Теперь мне нужно создать хранимую процедуру, которая возвращает все те штаб-квартиры, которые отображаются в больших таблицах, но либо отсутствуют в таблице Headquarters, либо были удалены (эта таблица удалена логически: у нее есть поле DeletionDate для проверки это).

Это запрос, я попробовал: производительность

CREATE PROCEDURE deletedHeadquarters 
AS 
BEGIN 
    DECLARE @headquartersFiles TABLE 
    (
     hq int, 
     countryFile varchar(MAX) 
    ); 

    SET NOCOUNT ON 

    INSERT INTO @headquartersFiles 
    SELECT headquarter, CONCAT(country, ' (', file, ')') 
    FROM 
    (
     SELECT DISTINCT CONVERT(int, headquarter) as headquarter, 
         CONVERT(int, country) as country, 
         file 
     FROM   LargeTable1  
     UNION 
     SELECT DISTINCT headquarter, 
         country, 
         file 
     FROM   LargeTable2 
     UNION 
     SELECT DISTINCT headquarter, 
         country, 
         file 
     FROM   LargeTable3 
     UNION 
     SELECT DISTINCT headquarter, 
         country, 
         file 
     FROM   LargeTable4 
     UNION 
     SELECT DISTINCT headquarter, 
         country, 
         file 
     FROM   LargeTable5 
     UNION 
     SELECT DISTINCT headquarter, 
         country, 
         file 
     FROM   LargeTable6 
     UNION 
     SELECT DISTINCT headquarter, 
         country, 
         file 
     FROM   LargeTable7 
    ) TC 

    SELECT RIGHT('000' + CAST(st.headquarter AS VARCHAR(3)), 3) as headquarter, 
      MAX(s.deletionDate) as deletionDate, 
      STUFF 
      (
       (SELECT DISTINCT ', ' + st2.countryFile 
       FROM @headquartersFiles st2 
       WHERE st2.headquarter = st.headquarter 
       FOR XML PATH('')), 
       1, 
       1, 
       '' 
      ) countryFile 
    FROM @headquartersFiles as st 
    LEFT JOIN headquarters s ON CONVERT(int, s.headquarter) = st.headquarter 
    WHERE s.headquarter IS NULL 
     OR s.deletionDate IS NOT NULL 
    GROUP BY st.headquarter 

END 

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

  • LargeTable1: 1516666 строк
  • LargeTable2: 645740 строк
  • LargeTable3 : 1950121 ряды
  • LargeTable4: 779336 ряды
  • LargeTable5: 1100999 ряды
  • LargeTable6: 16499 строк
  • LargeTable7: 24454 строки

Что я могу сделать для повышения производительности? Я попытался сделать следующее, без особой разницы:

  • Подставив в локальную таблицу партий, за исключение тех штаба я уже вставленным, а затем обновляет поле countryFile для тех, которые повторяются
  • Создание представления для этого запроса UNION
  • Создание индексов для LargeTables по головному поле

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

Благодаря

+2

AFAIK предложение о союзе будет делать отчетливое, поэтому нет необходимости включать в себя выделение по выбору – vercelli

+0

@vercelli В одной таблице может быть несколько строк с одной и той же комбинацией из штаб-квартиры страны. Удалит ли ЮНИОН их, даже если они придут из одного стола? – Heathcliff

+2

@Heathcliff да, это то, что UNION делает. UNION ALL не будет. – Hogan

ответ

1

Выполняйте фильтрацию на каждом шаге. Но сначала измените таблицу headquarters, чтобы она имела нужный тип для того, что вам нужно. , , наряду с индексом:

alter table headquarters add headquarter_int as (cast(headquarter as int)); 
create index idx_headquarters_int on headquarters(headquarters_int); 

SELECT DISTINCT headquarter, country, file 
FROM LargeTable5 lt5 
WHERE NOT EXISTS (SELECT 1 
        FROM headquarters s 
        WHERE s.headquarter_int = lt5.headquarter and s.deletiondate is not null 
       ); 

Затем вы хотите индекс на LargeTable5(headquarter, country, file).

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

+1

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

2

Возьмите этот фильтр

LEFT JOIN headquarters s ON CONVERT(int, s.headquarter) = st.headquarter 
WHERE s.headquarter IS NULL 
    OR s.deletionDate IS NOT NULL 

И добавить к каждому отдельному запросу в профсоюзе и вставить в @headquartersFiles

Казалось бы, как это делает гораздо больше фильтров, но это на самом деле ускорит работу, потому что вы фильтруете, прежде чем начинать обработку в качестве объединения.

Также выньте все свое DISTINCT, оно, вероятно, не ускорит его, но кажется глупым, потому что вы делаете UNION, а не UNION все.

+0

Это не работает, если головной офис отсутствует в одной таблице, но не в другом. –

+0

@TomH Я понятия не имею, что вы имеете в виду, критерий фильтра находится в объединенной таблице, а не в исходной таблице. – Hogan

+0

Запрос OP возвращает головной офис, если он отсутствует во всех таблицах. Ваш запрос вернет головной офис, если он отсутствует в ЛЮБОЙ из таблиц. –

1

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

SELECT 
    headquarter 
FROM 
(

    SELECT DISTINCT 
     headquarter, 
     'table1' AS large_table 
    FROM 
     LargeTable1 LT 
    LEFT OUTER JOIN Headquarters HQ ON HQ.headquarter = LT.headquarter 
    WHERE 
     HQ.headquarter IS NULL OR 
     HQ.deletion_date IS NOT NULL 
    UNION ALL 
    SELECT DISTINCT 
     headquarter, 
     'table2' AS large_table 
    FROM 
     LargeTable2 LT 
    LEFT OUTER JOIN Headquarters HQ ON HQ.headquarter = LT.headquarter 
    WHERE 
     HQ.headquarter IS NULL OR 
     HQ.deletion_date IS NOT NULL 
    UNION ALL 
    ... 
) SQ 
GROUP BY headquarter 
HAVING COUNT(*) = 5 

Это гарантирует, что он отсутствует во всех пяти таблицах.

1

Переменные таблицы имеют ужасную производительность, потому что сервер sql не генерирует статистику для них. Вместо переменной таблицы попробуйте вместо этого использовать временную таблицу, и если headquarter + country + file уникален в таблице temp, добавьте уникальное ограничение (которое создаст кластерный индекс) в определении таблицы temp. Вы можете устанавливать индексы в временную таблицу после ее создания, но по разным причинам SQL Server может игнорировать ее.

Редактировать: Как оказалось, вы можете создавать индексы для переменных таблицы, даже не уникальные в 2014+.

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

0

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

Смежные вопросы