Повышение производительности SQL-запросов с помощью динамического - PullRequest
0 голосов
/ 10 октября 2019

Мне нужно искать людей, чье имя указано (подстрока) в имени другого человека.

SELECT DISTINCT top 10 people.[Id], peopleName.[LastName], peopleName.[FirstName]       
    FROM [dbo].[people] people
    INNER JOIN [dbo].[people_NAME] peopleName on peopleName.[Id] = people.[Id]
    WHERE EXISTS (SELECT * 
                        FROM [dbo].[people_NAME] peopleName2 
                        WHERE peopleName2.[Id] != people.[id] 
                            AND peopleName2.[FirstName] LIKE '%' + peopleName.[FirstName] + '%')

Это так медленно! Я знаю, что это из-за "'%' + peopleName.[FirstName] + '%'", потому что, если я заменю его жестко запрограммированным значением, таким как '%G%', оно запустится мгновенно.

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

Что я могу сделать?

Ответы [ 4 ]

1 голос
/ 10 октября 2019

Взгляните на мой ответ об использовании LIKE оператора здесь

Это может быть весьма эффективным, если вы используете некоторые трюки

Вы можете получить большую скорость, есливы играете с сопоставлением, попробуйте это:

SELECT DISTINCT TOP 10 p.[Id], n.[LastName], n.[FirstName]       
FROM [dbo].[people] p
INNER JOIN [dbo].[people_NAME] n on n.[Id] = p.[Id]
WHERE EXISTS (
    SELECT 'x' x
    FROM [dbo].[people_NAME] n2
    WHERE n2.[Id] != p.[id]     
    AND 
        lower(n2.[FirstName]) collate latin1_general_bin 
        LIKE 
        '%' + lower(n1.[FirstName]) + '%' collate latin1_general_bin
)

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

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

Обычно предложение EXISTS лучше, чем INNER JOIN, но вы также используете DISTINCTэто GROUP BY во всех столбцах .. так почему бы не использовать это?

Вы можете переключиться на INNER JOIN и использовать GROUP BY вместо DISTINCT, поэтому тестирование COUNT(*)>1 будет(очень мало) более производительно, чем тестирование WHERE n2.[Id] != p.[id], особенно если ваше предложение TOP извлекает много строк.

Попробуйте это:

SELECT TOP 10 p.[Id], n.[LastName], n.[FirstName]
FROM [dbo].[people] p
INNER JOIN [dbo].[people_NAME] n on n.[Id] = p.[Id]
INNER JOIN [dbo].[people_NAME] n2 on 
    lower(n2.[FirstName]) collate latin1_general_bin 
    LIKE 
    '%' + lower(n1.[FirstName]) + '%' collate latin1_general_bin
GROUP BY n1.[Id], n1.[FirstName]
HAVING COUNT(*)>1

Здесь мы сопоставляем и само имя, поэтомумы найдем хотя бы одно совпадение для каждого имени. Но нам нужны только имена, совпадающие с другими именами, поэтому мы будем хранить только строки с количеством совпадений больше единицы (count (*) = 1 означает, что имя совпадает только с самим собой).

EDIT: Iвсе тестировали с использованием таблицы случайных имен с 100000 строк и обнаружили, что в этом сценарии обычное использование оператора LIKE примерно в три раза хуже, чем двоичное сравнение.

1 голос
/ 10 октября 2019

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

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

gor
ord
rdo
don

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

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

Я не уверен, стоит ли вся эта работа усилий для решения вашей проблемы. Но можно ускорить поиск.

0 голосов
/ 10 октября 2019

Вы можете сделать это,

With CTE as
(                       
    SELECT  top 10 peopleName.[Id], peopleName.[LastName], peopleName.[FirstName]       
    FROM 
    [dbo].[people_NAME] peopleName on peopleName.[Id] = people.[Id]
    WHERE EXISTS (SELECT 1 
                        FROM [dbo].[people_NAME] peopleName2 
                        WHERE peopleName2.[Id] != people.[id] 
                            AND peopleName2.[FirstName] LIKE '%' + peopleName.[FirstName] + '%')
    order by peopleName.[Id]                    
)

// здесь присоединиться к CTE с таблицей people, если вообще требуется

select * from CTE

ЕСЛИ соединение с people нетогда не нужно CTE.

0 голосов
/ 10 октября 2019

Вы пытались использовать JOIN вместо коррелированного запроса?.

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

SELECT DISTINCT top 10 people.[Id], peopleName.[LastName], peopleName.[FirstName]       
FROM [dbo].[people] people
     INNER JOIN [dbo].[people_NAME] peopleName on peopleName.[Id] = people.[Id]
     INNER JOIN [dbo].[people_NAME] peopleName2 on peopleName2.[Id] <> people.[id] AND
                                                   peopleName2.[FirstName] LIKE '%' + peopleName.[FirstName] + '%'
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...