亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 數(shù)據(jù)庫 > PostgreSQL > 正文

詳細講解PostgreSQL中的全文搜索的用法

2020-03-12 23:53:42
字體:
來源:轉載
供稿:網(wǎng)友

這篇文章詳細介紹了的PostgreSQL中的全文搜索的用法,包括對全文搜索的一些優(yōu)化的實現(xiàn),需要的朋友可以參考下

開發(fā)Web應用時,你經(jīng)常要加上搜索功能。甚至還不知能要搜什么,就在草圖上畫了一個放大鏡。

搜索是項非常重要的功能,所以像elasticsearch和SOLR這樣的基于lucene的工具變得很流行。它們都很棒。但使用這些大規(guī)模“殺傷性”的搜索武器前,你可能需要來點輕量級的,但又足夠好的搜索工具。

所謂“足夠好”,我是指一個搜索引擎擁有下列的功能:

詞根(Stemming)

排名/提升(Ranking / Boost)

支持多種語言

對拼寫錯誤模糊搜索

方言的支持

幸運的是PostgreSQL對這些功能全支持。

本文的目標讀者是:

使用PostgreSQL,同時又不想安裝其它的搜索引擎。

使用其它的數(shù)據(jù)庫(比如MySQL),同時需要更好的全文搜索功能。

本文中我們將通過下面的表和數(shù)據(jù)說明PostgreSQL的全文搜索功能。

 

 
  1. CREATE TABLE author( 
  2. id SERIAL PRIMARY KEY
  3. name TEXT NOT NULL); 
  4. CREATE TABLE post( 
  5. id SERIAL PRIMARY KEY
  6. title TEXT NOT NULL
  7. content TEXT NOT NULL
  8. author_id INT NOT NULL references author(id) ); 
  9. CREATE TABLE tag( 
  10. id SERIAL PRIMARY KEY
  11. name TEXT NOT NULL ); 
  12. CREATE TABLE posts_tags( 
  13. post_id INT NOT NULL references post(id), 
  14. tag_id INT NOT NULL references tag(id) 
  15. ); 
  16. INSERT INTO author (id, name)  
  17. VALUES (1, 'Pete Graham'),  
  18. (2, 'Rachid Belaid'),  
  19. (3, 'Robert Berry'); 
  20.  
  21. INSERT INTO tag (id, name)  
  22. VALUES (1, 'scifi'),  
  23. (2, 'politics'),  
  24. (3, 'science'); 
  25.  
  26. INSERT INTO post (id, title, content, author_id)  
  27. VALUES (1, 'Endangered species''Pandas are an endangered species', 1 ),  
  28. (2, 'Freedom of Speech''Freedom of speech is a necessary right missing in many countries', 2),  
  29. (3, 'Star Wars vs Star Trek''Few words from a big fan', 3); 
  30.  
  31. INSERT INTO posts_tags (post_id, tag_id)  
  32. VALUES (1, 3),  
  33. (2, 2),  
  34. (3, 1); 

這是一個類博客的應用。它有post表,帶有title和content字段。post通過外鍵關聯(lián)到author。post自身還有多個標簽(tag)。

什么是全文搜索

首先,讓我們看一下定義:

在文本檢索中,全文搜索是指從全文數(shù)據(jù)庫中搜索計算機存儲的單個或多個文檔(document)的技術。全文搜索不同于基于元數(shù)據(jù)的搜索或根據(jù)數(shù)據(jù)庫中原始文本的搜索。

-- 維基百科

這個定義中引入了文檔的概念,這很重要。當你搜索數(shù)據(jù)時,你在尋找你想要找到的有意義的實體,這些就是你的文檔。PostgreSQL的文檔中解釋地很好。

文檔是全文搜索系統(tǒng)中的搜索單元。比如,一篇雜質文章或是一封郵件消息。

-- Postgres 文檔

這里的文檔可以跨多個表,代表為我們想要搜索的邏輯實體。

構建我們的文檔(document)

上一節(jié),我們介紹了文檔的概念。文檔與表的模式無關,而是與數(shù)據(jù)相關,把字段聯(lián)合為一個有意義的實體。根據(jù)示例中的表的模式,我們的文檔(document)由這些組成:

post.title

post.content

post的author.name

關聯(lián)到post的所有tag.name

根據(jù)這些要求產(chǎn)生文檔,SQL查詢應該是這樣的:

 

 
  1. SELECT post.title || ' ' ||  
  2. post.content || ' ' || 
  3. author.name || ' ' || 
  4. coalesce((string_agg(tag.name' ')), ''as document FROM post JOIN author ON author.id = post.author_id JOIN posts_tags ON posts_tags.post_id = posts_tags.tag_id JOIN tag ON tag.id = posts_tags.tag_id GROUP BY post.id, author.id; 
  5.  
  6. document -------------------------------------------------- 
  7. Endangered species Pandas are an endangered species Pete Graham politics 
  8. Freedom of Speech Freedom of speech is a necessary right missing in many countries Rachid Belaid politics 
  9. Star Wars vs Star Trek Few words from a big fan Robert Berry politics 
  10. (3 rows

由于用post和author分組了,因為有多個tag關聯(lián)到一個post,我們使用string_agg()作聚合函數(shù)。即使author是外鍵并且一個post不能有多個author,也要求對author添加聚合函數(shù)或者把author加到GROUP BY中。

我們還用了coalesce()。當值可以是NULL時,使用coalesce()函數(shù)是個很好的辦法,否則字符串連接的結果將是NULL。

至此,我們的文檔只是一個長string,這沒什么用。我們需要用to_tsvector()把它轉換為正確的格式。

 

 
  1. SELECT to_tsvector(post.title) ||  
  2. to_tsvector(post.content) || 
  3. to_tsvector(author.name) || 
  4. to_tsvector(coalesce((string_agg(tag.name' ')), '')) as documentFROM post 
  5. JOIN author ON author.id = post.author_id 
  6. JOIN posts_tags ON posts_tags.post_id = posts_tags.tag_id 
  7. JOIN tag ON tag.id = posts_tags.tag_id 
  8. GROUP BY post.id, author.id; 
  9. document  
  10. --------------------------------------------------  
  11. 'endang':1,6 'graham':9 'panda':3 'pete':8 'polit':10 'speci':2,7 
  12. 'belaid':16 'countri':14 'freedom':1,4 'mani':13 'miss':11 'necessari':9 'polit':17 'rachid':15 'right':10 'speech':3,6 
  13. 'berri':13 'big':10 'fan':11 'polit':14 'robert':12 'star':1,4 'trek':5 'vs':3 'war':2 'word':7 
  14. (3 rows

這個查詢將返回適于全文搜索的tsvector格式的文檔。讓我們嘗試把一個字符串轉換為一個tsvector。

 

 
  1. SELECT to_tsvector('Try not to become a man of success, but rather try to become a man of value'); 

這個查詢將返回下面的結果:

 

 
  1. to_tsvector 
  2. ---------------------------------------------------------------------- 
  3. 'becom':4,13 'man':6,15 'rather':10 'success':8 'tri':1,11 'valu':17(1 row) 

發(fā)生了怪事。首先比原文的詞少了,一些詞也變了(try變成了tri),而且后面還有數(shù)字。怎么回事?

一個tsvector是一個標準詞位的有序列表(sorted list),標準詞位(distinct lexeme)就是說把同一單詞的各種變型體都被標準化相同的。

標準化過程幾乎總是把大寫字母換成小寫的,也經(jīng)常移除后綴(比如英語中的s,es和ing等)。這樣可以搜索同一個字的各種變體,而不是乏味地輸入所有可能的變體。

數(shù)字表示詞位在原始字符串中的位置,比如“man"出現(xiàn)在第6和15的位置上。你可以自己數(shù)數(shù)看。

Postgres中to_tesvetor的默認配置的文本搜索是“英語“。它會忽略掉英語中的停用詞(stopword,譯注:也就是am is are a an等單詞)。

這解釋了為什么tsvetor的結果比原句子中的單詞少。后面我們會看到更多的語言和文本搜索配置。

查詢

我們知道了如何構建一個文檔,但我們的目標是搜索文檔。我們對tsvector搜索時可以使用@@操作符,使用說明見此處。看幾個查詢文檔的例子。

 

 
  1. select to_tsvector('If you can dream it, you can do it') @@ 'dream'
  2. ?column
  3. ---------- 
  4. (1 row) 
  5.  
  6. select to_tsvector('It''s kind of fun to do the impossible') @@ 'impossible'
  7.  
  8. ?column
  9. ---------- 
  10. (1 row) 

第二個查詢返回了假,因為我們需要構建一個tsquery,使用@@操作符時,把字符串轉型(cast)成了tsquery。下面顯示了這種l轉型和使用to_tsquery()之間的差別。

 

 
  1. SELECT 'impossible'::tsquery, to_tsquery('impossible'); 
  2. tsquery | to_tsquery 
  3. --------------+------------ 
  4. 'impossible' | 'imposs'(1 row) 

但"dream"的詞位與它本身相同。

 

 
  1. SELECT 'dream'::tsquery, to_tsquery('dream'); 
  2. tsquery | to_tsquery 
  3. --------------+------------ 
  4. 'dream' | 'dream'(1 row) 

從現(xiàn)在開始我們使用to_tsquery查詢文檔。

 

 
  1. SELECT to_tsvector('It''s kind of fun to do the impossible') @@ to_tsquery('impossible'); 
  2.  
  3. ?column
  4. ---------- 
  5. (1 row) 

tsquery存儲了要搜索的詞位,可以使用&(與)、|(或)和!(非)邏輯操作符??梢允褂脠A括號給操作符分組。

 

 
  1. SELECT to_tsvector('If the facts don't fit the theory, change the facts') @@ to_tsquery('! fact'); 
  2.  
  3. ?column
  4. ---------- 
  5. (1 row) 
  6.  
  7. SELECT to_tsvector('If the facts don''t fit the theory, change the facts') @@ to_tsquery('theory & !fact'); 
  8.  
  9. ?column
  10. ---------- 
  11. (1 row) 
  12.  
  13. SELECT to_tsvector('If the facts don''t fit the theory, change the facts.') @@ to_tsquery('fiction | theory'); 
  14.  
  15. ?column
  16. ---------- 
  17. (1 row) 

我們也可以使用:*來表達以某詞開始的查詢。

 

 
  1. SELECT to_tsvector('If the facts don''t fit the theory, change the facts.') @@ to_tsquery('theo:*'); 
  2.  
  3. ?column
  4. ---------- 
  5. (1 row) 

既然我們知道了怎樣使用全文搜索查詢了,我們回到開始的表模式,試著查詢文檔。

 

 
  1. SELECT pid, p_titleFROM (SELECT post.id as pid, 
  2. post.title as p_title, 
  3. to_tsvector(post.title) ||  
  4. to_tsvector(post.content) || 
  5. to_tsvector(author.name) || 
  6. to_tsvector(coalesce(string_agg(tag.name' '))) as document 
  7. FROM post 
  8. JOIN author ON author.id = post.author_id 
  9. JOIN posts_tags ON posts_tags.post_id = posts_tags.tag_id 
  10. JOIN tag ON tag.id = posts_tags.tag_id 
  11. GROUP BY post.id, author.id) p_search WHERE p_search.document @@ to_tsquery('Endangered & Species'); 
  12.  
  13. pid | p_title 
  14. -----+-------------------- 
  15. 1 | Endangered species 
  16. (1 row) 

這個查詢將找到文檔中包含Endangered和Species或接近的詞。

語言支持

Postgres 內(nèi)置的文本搜索功能支持多種語言: 丹麥語,荷蘭語,英語,芬蘭語,法語,德語,匈牙利語,意大利語,挪威語,葡萄牙語,羅馬尼亞語,俄語,西班牙語,瑞典語,土耳其語。

 

 
  1. SELECT to_tsvector('english''We are running'); 
  2. to_tsvector------------- 
  3. 'run':3 
  4. (1 row)SELECT to_tsvector('french''We are running'); 
  5. to_tsvector---------------------------- 
  6. 'are':2 'running':3 'we':1 
  7. (1 row) 

基于我們最初的模型,列名可以用來創(chuàng)建tsvector。 假設post表中包含不同語言的內(nèi)容,且它包含一列l(wèi)anguage。

ALTER TABLE post ADD language text NOT NULL DEFAULT('english');

為了使用language列,現(xiàn)在我們重新編譯文檔。

 

 
  1. SELECT to_tsvector(post.language::regconfig, post.title) ||  
  2. to_tsvector(post.language::regconfig, post.content) || 
  3. to_tsvector('simple', author.name) || 
  4. to_tsvector('simple'coalesce((string_agg(tag.name' ')), '')) as documentFROM postJOIN author ON author.id = post.author_idJOIN posts_tags ON posts_tags.post_id = posts_tags.tag_idJOIN tag ON tag.id = posts_tags.tag_idGROUP BY post.id, author.id; 

如果缺少顯示的轉化符::regconfig,查詢時會產(chǎn)生一個錯誤:

 

 
  1. ERROR: function to_tsvector(text, text) does not exist 

regconfig是對象標識符類型,它表示Postgres文本搜索配置項。:http://www.postgresql.org/docs/9.3/static/datatype-oid.html

現(xiàn)在,文檔的語義會使用post.language中正確的語言進行編譯。

我們也使用simple,它也是Postgres提供的一個文本搜索配置項。simple并不忽略禁用詞表,它也不會試著去查找單詞的詞根。使用simple時,空格分割的每一組字符都是一個語義;對于數(shù)據(jù)來說,simple文本搜索配置項很實用,就像一個人的名字,我們也許不想查找名字的詞根。

 

 
  1. SELECT to_tsvector('simple''We are running'); 
  2. to_tsvector 
  3. ---------------------------- 'are':2 'running':3 'we':1(1 row) 

重音字符

當你建立一個搜索引擎支持多種語言時你也需要考慮重音問題。在許多語言中重音非常重要,可以改變這個詞的含義。Postgres附帶一個unaccent擴展去調(diào)用 unaccentuate內(nèi)容是有用處的。

 

 
  1. CREATE EXTENSION unaccent;SELECT unaccent('èéê?'); 
  2. unaccent---------- 
  3. eeee 
  4. (1 row) 

讓我們添加一些重音的你內(nèi)容到我們的post表中。

 

 
  1. INSERT INTO post (id, title, content, author_id, language)  
  2. VALUES (4, 'il était une fois''il était une fois un h?tel ...', 2,'french'

如果我們想要忽略重音在我們建立文檔時,之后我們可以簡單做到以下幾點:

 

 
  1. SELECT to_tsvector(post.language, unaccent(post.title)) ||  
  2. to_tsvector(post.language, unaccent(post.content)) || 
  3. to_tsvector('simple', unaccent(author.name)) || 
  4. to_tsvector('simple', unaccent(coalesce(string_agg(tag.name' '))))JOIN author ON author.id = post.author_idJOIN posts_tags ON posts_tags.post_id = posts_tags.tag_idJOIN tag ON author.id = post.author_idGROUP BY p.id 

這樣工作的話,如果有更多錯誤的空間它就有點麻煩。 我們還可以建立一個新的文本搜索配置支持無重音的字符。

 

 
  1. CREATE TEXT SEARCH CONFIGURATION fr ( COPY = french );ALTER TEXT SEARCH CONFIGURATION fr ALTER MAPPINGFOR hword, hword_part, word WITH unaccent, french_stem; 

當我們使用這個新的文本搜索配置,我們可以看到詞位

 

 
  1. SELECT to_tsvector('french''il était une fois'); 
  2. to_tsvector------------- 
  3. 'fois':4 
  4. (1 row)SELECT to_tsvector('fr''il était une fois'); 
  5. to_tsvector-------------------- 
  6. 'etait':2 'fois':4 
  7. (1 row) 

這給了我們相同的結果,第一作為應用unaccent并且從結果建立tsvector。

 

 
  1. SELECT to_tsvector('french', unaccent('il était une fois')); 
  2. to_tsvector-------------------- 
  3. 'etait':2 'fois':4 
  4. (1 row) 

詞位的數(shù)量是不同的,因為il était une在法國是一個無用詞。這是一個問題讓這些詞停止在我們的文件嗎?我不這么認為etait不是一個真正的無用詞而是拼寫錯誤。

 

 
  1. SELECT to_tsvector('fr''H?tel') @@ to_tsquery('hotels'as result; 
  2. result-------- 
  3. (1 row) 

如果我們?yōu)槊糠N語言創(chuàng)建一個無重音的搜索配置,這樣我們的post可以寫入并且我們保持這個值在post.language的中,然后我們可以保持以前的文檔查詢。

 

 
  1. SELECT to_tsvector(post.language, post.title) ||  
  2. to_tsvector(post.language, post.content) || 
  3. to_tsvector('simple', author.name) || 
  4. to_tsvector('simple'coalesce(string_agg(tag.name' ')))JOIN author ON author.id = post.author_idJOIN posts_tags ON posts_tags.post_id = posts_tags.tag_idJOIN tag ON author.id = post.author_idGROUP BY p.id 

如果你需要為每種語言創(chuàng)建無重音的文本搜索配置由Postgres支持,然后你可以使用gist

我們當前的文檔大小可能會增加,因為它可以包括無重音的無用詞但是我們并沒有關注重音字符查詢。這可能是有用的如有人用英語鍵盤搜索法語內(nèi)容。

歸類

當你創(chuàng)建了一個你想要的搜索引擎用來搜索相關的結果(根據(jù)相關性歸類)的時候,歸類可以是基于許多因素的,它的文檔大致解釋了這些(歸類依據(jù))內(nèi)容。

歸類試圖處理特定的上下文搜索, 因此有許多個配對的時候,相關性最高的那個會被排在第一個位置。PostgreSQL提供了兩個預定義歸類函數(shù),它們考慮到了詞法解釋,接近度和結構信息;他們考慮到了在上下文中的詞頻,如何接近上下文中的相同詞語,以及在文中的什么位置出現(xiàn)和其重要程度。

-- PostgreSQL documentation

通過PostgreSQL提供的一些函數(shù)得到我們想要的相關性結果,在我們的例子中我們將會使用他們中的2個:ts_rank() 和 setweight() 。

函數(shù)setweight允許我們通過tsvector函數(shù)給重要程度(權)賦值;值可以是'A', 'B', 'C' 或者 'D'。

 

 
  1. SELECT pid, p_titleFROM (SELECT post.id as pid, 
  2. post.title as p_title, 
  3. setweight(to_tsvector(post.language::regconfig, post.title), 'A') ||  
  4. setweight(to_tsvector(post.language::regconfig, post.content), 'B') || 
  5. setweight(to_tsvector('simple', author.name), 'C') || 
  6. setweight(to_tsvector('simple'coalesce(string_agg(tag.name' '))), 'B'as document FROM post JOIN author ON author.id = post.author_id JOIN posts_tags ON posts_tags.post_id = posts_tags.tag_id JOIN tag ON tag.id = posts_tags.tag_id GROUP BY post.id, author.id) p_searchWHERE p_search.document @@ to_tsquery('english''Endangered & Species')ORDER BY ts_rank(p_search.document, to_tsquery('english''Endangered & Species')) DESC

上面的查詢,我們在文中不同的欄里面賦了不同的權值。post.title的重要程度超過post.content和tag的總和。最不重要的是author.name。

這意味著如果我們搜索關鍵詞“Alice”,那么在題目中包含這個關鍵詞的文檔就會排在搜索結果的前面,在此之后是在內(nèi)容中包含這些關鍵詞的文檔,最后才是作者名字中包含這些關鍵詞的文檔.

基于對文檔各個部分的權重分配ts_rank()這個函數(shù)返回一個浮點數(shù),這個浮點數(shù)代表了文檔和查詢關鍵詞的相關性.

 

 
  1. SELECT ts_rank(to_tsvector('This is an example of document'),  
  2. to_tsquery('example | document')) as relevancy; 
  3. relevancy----------- 
  4. 0.0607927 
  5. (1 row)SELECT ts_rank(to_tsvector('This is an example of document'),  
  6. to_tsquery('example ')) as relevancy; 
  7. relevancy----------- 
  8. 0.0607927 
  9. (1 row)SELECT ts_rank(to_tsvector('This is an example of document'),  
  10. to_tsquery('example | unkown')) as relevancy; 
  11. relevancy----------- 
  12. 0.0303964 
  13. (1 row)SELECT ts_rank(to_tsvector('This is an example of document'), 
  14. to_tsquery('example & document')) as relevancy; 
  15. relevancy----------- 
  16. 0.0985009 
  17. (1 row)SELECT ts_rank(to_tsvector('This is an example of document'),  
  18. to_tsquery('example & unknown')) as relevancy; 
  19. relevancy----------- 
  20. 1e-20 
  21. (1 row) 

但是, 相關性的概念是模糊的,而且是與特定的應用相關. 不同的應用可能需要額外的信息來得到想要的排序結果,比如,文檔的修改時間. 內(nèi)建的排序功能如asts_rank只是個例子. 你可以寫出自己的排序函數(shù) 并且/或者 將得到的結果和其他因素混合來適應你自己的特定需求.

這里說明一下, 如果我們想是新的文章比舊的文章更重要,可以講ts_rank函數(shù)的數(shù)值除以文檔的年齡+1(為防止被0除).

優(yōu)化與索引

將一個表中的搜索結果優(yōu)化為直線前進的. PostgreSQL 支持基于索引的功能,因此你可以用tsvector()函數(shù)方便地創(chuàng)建GIN索引.

 

 
  1. CREATE INDEX idx_fts_post ON post  
  2. USING gin(setweight(to_tsvector(language, title),'A') ||  
  3. setweight(to_tsvector(language, content), 'B')); 

GIN還是GiST索引? 這兩個索引會成為與他們相關的博文的主題. GiST會導出一個錯誤的匹配,之后需要一個額外的表行查找來驗證得到的匹配. 另一方面, GIN 可以更快地查找但是在創(chuàng)建時會更大更慢.

一個經(jīng)驗, GIN索引適合靜態(tài)的數(shù)據(jù)因為查找是迅速的. 對于動態(tài)數(shù)據(jù), GiST 可以更快的更新. 具體來說, GiST索引在動態(tài)數(shù)據(jù)上是好用的并且如果單獨的字(詞位)在100,000以下也是快速的,然而GIN 索引在處理100,000詞位以上時是更好的但是更新就要慢點了.

-- Postgres 文檔 : 第12章 全文搜索

在我們的例子中,我們選擇GIN。但是這個選擇不是一定的,你可以根據(jù)你自己的數(shù)據(jù)來作出決定。

我們的架構例子中有一個問題; 分當時分布在擁有不同權重的不同表中的. 為了更好的運行,通過觸發(fā)器和物化視圖使得數(shù)據(jù)非規(guī)范化是必要的.

我們并非總是需要非規(guī)范化并且有時也需要加入基于索引的功能,就像上面所做的那樣. 另外你可以通過postgres觸發(fā)器 功能tsvector_update_trigger(...)或者tsvector_update_trigger_column(...)實現(xiàn)相同表的數(shù)據(jù)的非規(guī)范化.參見Postgres文檔以得到更多詳細的信息.

在我們的應用中在結果返回之前存在著一些可接受的延遲. 這是一個使用物化視圖將額外索引加載其中的好的情況.

 

 
  1. CREATE MATERIALIZED VIEW search_index AS SELECT post.id, 
  2. post.title, 
  3. setweight(to_tsvector(post.language::regconfig, post.title), 'A') ||  
  4. setweight(to_tsvector(post.language::regconfig, post.content), 'B') || 
  5. setweight(to_tsvector('simple', author.name), 'C') || 
  6. setweight(to_tsvector('simple'coalesce(string_agg(tag.name' '))), 'A'as documentFROM postJOIN author ON author.id = post.author_idJOIN posts_tags ON posts_tags.post_id = posts_tags.tag_idJOIN tag ON tag.id = posts_tags.tag_idGROUP BY post.id, author.id 

之后重新索引搜索引擎就是定期運行REFRESH MATERIALIZED VIEW search_index這么簡單.

現(xiàn)在我們可以給物化視圖添加索引.

 

 
  1. CREATE INDEX idx_fts_search ON search_index USING gin(document); 

查詢也變得同樣簡單.

 

 
  1. SELECT id as post_id, titleFROM search_indexWHERE document @@ to_tsquery('english''Endangered & Species')ORDER BY ts_rank(p_search.document, to_tsquery('english''Endangered & Species')) DESC

如果延遲變得無法忍受,你就應該去研究一下使用觸發(fā)器的替代方法.

建立文檔存儲的方式并不唯一;這取決于你文檔的情況: 單表、多表,多國語言,數(shù)據(jù)量 ...

Thoughtbot.com 發(fā)表了文章"Implementing Multi-Table Full Text Search with Postgres in Rails" 我建議閱讀以下.

拼寫錯誤

PostgreSQL 提供了一個非常有用的擴展程序pg_trgm。 相關文檔見pg_trgm doc。

 

 
  1. CREATE EXTENSION pg_trgm; 

pg_trgm支持N元語法如N==3。N元語法比較有用因為它可以查找相似的字符串,其實,這就是拼寫錯誤的定義 – 一個相似但不正確的單詞。

 

 
  1. SELECT similarity('Something''something'); 
  2. similarity------------ 
  3. (1 row)SELECT similarity('Something''samething'); 
  4. similarity------------ 
  5. 0.538462 
  6. (1 row)SELECT similarity('Something''unrelated'); 
  7. similarity------------ 
  8. (1 row)SELECT similarity('Something''everything'); 
  9. similarity  
  10. ------------ 
  11. 0.235294 
  12. (1 row)SELECT similarity('Something''omething'); 
  13. similarity------------ 
  14. 0.583333 
  15. (1 row) 

通過上面的示例你可以看到,similarity 函數(shù)返回一個表示兩個字符串之間相似度的浮點值。 檢測拼寫錯誤就是一系列的收集文檔中使用的詞位、比較詞位與輸入文本的相似度的過程。 我發(fā)現(xiàn)檢測拼寫錯誤時,相似度臨界值設置為0.5比較合適。 首先,我們需要根據(jù)文檔創(chuàng)建一個唯一性詞位列表,在列表中每一個詞位都是唯一的。

 

 
  1. CREATE MATERIALIZED VIEW unique_lexeme ASSELECT word FROM ts_stat('SELECT to_tsvector('simple', post.title) ||  
  2. to_tsvector('simple', post.content) || 
  3. to_tsvector('simple', author.name) || 
  4. to_tsvector('simple'coalesce(string_agg(tag.name' '))) 
  5. FROM post 
  6. JOIN author ON author.id = post.author_id 
  7. JOIN posts_tags ON posts_tags.post_id = posts_tags.tag_id 
  8. JOIN tag ON tag.id = posts_tags.tag_id 
  9. GROUP BY post.id, author.id'); 

上面的腳本使用word列創(chuàng)建了一個視圖,word列內(nèi)容來自于詞位列表。 我們使用simple關鍵字,這樣table表中可以存儲多種語言的文本。 一旦創(chuàng)建了這個實體化視圖,我們需要添加一個索引來使相似度查詢速度更快。

 

 
  1. CREATE INDEX words_idx ON search_words USING gin(word gin_trgm_ops); 

幸運的是,搜索引擎中使用的唯一性詞位列表不會快速變化,這樣我們就無需通過下面腳本經(jīng)常刷新實體化視圖:

 

 
  1. REFRESH MATERIALIZED VIEW unique_lexeme; 

一旦我們建立起這個表,查找最接近的匹配是很容易的。

 

 
  1. SELECT word  
  2. WHERE similarity(word, 'samething') > 0.5 ORDER BY word <-> 'samething'LIMIT 1; 

這個查詢返回的是這樣一個語義,它相似度滿足(>0.5),再根據(jù)輸入的samething將其最接近的排在首位。操作符<->返回的是參數(shù)間的“距離”,而且是一減去similarity()的值。

當你決定在你的搜索中處理拼寫錯誤的時候,你不會希望看到它(拼寫錯誤)出現(xiàn)在每一個查詢中。相反地,當你在搜索無結果時,可以為了拼寫錯誤去查詢,并使用查詢所提供結果給用戶一些建議。如果數(shù)據(jù)來自于非正式的通訊,例如:社交網(wǎng)絡,可能你的數(shù)據(jù)中會包含拼寫錯誤。你可以通過追加一個類似的語義到你的tsquery中,來獲得一個好點的結果。

"Super Fuzzy Searching on PostgreSQL" 是一篇很好的關于為拼寫錯誤和搜索Postgres使用三字母組的參考文章。

在我使用的例子中,使用unique語義的表不會大于2000行,而且我的理解是,如果你有超過1M的文本時使用unique語義,你將會遇到該方法的性能問題。

關于MySQL和RDS(遠程數(shù)據(jù)服務)

這在Postgres RDS上能運行嗎?

上面所有的示例在RDS上都是可以運行的。 據(jù)我所知,RDS搜索特性中唯一的限制是搜索某些數(shù)據(jù)時需要訪問文件系統(tǒng),如自定義字典,拼寫檢查程序,同義詞,主題詞表。 相關信息見亞馬遜aws論壇。

我使用的是MYSQL數(shù)據(jù)庫,我可以使用內(nèi)置的全文本搜索功能嗎?

如果是我,我不會去用這個功能。 無需爭論,MySQL的全文本搜索功能非常局限。 默認情況,它不支持任何語言的詞干提取功能。 我偶然發(fā)現(xiàn)一個可以安裝的詞干提取的函數(shù),但是MYSQL不支持基于索引的函數(shù)。

那么你可以做些什么? 鑒于我們上面的討論,如果 Postgres能夠勝任你使用的各個場景,那么考慮下把數(shù)據(jù)庫換為 Postgres。 數(shù)據(jù)庫遷移工作可以通過工具如py-mysql2pgsql方便地完成。 或者你可以研究一下更高級的解決方案如 SOLR(基于 Lucene的全文搜索服務器)和 Elasticsearch(基于 Lucene的開源、分布式、 RESTful搜索引擎)。

總結

我們已經(jīng)了解了基于一個特殊的文檔如何構建一個性能良好且支持多語言的文本搜索引擎。 這篇文章只是一個概述,但是它已經(jīng)給你提供了足夠的背景知識和示例,這樣你可以開始構建自己的搜索引擎。 在這篇文章中,我也許犯了一些錯誤,如果你能把錯誤信息發(fā)送到blog@lostpropertyhq.com,我將感激不盡。

Postgres的全文本搜索特性非常好,而且搜索速度足夠快。 這可以使你的應用中的數(shù)據(jù)不斷增長,而無需依賴其它工具進行處理。 Postgres的搜索功能是銀彈嗎? 如果你的核心業(yè)務圍繞搜索進行,它可能不是的。

它移除了一些特性,但是在大部分場景中你不會用到這些特性。 毫無疑問,你需要認真分析和理解你的需求來決定使用哪種搜索方式。

就我個人而言,我希望Postgres全文本搜索功能繼續(xù)改善,并新增下面的一些特性:

額外的內(nèi)置語言支持: 漢語,日語...

圍繞Lucene的外國數(shù)據(jù)包裝程序。 在全文本搜索功能上,Lucene仍然是最優(yōu)秀的工具,把它集成到Postgres中會有很多好處。

更多排名結果的提高或評分特性會是一流的。 Elasticsearch 和 SOLR已經(jīng)提供了先進的解決方案。

進行模糊查詢(tsquery)時不使用trigram的方式會非常棒。 Elasticsearch 提供了一種非常簡單的方式來實現(xiàn)模糊搜索查詢。

能夠通過SQL動態(tài)創(chuàng)建和編輯如字典內(nèi)容、同義詞、主題詞表的特性,而不再使用把文件添加到文件系統(tǒng)的方式。

Postgres 沒有ElasticSearch 和 SOLR 那么先進,畢竟ElasticSearch 和 SOLR是專門進行全文本搜索的工具,而全文本搜索只是PostgresSQL一個比較優(yōu)秀的特性。

發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
久久综合九色综合久| 亚洲欧洲精品一区| 麻豆视频在线观看免费网站黄| 国产乱子视频| 无码日韩人妻精品久久蜜桃| 激情小说亚洲图片| 国产精品加勒比| 成年人视频观看| 亚洲欧美视频| 岛国精品在线播放| 亚洲美女www午夜| 国产精品人人爽人人爽| 一级在线观看| 激情五月综合婷婷| 天天操人人干| 欧美一区影院| 97免费在线观看视频| 国产视频中文字幕在线观看| 日韩一区二区在线观看视频播放| 亚洲欧美一区二区在线观看| 三上悠亚在线观看视频| 成人av资源在线播放| 精品97人妻无码中文永久在线| 一区二区三区在线观看网站| 亚洲国产精品精华素| 亚洲综合色区另类av| www.成人免费视频| 欧美性猛交xxxx乱大交蜜桃| 亚洲午夜精品久久久| 成人免费一区二区三区在线观看| 男人草女人视频| 色综合视频一区二区三区日韩| 26uuu另类欧美| 久久av.com| 国产原厂视频在线观看| 亚洲一二三专区| 亚洲手机在线| 久久网福利资源网站| 日本不卡一二三| 快播日韩欧美| 天天色棕合合合合合合合| 国产精品视频第一区二区三区| 欧美激情成人动漫| 毛片在线网址| 波多野结衣视频播放| 亚洲小说区图片区情欲小说| 国内自拍亚洲| 蜜桃传媒视频麻豆一区| 黄色免费直接看| 日本美女视频一区二区| 中文字幕一区二区三区人妻电影| 刘亦菲久久免费一区二区| 亚洲 欧美 综合 另类 中字| 久久黄色免费视频| 中文字幕中文字幕中文字幕亚洲无线| 久热精品视频| 国产一区二区四区| 久久久9色精品国产一区二区三区| 99国内精品久久久久久久软件| 日韩av毛片在线观看| 日本一二区视频| 欧美男人天堂| 成人手机电影网| 色777狠狠狠综合伊人| 欧美激情久久久久久久久久久| 香蕉国产精品偷在线观看不卡| 亚洲色图美女| 成人在线app| 偷偷www综合久久久久久久| 欧美男女交配| 无码成人精品区在线观看| mm131亚洲精品| 成人高清在线观看| 精品日本一区二区三区在线观看| 精品国产亚洲日本| 午夜剧场免费看| 东京干手机福利视频| 日韩高清在线观看| 欧美爱爱小视频| 一本加勒比hezyo黑人| 超碰在线观看免费版| 一区二区三区少妇| 欧美精品v日韩精品v国产精品| 国产精品色午夜在线观看| 91黄页在线观看| 日韩在线一级片| 欧美熟妇激情一区二区三区| 日本黄色免费在线观看| 日本黄色中文字幕| 黄页网站在线观看| 91九色在线视频| 欧洲专线二区三区| 欧美不卡三区| 成人免费观看在线视频| 国产一区二区三区四区hd| 91夜夜未满十八勿入爽爽影院| 欧美成人一区二区三区四区| 亚洲精华液一区二区三区| av片哪里在线观看| 国产伦精品一区二区三区88av| 日本黄色大片视频| 国产无人区一区二区三区| 日本中文字幕一区二区视频| 69成人精品免费视频| 美女主播精品视频一二三四| 亚州国产精品视频| 久久久美女艺术照精彩视频福利播放| 欧美有码在线观看视频| 免费h精品视频在线播放| h网站在线免费观看| 亚洲系列中文字幕| 国产欧美一区二区精品性色超碰| c++连点器| 亚洲av毛片基地| 99久久99视频只有精品| 国产精品一级二级三级| 欧美国产日产韩国视频| 午夜性色一区二区三区免费视频| 亚洲精品美女久久久久| 国产日产精品一区二区三区四区| 九九热久久66| canopen超线视频网线的应用| 日韩最新中文字幕电影免费看| 99精品国产九九国产精品| 黑人巨大精品欧美一区二区奶水| 天天色综合久久| 久久婷婷亚洲| 一区二区高清免费观看影视大全| 日本不卡视频在线观看| 中文国产在线观看| 国产精品电影一区二区| 色之综合天天综合色天天棕色| 欧洲亚洲妇女av| 亚洲精品电影在线一区| 亚洲播播91| 国产综合视频一区二区三区免费| av观看在线免费| 亚洲影院免费| 美女福利视频一区二区| 亚洲人精品午夜在线观看| 久久久久久久久97黄色工厂| 色综合色综合色综合色综合色综合| 黄上黄在线观看| 久久精品亚洲a| 2020av在线| 国产树林野战在线播放| 一本久久精品一区二区| 日韩成人一区二区| 亚洲一区二区三区久久久| 在线综合亚洲欧美在线视频| 麻豆国产精品官网| 91久久国产综合久久蜜月精品| 99蜜桃臀久久久欧美精品网站| 久久久婷婷一区二区三区不卡| 97在线免费公开视频| 五月婷婷激情综合| av电影在线观看完整版一区二区| 免费观看成人在线视频| 色吧亚洲视频| 久久久精品一区二区三区| 性感美女一级片| 色偷偷888欧美精品久久久| wwwwxxxx国产| 热re91久久精品国99热蜜臀| 久久精品一区二| 国产欧美日韩中文| chinese少妇国语对白| 中文字幕乱码中文字幕| 青青草原成人在线视频| www.99精品| 熟妇人妻中文av无码| 在线亚洲免费视频| 性做久久久久久免费观看欧美| 亚洲天堂第一页| 亚洲色婷婷一区二区三区| 国产成人无码精品久在线观看| 手机亚洲手机国产手机日韩| 波多野结衣毛片| 天天操天天爱天天干| 国产欧美一区二区三区在线观看视频| 丁香花在线电影| 日韩精品成人| 中文字幕日本精品| 亚洲精品视频一区二区| 免费a级观看| 中文字幕av第一页| 久草福利资源在线视频| 无人区乱码一区二区三区| 亚洲日本在线天堂| 久久国产精品久久w女人spa| 91系列在线观看| 蜜臀av亚洲一区中文字幕| 国产一区二区三区四区hd| av中文字幕在线观看第一页| 亚洲一区中文字幕| 久久国产精品免费| www.桃色av嫩草.com| 夜夜爽99久久国产综合精品女不卡| 亚洲精品亚洲人成在线| 国产视频一视频二| 国产成人自拍偷拍| 五月天激情国产综合婷婷婷| 精品福利在线导航| av网站免费看| www视频在线观看免费| 夜先锋av资源| 日韩中文字幕组| 色综合天天综合给合国产| 日韩电影免费网站| 亚洲国产精品福利| 亚洲黄一区二区| 免费成人高清在线视频| 日本一级在线观看| 黄页大全在线免费观看| 一区精品在线| 一区二区高清在线| 亚洲综合精品自拍| 国产精品一二三区在线| 一卡二卡3卡四卡高清精品视频| 国产福利一区二区三区视频在线| 久久一区视频| 极品尤物av丝袜美腿在线观看| 亚洲一区二区三区四区五区黄| 欧美老**bbbb毛片| 亚洲人成亚洲人成在线观看| 啪一啪鲁一鲁2019在线视频| 三级一区在线视频先锋| 热re99久久精品国99热蜜月| 亚洲成人网在线播放| 亚洲精品无播放器在线播放| 视频福利在线| 国内揄拍国内精品久久| 久久这里只有精品23| 欧美在线观看视频一区二区三区| 国产免费观看久久黄| 高清电影一区| 久久伊伊香蕉| 欧美美女直播网站| 久久国产精品久久久久久电车| av中文字幕在线免费观看| 日韩成人免费观看| 国产美女特级嫩嫩嫩bbb片| 国产调教视频在线观看| 日本91福利区| 国产精品刘玥久久一区| 日本高清不卡aⅴ免费网站| 女厕盗摄一区二区三区| 国产精品久久久久久久久久免费| 国产精品成人免费电影| 一区二区在线高清视频| 国产四区在线观看| 欧美在线观看你懂的| 日韩视频一区二区| 天天影视涩香欲综合网| 99精品人妻国产毛片| 噜噜噜噜噜在线视频| 成视频免费观看在线看| 国产精品视频久久久久久久| 乱中年女人av三区中文字幕| 日韩精品五月天| 日韩人妻精品中文字幕| 久久久久久一二三区| 濑亚美莉大战黑人中文字幕| 在线观看免费一区| 日本一区二区网站| 中文字幕视频一区二区| 欧美一区二区三级| 亚洲大片一区二区三区| 国产精自产拍久久久久久蜜| gogo大胆日本视频一区| 久久久影视传媒| 欧美成人精品一区二区男人小说| 国产又黄又粗又猛又爽| 大胆av不用播放器在线播放| 91网页版在线| 中文字幕一区二区三区久久网站| 欧美综合天天夜夜久久| 又长又粗又大又爽| 国产中文欧美日韩在线| 99成人在线观看| 久久午夜视频| www.亚洲成人| 久久香蕉国产线看观看99| 国产乱叫456| 色噜噜狠狠一区二区三区狼国成人| 色老板亚洲精品一区| 日韩精品一区二区免费| 免费在线观看av电影| 蜜桃视频一区二区三区在线观看| 国产欧美123| 欧美久久精品午夜青青大伊人| 日韩精品一区中文字幕| 成人一区而且| 乱子伦视频在线看| 日韩一级片av| aaa日本高清在线播放免费观看| 亚亚洲欧洲精品| 最大av网站| 免费毛片一区二区三区久久久| 四虎影视在线播放| 一区一区视频| 九九热在线视频免费观看| 韩国一区二区三区| 天天摸夜夜添狠狠添婷婷| 国产精品第一视频| jjzzjjzzjjzz| 亚洲欧洲一区二区天堂久久| 欧美性受xxxx黑人爽| 国产一级一片免费播放放a| 日韩写真福利视频在线| 激情久久婷婷| 日韩一区二区在线| 精品国产乱码久久久久久老虎| 国产日韩欧美一区在线| www.99久久热国产日韩欧美.com| 日韩一级免费毛片| 午夜精品久久久久久久96蜜桃| 精品福利免费观看| 日韩亚洲视频在线| 日韩高清dvd碟片| 99精品一区二区三区无码吞精| 九九在线免费视频| 一区二区小视频| 国产寡妇亲子伦一区二区三区四区| 欧美成人三级视频网站| 成人美女免费网站视频| 亚洲国产aⅴ天堂久久| 亚洲国产欧美日韩精品|