【问题标题】:How do I search for ALL words within ANY columns of multiple Full Text indexes?如何在多个全文索引的任何列中搜索所有单词?
【发布时间】:2017-04-24 16:36:58
【问题描述】:

如果我在ContactsCompanies 等表上有两个全文索引,我如何编写一个查询来确保搜索短语的所有单词都存在于两者的任何一个 中索引?

例如,如果我正在搜索联系人记录或公司中存在所有关键字的联系人,我将如何编写查询?

我尝试在联系人和公司表上执行CONTAINSTABLE,然后将这些表连接在一起,但如果我将搜索短语作为'"searchTerm1*' AND '"searchTerm2*"' 传递给每个表,那么它仅在 all em> 搜索词在 both 索引上,返回的记录太少。如果我像'"searchTerm1*' OR '"searchTerm2*"' 一样传递它,那么它匹配any(而不是all)的搜索词在either 的索引中的位置并返回太多记录。

我还尝试创建一个索引视图,将联系人与公司联系起来,这样我就可以一次搜索所有列,但不幸的是,一个联系人可以属于多个公司,因此我将使用 ContactKey 作为视图的键不再是唯一的,因此无法创建。

似乎我可能需要将短语分开并分别查询每个单词,然后将结果重新组合在一起以确保所有单词都匹配,但我想不出我会怎么做编写该查询。

以下是模型的示例:

Contact           CompanyContact    Company
--------------    --------------    ------------
ContactKey        ContactKey        CompanyKey
FirstName         CompanyKey        CompanyName
LastName

我有一个关于 FirstName、LastName 和 CompanyName 的全文索引。

【问题讨论】:

  • 你能分享一些关于模型的信息吗?到目前为止,我所了解的是您有两个表:联系人和公司,并且每个表都包含 ContactKey。我猜 ContactKey 不是您要搜索的值。
  • @Paurian,请查看我的更新,如果您需要更多信息,请告诉我。谢谢。
  • @BogdanSahlean 抛出错误,指出 CompanyContact 不能成为 CONTAINSTABLE 查询的一部分,因为它没有全文索引,我无法向其中添加全文索引,因为其中没有文本表。
  • @BogdanSahlean 我做了所有这些。然后,当我使用 CONTAINSTABLE 查询该视图时,我收到了我在之前的评论中提到的错误。
  • @BogdanSahlean 它正在引用索引视图。我认为错误是因为索引视图使用 CompanyContact。

标签: sql sql-server sql-server-2008-r2 full-text-search full-text-indexing


【解决方案1】:

重建此答案是为了解决您的问题,即多个字符串必须存在于各个字段中。注意 CompanyContactLink 链接表中的单个键:

CREATE FULLTEXT CATALOG CompanyContact WITH ACCENT_SENSITIVITY = OFF
GO

CREATE TABLE Contact ( ContactKey INT IDENTITY, FirstName VARCHAR(20) NOT NULL, LastName VARCHAR(20) NOT NULL )
ALTER TABLE Contact ADD CONSTRAINT PK_Contact PRIMARY KEY NONCLUSTERED ( ContactKey )

CREATE TABLE Company ( CompanyKey INT IDENTITY, CompanyName VARCHAR(50) NOT NULL )
ALTER TABLE Company ADD CONSTRAINT PK_Company PRIMARY KEY NONCLUSTERED ( CompanyKey )
GO

CREATE TABLE CompanyContactLink ( CompanyContactKey INT IDENTITY NOT NULL, CompanyKey INT NOT NULL, ContactKey INT NOT NULL )
GO

INSERT INTO Contact ( FirstName, LastName ) VALUES ( 'Dipper', 'Pines' )
INSERT INTO Contact ( FirstName, LastName ) VALUES ( 'Mabel', 'Pines' )
INSERT INTO Contact ( FirstName, LastName ) VALUES ( 'Stanley', 'Pines' )
INSERT INTO Contact ( FirstName, LastName ) VALUES ( 'Soos', 'Ramirez' )
INSERT INTO Contact ( FirstName, LastName ) VALUES ( 'Wendy', 'Corduroy' )
INSERT INTO Contact ( FirstName, LastName ) VALUES ( 'Sheriff', 'Blubs' )
INSERT INTO Contact ( FirstName, LastName ) VALUES ( 'Bill', 'Cipher' )
INSERT INTO Contact ( FirstName, LastName ) VALUES ( 'Pine Dip', 'Nobody' )
INSERT INTO Contact ( FirstNAme, LastName ) VALUES ( 'Nobody', 'Pine Dip' )

INSERT INTO Company ( CompanyName ) VALUES ( 'Mystery Shack' )
INSERT INTO Company ( CompanyName ) VALUES ( 'Greesy Diner' )
INSERT INTO Company ( CompanyName ) VALUES ( 'Watertower' )
INSERT INTO Company ( CompanyName ) VALUES ( 'Manotaur Cave' )
INSERT INTO Company ( CompanyName ) VALUES ( 'Big Dipper Watering Hole' )
INSERT INTO Company ( CompanyName ) VALUES ( 'Lost Pines Dipping Pool' )
GO

INSERT INTO CompanyContactLink Values (3, 5), (1, 1), (1, 2), (1, 3), (1, 4), (1,5), (5,1), (3,1), (4,1)
GO

CREATE FULLTEXT INDEX ON Contact (LastName, FirstName)
KEY INDEX PK_Contact
ON CompanyContact
WITH STOPLIST = SYSTEM

CREATE FULLTEXT INDEX ON Company (CompanyName)
KEY INDEX PK_Company
ON CompanyContact
WITH STOPLIST = SYSTEM
GO

CREATE VIEW CompanyContactView
WITH SCHEMABINDING
AS
  SELECT
    CompanyContactKey,
    CompanyName,
    FirstName,
    LastName
  FROM
    dbo.CompanyContactLink
    INNER JOIN dbo.Company ON Company.CompanyKey = CompanyContactLink.CompanyKey
    INNER JOIN dbo.Contact ON Contact.ContactKey = CompanyContactLink.ContactKey
GO

CREATE UNIQUE CLUSTERED INDEX idx_CompanyContactView ON CompanyContactView (CompanyContactKey);
GO

CREATE FULLTEXT INDEX ON CompanyContactView (CompanyName, LastName, FirstName)
KEY INDEX idx_CompanyContactView
ON CompanyContact
WITH STOPLIST = SYSTEM
GO

-- Wait a few moments for the FULLTEXT INDEXing to take place.
-- Check to see how the index is doing ... repeat the following line until you get a zero back.

DECLARE @ReadyStatus INT
SET @ReadyStatus = 1
WHILE (@ReadyStatus != 0)
BEGIN
  SELECT @ReadyStatus = FULLTEXTCATALOGPROPERTY('CompanyContact', 'PopulateStatus')
END

SELECT
  CompanyContactView.*
FROM
  CompanyContactView
WHERE
  FREETEXT((FirstName,LastName,CompanyName), 'Dipper') AND
  FREETEXT((FirstName,LastName,CompanyName), 'Shack')
GO

为了你和温蒂在水塔的例子:

SELECT
  CompanyContactView.*
FROM
  CompanyContactView
WHERE
  FREETEXT((FirstName,LastName,CompanyName), 'Wendy') AND
  FREETEXT((FirstName,LastName,CompanyName), 'Watertower')
GO

【讨论】:

  • 这只会搜索联系人或公司。使用您的数据,如果我正在搜索联系人,而 Wendy 是 Watertower 的联系人,如果我搜索 Wendy Watertower,我希望得到 Wendy 的联系人密钥,但您没有定义联系人和公司之间的任何关系。
  • 已重建答案以解决您的问题;具体来说,如何在多个字段中拥有多个必需的搜索字符串。
  • 并且对它应得的地方给予一些信任。碰巧的是,在彻底检查了答案之后,我意识到这些更改完全符合 Bogdan Sahlean 的建议。如果您发现此完整代码解决方案对您的问题有用,请确保也支持他的评论。
【解决方案2】:

我创建了一个适用于任意数量的全文索引和列的方法。使用这种方法,很容易添加额外的方面进行搜索。

  1. 将搜索短语拆分为临时表中的行
  2. 加入此临时表以在每个适用的全文索引上使用 CONTAINSTABLE 搜索每个搜索词。
  3. 将结果合并在一起并获得找到的搜索词的不同计数。
  4. 过滤掉指定的搜索词数量与找到的搜索词数量不匹配的结果。

例子:

DECLARE @SearchPhrase nvarchar(255) = 'John Doe'
DECLARE @Matches Table(
    MentionedKey int,
    CoreType char(1),
    Label nvarchar(1000),
    Ranking int
)

-- Split the search phrase into separate words.
DECLARE @SearchTerms TABLE (Term NVARCHAR(100), Position INT)
INSERT INTO @SearchTerms (Term, Position)
SELECT dbo.ScrubSearchTerm(Term)-- Removes invalid characters and convert the words into search tokens for Full Text searching such as '"word*"'.
FROM dbo.SplitSearchTerms(@SearchPhrase)

-- Count the search words.
DECLARE @numSearchTerms int = (SELECT COUNT(*) FROM @SearchTerms)

-- Find the matching contacts.
;WITH MatchingContacts AS
(
    SELECT
        [ContactKey] = sc.[KEY],
        [Ranking] = sc.[RANK],
        [Term] = st.Term
    FROM @SearchTerms st
    CROSS APPLY dbo.SearchContacts(st.Term) sc -- I wrap my CONTAINSTABLE query in a Sql Function for convenience
)
-- Find the matching companies
,MatchingContactCompanies AS
(
    SELECT
        c.ContactKey,
        Ranking = sc.[RANK],
        st.Term
    FROM @SearchTerms st
    CROSS APPLY dbo.SearchCompanies(st.Term) sc
    JOIN dbo.CompanyContact cc ON sc.CompanyKey = cc.CompanyKey
    JOIN dbo.Contact c ON c.ContactKey = cc.ContactKey
)
-- Find the matches where ALL search words were found.
,ContactsWithAllTerms AS
(
    SELECT
        c.ContactKey,
        Ranking = SUM(x.Ranking)
    FROM (
        SELECT ContactKey, Ranking, Term FROM MatchingContacts  UNION ALL
        SELECT ContactKey, Ranking, Term FROM MatchingContactCompanies
    ) x
    GROUP BY c.ContactKey
    HAVING COUNT(DISTINCT x.Term) = @numSearchTerms
)
SELECT
    *
FROM ContactsWithAllTerms c

更新 根据 cmets,这是我的 SearchContacts 函数的示例。它只是一个简单的包装函数,因为我在多个过程中使用它。

CREATE FUNCTION [dbo].[SearchContacts]
(
    @contactsKeyword nvarchar(4000)
)
RETURNS @returntable TABLE
(
    [KEY] int,
    [RANK] int
)
AS
BEGIN
    INSERT @returntable
    SELECT [KEY],[RANK] FROM CONTAINSTABLE(dbo.Contact, ([FullName],[LastName],[FirstName]), @contactsKeyword)
    RETURN
END
GO

【讨论】:

  • 嗨,我正在考虑做类似的事情,但对 dbo.SearchContacts 发生了什么感到困惑,你能举一个这个函数的例子吗?
  • 嗨,亚当,感谢您的回复,非常感谢。
猜你喜欢
  • 2015-08-20
  • 1970-01-01
  • 2018-11-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-02
  • 2020-12-29
相关资源
最近更新 更多