2013-09-08 5 views
1

У меня есть таблица с компаниями и одна с категориями. Я использую SQL Server Free Text, и поисковые компании (по названию и описанию) отлично работают. Но теперь я также хочу включить таблицу категорий.SQL Server Бесплатный текстовый поиск: поиск слов из фразы в двух таблицах

Я хочу найти что-то вроде: ABC 24 Supermarket.

Теперь ABC 24 должен сделать матч с Name столбца в таблице company и Supermarket это название category эта компания подключена.

Сейчас у меня есть что-то вроде этого:

DECLARE @SearchString VARCHAR(100) = '"ABC 24 Supermarket"' 
SELECT * FROM Company CO 
INNER JOIN Category CA 
ON CA.CategoryId = CO.CategoryId 
WHERE CONTAINS((CO.[Description], CO.[Name]), @SearchString) 
AND CONTAINS(CA.[Description], @SearchString) 

Но это, конечно, не дает мне ничего, потому что моя строка поиска не может быть найден в компанию или в таблице категории. Кто-нибудь имеет представление о том, как выполнять комбинированный поиск в моей компании и таблице категорий?

Идея разделения строк, как это предлагается в ответе Лобо ниже, на самом деле не вариант. Потому что я не знаю, какая часть будет той, которая должна соответствовать категории, а какая часть должна использоваться для соответствия названиям/описаниям компаний. Пользователи могут так же набрать «Супермаркет ABC 24».

+0

почему дон 'использовать для переменных один для«ABC 24», а другой для«Супермаркет» – danisius

+0

У меня есть только один входной ящик для строки поиска. И пользователям должно быть возможно также войти в «Супермаркет ABC 24». – Tys

+0

Вы считаете конкатенатом поля, которое ищете, вместо того, чтобы разбивать строку поиска? Что-то вроде WHERE CONTAINS ((CO. [Description] + '' + 'CA. [Description], CO. [Name]), @SearchString)? – Twelfth

ответ

4

Имхо правильный способ сделать это, чтобы создать индексированный вид, содержащий первичный ключ главной таблицы («компании» в вашем примере) и второй столбец, содержащий все вещи, которую вы на самом деле хотите поиск, т.е.

create View View_FreeTextHelper with schemabinding as 
select CO.PrimaryKey,       -- or whatever your PK is named 
     CO.description +' '+CA.description +' '+CO.whatever as Searchtext 
    from dbo.company CO join 
     dbo.category CA on CA.CategoryId = CO.CategoryId 

Обратите внимание на двухчастную форму ваших таблиц. Из этого вытекает несколько ограничений, например. все задействованные таблицы должны находиться в одном и том же табличном пространстве, и, насколько я помню, в этом типе конкатенации не разрешены столбцы TEXT (вы можете их отбросить).

Теперь создать уникальный индекс на PrimaryKey колонке

create unique clustered index [View_Index] 
    on View_FreeTextHelper (PrimaryKey ASC) 

Наконец создать полнотекстовый индекс на представлении, используя столбец «SearchText» в качестве единственного столбца индекса. Конечно, вы можете добавить больше столбцов, если вы, например,желают различать поиск имени и местоположения компании, а также имена менеджеров (вы просто соедините их во второй колонке).

Получение ваши данные теперь легко:

select tbl.RANK, 
     co.* 
    from freetextTable(View_FreeTextHelper,Search,'Your searchtext here') tbl 
    join company co on tbl.key=co.PrimaryKey 
order by tbl.RANK desc 

Вы также можете ограничить вывод, используя select top 50 как предложение freetexttable в конечном итоге будет возвращать довольно много близких и не очень близких результатов.

И, наконец, не путайте, если вы не можете найти такую ​​вещь, как «с полки вкл.». Остерегайтесь стоп-листов. Это список слов, которые очень распространены, не имеют семантического использования (например, the) и поэтому удаляются из текста для индексирования. Чтобы включить их, вам нужно переключить функцию стоп-листа.

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

Удачи.

+0

Оказывается, что для теперь это самое чистое и эффективное решение. Спасибо за помощь! – Tys

+0

Спасибо за улучшение моих знаний FTS;) поймать +1 –

+0

Спасибо, добро пожаловать – alzaimar

0

(У меня нет установки SQL-сервера, чтобы попытаться CONTAINS Вы можете заменить column LIKE '%string%' с CONTAINS(column, 'string') и попробовать..)

See all queries here.


Очередное обновление - После чтения другие ответы и руководство, кажется, что вам не нужны значения в скобках в строке сложения, в отличие от ожидаемого. Так это должно работать слишком - (вы можете даже попробовать ' | ' вместо ' OR '

SELECT CO.name, CA.description FROM company CO 
     INNER JOIN category CA 
      ON CA.CategoryId = CO.CategoryId 
WHERE CONTAINS((CO.name,CO.description), REPLACE('ABC 25 SuperMarket', ' ', ' OR ')) 
     AND 
     CONTAINS(CA.description, REPLACE('ABC 25 SuperMarket', ' ', ' OR ')) 

Если он жалуется на синтаксическую ошибку рядом заменить, вы можете создать поисковую строку DECLARE @SearchString varchar(MAX) = REPLACE('ABC 25 SuperMarket',' ', ' OR '), а затем использовать его вместо replace(......) в качестве второго аргумента.


Update согласно измененному вопрос -

Во-первых, вы должны двигаться логику приложения ле vel если возможно. Я думаю, это слишком много, чтобы справиться с этим здесь. Я придумал этот запрос, но обратите внимание, что это разделит каждое слово и будет искать его как в name, так и в description, чтобы вы получили еще несколько результатов, чем вы могли бы подумать. Напр. это вернет все Supermarket, у которых либо есть ABC, либо 24 на свое имя по сравнению с возвратом только одного Supermarket с именем ABC 24 в моем предыдущем запросе.Это должно реально помочь вам, потому что, как и за вами, пользователь может просто ввести "ABC Supermarket 24" or "24 ABC Supermarket" or ...

DECLARE @SearchString varchar(MAX) = 'ABC 24 SuperMarket' 
DECLARE @separator varchar(MAX) = ' ' 
DECLARE @Like1 varchar(MAX) = 'CO.name LIKE' 
DECLARE @Like2 varchar(MAX) = 'CA.description LIKE' 

DECLARE @WHERE1 varchar(MAX) = '(' + @Like1 + ' ''%' + 
      REPLACE(@SearchString,@separator,'%'' OR ' + @Like1 + ' ''%')+'%'')' 

DECLARE @WHERE2 varchar(MAX) = '(' + @Like2 + ' ''%' + 
      REPLACE(@SearchString,@separator,'%'' OR ' + @Like2 + ' ''%')+'%'')' 

DECLARE @QueryString varchar(MAX) = 
      CONCAT('SELECT CO.name, CA.description FROM company CO 
         INNER JOIN category CA 
          ON CA.CategoryId = CO.CategoryId 
        WHERE ', @WHERE1, ' AND ', @WHERE2) 
exec(@QueryString); 

Если выход @WHERE1 вы должны увидеть

(CO.name LIKE '%ABC%' OR CO.name LIKE '%25%' OR CO.name LIKE '%SuperMarket%') 

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

DECLARE @SearchString varchar(MAX) = 'ABC 25 SuperMarket' 
DECLARE @separator varchar(MAX) = ' ' 

DECLARE @WHEREString varchar(MAX) = '''"' + 
     REPLACE(@SearchString, @separator, '" OR "')+'"''' 

SELECT CO.name, CA.description FROM company CO 
     INNER JOIN category CA 
      ON CA.CategoryId = CO.CategoryId 
WHERE CONTAINS((CO.name,CO.description), @WHEREString) 
     AND 
     CONTAINS(CA.description, @WHEREString) 

Если вы выводите @WHEREString вы должны увидеть

-
'"ABC" OR "25" OR "SuperMarket"' 

Предыдущий ответ:

Это будет предполагает, что слово после последнего пространства является description и остальное является `имя.

Вы можете разбить строку поиска и использовать их, как показано ниже. Этот запрос использует like, поскольку у меня нет установки sql-сервера.

DECLARE @SearchString VARCHAR(100) = 'ABC 24 Supermarket' 
DECLARE @searchLength int = len(@SearchString) 
DECLARE @searchReverse VARCHAR(100) = reverse(@SearchString) 

SELECT CO.name, CA.description FROM company CO 
     INNER JOIN category CA 
      ON CA.CategoryId = CO.CategoryId 
WHERE CO.name LIKE concat('%', SUBSTRING(@SearchString,0,@searchLength-charindex(' ',@searchReverse)+1), '%') 
    AND 
    CA.description LIKE concat('%', SUBSTRING(@SearchString,@searchLength-charindex(' ',@searchReverse)+2,@searchLength), '%') 

Это должно сработать. Обратите внимание, что предложение where использует AND вместо OR.

DECLARE @SearchString VARCHAR(100) = 'ABC 24 Supermarket' 
DECLARE @searchLength int = len(@SearchString) 
DECLARE @searchReverse VARCHAR(100) = reverse(@SearchString) 
DECLARE @company VARCHAR(100) = SUBSTRING(@SearchString,0,@searchLength-charindex(' ',@searchReverse)+1) 
DECLARE @category VARCHAR(100) = SUBSTRING(@SearchString,@searchLength-charindex(' ',@searchReverse)+2,@searchLength) 

SELECT CO.name, CA.description FROM company CO 
     INNER JOIN category CA 
      ON CA.CategoryId = CO.CategoryId 
WHERE CONTAINS((CO.name, CO.description), @company) 
     AND 
     CONTAINS(CA.description , @category) 
+0

Это будет работать только в том случае, если ввод введен в точном формате. Категория всегда должна быть в конце. Но практически это не так. Люди могут просто войти в «Супермаркет ABC 24 часа в сутки». – Tys

+0

@TysHTTP - это боль; вы должны справиться с этим на уровне приложения. – Lobo

0

Я не знаю, будет ли это представлять большой ответ (я склонен сомневаться в этом), но я хотел проблема работать и я случался, чтобы забрать твои, так вот мое решение:

DECLARE @SearchString VARCHAR(100) = '"ABC 24 Supermarket"' 

DECLARE @AllItems table (
    SearchItem varchar(100) 
), 
     @x int; 

-- break up @SearchString into component search items: 
select @x = charindex(' ', @SearchString); 
while @x > 0 begin 
    insert @AllItems (SearchItem) values (substring(@SearchString, 1, @x - 1)); 
    select @SearchString = substring(@searchstring, @x + 1); 
    select @x = charindex(' ', @Searchstring); 
end; 
-- add the last item 
insert @AllItems (SearchItem) values (@SearchString) 


DECLARE @this varchar(100), -- = current search item 
     @found table (  -- table to contain rows matching the current search item 
      ID int 
     ), 
     @usable table (  -- table to contain rows matching all search items 
      ID int    -- already tested 
     ); 

--now process search items one-at-a-time 
while (select count(*) from @AllItems) > 0 begin 
    select @this = min(SearchItem) from @AllItems; 
    delete @AllItems where SearchItem = @this; 

    if (select count(*) from @usable) = 0 begin --first search item 
    --for the first item, just find the companies matching this item, in either the 
    --company name or description or category description columns: 
    insert @found (ID) 
    select CO.CompanyID 
    from Company CO inner join Category CA on CO.CategoryID = CA.CategoryID 
    where contains ((CO.[Description], [CO.[Name]) @this) 
     or contains (CA.[Description], @this) 
    end 
    else begin         --other search items 
    -- for subsequent items, its got to match with the company name or description 
    -- or category description as above - BUT it's also got to be a company we 
    -- already identified when processing the previous term 
    insert @found (ID) 
    select CO.CompanyID 
    from Company CO inner join Category CA on CO.CategoryID = CA.CategoryID inner join @usable U on CO.CompanyID = U.ID 
    where contains ((CO.[Description], [CO.[Name]) @this) 
     or contains (CA.[Description], @this) 
    end 

    --now clear out and re-populate the usable companies table ready for processing the 
    --next search item 
    delete @usable; 
    insert @usable (ID) 
    select ID 
    from @found; 

    --and clear out the current matches table, ready for the next search item 
    delete @found; 
end; 


--whatever is in @usable now, is a match with all the component search items, so: 
select CO.* 
from Company CO inner join Category CA on CO.CategoryId = CA.CategoryId inner join @usable U on CO.CompanyID = U.ID; 
0

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

Так вот еще один потенциальный решение вашей проблемы, которое я вхожу как отдельный ответ, поскольку это не имеет никакого отношения к решению я уже писал:

DECLARE @SearchString VARCHAR(100) = '"ABC 24 Supermarket"' 

DECLARE @AllItems table (
    SearchItem varchar(100) 
), 
     @x int, 
     @cmd varchar(1000), 
     @wc varchar(8000), 
     @this varchar(100); 

-- break up @SearchString into component search items: 
select @x = charindex(' ', @SearchString); 
while @x > 0 begin 
    insert @AllItems (SearchItem) values (substring(@SearchString, 1, @x - 1)); 
    select @SearchString = substring(@searchstring, @x + 1); 
    select @x = charindex(' ', @Searchstring); 
end; 
-- add the last item 
insert @AllItems (SearchItem) values (@SearchString) 

select @cmd = 'select CO.* from Company CO inner join Category CA on CO.CategoryId = CA.CategoryId WHERE'; 


--now process search items one-at-a-time building up a where clause to plug into @cmd: 
while (select count(*) from @AllItems) > 0 begin 
    select @this = min(SearchItem) from @AllItems; 
    delete @AllItems where SearchItem = @this; 

    select @wc = @wc + 
    'AND (contains ((CO.[Description], [CO.[Name]) ''' + @this + ''') or contains (CA.[Description], ''' + @this + ''') ' 
end; 

--ready to go: 
exec (@cmd + substring(@wc, 4)); --substring removes first AND 
-1

вы можете использовать FREETEXT вместо СОДЕРЖАТЬ.

DECLARE @SearchString VARCHAR(100) = '"ABC 24 Supermarket"' 
SELECT * FROM Company CO 
INNER JOIN Category CA 
ON CA.CategoryId = CO.CategoryId 
WHERE FREETEXT((CO.[Description], CO.[Name]), @SearchString) 
OR FREETEXT(CA.[Description], @SearchString) 
+0

Это никогда не сработает. Он будет искать в обеих таблицах (индексы) для всего набора условий поиска. – Tys

+0

FREETEXT, найдите любое из этих слов, а не весь его набор. вы можете проверить пример части A по этой ссылке: http://technet.microsoft.com/en-us/library/ms176078.aspx или проверьте это: http://forta.com/blog/index.cfm/2007/1/4/Performing-SQL-Server-FREETEXT-Searches – user1066713

0

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

DECLARE @SearchString VARCHAR(100) = '"ABC 24 Supermarket"', @A varchar(100), @B varchar(100), @C varchar(100),@index int 

set @A = Substring(@searchString, 1, PATINDEX('% %', @searchString) -1) 
set @index = PATINDEX('% %', @searchString) + 1 
set @B = Substring(@searchString, @index, PATINDEX('% %', @substring(@searchstring, @index, 100)) -1) 
Set @index = PATINDEX('% %', @substring(@searchstring, @index, 100)) + 1 
set @C = Substring(@searchString, @index, PATINDEX('% %', @substring(@searchstring, @index, 100)) -1) 

SELECT * FROM Company CO 
INNER JOIN Category CA 
    ON CA.CategoryId = CO.CategoryId 
WHERE CO.[Description] like @A 
    or CO.[Description] like @B 
    or CO.[Description] like @c 
    or CO.[Name] like @A 
    or CO.[Name] like @B 
    or CO.[Name] like @C 
    or CA.[Description] like @A 
    or CA.[Description] like @B 
    or CA.[Description] like @C 

Этот код выглядит уродливым для меня, но он должен выполнять требования для пользователя, вводящего до 3 элементов для поиска. У кого-нибудь есть предложения по его очистке?

1

Если мы предположим, что имя столбцов уникально для каждой строки, вы можете использовать нижеприведенный запрос. Следующий пример возвращает все строки, содержащие либо фразу «ABC», «24» или «Супермаркет» в каждом из столбцов

DECLARE @SearchString nvarchar(100) = N'ABC 24 Supermarket' 
SET @SearchString = REPLACE(LTRIM(RTRIM(@SearchString)), ' ', '|') 
SELECT * 
FROM Company CO JOIN Category CA ON CA.CategoryId = CO.CategoryId 
WHERE CONTAINS(CO.[Name], @SearchString) 
AND CONTAINS(CO.[Description], @SearchString) 
AND CONTAINS(CA.[Description], @SearchString) 

Прежде всего, вам нужно подготовить значение поиска для СОДЕРЖИТ предикат, используемый в предложение WHERE. В этом случае я заменил пробелы между словами на «|» логический оператор (символ символа (|) может использоваться вместо ключевого слова OR для представления оператора OR.)

+0

Смотрите интересно. Я собираюсь попробовать это, как только у меня будет время сделать это. – Tys

0

это должно работать.

DECLARE @SearchString VARCHAR(100) = '"ABC 24 Supermarket"' 
set @SearchString = replace(@SearchString,' ','" or "') 

SELECT * FROM Company CO 
INNER JOIN Category CA 
ON CA.CategoryId = CO.CategoryId 
WHERE CONTAINS((CO.[Description], CO.[Name]), @SearchString) 
AND CONTAINS(CA.[Description], @SearchString) 

надежда помогает немного

0

Почему вы просто не обратить вспять логику? Ваш пример пытался найти строку поиска в ваших значениях поля, но то, что вы действительно хотите сделать, это найти значения полей в вашей строке поиска, нет?

DECLARE @SearchString VARCHAR(100) = '"ABC 24 Supermarket"' 
SELECT * FROM Company CO 
INNER JOIN Category CA 
ON CA.CategoryId = CO.CategoryId 
WHERE (CONTAINS(@SearchString, CO.[Description]) OR CONTAINS(@SearchString, CO.[Name])) 
AND CONTAINS(@SearchString, CA.[Description]) 
Смежные вопросы