如何在多个全文索引的任何列中搜索所有单词?

时间:2017-04-24 16:36:59

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

如果我在ContactsCompanies这样的表上有两个全文索引,我该如何编写一个查询来确保搜索短语的所有单词都存在于这两个指数?

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

我已尝试在联系人和公司表上执行CONTAINSTABLE,然后将表格加在一起,但如果我将搜索词组传递给每个'"searchTerm1*' AND '"searchTerm2*"',那么它只会在所有搜索字位于两个索引上,并且返回的记录太少。如果我像'"searchTerm1*' OR '"searchTerm2*"'那样传递它,那么它匹配搜索词的任何(而不是所有 索引并返回太多记录。

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

似乎我可能需要将这个短语分开并分别查询每个单词,然后将结果重新加入,以确保所有单词都匹配,但我无法想到我和#39; d写下那个查询。

以下是该模型的示例:

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

我在FirstName,LastName和CompanyName上有另一个全文索引。

2 个答案:

答案 0 :(得分:0)

重建此答案是为了解决您的问题,以便在字段之间必须存在多个字符串。请注意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

为了你在Watertower与温迪的榜样:

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

答案 1 :(得分:0)

我创建了一个适用于任意数量的全文索引和列的方法。使用此方法,可以非常轻松地添加其他构面以进行搜索。

  1. 将搜索词组拆分为临时表中的行
  2. 加入此临时表,在每个适用的全文索引上使用CONTAINSTABLE搜索每个搜索词。
  3. 将结果合并在一起,得到搜索词的明显计数。
  4. 过滤掉指定的搜索字词数与找到的搜索字词数不匹配的结果。
  5. 示例:

    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