Выполнение поиска по столбцам имени и фамилии с помощью одной строки поиска, содержащей более 2 слов

#c# #tsql

#c# #tsql

Вопрос:

У меня есть запрос, который в настоящее время принимает единственную строку поиска, предоставленную пользователем, и пытается выполнить поиск по таблице, которая содержит отдельный столбец для FirstName и LastName. Если в строке поиска есть пробел, выполняемый запрос по существу выглядит следующим образом:

 SELECT * FROM table
WHERE table.firstName LIKE @firstName   '%'
AND table.lastName LIKE @lastName   '%'
  

Нам не нужно беспокоиться о случае отсутствия пробелов в строке поиска.

Общий случай довольно прост — разделите строку поиска на пробел, первая часть — это имя, вторая часть — фамилия. Таким образом, «Боб Смит» становится

 @firstName = "Bob", @lastName = "Smith"
  

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

 table.firstName        table.lastName
---------------        --------------
Bob                    van Smith
Billy Bob              Smith
Bob                    van der Smith  
Billy Bob              van der Smith
  

И так далее. Прямо сейчас мы разделяем первый пробел, поэтому первый пример «Bob van Smith» будет работать, потому что он разбивается на

 @firstName: "Bob", @lastName: "van Smith" 
  

Но это не улавливает второй случай «Билли Боб Смит», поскольку он разбивается на

 @firstName: "Billy", @lastName: "Bob Smith"
  

Текущая настройка также будет работать в третьем тестовом примере, поскольку она разбивается на

 @firstName: "Bob", @lastName: "van der Smith"
  

В последнем случае были бы бонусные баллы, если бы существовал способ заставить это работать.

Моей первой идеей было просто изменить запрос на

 SELECT * FROM table
WHERE table.firstName   ' '    table.lastName LIKE '%'   @searchString   '%'
  

Но это было отменено, потому что мы не хотим, чтобы кто-то искал только букву «a», например, чтобы вернуть тонны записей, которые создал бы двойной подстановочный знак.

Есть ли какие-либо хитрости для выполнения такого рода разделения строк / поиска? Это не может быть первым случаем, когда возникает проблема, но при поиске в Интернете я не смог найти ничего, кроме «разделить на пробел, но обратите внимание, что это не сработает, если в имени 3 или более слов».

Мне хочется чего-то вроде включения «между именами» как части @FirstName и @LastName и сделать что-то умное, или сделать часть SQL более универсальной, а затем выполнить дополнительную фильтрацию с помощью LINQ в моем коде C #, но решение ускользает от меня.

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

1. Это нетривиальная проблема. Возьмем, к примеру, такое имя, как Мария де лос-Анджелес Гомес де ла Крус. Я думаю, что ваш дизайн поставил вас в тупик, из которого нелегко выбраться. Надежный алгоритм сопоставления имен должен учитывать возможность того, что люди могут по-разному относиться к одному и тому же имени, и допускать пропуск слов в имени, пересечение границ имени / фамилии, орфографические ошибки и т.д. Это слишком широко для простого ответа SO.

2. @hatchet Да, это то, чего я боялся. Я унаследовал этот дизайн и сопротивляюсь внесению в него фундаментальных изменений, поэтому я подумал, что не помешает посмотреть, не упустил ли я какой-нибудь трюк

Ответ №1:

Я сделал это с помощью:

  1. Использование подстановочного знака для сопоставления каждого имени, разделенного пробелом, с обоими столбцами (я не мог контролировать порядок имен или требовать имя и фамилию).
  2. Затем я присваиваю оценку на основе каждого совпадения (т. Е. Совпадало ли это целое слово и были ли совпадения в столбцах имени и фамилии)
  3. У меня также было минимальное количество букв для поиска (минимум 2 буквы)

У меня это хорошо сработало. Вы все еще можете сгенерировать большое количество совпадений для маленьких слов. Ранжирование совпадений приведет к тому, что плохие частичные совпадения окажутся в нижней части списка.

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

1. Мне нравится эта идея, и я думаю, что это может быть полезно для кого-то еще, кто может найти этот вопрос в будущем. К сожалению, в моем случае требуется «совпадение» или «нет совпадения». Я подозревал, что у меня слишком много ограничений, чтобы это действительно было выполнимо, и, похоже, я был прав

2. Когда вы говорите «совпадает» или «не совпадает» — вы имеете в виду по всему имени? Если это так, сначала выполните поиск по concat вашим Firstname / Lastname столбцам (т. Е. Боб Смит не совпадает с Бобом ван Смитом, вы золотой). Если вы имеете в виду не поиск частичных совпадений слов, а гибкость в подборе не всех имен, вы также можете это сделать, разделив ваши поисковые слова и слова в ваших FirstName / LastName столбцах и оценив их по порядку.

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

Ответ №2:

Если вы используете sql server 2012 , вы можете использовать CONCAT (Transact-SQL).

 select *  from [table] where CONCAT(FirstName, ' ', LastName) like '%Bob Smith%'
  

В этом случае вам не нужно ничего разделять, просто ОБЪЕДИНИТЕ ваши имя и фамилию и сравните их с вашими входными данными. Если вы собираетесь всегда получать полное имя из входных данных, вы даже можете перейти:

 select *  from [table] where CONCAT(FirstName, ' ', LastName) ='Bob Smith'
  

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

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

1. Приведет ли это к сканированию таблицы для каждого запроса?

2. @hatchet вероятно, я не знаю, как он настроил свою таблицу, для таких проверок есть профилировщик.

3. Разве это не та идея, которая у меня была, которая была отвергнута?

4. @Adam Я тебя не понял. Наверняка это будет сделано быстрее, чем поиск по 2 столбцам с помощью like.

Ответ №3:

Если вы ищете только точное соответствие тому, что ввел пользователь, попробуйте это:

 select * 
from table
where @searchString like table.firstName   '%' and @searchString like '%'   table.lastName
and len(table.firstName   ' '   table.lastName) = len(@searchString)