Оптимальная строка поиска в предложении where - PullRequest
0 голосов
/ 29 ноября 2018

Хотите найти строку, используя PATINDEX и SOUNDEX в предложении WHERE или любым другим оптимальным способом.

У меня есть следующая таблица с некоторыми примерами данных для поиска в указанной строке, используя PATINDEX и SOUNDEX.

create table tbl_pat_soundex
(
    col_str varchar(max)
);

insert into tbl_pat_soundex values('Smith A Steve');
insert into tbl_pat_soundex values('Steve A Smyth');
insert into tbl_pat_soundex values('A Smeeth Stive');
insert into tbl_pat_soundex values('Steve Smith A');
insert into tbl_pat_soundex values('Smit Steve A');

Примечание : у меня есть 100 Millions записей в таблице для поиска.

Строка для поиска: - 'SmithA Steve '

SELECT col_str
FROM tbl_pat_soundex
WHERE PATINDEX('%Smith%',col_str) >= 1 AND PATINDEX('%A%',col_str) >= 1 AND PATINDEX('%Steve%',col_str) >= 1

Получение результата:

col_str
--------------
Smith A Steve
Steve Smith A

Ожидаемый результат:

col_str         
----------------
Smith A Steve   
Steve A Smyth   
A Smeeth Stive  
Steve Smith A   
Smit Steve A    

Пытался:

1 :

SELECT col_str
FROM tbl_pat_soundex
WHERE PATINDEX('%Smith%',col_str) >= 1 AND 
      PATINDEX('%A%',col_str) >= 1 AND 
      PATINDEX('%Steve%',col_str) >= 1

2 :

SELECT col_str
FROM tbl_pat_soundex
WHERE PATINDEX('%'+SOUNDEX('Smith')+'%',SOUNDEX(col_str)) >= 1 AND 
      PATINDEX('%'+SOUNDEX('A')+'%',SOUNDEX(col_str)) >= 1 AND 
      PATINDEX('%'+SOUNDEX('Steve')+'%',SOUNDEX(col_str)) >= 1

3 :

SELECT col_str
FROM tbl_pat_soundex    
WHERE DIFFERENCE('Smith',col_str) = 4 AND 
      DIFFERENCE('A',col_str) =4 AND 
      DIFFERENCE('Steve',col_str) = 4

4 :

--Following was taking huge time(was kept running more than 20 minutes) to execute.
SELECT DISTINCT col_str
FROM tbl_pat_soundex [a]
CROSS APPLY SplitString([a].[col_str], ' ') [b]
WHERE DIFFERENCE([b].Item,'Smith') >= 1 AND 
      DIFFERENCE([b].Item,'A') >= 1 AND 
      DIFFERENCE([b].Item,'Steve') >= 1

Ответы [ 2 ]

0 голосов
/ 29 ноября 2018

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

Следующее будет работать, но я обещаю, что это будет медленно ...

- установить тестовую базу данных

USE master;
GO
CREATE DATABASE shnugo;
GO
USE shnugo;
GO

- в вашу таблицу я добавил ID-столбец

create table tbl_pat_soundex
(
    ID INT IDENTITY --needed to distinguish rows
   ,col_str varchar(max)
);
GO

- функция, которая будет возвращать разделенную пробелом строку в виде отсортированного по алфавиту списка различных значений soundex, разделенных /: «Смит А Стив» возвращается как /A000/S310/S530/

CREATE FUNCTION dbo.ComputeSoundex(@str VARCHAR(MAX))
RETURNS VARCHAR(MAX)
AS
BEGIN
    DECLARE @tmpXML XML=CAST('<x>' + REPLACE((SELECT @str AS [*] FOR XML PATH('')),' ','</x><x>') + '</x>' AS XML);
    RETURN (SELECT DISTINCT '/' + SOUNDEX(x.value('text()[1]','varchar(max)')) AS [se]
            FROM @tmpXML.nodes('/x[text()]') A(x)
            ORDER BY se
            FOR XML PATH(''),TYPE).value('.','nvarchar(max)') + '/';
END
GO

- Добавить столбец для постоянного хранения вычисленной цепочки звуков:

ALTER TABLE tbl_pat_soundex ADD SortedSoundExPattern VARCHAR(MAX);
GO

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

CREATE TRIGGER RefreshComputeSoundex ON tbl_pat_soundex
FOR INSERT,UPDATE
AS
BEGIN
    UPDATE s SET SortedSoundExPattern=dbo.ComputeSoundex(i.col_str)
    FROM tbl_pat_soundex s
    INNER JOIN inserted i ON s.ID=i.ID;
END
GO

- тестовые данные

insert into tbl_pat_soundex(col_str) values
 ('Smith A Steve')
,('Steve A Smyth')
,('A Smeeth Stive')
,('Steve Smith A')
,('Smit Steve A')
,('Smit Steve') --no A
,('Smit A') --no Steve
,('Smit Smith Robert Peter A') --add noise
,('Shnugo'); --something else entirely

- проверка промежуточного результата

SELECT * 
FROM tbl_pat_soundex

/*
+----+---------------------------+-----------------------+
| ID | col_str                   | SortedSoundExPattern  |
+----+---------------------------+-----------------------+
| 1  | Smith A Steve             | /A000/S310/S530/      |
+----+---------------------------+-----------------------+
| 2  | Steve A Smyth             | /A000/S310/S530/      |
+----+---------------------------+-----------------------+
| 3  | A Smeeth Stive            | /A000/S310/S530/      |
+----+---------------------------+-----------------------+
| 4  | Steve Smith A             | /A000/S310/S530/      |
+----+---------------------------+-----------------------+
| 5  | Smit Steve A              | /A000/S310/S530/      |
+----+---------------------------+-----------------------+
| 6  | Smit Steve                | /S310/S530/           |
+----+---------------------------+-----------------------+
| 7  | Smit A                    | /A000/S530/           |
+----+---------------------------+-----------------------+
| 8  | Smit Smith Robert Peter A | /A000/P360/R163/S530/ |
+----+---------------------------+-----------------------+
| 9  | Shnugo                    | /S520/                |
+----+---------------------------+-----------------------+
*/

- Теперь мы можем начать поиск:

DECLARE @StringToSearch VARCHAR(MAX)=' A Steve';

WITH SplittedSearchString AS
(
    SELECT soundexCode.value('text()[1]','nvarchar(max)') AS SoundExCode
    FROM (SELECT CAST('<x>' + REPLACE(dbo.ComputeSoundex(@StringToSearch),'/','</x><x>') + '</x>' AS XML)) A(x)
    CROSS APPLY x.nodes('/x[text()]') B(soundexCode)
)
SELECT a.ID,col_str
FROM tbl_pat_soundex a
INNER JOIN SplittedSearchString s On SortedSoundExPattern LIKE '%/' +  s.SoundExCode + '/%'
GROUP BY ID,col_str
HAVING COUNT(ID)=(SELECT COUNT(*) FROM SplittedSearchString)
ORDER BY ID 
GO

- очистка

USE master;
GO
DROP DATABASE shnugo;

Краткое объяснение

Вот как это работает:

  • Эта команда будет использовать ту же функцию для возврата soundex-цепочки всех фрагментов ввода
  • Затем запрос INNER JOIN выполнит тест с LIKE - это будетe sloooooow ...
  • Последняя проверка заключается в том, совпадает ли количество попаданий с количеством фрагментов.

И последний совет: если вы хотите найти точное совпадение, но хотите включить разные записи, вы можете просто напрямую сравнить две строки.Вы можете даже поместить индекс в новый столбец SortedSoundExPattern.Благодаря способу создания все виды «Стивен А Смит», «Стивен А Смит» и даже в различном порядке, как «Смит Стивен А», будут производить точно такой же шаблон.

0 голосов
/ 29 ноября 2018

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

Например, у вас есть таблица:

create table tbl_pat_soundex
(
    id int,
    col_str varchar(max)
)

И у вас есть следующий кластеризованный индекс или любой другой индекс (таблица с более чем 100 миллионами строк должна иметь некоторый индекс):

CREATE NONCLUSTERED INDEX myIndex ON dbo.tbl_pat_soundex(id) INCLUDE (col_str)*/

Поэтому попробуйте создать следующий динамический SQL-запрос на основе вашей логики и выполнить его.Результат запроса должен выглядеть следующим образом:

DECLARE @statement NVARCHAR(4000)
SET @statement = N'
SELECT col_str
FROM tbl_pat_soundex
WHERE col_str like '%Smith%' AND id > 0
UNION ALL
SELECT col_str
FROM tbl_pat_soundex
WHERE col_str like '%Steve%' AND id > 0
UNION ALL
SELECT col_str
FROM tbl_pat_soundex
WHERE 
    PATINDEX('%Smith%',col_str) >= 1 AND PATINDEX('%A%',col_str) >= 1 AND 
    PATINDEX('%Steve%',col_str) >= 1
    AND id > 0'

По сути, мы создаем отдельные поисковые запросы, которые будут искать по индексу, а затем объединять все результаты.

Этот запрос будет иметь поиск по индексупоскольку мы используем предикат id > 0 (при условии, что все идентификаторы больше 0 или вы можете написать свое собственное отрицательное число):

SELECT col_str
FROM tbl_pat_soundex
WHERE col_str like '%Smith%' AND id > 0
...