SQL Получает первое совпадение с настройкой производительности ранжированной сортировки

#sql #sql-server #join #performance-testing #query-performance

#sql #sql-server #Присоединиться #тестирование производительности #запрос-производительность

Вопрос:

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

В качестве эксперимента я выполнил только первую половину запроса, и это заняло около 3-4 минут на 40 тыс. строк (см. Ниже, что я имею в виду). Я уже изменил левые соединения на внутренние соединения с небольшим повышением производительности, но у меня заканчиваются идеи о том, что еще я могу сделать.

Обратите внимание, что, если это вообще возможно, я хочу максимально использовать SQL, основанный на непатентованных стандартах, поскольку в ближайшем будущем я, вероятно, перейду с MS SQL Server на MySQL или Oracle, и я не хочу делать ненужные перезаписи.

Запрос

 SELECT Q.*
FROM (
SELECT 
    A.name
  , B.id
  , B.status
  , B.rank

FROM TestA A
LEFT JOIN TestB B
ON B.name = A.name
WHERE 
  B.rank = (
            SELECT MIN(B2.rank) 
            FROM TestB B2 
            WHERE B.name = B2.name
           )
) Q
-- by first half I mean what is above this line here
WHERE
    Q.id = (
        SELECT MIN(Q2.id) 
        FROM (
            SELECT 
                A.name
              , B.id
              , B.status
              , B.rank

            FROM TestA A
            LEFT JOIN TestB B
            ON B.name = A.name
            WHERE 
              B.rank = (
                        SELECT MIN(B2.rank) 
                        FROM TestB B2 
                        WHERE B.name = B2.name
                       )
        ) Q2 
        WHERE Q.name = Q2.name
       )
;
 

Схема: тестовых данных

 CREATE TABLE TestA
(`name` varchar(4))
;

INSERT INTO TestA
(`name`)
VALUES
('bob'),
('john'),
('will')
;

CREATE TABLE TestB
(`name` varchar(4), `id` int, `status` varchar(18), `rank` int)
;

INSERT INTO TestB
(`name`, `id`, `status`, `rank`)
VALUES
('bob', 11, 'happy', 1),
('bob', 12, 'active', 1),
('bob', 93, 'inactive', 2),
('bob', 94, 'canceled', 2),
('bob', 95, 'pending deletion', 3),
('john', 32, 'pending activation', 10),
('john', 24, 'inactive', 4),
('will', 555, 'vacation', 5),
('will', 511, 'vacation', 5),
('will', 661, 'on hold', 9)
;
 

Вот скрипка, которую я сделал из схемы и кода

http://sqlfiddle.com /#!2/e91a8/3/0

Комментарии:

1. Какую СУБД вы используете? Скрипкой является mysql, но вы отметили вопрос sql-server .

2. Я перечитал сообщение и вижу, что вы упоминаете там СУБД. Проблема в том, что и sql-server, и Oracle принимают намного больше стандартов sql, чем mysql. Для вашей проблемы будут полезны функции CTE и OLAP, oracle и msql поддерживают их, mysql — нет.

3. Меня больше интересует фактическая производительность.. для его работы с 40 тыс. строк данных требуется около 20 минут .. эти данные, вероятно, будут расти на 5-10 тыс. в год, что делает их уродливыми в долгосрочной перспективе .. и изменения в среде будут просто большим раздражением.

Ответ №1:

Первое, что я хотел бы упомянуть, это то, что вы присоединяетесь к name, и я предполагаю, что вы не добавили никакого индекса в этот столбец (в схеме не определен какой-либо индекс).). Если вы можете использовать идентификатор автоматического увеличения в TableA и использовать его в качестве внешнего ключа в TableB, использование join с ID значительно ускорит ваш запрос.

Я бы также рекомендовал вам запустить профилировщик в среде SQL Server Management Studio (SSMS) во время выполнения запроса, если у вас есть права администратора. Вы также можете проверить sqlsentry, который утверждает, что он лучше, чем SSMS для профилирования. http://www.sqlsentry.com/products/plan-explorer/sql-server-query-view (Я еще не пробовал, но выглядит многообещающе).

Дайте мне знать, помогают ли индексы или нет.

Комментарии:

1. Индексы уже присутствуют и настроены в рабочей базе данных, хотя и не в примере для краткости. Я использую инструмент профилировщика, но обязательно проверю sentry, чтобы узнать, полезно ли это.

Ответ №2:

Присоединение к строке, как уже упоминалось, плохо … тем не менее, этот запрос должен быть немного быстрее и совместим с SQL Server и MySQL (не уверен в Oracle).

 SELECT A.name, B.id, B.status, B.rank
FROM TestA A
INNER JOIN TestB B 
   ON A.name = B.name
   INNER JOIN (SELECT MIN(id) AS id FROM TestB GROUP BY name) AS MinID
      ON B.id = MinID.id
 

Скрипка

Ответ №3:

 SELECT 
    A.name
  , B.id
  , B.status
  , B.rank
FROM TestA A
LEFT JOIN (
    SELECT *
    FROM (
        SELECT *, ROW_NUMBER() OVER(PARTITION BY B.id ORDER BY B.rank) rownum
        FROM B
    ) b1
    WHERE rownum = 1
) B
ON B.name = A.name
 

Комментарии:

1. Вы, вероятно, имели в виду разделение по имени, верно? И упорядочение по рангу неоднозначно, поскольку ранг не является уникальным в разделе. Похоже, OP хочет наименьший идентификатор для дубликатов ранга, поэтому просто ad ID в качестве второго порядка по столбцу, и это будет работать просто отлично.

Ответ №4:

Попробуйте что-то вроде этого…..

 SELECT A.Name
      ,B.ID
      ,B.[Status]
      ,B.[Rank]
FROM TestA A 
INNER JOIN 
          (
           SELECT name, id, status, rank
              ,ROW_NUMBER() OVER (PARTITION BY NAME ORDER BY [Rank] ASC) rn
           FROM TestB
          ) B
ON A.Name = B.Name 
WHERE B.RN = 1
 

РАБОЧАЯ СКРИПКА SQL

Комментарии:

1. Упорядочение по рангу неоднозначно, поскольку ранг не является уникальным в пределах раздела. Похоже, OP хочет наименьший идентификатор для дубликатов ранга, поэтому просто ad ID в качестве второго порядка по столбцу, и это будет работать просто отлично.

Ответ №5:

Вот версия, которая работает как в SQL Server, так и в MySQL. Вероятно, немного медленнее, чем при использовании row_number() .

 select A.Name,
       B1.id,
       B1.status,
       B1.rank
from TestB as B1
  inner join (
             select B3.name,
                    B3.rank,
                    min(B3.id) as id 
             from (
                    select B4.name,
                           B4.id,
                           B4.rank
                    from TestB as B4
                      inner join (
                                 select min(B6.rank) as rank,
                                        B6.name
                                 from TestB as B6
                                 group by B6.name
                                 ) as B5
                        on B4.name = B5.name and
                           B4.rank = B5.rank
                  ) as B3
             group by B3.Name, 
                      B3.rank
             ) as B2
    on B1.name = B2.Name and
       B1.rank = B2.rank and
       B1.id = B2.id
  inner join TestA as A
    on B1.name = A.name