SQL Server自由文本搜索:从两个表中的短语中搜索单词

时间:2013-09-08 17:35:24

标签: sql sql-server contains freetext

我有一个公司的表和一个类别的表。我正在使用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)

但是这当然没有给我什么,因为我的搜索字符串无法在公司或类别表中找到。有没有人知道如何在我的公司和类别表上进行组合搜索?

如下面的Lobo答案所示,分裂字符串的想法并不是一个真正的选择。因为我不知道哪个部分应该与类别匹配,哪个部分应该用于匹配公司名称/描述。用户也可以输入“Supermarket ABC 24”。

9 个答案:

答案 0 :(得分:4)

Imho正确的方法是创建一个索引视图,其中包含主表的主键(示例中为“company”),第二列包含您实际想要搜索的所有内容,即< / p>
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),因此从要编入索引的文本中删除。要包含它们,您必须切换停止列表功能。

最后一个tipp:全文非常强大,但有很多功能,技巧和警告。完全理解这些技术并获得您想要的最佳结果需要相当多的时间。

玩得开心。

答案 1 :(得分: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子句中使用的CONTAINS谓词准备搜索值。在这种情况下,我替换了“|”上的单词之间的空格逻辑运算符(可以使用条形符号(|)代替OR关键字来表示OR运算符。)

答案 2 :(得分:0)

我没有安装sql-server来试用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(......)作为第二个参数。


根据修改过的问题

更新 -

首先,如果可能,您应该将逻辑移动到应用程序级别。我想,在这里处理它太过分了。我已经提出了这个问题,但请注意,这会拆分每个单词并在namedescription中搜索它,这样您最终会得到比您想象的更多的结果。对于例如这将返回名称中包含SupermarketABC的所有24,而不是在我之前的查询中只返回一个名为Supermarket的{​​{1}}。这实际上应该可以帮到你,因为根据你的说法,用户可能只需输入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"' ,其余是`name。

您可以拆分搜索字符串并使用它们,如下所示。此查询使用description,因为我没有安装sql-server。

like

这应该有效。请注意,where子句使用的是AND而不是OR。

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), '%')

答案 3 :(得分: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;

答案 4 :(得分: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

答案 5 :(得分: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个要搜索的项目的要求。有人建议清理它吗?

答案 6 :(得分: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)

希望有所帮助

答案 7 :(得分: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])

答案 8 :(得分:-1)

您可以使用FREETEXT代替CONTAIN。

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)