MySQL - ngram 全文解析器



通常在全文搜尋中,內建的 MySQL 全文解析器將單詞之間的空格視為分隔符。這決定了單詞的實際開始和結束位置,使搜尋更簡單。但是,這僅適用於使用空格分隔單詞的語言。

幾種表意文字語言,如中文、日語和韓語,不使用單詞分隔符。為了支援這些語言的全文搜尋,使用了 ngram 解析器。此解析器由 InnoDB 和 MyISAM 儲存引擎都支援。

ngram 全文解析器

ngram 是從給定文字序列中連續的“n”個字元。ngram 解析器將文字序列劃分為標記,作為 n 個字元的連續序列。

例如,考慮文字“Tutorial”並觀察 ngram 解析器如何對其進行標記化:

n=1: 'T', 'u', 't', 'o', 'r', 'i', 'a', 'l'
n=2: 'Tu', 'ut', 'to' 'or', 'ri', 'ia' 'al'
n=3: 'Tut', 'uto', 'tor', 'ori', 'ria', 'ial'
n=4: 'Tuto', 'utor', 'tori', 'oria', 'rial'
n=5: 'Tutor', 'utori', 'toria', 'orial'
n=6: 'Tutori', 'utoria', 'torial'
n=7: 'Tutoria', 'utorial'
n=8: 'Tutorial'

ngram 全文解析器是一個內建的伺服器外掛。與其他內建伺服器外掛一樣,它在伺服器啟動時自動載入。

配置 ngram 令牌大小

要更改標記大小(從其預設大小 2 開始),請使用 ngram_token_size 配置選項。ngram 值的範圍是 1 到 10。但為了提高搜尋查詢的速度,請使用較小的標記大小;因為較小的標記大小允許使用較小的全文搜尋索引進行更快的搜尋。

因為 ngram_token_size 是一個只讀變數,所以您只能使用兩種選項來設定它的值

在啟動字串中設定 --ngram_token_size

mysqld --ngram_token_size=1

在配置檔案“my.cnf”中設定 ngram_token_size

[mysqld]

ngram_token_size=1

使用 ngram 解析器建立 FULLTEXT 索引

可以使用 FULLTEXT 關鍵字在表的列上建立 FULLTEXT 索引。這與 CREATE TABLE、ALTER TABLE 或 CREATE INDEX SQL 語句一起使用;您只需指定“WITH PARSER ngram”。以下是語法:

CREATE TABLE table_name (
   column_name1 datatype,
   column_name2 datatype,
   column_name3 datatype,
   ...
   FULLTEXT (column_name(s)) WITH PARSER NGRAM
) ENGINE=INNODB CHARACTER SET UTF8mb4;

示例

在此示例中,我們使用 CREATE TABLE 語句建立 FULLTEXT 索引,如下所示:

CREATE TABLE blog (
   ID INT AUTO_INCREMENT NOT NULL,
   TITLE VARCHAR(255),
   DESCRIPTION TEXT,
   FULLTEXT ( TITLE, DESCRIPTION ) WITH PARSER NGRAM,
   PRIMARY KEY(id)
) ENGINE=INNODB CHARACTER SET UTF8MB4;

SET NAMES UTF8MB4;

現在,將資料(以任何表意文字語言)插入到建立的此表中:

INSERT INTO BLOG VALUES 
(NULL, '教程', '教程是對一個概念的冗長研究'),
(NULL, '文章', '文章是關於一個概念的基於事實的小資訊');

要檢查文字是如何標記化的,請執行以下語句:

SET GLOBAL innodb_ft_aux_table = "customers/blog";

SELECT * FROM
INFORMATION_SCHEMA.INNODB_FT_INDEX_CACHE
ORDER BY doc_id, position;

ngram 解析器空格處理

ngram 解析器在解析時會消除任何空格字元。例如,考慮以下帶有標記大小 2 的 TEXT:

  • “ab cd”解析為“ab”、“cd”

  • “a bc”解析為“bc”

ngram 解析器停用詞處理

除了空格字元之外,MySQL 還具有一個停用詞列表,其中包含被視為停用詞的各種詞。如果解析器在文字中遇到停用詞列表中存在的任何單詞,則該單詞將從索引中排除。

普通短語搜尋將轉換為 ngram 短語搜尋。例如,搜尋短語“abc”將轉換為“ab bc”,這將返回包含“abc”和“ab bc”的文件;搜尋短語“abc def”將轉換為“ab bc de ef”,這將返回包含“abc def”和“ab bc de ef”的文件。包含“abcdef”的文件不會返回。

對於自然語言模式搜尋,搜尋詞將轉換為 ngram 術語的並集。例如,字串“abc”(假設 ngram_token_size=2)將轉換為“ab bc”。給定兩個文件,一個包含“ab”,另一個包含“abc”,搜尋詞“ab bc”匹配這兩個文件。

對於布林模式搜尋,搜尋詞將轉換為 ngram 短語搜尋。例如,字串“abc”(假設 ngram_token_size=2)將轉換為““ab bc””。給定兩個文件,一個包含“ab”,另一個包含“abc”,搜尋短語““ab bc””僅匹配包含“abc”的文件。

因為 ngram FULLTEXT 索引僅包含 ngram,並且不包含有關術語開頭的資訊,所以萬用字元搜尋可能會返回意外的結果。以下行為適用於使用 ngram FULLTEXT 搜尋索引的萬用字元搜尋

  • 如果萬用字元搜尋的字首詞短於 ngram 令牌大小,則查詢返回所有包含以該字首詞開頭的 ngram 令牌的索引行。例如,假設 ngram_token_size=2,則對 "a*" 進行搜尋將返回所有以 "a" 開頭的行。

  • 如果萬用字元搜尋的字首詞長於 ngram 令牌大小,則將字首詞轉換為 ngram 短語,並忽略萬用字元運算子。例如,假設 ngram_token_size=2,則 "abc*" 萬用字元搜尋將轉換為 "ab bc"。

使用客戶端程式的 ngram 全文解析器

我們也可以使用客戶端程式執行 ngram 全文解析器操作。

語法

要透過 PHP 程式執行 ngram 全文解析器,我們需要使用 **mysqli** 函式 **query()** 執行 "Create" 語句,如下所示:

$sql = "CREATE TABLE blog (ID INT AUTO_INCREMENT NOT NULL, title VARCHAR(255), DESCRIPTION TEXT, FULLTEXT ( title, DESCRIPTION ) WITH PARSER NGRAM, PRIMARY KEY(id) )ENGINE=INNODB CHARACTER SET UTF8MB4";
$mysqli->query($sql);

要透過 JavaScript 程式執行 ngram 全文解析器,我們需要使用 **mysql2** 庫的 **query()** 函式執行 "Create" 語句,如下所示:

sql = `CREATE TABLE blog (ID INT AUTO_INCREMENT NOT NULL, title VARCHAR(255), DESCRIPTION TEXT, FULLTEXT ( title, DESCRIPTION ) WITH PARSER NGRAM, PRIMARY KEY(id) )ENGINE=INNODB CHARACTER SET UTF8MB4`;
con.query(sql);

要透過 Java 程式執行 ngram 全文解析器,我們需要使用 **JDBC** 函式 **execute()** 執行 "Create" 語句,如下所示:

String sql = "CREATE TABLE blog (ID INT AUTO_INCREMENT NOT NULL, title VARCHAR(255), description TEXT," + 
" FULLTEXT ( title, description ) WITH PARSER NGRAM, PRIMARY KEY(id) )ENGINE=INNODB CHARACTER SET UTF8MB4";
statement.execute(sql);

要透過 Python 程式執行 ngram 全文解析器,我們需要使用 **MySQL Connector/Python** 的 **execute()** 函式執行 "Create" 語句,如下所示:

create_table_query = 'CREATE TABLE blog(ID INT AUTO_INCREMENT NOT NULL, title VARCHAR(255), description TEXT, FULLTEXT (title, description) WITH PARSER NGRAM, PRIMARY KEY(id)) ENGINE=INNODB CHARACTER SET UTF8MB4'
cursorObj.execute(queryexpansionfulltext_search)

示例

以下是程式:

$dbhost = "localhost";
$dbuser = "root";
$dbpass = "password";
$dbname = "TUTORIALS";
$mysqli = new mysqli($dbhost, $dbuser, $dbpass, $dbname);
if ($mysqli->connect_errno) {
    printf("Connect failed: %s
	
", $mysqli->connect_error); exit(); } // printf('Connected successfully.
'); /*CREATE Table*/ $sql = "CREATE TABLE blog (ID INT AUTO_INCREMENT NOT NULL, title VARCHAR(255), description TEXT, FULLTEXT ( title, description ) WITH PARSER NGRAM, PRIMARY KEY(id) )ENGINE=INNODB CHARACTER SET UTF8MB4"; $result = $mysqli->query($sql); if ($result) { printf("Table created successfully...!\n"); } //insert data $q = "INSERT INTO blog (id, title, description) VALUES (NULL, '教程', '教程是對一個概念的冗長研究'), (NULL, '文章', '文章是關於一個概念的基於事實的小資訊')"; if ($res = $mysqli->query($q)) { printf("Data inserted successfully...!\n"); } //we will use the below statement to see how the ngram tokenizes the data: $setglobal = "SET GLOBAL innodb_ft_aux_table = 'TUTORIALS/blog'"; if ($mysqli->query($setglobal)) { echo "global innodb_ft_aux_table set...!"; } $s = "SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_INDEX_CACHE ORDER BY doc_id, position "; if ($r = $mysqli->query($s)) { print_r($r); } //display data (ngram parser phrase search); $query = "SELECT * FROM blog WHERE MATCH (title, description) AGAINST ('教程')"; if ($r = $mysqli->query($query)) { printf("Table Records: \n"); while ($row = $r->fetch_assoc()) { printf( "ID: %d, Title: %s, Descriptions: %s", $row["id"], $row["title"], $row["description"] ); printf("\n"); } } else { printf("Failed"); } $mysqli->close();

輸出

獲得的輸出如下所示:

global innodb_ft_aux_table set...!mysqli_result Object
(
    [current_field] => 0
    [field_count] => 6
    [lengths] =>
    [num_rows] => 62
    [type] => 0
)
Table Records:
ID: 1, Title: 教程, Descriptions: 教程是對一個概念的冗長研究
ID: 3, Title: 教程, Descriptions: 教程是對一個概念的冗長研究
var mysql = require("mysql2");
var con = mysql.createConnection({
  host: "localhost",
  user: "root",
  password: "password",
}); //Connecting to MySQL

con.connect(function (err) {
  if (err) throw err;
  //   console.log("Connected successfully...!");
  //   console.log("--------------------------");
  sql = "USE TUTORIALS";
  con.query(sql);

  //create a table...
  sql = `CREATE TABLE blog (ID INT AUTO_INCREMENT NOT NULL, title VARCHAR(255), description TEXT, FULLTEXT ( title, description ) WITH PARSER NGRAM, PRIMARY KEY(id) )ENGINE=INNODB CHARACTER SET UTF8MB4`;
  con.query(sql);

  //insert data
  sql = `INSERT INTO blog (id, title, description) VALUES 
  (NULL, '教程', '教程是對一個概念的冗長研究'), 
  (NULL, '文章', '文章是關於一個概念的基於事實的小資訊')`;
  con.query(sql);

  //we will use the below statement to see how the ngram tokenizes the data:
  sql = "SET GLOBAL innodb_ft_aux_table = 'TUTORIALS/blog'";
  con.query(sql);

  //display the table details;
  sql = `SELECT * FROM blog WHERE MATCH (title, description) AGAINST ('教程')`;
  con.query(sql, function (err, result) {
    if (err) throw err;
    console.log(result);
  });
});                   

輸出

獲得的輸出如下所示:

[ { id: 1, title: '教程', description: '教程是對一個概念的冗長研究' } ]
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

public class NgRamFSearch {
   public static void main(String[] args) {
      String url = "jdbc:mysql://:3306/TUTORIALS";
      String username = "root";
      String password = "password";
      try {
         Class.forName("com.mysql.cj.jdbc.Driver");
         Connection connection = DriverManager.getConnection(url, username, password);
         Statement statement = connection.createStatement();
         System.out.println("Connected successfully...!");

         //creating a table that takes fulltext column with parser ngram...!
         String sql = "CREATE TABLE blog (id INT AUTO_INCREMENT NOT NULL, title VARCHAR(255), description TEXT," + 
         " FULLTEXT ( title, description ) WITH PARSER NGRAM, PRIMARY KEY(id) )ENGINE=INNODB CHARACTER SET UTF8MB4";
         statement.execute(sql);
         //System.out.println("Table created successfully...!");

         //inserting data to the table
         String insert = "INSERT INTO blog (id, title, description) VALUES (NULL, '教程', '教程是對一個概念的冗長研究')," +
                 " (NULL, '文章', '文章是關於一個概念的基於事實的小資訊')";
         statement.execute(insert);
         //System.out.println("Data inserted successfully...!");

         //we will use the below statement to see how the ngram tokenizes the data:
         String set_global = "SET GLOBAL innodb_ft_aux_table = 'TUTORIALS/blog'";
         statement.execute(set_global);

         ResultSet resultSet = statement.executeQuery("SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_INDEX_CACHE ORDER BY doc_id, position ");
         System.out.println("Information schema order by Id...!");
         while (resultSet.next()){
            System.out.println(resultSet.getString(1)+" "+resultSet.getString(2));
         }

         //displaying the data...!
         String query = "SELECT * FROM blog WHERE MATCH (title, description) AGAINST ('教程')";
         ResultSet resultSet1 =  statement.executeQuery(query);
         System.out.println("table records:");
         while (resultSet1.next()){
            System.out.println(resultSet1.getString(1)+" "+resultSet1.getString(2)+ " "+resultSet1.getString(3));
         }

         connection.close();
      } catch (Exception e) {
         System.out.println(e);
      }
   }
}    

輸出

獲得的輸出如下所示:

Connected successfully...!
Information schema order by Id...!
教程 2
教程 2
程是 2
是對 2
對一 2
一個 2
個概 2
概念 2
唸的 2
的冗 2
冗長 2
長研 2
研究 2
文章 3
文章 3
章是 3
是關 3
關於 3
於一 3
一個 2
個概 2
概念 2
唸的 2
的基 3
基於 3
於事 3
事實 3
實的 3
的小 3
小信 3
資訊 3
table records:
1 教程 教程是對一個概念的冗長研究
import mysql.connector
# Establishing the connection
connection = mysql.connector.connect(
    host='localhost',
    user='root',
    password='password',
    database='tut'
)
# Creating a cursor object
cursorObj = connection.cursor()
# Create the blog table with NGRAM full-text parser
create_table_query = '''
CREATE TABLE blog (
    id INT AUTO_INCREMENT NOT NULL,
    title VARCHAR(255),
    description TEXT,
    FULLTEXT (title, description) WITH PARSER NGRAM,
    PRIMARY KEY(id)
) ENGINE=INNODB CHARACTER SET UTF8MB4;
'''
cursorObj.execute(create_table_query)
print("Table 'blog' is created successfully!")
# Set the character set to UTF8MB4
set_charset_query = "SET NAMES UTF8MB4;"
cursorObj.execute(set_charset_query)
print("Character set is set to UTF8MB4.")
# Insert data into the blog table
data_to_insert = [
    ('教程', '教程是對一個概念的冗長研究'),
    ('文章', '文章是關於一個概念的基於事實的小資訊')
]
insert_query = "INSERT INTO blog (title, description) VALUES (%s, %s)"
cursorObj.executemany(insert_query, data_to_insert)
connection.commit()
print("Data inserted into the 'blog' table.")
# Query the INNODB_FT_INDEX_CACHE table to get the full-text index information
query_index_cache_query = "SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_INDEX_CACHE ORDER BY doc_id, position;"
cursorObj.execute(query_index_cache_query)
results = cursorObj.fetchall()
print("Results of INNODB_FT_INDEX_CACHE table:")
for row in results:
    print(row)
# Close the cursor and connection
cursorObj.close()
connection.close() 

輸出

獲得的輸出如下所示:

Table 'blog' is created successfully!
Character set is set to UTF8MB4.
Data inserted into the 'blog' table.
Results of INNODB_FT_INDEX_CACHE table:
廣告