【SqlServer】管理全文索引(FULL TEXT INDEX)

Sql Server中的全文索引(下面統一使用FULLTEXT INDEX來表示全文索引),是一種特定語言搜索索引功能。它和LIKE的不同,LIKE主要是根據搜索模板搜索數據,它的效率比FULLTEXT INDEX要低。在幾百萬的字符串中,LIKE須要花幾分鐘才能返回的結果,FULLTEXT INDEX可能只須要幾秒鐘。前端

FULLTEXT INDEX功能是Sql Server的可選項。你能夠經過 SELECT FULLTEXTSERVICEPROPERTY('IsFullTextInstalled') 命令,來檢查你是否安裝了FULLTEXT INDEX。sql

 在開始介紹FULLTEXT INDEX以前,還須要介紹幾個重要的相關概念。它們分別是catalog, fulltext index fragment,  數據庫

 

Catalog: 在建立FULLTEXT INDEX以前,都須要先建立一個CATALOG, CATALOG是一個虛擬容器對象,它就是用來存儲FULLTEXT Index的,一個CATALOG能夠關聯多個FULLTEXT Index索引。CATALOG不屬於任何文件組。架構

FULLTEXT Index Fragments:  一般一個FULLTEXT索引都是由多個內部表組成的,這些內部表就被稱爲Fragments。當用戶更新表中的數據後,數據庫會對改變的部分自動建立一個Fragment對象(前提是在這個表上創建了FULLTEXT INDEX,而且設置了自動跟蹤改變)。app

你可經過sys.fulltext_index_fragments表來查詢全部fragments記錄:運維

SELECT * FROM sys.fulltext_index_fragments;

輸出:函數

經過上面的輸出結果,能夠看出一個表有了兩個fragment對象。若是像這樣的fragment對象愈來愈多的話,是會影響FULTEXT INDEX的查詢效率的。爲了減小Fragment的數量,可使用ALTER命令將全部的Fragment整合到一塊兒。學習

ALTER FULLTEXT CATALOG [your-catalog-name] REORGANIZE;

 

1. 建立和刪除全文索引

建立測試表:測試

create table Text_Test_Table(
    id int not null IDENTITY(1,1) PRIMARY KEY,
    txt nvarchar(1000) null
);

建立CATALOG:優化

CREATE FULLTEXT CATALOG my_catalog;

建立全文索引:

CREATE FULLTEXT INDEX ON Text_Test_Table
(  
    txt                         --Full-text index column name     
    Language 2057               --2057 is the LCID for British English  
)  
KEY INDEX PK__Text_Tes__3213E83F0F8DA0F2 ON my_catalog   --Unique index  
WITH CHANGE_TRACKING AUTO                                --Population type;  
GO  

上面的表中指定了全文索引的字段爲txt,同時也指定了它的語言爲英式英語。你也能夠指定爲其它語言,在SQL SERVER中用 select * from sys.fulltext_languages; 能夠查詢全部支持的語言編號。爲字段指定正確的語言編號是必需的,由於不一樣的語言編號會使用不一樣的單詞分割器,不一樣的單詞分割器會分割出不一樣的關鍵詞列表,當查詢的時候就須要匹配關鍵詞列表中的數據。

建立全文索引,須要當前表至少包含一個惟一索引(unique index)。上面案例中, PK__Text_Tes__3213E83F0F8DA0F2  是主鍵,知足惟一性的要求,所以能夠用來建立全文索引。

CHANGE_TRACKING AUTO:是指當表中數據有變化時,會自動更新全文索引中的記錄。(除了AUTO,還能夠爲MANUAL值,表示須要手動去更新全文索引的記錄)。

 

在建立建立全文索引時,爲何須要指定一個 Unique 索引?

其實這個比較好理解,能夠把全文索引理解爲一張 關鍵詞與數據表的關係對照表。只有數據表上有一個Unique索引時,全文索引才能夠惟一地關聯關鍵詞與數據表之間的關係。

有點繞?直接上代碼!

--插入測試數據
insert into Text_Test_Table values('She''s great fun, but she''s a few sandwiches short of a picnic.');
insert into Text_Test_Table values('She''s funny, but she only eat a few sandwiches.');

--查詢數據表
select * from Text_Test_Table;

--查詢全文索引關鍵詞對照關係,
--數據庫爲Test,數據表爲Text_Test_Table
SELECT * FROM sys.dm_fts_index_keywords_by_document(db_id('Test'), object_id('dbo.Text_Test_Table'))

效果對照圖:

索引表中的document_id對應的是Text_Test_Table表中的id字段。display_term是關鍵詞。occurence_count是關鍵詞的出現次數。document_id就是對應了數據表中惟一約束數據的編號,當惟一約束做用在數字字段上時,約束字段的值就會和document_id同樣。若是惟一約束做用在字符字段上的話,那麼document_id將會是對應數據的編號。 

2. 使用全文索引查詢數據

 

2.1 匹配查詢含有全部關鍵詞的語句(必需包含全部關鍵詞)

 若是要徹底匹配查詢的關鍵字,那麼能夠用contains函數。

語法:

CONTAINS(字段,關鍵詞)

關鍵詞須要用冒號""括起來,多個關鍵詞用空格隔開。格式:"關鍵詞1 關鍵詞2 關鍵詞3..."。contains中的關鍵詞就至關於AND關鍵詞,這些關鍵詞並不須要連在一塊兒,可是須要全部的關鍵詞都存在。

案例:

-- 查詢Text_Test_Table表中txt字段包含 a 和 few 和 sandwiches 的關鍵詞的全部數據
declare @terms nvarchar(1000) = '"a few sandwiches"';
select * from Text_Test_Table where contains(txt, @terms);

-- 能夠查詢出:
-- she has a few sandwiches
-- a few lemon and sandwiches
-- a man and few person both have sandwiches

2.2 匹配查詢含有關鍵詞的語句(只要含有一個關鍵詞就好了)

 若是要包含全部的關鍵詞,那麼應使用FREETEXT函數。

語法:

FREETEXT(字段, 關鍵詞)

關鍵詞須要用冒號""括起來,多個關鍵詞用空格隔開。格式:"關鍵詞1 關鍵詞2 關鍵詞3..."。freetext中的關鍵詞就至關於OR關聯詞,這些關鍵詞並不須要連在一塊兒,只要有一個關鍵詞存在數據就會被選取。

-- 查詢Text_Test_Table表中txt字段包含 a 或 few 或 sandwiches 的關鍵詞的全部數據
declare @terms nvarchar(1000) = '"a few sandwiches"';
select * from Text_Test_Table where freetext(txt, @terms);

-- 能夠查詢出:
-- she has a few sandwiches
-- sandwiches is good
-- There are few apples

 

注意:

Freetext函數還能夠匹配關鍵詞的變體,好比經過關鍵詞catch匹配相應的變體caught, catching, 和 catches 等。但Contains函數想要查詢變體,就必需使用FORMSOF語句。請移步FORMSOF Predicate獲取更詳細的信息。

 

2.3 案例:查詢有不一樣匹配精確度的多個字段

有一張products表,其中有id,name和description字段, id是主鍵。name 和 description是nvarchar字段類型。如今要查詢Products中的數據,name必需要包含全部的查詢關鍵字,description至少須要匹配一個提供的關鍵詞。

create table Products(
    id int not null IDENTITY(1,1) PRIMARY KEY,
    [name] nvarchar(1000) null,
    [description] nvarchar(1000) null
);

CREATE FULLTEXT INDEX ON Products
(  
    [name]  Language 2057,
    [description] Language 2057
)  
KEY INDEX PK__Products__3213E83F67020225 ON my_catalog   --Unique index  
WITH CHANGE_TRACKING AUTO                                --Population type;  
GO  

insert into Products values('Mouse Anti-Cattle FOXP3','This is Mouse Anti-Cattle FOXP3 introduction');
insert into Products values('Rabbit Anti-Mouse KRT13','This is Rabbit Anti-Mouse KRT13 introduction');
insert into Products values('Mouse Anti-C elegans FOXP3','This is Mouse Anti-C elegans FOXP3 introduction');

案例查詢SQL語句:

declare @terms nvarchar(255) = 'Mouse Anti';
declare @termsQuote nvarchar(255) = '"'+@terms+'"';
select * from Products  p
JOIN (
SELECT tp.[id],
   (case 
   when name like @terms+'%' then 4
   when name like '%'+@terms then 3
   when contains(name,@termsQuote) then 2
   else 1
   end) as 'sort'
  FROM [Products] tp
  where FREETEXT(description,@termsQuote) or Contains(name,@termsQuote)
  ) tp
ON p.id = tp.id 
order by tp.sort desc;

上面的查詢比使用傳統的LIKE查詢功能要加強很多。其實Contains和Freetext還有許多其它的用法,上面只是展現了一下基本用法,。上面的查詢語句僅僅是作了關鍵詞匹配,在完成接下來的章節學習後,將增長以下功能:

  • 結合SOUNDEX和全文索引,完成查詢語句糾錯機制。
  • 結合全文索引,完成搜素提示功能。

 

  • 1. 全文索引不能查詢同音詞。好比:不一樣經過rabbet查rabbit。
  • 2. 全文索引不能查詢同義詞。好比:不能經過rat查mouse。
  • 3. 全文索引缺少糾錯機制,雖然能夠經過freetext函數查詢關鍵詞的變體,可是不能糾正輸入錯誤的單詞。好比:能夠經過catch查出相應的變體caught, catching, catches等,但若是我輸入的是catsh就匹配不到catch單詞。
  • 4. 全文索引缺乏搜索提示模塊,全文索引僅僅是完成搜索部分,關於搜索提示卻須要開發者本身去架構實現相應的功能,這增長了開發者的工做量。 

上面的四點問題中,在討論完下一節《4. 如何查看本身的數據都被分解成了那些關鍵字》後,筆者會解決上面的侷限性問題,以及提供一些合理的建議。

 

3. 如何查看本身的數據都被分解成了那些關鍵詞

當咱們創建全文索引時,首先須要給全文索引指定一個語言編號,而後全文索引會根據不一樣的語言編號使用不一樣的語言分割器,不一樣的分割器會分割出不一樣的關鍵詞列表。當使用全文索引查詢數據時,就會匹配分割獲得的關鍵詞列表。

 查看全部註冊了的詞語分割器列表

EXEC sp_help_fulltext_system_components 'wordbreaker';  

查看全部支持的語言列表

select * from sys.fulltext_languages

使用dm_fts_parser分解語句得到關鍵詞列表

-- 分解語句:She catches a cat
SELECT * FROM sys.dm_fts_parser (
'"She catches a cat"',  --待分解的語句
1033,  --語言編號, 1033表明English,語言編號信息能夠經過查看sys.fulltext_languages表獲取
0,  -- stoplist: 0表示使用默認的,NULL表示不使用。
0  -- accent_sensitivity,0表示insensitivity,1表示sensitivity
);

--查看一個表被分解成的全部關鍵詞列表
SELECT * FROM sys.dm_fts_index_keywords_by_document(
db_id('Test'),  -- 數據庫對象ID
object_id('dbo.Text_Test_Table') -- 表對象ID
)

到這裏,咱們已經知道如何分解語句獲取關鍵詞列表。接下來咱們將繼續優化上面的查詢案例,第一點是如何結合SOUNDEX和全文索引創建關鍵詞搜索糾錯機制,第二點是如何創建一個搜索提示功能。

當用戶搜索某關鍵詞語句時,若是有其它的關鍵詞和用戶搜索的關鍵詞發音是同樣的,那麼就須要提示給用戶。就相似Google搜索這樣的提供功能。

當在Google搜索 she catsh a cat的時候,Google會提示是不是指 she catch a cat.  相似這樣的糾錯提示,咱們能夠經過下面這個T-SQL實現一個簡單的版本。

declare @searchTerms nvarchar(1000) = N'"she catsh a cat"';
select * from (
select display_term,SOUNDEX(display_term) as st_soundindex from sys.dm_fts_parser(
    @searchTerms,
    (select lcid from sys.fulltext_languages where [name] = 'British English'),--語言須要和Products表使用的語言保持一致
    NULL,
    0
)) st
join (SELECT display_term,SOUNDEX(display_term) o_soundindex
FROM sys.dm_fts_index_keywords_by_document(db_id('Test'), object_id('dbo.Products'))) ot
on st.display_term != ot.display_term and st.st_soundindex = ot.o_soundindex;

效果圖:

 經過上面的腳本對比,咱們就能夠查詢出catsh和catch是同音的,而後程序就能夠拿到這個結果給用戶作糾錯提示了。

 

關於搜索提示這部分,很是遺憾,全文索引並未提供相關的功能。這部分須要程序開發本身來實現,這裏筆者談一談本身的實現思路:

  1. 創建一張表單獨維護搜索提示詞(search_terms)
  2. 給search_terms表中插入一些預約義的搜索提示詞
  3. 用戶本身搜索的詞,也能夠插入到search_terms表中。可是最終須要運維審覈後,才能夠顯示給前端的用戶搜索。
  4. 當用戶在搜索框輸入的時,動態加載search_terms表中的審覈數據顯示到前端頁面。完成搜索提示功能。

到這裏的,全文索引的主體知識點本身都講到了。全文索引就是一個特定語言的搜索功能(Language-Specific Search),因此給本身的數據指定語言類型是很是重要的。全文索引的功能較LIKE的功能有所提高,但全文索引也不是萬能的(咱們也不能指望全文索引把全部功能都完成),好比:

  • 同義詞搜索,好比搜rat, 能夠顯示mouse 或 rodent.
  • 同義句的搜索,好比搜索rat is big, 能夠顯示giant mouse monstor之類的。
  • 搜索提示功能
  • ...

4. 參考文獻

1. sys.dm_fts_index_keywords_by_document (Transact-SQL)

2. Full-Text Search

3. Hands on Full-Text Search in SQL Server

相關文章
相關標籤/搜索