【问题标题】:Tell me SQL Server Full-Text searcher is crazy, not me告诉我 SQL Server 全文搜索器疯了,不是我
【发布时间】:2011-02-27 07:48:10
【问题描述】:

我有一些客户正在搜索特定地址:

123 通用方式

数据库中有5行匹配:

ResidentialAddress1
=============================
123 GENERIC WAY
123 GENERIC WAY
123 GENERIC WAY
123 GENERIC WAY
123 GENERIC WAY

我运行 FT 查询来查找这些行。当我向搜索添加更多条件时,我将向您展示每一步:

SELECT ResidentialAddress1 FROM Patrons
WHERE CONTAINS(Patrons.ResidentialAddress1, '"123*"')

ResidentialAddress1
=========================
123 MAPLE STREET
12345 TEST
123 MINE STREET
123 GENERIC WAY
123 FAKE STREET
...

(30 row(s) affected)

好的,到目前为止一切顺利,现在添加单词“generic”:

SELECT ResidentialAddress1 FROM Patrons
WHERE  CONTAINS(Patrons.ResidentialAddress1, '"123*"')
AND CONTAINS(Patrons.ResidentialAddress1, '"generic*"')

ResidentialAddress1
=============================
123 GENERIC WAY
123 GENERIC WAY
123 GENERIC WAY
123 GENERIC WAY
123 GENERIC WAY

(5 row(s) affected)

非常好。现在我将添加用户想要确保存在的最终关键字:

SELECT ResidentialAddress1 FROM Patrons
WHERE  CONTAINS(Patrons.ResidentialAddress1, '"123*"')
AND CONTAINS(Patrons.ResidentialAddress1, '"generic*"')
AND CONTAINS(Patrons.ResidentialAddress1, '"way*"')


ResidentialAddress1            
------------------------------ 

(0 row(s) affected)

嗯?没有行?如果我只查询“way*”怎么办:

SELECT ResidentialAddress1 FROM Patrons
WHERE CONTAINS(Patrons.ResidentialAddress1, '"way*"')

ResidentialAddress1            
------------------------------ 

(0 row(s) affected)

起初我认为可能是因为*,它要求根way 后面有更多字符。但事实并非如此:

  • 搜索“123*”匹配“123”
  • 搜索“generic*”匹配“generic”
  • 在线书籍说,星号匹配零个、一个或多个字符

如果我删除 * 只是为了 s&g:

SELECT ResidentialAddress1 FROM Patrons
WHERE CONTAINS(Patrons.ResidentialAddress1, '"way"')

Server: Msg 7619, Level 16, State 1, Line 1
A clause of the query contained only ignored words. 

所以有人可能会认为您甚至不能搜索 way,无论是单独还是作为根。但这也不是真的:

SELECT * FROM Patrons
WHERE CONTAINS(Patrons.*, '"way*"')

AccountNumber FirstName Lastname
------------- --------- --------
33589         JOHN      WAYNE                    

总结一下,用户正在搜索包含所有单词的行:

123 通用方式

我正确地翻译成WHERE 子句:

SELECT * FROM Patrons
WHERE CONTAINS(Patrons.*, '"123*"')
AND CONTAINS(Patrons.*, '"generic*"')
AND CONTAINS(Patrons.*, '"way*"')

不返回任何行。告诉我这行不通,这不是我的错,SQL Server 疯了。

注意:我已经清空了 FT 索引并重建了它。

更新一

SELECT Lastname, ResidentialAddress1 FROM Patrons
WHERE CONTAINS(Patrons.*, '"gen*"')

Lastname                  ResidentialAddress1            
------------------------- ------------------------------ 
SAVE                      123 GENERIC WAY
Genders                   
SAVE                      123 GENERIC WAY
Patron                    123 GENERIC WAY
SAVE                      123 GENERIC WAY
SAVE                      234 GENERIC WAY
SAVE                      123 GENERIC WAY

(7 row(s) affected)

更新二

假装用户输入:

123 通用 wa

SELECT ResidentialAddress1 FROM Patrons
WHERE  CONTAINS(Patrons.ResidentialAddress1, '"123*"')
AND CONTAINS(Patrons.ResidentialAddress1, '"generic*"')
AND CONTAINS(Patrons.ResidentialAddress1, '"wa*"')

ResidentialAddress1            
------------------------------ 

(0 row(s) affected)

真正的问题是用户输入的内容完全有效,他们希望看到任何人都希望看到的内容。


更新三

这一切都是有人要求的,这不是我的错!:

CREATE TABLE [dbo].[Patrons] (
    [PatronGUID]  uniqueidentifier ROWGUIDCOL  NOT NULL ,
    [AccountNumber] [bigint] NULL ,
    [FirstName] [varchar] (25) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [MiddleInitial] [varchar] (1) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [Lastname] [varchar] (25) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [EyeColor] [varchar] (30) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [HairColor] [varchar] (30) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [Gender] [varchar] (1) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [Birthday] [datetime] NULL ,
    [Height] [int] NULL ,
    [Weight] [int] NULL ,
    [FacialHair] [tinyint] NULL ,
    [Nationality] [varchar] (50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [IdentifyingMarks] [varchar] (30) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [DriversLicenseNumber] [varchar] (25) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [DriversLicenseRegion] [varchar] (20) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [DriversLicenseCountry] [varchar] (2) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [DriversLicenseExpires] [datetime] NULL ,
    [DriversLicenseDateVerified] [datetime] NULL ,
    [PassportNumber] [varchar] (25) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [PassportRegion] [varchar] (20) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [PassportCountry] [varchar] (2) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [PassportExpires] [datetime] NULL ,
    [PassportDateVerified] [datetime] NULL ,
    [OtherIdentificationNumber] [varchar] (25) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [OtherIdentificationRegion] [varchar] (20) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [OtherIdentificationCountry] [varchar] (2) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [OtherIdentificationType] [varchar] (50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [OtherIdentificationExpires] [datetime] NULL ,
    [OtherIdentificationDateVerified] [datetime] NULL ,
    [ResidentialAddress1] [varchar] (30) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [ResidentialAddress2] [varchar] (30) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [ResidentialAddress3] [varchar] (30) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [ResidentialCity] [varchar] (25) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [ResidentialZipCode] [varchar] (15) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [ResidentialRegion] [varchar] (20) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [ResidentialCountry] [varchar] (50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [ResidentialPhoneNumber] [varchar] (20) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [CountryOfResidence] [varchar] (50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [BusinessAddress1] [varchar] (30) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [BusinessAddress2] [varchar] (30) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [BusinessAddress3] [varchar] (30) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [BusinessCity] [varchar] (25) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [BusinessRegion] [varchar] (20) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [BusinessZipCode] [varchar] (15) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [BusinessCountry] [varchar] (50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [BusinessName] [varchar] (25) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [BusinessPhone] [varchar] (20) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [PositionWithFirm] [varchar] (30) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [EmployerTelephone] [varchar] (20) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [MemberCardType] [varchar] (1) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [PlayerStatusCode] [varchar] (50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [AccountType] [varchar] (1) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [AccountStatus1] [varchar] (1) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [AccountStatus2] [varchar] (50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [IsVIPExchangeRate] [tinyint] NULL ,
    [ChangedUserGUID_Depricated] [uniqueidentifier] NULL ,
    [ChangedUser] [varchar] (50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [ChangedDate] [datetime] NULL ,
    [ChangedWorkstation] [varchar] (50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ,
    [PendingUpdates_Depricated] [varchar] (255) COLLATE SQL_Latin1_General_CP1_CI_AS NULL 
) ON [PRIMARY]
GO

ALTER TABLE [dbo].[Patrons] ADD 
    CONSTRAINT [DF_Patrons_PatronGUID] DEFAULT (newid()) FOR [PatronGUID],
    CONSTRAINT [PK_Patrons] PRIMARY KEY  NONCLUSTERED 
    (
        [PatronGUID]
    ) WITH  FILLFACTOR = 90  ON [PRIMARY] 
GO

if (select DATABASEPROPERTY(DB_NAME(), N'IsFullTextEnabled')) <> 1 
exec sp_fulltext_database N'enable' 

GO

if not exists (select * from dbo.sysfulltextcatalogs where name = N'TheFullTextCatalog')
exec sp_fulltext_catalog N'TheFullTextCatalog', N'create' 

GO

exec sp_fulltext_table N'[dbo].[Patrons]', N'create', N'TheFullTextCatalog', N'PK_Patrons'
GO

exec sp_fulltext_column N'[dbo].[Patrons]', N'FirstName', N'add', 1033  
GO

exec sp_fulltext_column N'[dbo].[Patrons]', N'MiddleInitial', N'add', 1033  
GO

exec sp_fulltext_column N'[dbo].[Patrons]', N'Lastname', N'add', 1033  
GO

exec sp_fulltext_column N'[dbo].[Patrons]', N'EyeColor', N'add', 1033  
GO

exec sp_fulltext_column N'[dbo].[Patrons]', N'IdentifyingMarks', N'add', 1033  
GO

exec sp_fulltext_column N'[dbo].[Patrons]', N'ResidentialAddress1', N'add', 1033  
GO

exec sp_fulltext_column N'[dbo].[Patrons]', N'ResidentialAddress2', N'add', 1033  
GO

exec sp_fulltext_column N'[dbo].[Patrons]', N'ResidentialAddress3', N'add', 1033  
GO

exec sp_fulltext_column N'[dbo].[Patrons]', N'ResidentialCity', N'add', 1033  
GO

exec sp_fulltext_column N'[dbo].[Patrons]', N'ResidentialZipCode', N'add', 1033  
GO

exec sp_fulltext_column N'[dbo].[Patrons]', N'ResidentialRegion', N'add', 1033  
GO

exec sp_fulltext_column N'[dbo].[Patrons]', N'ResidentialCountry', N'add', 1033  
GO

exec sp_fulltext_column N'[dbo].[Patrons]', N'ResidentialPhoneNumber', N'add', 1033  
GO

exec sp_fulltext_column N'[dbo].[Patrons]', N'CountryOfResidence', N'add', 1033  
GO

exec sp_fulltext_table N'[dbo].[Patrons]', N'activate'  
GO

这是不相信我的人的屏幕截图:

应该有效但无效的查询:

有效但无用的查询:

有效但无用的查询,带有证明内容:


更新四

查询不能写成

CONTAINS(Patrons.*, 'words...')

由于 FT 索引在逻辑上或物理上未涵盖某些项目。例如用户查询 为:

2010 年 6 月 4 日伊恩博伊德 619

提供四个关键字:

  • 2010 年 6 月 4 日
  • 伊恩
  • 男孩
  • 619

这意味着他们希望所有条件都成立,伪代码为:

WHERE 6/4/2010 is in the row
AND ian is in the row
AND boyd is in the row
AND 619 is in the row

翻译成部分查询:

WHERE --Keyword 1: 6/4/2010
(
   ((ChangedDate >= '20100604') AND (ChangedDate < '20100605'))
   OR 
   ((LastTransactionDate >= '20100604') AND (LastTransactionDate < '20100605'))
   OR 
   (CONTAINS(Patrons.*, '"6/4/2010*"')
)
AND --Keyword 2: ian
(
    CONTAINS(Patrons.*, '"ian*"')
)
AND --Keyword 3: boyd
(
    CONTAINS(Patrons.*, '"boyd*"')
)
AND --Keyword 4: 619
(
    (AccountNumber IN (SELECT CAST(619 AS bigint)))
    OR
    (CONTAINS(Patrons.*, '"619*"'))
)

其中一位回答者正在查看原始问题中提供的简化示例;不是现实世界。要说有多个 AND 子句是不正确,这是不切实际的。

【问题讨论】:

  • SQL Server 全文搜索器疯了,不是你! ;-)
  • 多么棒的问题。这怎么只收到了 2 票,这超出了我的理解。

标签: sql-server sql-server-2000 full-text-search full-text-indexing


【解决方案1】:

您在创建 FT 索引时可能使用了系统停止列表。 way 这个词恰好在里面。您可以通过以下查询看到它:

SELECT *
FROM sys.fulltext_system_stopwords
WHERE stopword = 'way'
AND language_id = 1033

您可以关闭停止列表或创建自定义停止列表,但更好的解决方案是正确编写查询;不要使用多个WHERE CONTAINS 子句,将它们合并为一个。否则 SQL Server 可能无法有效地使用 FT 索引。

您的查询应如下所示:

SELECT ResidentialAddress1 FROM Patrons
WHERE  CONTAINS(Patrons.ResidentialAddress1, '"123*" AND "generic*" AND "way*"')

如果你这样做,停用词就会被忽略;如果您没有包含术语 way*,它仍然会返回所有相同的结果。


编辑:刚刚注意到你标记了这个sql-server-2000,所以第一个查询可能不起作用。在 SQL 2000 中,它们是“干扰词”,我相信配置是全局的,您没有单独的停止列表。尽管如此,如果您编写一个 WHERE CONTAINS 子句而不是多个子句,您仍然会得到结果。

要编辑 SQL Server 2000 中的干扰词,您必须编辑 SQL Server FTDATA 配置文件夹中的语言特定文件。更多详情在这里:SQL Server Full Text Search Noise Words and Thesaurus Configurations

【讨论】:

  • 运行 SELECT * FROM Patrons WHERE CONTAINS(Patrons.*, '"123*" AND "generic*" AND "way*"') 不返回任何行。是的:2000,noisewords,不可查询。
  • @Ian:这似乎根本不对。我们可以得到一个包含表模式和 FT DDL 的 repro 吗?
  • 那是一个 hellavalotta ddl 和脚本,供任何人使用。
  • 也将查询编写为单个包含,因为它们是需要单独子句的 FT 索引(例如 tinyint)未涵盖的字段。因此,检查 all 关键字是否存在的解决方案是正确的。
  • @Ian:它不应该是一个“hellavalotta”——如果你不能在小范围内复制它,那么还有其他问题。是的,很明显,如果您要在其他字段中查找数值,则必须编写AND 条件,但所有全文条件都应位于单个搜索字符串中。编写多个CONTAINS 子句是正确的,除非您需要为每个检查不同的字段。
【解决方案2】:

解决方案 1:

您想尝试Transform Noise Word 选项(SQL 2008)

关闭此功能,应该会停止删除字词。

示例:

sp_configure 'show advanced options', 1
RECONFIGURE
GO
sp_configure 'transform noise words', 1
RECONFIGURE
GO

编辑 1:

希望旧版本的 MS SQL 也有类似的东西吗?

【讨论】:

    【解决方案3】:

    【讨论】:

      【解决方案4】:

      也许它需要三个以上的字母字符。尝试另一个三个字母的单词,例如gen*

      【讨论】:

      • "gen*" 有效,这是有道理的,因为 "123*" 也有效。无论如何都会添加问题
      【解决方案5】:

      该消息告诉您“方式”是一个停用词,这意味着它被忽略且未编入索引。这就是为什么你可以找到“wayne”而不是“way”的原因。

      所以,不,这并不疯狂,你也一样。这只是一个简单的误解。

      【讨论】:

      • 你有什么建议? IE。我如何告诉SQL Server在可以搜索方式时搜索方式,但在无法搜索方式时不搜索方式。
      • @Ardman 尝试“wa*”,结果添加到问题中。没用。
      • @Ian:我赞成两个​​更详细的答案,所以试试这些。简短的回答是您需要从停止列表中删除“方式”并重新索引。
      • /facepalm 我希望有任何其他解决方案,而不是需要在我无权访问的机器上手动编辑文本文件的解决方案。最后,解决方案可能不得不涉及将CONTAINS(table.*) 变成几十个LIKE 子句...
      • @Ian:根据我的经验,开始使用数据库的 FT 搜索功能的项目往往最终会将这方面的工作交给 Lucene 和 DTSearch 等引擎,这正是因为关系数据库受到限制且令人沮丧.
      猜你喜欢
      • 2018-07-26
      • 1970-01-01
      • 1970-01-01
      • 2010-09-06
      • 2015-06-15
      • 2014-04-28
      • 2011-02-08
      • 2013-06-27
      • 1970-01-01
      相关资源
      最近更新 更多