2010-07-23 72 views
7

我們在這裏有一個簡單的SQL問題。在varchar列中,我們想要在字段中的任何位置搜索字符串。實現這種性能的最佳方式是什麼?顯然索引不會幫助這裏,任何其他技巧?在SQL中實現子字符串搜索的最佳方式是什麼?

我們使用MySQL並擁有約300萬條記錄。我們需要每秒執行很多這些查詢,所以真的想要以最佳性能實現這些查詢。

最簡單的方式做,這是迄今爲止是:

Select * from table where column like '%search%' 

我應該指定該列實際上是一個長字符串,如「sadfasdfwerwe」,我要搜索「ASDF 「在這一欄中。 所以他們不是句子,並試圖匹配他們的一個詞。全文搜索仍然有幫助嗎?

回答

0

我想匹配整個單詞,看看FULLTEXT索引& MATCH() AGAINST()。當然,請加載數據庫服務器:爲您的特定需求緩存適當的時間。

0

首先,也許這是一個設計糟糕的表格的問題,該表格在一個字段中存儲了分隔字符串,而不是正確設計以創建相關表格。如果是這樣,你應該修復你的設計。

如果您有一個長描述性文本字段(比如一個筆記字段),並且搜索始終是整個單詞,則可以執行全文搜索。

考慮如果您可以要求您的用戶至少爲您提供搜索的第一個字符,如果它是像Last_name這樣的普通字段。

考慮先做一個完全匹配搜索,如果沒有結果返回,只執行通配符匹配。如果您有能夠提供完全匹配的用戶,這將起作用。我們在機場名稱搜索中這樣做了一次,如果他們輸入了確切的名稱,則返回的速度非常快,如果不確定,則返回的速度會變慢。

如果您只想搜索不是可能位於文本某處的字詞的字符串,那麼您幾乎陷入了糟糕的表現。

14

看看我的介紹Practical Fulltext Search in MySQL

我比較:

今天,我會用什麼Apache Solr,這使Lucene的到服務與一堆額外的功能和工具。


重新發表您的評論:Aha,好的,沒有。我提到的全文搜索功能都沒有幫助,因爲它們都假設某種字邊界

另一種有效查找任意子字符串的方法是N-gram方法。基本上,創建一個所有可能的N個字母序列的索引,並指向每個相應序列出現的字符串。通常這是在N = 3或trigram的情況下完成的,因爲這是在匹配更長的子字符串和將索引保持爲可管理的大小之間的妥協點。

我不知道,支持N-克索引透明的任何SQL數據庫,但是你可以使用倒排索引設置它自己:

create table trigrams (
    trigram char(3) primary key 
); 

create table trigram_matches (
    trigram char(3), 
    document_id int, 
    primary key (trigram, document_id), 
    foreign key (trigram) references trigrams(trigram), 
    foreign key (document_id) references mytable(document_id) 
); 

現在填充它堅硬方式:

insert into trigram_matches 
    select t.trigram, d.document_id 
    from trigrams t join mytable d 
    on d.textcolumn like concat('%', t.trigram, '%'); 

當然,這將需要相當長的一段時間!但是,一旦它的完成,你可以更快速地搜索:

select d.* 
from mytable d join trigram_matches t 
    on t.document_id = d.document_id 
where t.trigram = 'abc' 

當然你可能會超過三個字符搜索模式,但倒排索引還有助於縮小搜索了很多:

select d.* 
from mytable d join trigram_matches t 
    on t.document_id = d.document_id 
where t.trigram = 'abc' 
    and d.textcolumn like '%abcdef%'; 
+0

我重新編輯的問題一點點,這是否仍然適用? – erotsppa 2010-07-23 18:05:53

+7

PostgreSQL有pg_trgm contrib包,它引入了一種索引trigrams的方法。 http://www.postgresql.org/docs/current/static/pgtrgm.html – nertzy 2011-02-12 21:47:55

+0

@nertzy:這太酷了!感謝您指點我們。 – 2011-02-13 01:21:22

0
  1. mysql的全文檢索的質量(爲此)差,如果你的母語不是英語

  2. 卦搜索給出了很好的效果,在t他的任務

  3. PostgreSQL有trigram index,很容易使用:)

  4. ,但如果你需要做的是在MySQL中,試試這個,改進法案Karwin的答案的版本:

    - 每一個卦是僅存儲一次

    -a簡單的PHP類使用數據

    <?php 
    
        /* 
    
        # mysql table structure 
        CREATE TABLE `trigram2content` (
    `trigram_id` int NOT NULL REFERENCES trigrams(id), 
    `content_type_id` int(11) NOT NULL, 
    `record_id` int(11) NOT NULL, 
    PRIMARY KEY (`content_type_id`,`trigram_id`,`record_id`) 
    ); 
    
    #each trigram is stored only once 
    CREATE TABLE `trigrams` (
    `id` int not null auto_increment, 
    `token` varchar(3) NOT NULL, 
    PRIMARY KEY (id), 
    UNIQUE token(token) 
    ) DEFAULT CHARSET=utf8 COLLATE=utf8_bin; 
    
    
    SELECT count(*), record_id FROM trigrams t 
    inner join trigram2content c ON t.id=c.trigram_id 
    WHERE (
    t.token IN ('loc','ock','ck ','blo',' bl', ' bu', 'bur', 'urn') 
    AND c.content_type_id = 0 
    ) 
    GROUP by record_id 
    ORDER BY count(*) DESC 
    limit 20; 
    
    
    */ 
    class trigram 
    { 
    
        private $dbLink; 
    
        var $types = array(
         array(0, 'name'), 
         array(1, 'city')); 
    
    
        function trigram() 
        { 
         //connect to db 
         $this->dbLink = mysql_connect("localhost", "username", "password"); 
         if ($this->dbLink) mysql_select_db("dbname"); 
         else mysql_error(); 
    
         mysql_query("SET NAMES utf8;", $this->dbLink); 
        } 
    
        function get_type_value($type_name){ 
         for($i=0; $i<count($this->types); $i++){ 
          if($this->types[$i][1] == $type_name) 
           return $this->types[$i][0]; 
         } 
         return ""; 
        } 
    
        function getNgrams($word, $n = 3) { 
         $ngrams = array(); 
         $len = mb_strlen($word, 'utf-8'); 
         for($i = 0; $i < $len-($n-1); $i++) { 
          $ngrams[] = mysql_real_escape_string(mb_substr($word, $i, $n, 'utf-8'), $this->dbLink); 
         } 
         return $ngrams; 
        } 
    
        /** 
        input: array('hel', 'ell', 'llo', 'lo ', 'o B', ' Be', 'Bel', 'ell', 'llo', 'lo ', 'o ') 
        output: array(1,  2,  3,  4,  5,  6,  7,  2, 3, 4,  8) 
        */ 
        private function getTrigramIds(&$t){ 
         $u = array_unique($t); 
         $q = "SELECT * FROM trigrams WHERE token IN ('" . implode("', '", $u) . "')"; 
    
         $query = mysql_query($q, $this->dbLink); 
         $n = mysql_num_rows($query); 
    
         $ids = array(); //these trigrams are already in db, they have id 
         $ok = array(); 
    
         for ($i=0; $i<$n; $i++) 
         { 
          $row = mysql_fetch_array($query, MYSQL_ASSOC); 
          $ok []= $row['token']; 
          $ids[ $row['token'] ] = $row['id']; 
         } 
         $diff = array_diff($u, $ok); //these trigrams are not yet in the db 
         foreach($diff as $n){ 
          mysql_query("INSERT INTO trigrams (token) VALUES('$n')", $this->dbLink); 
          $ids[$n]= mysql_insert_id(); 
         } 
    
         //so many ids than items (if a trigram occurs more times in input, then it will occur more times in output as well) 
         $result = array(); 
         foreach($t as $n){ 
          $result[]= $ids[$n]; 
         } 
         return $result; 
        } 
    
        function insertData($id, $data, $type){ 
         $t = $this->getNgrams($data); 
    
         $id = intval($id); 
         $type = $this->get_type_value($type); 
         $tIds = $this->getTrigramIds($t); 
         $q = "INSERT INTO trigram2content (trigram_id, content_type_id, record_id) VALUES "; 
         $rows = array(); 
         foreach($tIds as $n => $tid){ 
          $rows[]= "($tid, $type, $id)"; 
         } 
         $q .= implode(", ", $rows); 
         mysql_query($q, $this->dbLink); 
        } 
    
        function updateData($id, $data, $type){ 
         mysql_query("DELETE FROM trigram2content WHERE record_id=".intval($id)." AND content_type_id=".$this->get_type_value($type), $this->dbLink); 
         $this->insertData($id, $data, $type); 
        } 
    
        function search($str, $type){ 
    
         $tri = $this->getNgrams($str); 
         $max = count($tri); 
         $q = "SELECT count(*), count(*)/$max as score, record_id FROM trigrams t inner join trigram2content c ON t.id=c.trigram_id 
    WHERE (
    t.token IN ('" . implode("', '", $tri) . "') 
    AND c.content_type_id = ".$this->get_type_value($type)." 
    ) 
    GROUP by record_id 
    HAVING score >= 0.6 
    ORDER BY count(*) DESC 
    limit 20;"; 
         $query = mysql_query($q, $this->dbLink); 
         $n = mysql_num_rows($query); 
    
         $result = array(); 
         for ($i=0; $i<$n; $i++) 
         { 
          $row = mysql_fetch_array($query, MYSQL_ASSOC); 
          $result[] = $row; 
         } 
         return $result; 
        } 
    
    
    }; 
    

與用法:

$t = new trigram(); 

$t->insertData(1, "hello bello", "name"); 
$t->insertData(2, "hellllo Mammmma mia", "name"); 

    print_r($t->search("helo", "name")); 
相關問題