<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom"><title type="text">博客园_破宝</title><subtitle type="text">我是一块破破烂烂的宝贝石头。</subtitle><id>http://feed.cnblogs.com/blog/u/14391/rss</id><updated>2011-06-02T20:51:58Z</updated><author><name>破宝</name><uri>http://www.cnblogs.com/percyboy/</uri></author><generator>CNBlogs BlogServer</generator><link rel="alternate" type="text/html" href="http://www.cnblogs.com/percyboy/"/><link rel="self" type="application/atom+xml" href="http://feed.cnblogs.com/blog/u/14391/rss"/><entry><id>http://www.cnblogs.com/percyboy/archive/2011/02/11/1951053.html</id><title type="text">imdict-chinese-analyzer .NET转写版</title><summary type="text">中文切词领域，中科院开发的 ICTCLAS 占有重要一席，号称是世界上最好的中文分词系统。ICTCLAS 初期曾发布过一个免费版本（C++），采用&amp;amp;ldquo;自然语言处理开放资源许可证&amp;amp;rdquo;公开。后来走向商业开发道路，最新版本是 ICTCLAS 2010，提供有 C++, Java, C# 等多种版本可供购买。从 ICTCLAS Free 版有一些衍生版本：ictclas4j 是张新波（sinboy）移植的 Java 版本，SharpICTCLAS 是吕震宇移植的 C# 版本。这两个版本也采用&amp;amp;ldquo;自然语言处理开放资源许可证&amp;amp;rdquo;。20</summary><published>2011-02-10T21:04:00Z</published><updated>2011-02-10T21:04:00Z</updated><author><name>破宝</name><uri>http://www.cnblogs.com/percyboy/</uri></author><link rel="alternate" href="http://www.cnblogs.com/percyboy/archive/2011/02/11/1951053.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/percyboy/archive/2011/02/11/1951053.html"/><content type="html">&lt;p&gt;中文切词领域，中科院开发的 &lt;a href="http://ictclas.org" target="_blank"&gt;ICTCLAS&lt;/a&gt; 占有重要一席，号称是世界上最好的中文分词系统。ICTCLAS 初期曾发布过一个&lt;a href="http://www.nlp.org.cn/project/project.php?proj_id=6" target="_blank"&gt;免费版本&lt;/a&gt;（C++），采用&amp;ldquo;自然语言处理开放资源许可证&amp;rdquo;公开。后来走向商业开发道路，最新版本是 ICTCLAS 2010，提供有 C++, Java, C# 等多种版本可供购买。&lt;/p&gt;&#xD;
&lt;p&gt;从 ICTCLAS Free 版有一些衍生版本：ictclas4j 是&lt;a href="http://blog.csdn.net/sinboy" target="_blank"&gt;张新波（sinboy）&lt;/a&gt;移植的 Java 版本，SharpICTCLAS 是&lt;a href="http://zhenyulu.cnblogs.com/" target="_blank"&gt;吕震宇&lt;/a&gt;移植的 C# 版本。这两个版本也采用&amp;ldquo;自然语言处理开放资源许可证&amp;rdquo;。&lt;/p&gt;&#xD;
&lt;p&gt;2009年中科院高小平针对 Lucene 用 Java 重写了 ICTCLAS 代码，采用 Apache Licence 2.0 协议&lt;a href="http://code.google.com/p/imdict-chinese-analyzer/" target="_blank"&gt;公开了源码和词库数据&lt;/a&gt;，目前已并入了 Lucene contrib 代码树中。我花了些时间将这个版本转写为 C# 版。&lt;/p&gt;&#xD;
&lt;p&gt;下图表示了这些版本间的关系：（红色为商业软件，紫色为&amp;ldquo;自然语言处理开放资源许可证&amp;rdquo;，绿色为&amp;ldquo;Apache Licence 2.0&amp;rdquo;）&lt;/p&gt;&#xD;
&lt;p&gt;&lt;img src="http://pic002.cnblogs.com/images/2011/10836/2011021103471227.png" /&gt;&lt;/p&gt;&#xD;
&lt;p&gt;目前讨论较多的切词器，如&lt;a href="http://code.google.com/p/paoding/" target="_blank"&gt;庖丁解牛&lt;/a&gt;、&lt;a href="http://pangusegment.codeplex.com/" target="_blank"&gt;盘古分词&lt;/a&gt;等，多采用查词典的方式切分，词典质量决定切分效果。&lt;/p&gt;&#xD;
&lt;p&gt;ICTCLAS 切词基于概率统计的语料库（高小平称之为&amp;ldquo;智能词典&amp;rdquo;），算法基于&amp;ldquo;层叠式隐含马尔可夫模型&amp;rdquo;（Hierarchical Hidden&amp;nbsp;Markov Model, HHMM）。仅就算法而言，应该说这是一种较为先进的方法，Google 研究员也说&amp;ldquo;统计语言模型比任何已知的借助某种规则的解决方法都有效&amp;rdquo;（&lt;a href="http://www.google.com.hk/ggblog/googlechinablog/2006/04/blog-post_2507.html" target="_blank"&gt;数学之美系列&lt;/a&gt;）。&lt;/p&gt;&#xD;
&lt;p&gt;但是高小平贡献的版本（包括我转写的版本），从 ICTCLAS 中去除了一些功能，如：词性标注、人名识别、日期识别等等，特别是后两项，切分结果在这方面不是很理想。另外，&amp;ldquo;智能词典&amp;rdquo;里存储的是各种词汇出现的概率，是通过机器训练得到的，难以采用人工方式维护，这也是一个不方便之处。&lt;/p&gt;&#xD;
&lt;p&gt;。&lt;/p&gt;&#xD;
&lt;p&gt;关于我转写的版本：基本上没有改变高小平版的代码逻辑，仅将 Lucene Tokenizer 等相关接口改用了新版的接口标准（IncrementToken 方式）。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;a style="background: yellow; display: inline-block; padding: 10px 20px;" href="http://files.cnblogs.com/percyboy/imdict-chinese-analyzer-.net.rar"&gt;下载&lt;/a&gt; (Apache Licence 2.0)&lt;/p&gt;&lt;img src="http://www.cnblogs.com/percyboy/aggbug/1951053.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/percyboy/archive/2011/02/11/1951053.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/percyboy/archive/2011/02/10/1950492.html</id><title type="text">SQLite全文检索(2)</title><summary type="text">距上一篇有好久了，因为乏人问津所以一直也没写这第二篇。年前看到有人给我发消息问 SQLite 全文检索的事，我想哪怕只有一个人看吧，我也整理整理。这一篇就写写如何扩展 SQLite 使它支持东亚文字的切词。熟悉 Lucene 的童鞋大概知道，切词是在索引时进行的。对 SQLite 来说，也就是 INSERT UPDATE 时发生切词。SQLite 的做法是，在定义 FTS 虚表时指定切词器：还记得&amp;amp;ldquo;porter&amp;amp;rdquo;吗？当然这里不是哈利波特，其实是指Martin Porter设计的切词算法。或许你在 Lucene 里见过，这个切词器主要用于英语词的整形（如复</summary><published>2011-02-09T23:33:00Z</published><updated>2011-02-09T23:33:00Z</updated><author><name>破宝</name><uri>http://www.cnblogs.com/percyboy/</uri></author><link rel="alternate" href="http://www.cnblogs.com/percyboy/archive/2011/02/10/1950492.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/percyboy/archive/2011/02/10/1950492.html"/><content type="html">&lt;p&gt;距上一篇有好久了，因为乏人问津所以一直也没写这第二篇。年前看到有人给我发消息问 SQLite 全文检索的事，我想哪怕只有一个人看吧，我也整理整理。这一篇就写写如何扩展 SQLite 使它支持东亚文字的切词。&lt;/p&gt;&#xD;
&lt;p&gt;熟悉 Lucene 的童鞋大概知道，切词是在索引时进行的。对 SQLite 来说，也就是 INSERT UPDATE 时发生切词。SQLite 的做法是，在定义 FTS 虚表时指定切词器：&lt;/p&gt;&#xD;
&lt;div &gt;&#xD;
&lt;pre &gt;CREATE VIRTUAL TABLE pages USING fts3(title, body, tokenize=porter);&#xD;
&lt;/pre&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;p&gt;还记得&amp;ldquo;porter&amp;rdquo;吗？当然这里不是哈利波特，其实是指&amp;nbsp;&lt;a target="_blank" href="http://tartarus.org/~martin/PorterStemmer/"&gt;Martin Porter&lt;/a&gt;&amp;nbsp;设计的切词算法。或许你在 Lucene 里见过，这个切词器主要用于英语词的整形（如复数变单数，去词尾变词根等等）。porter 是 SQLite 内置的切词器，可以直接使用。而我们需要扩展自己的切词器。&lt;/p&gt;&#xD;
&lt;p&gt;SQLite 是一个 C 语言开发的、定位于嵌入型的轻量级数据库，因此它的切词器接口也是以 C 语言的形式给出的。这里仅简单介绍一下：&lt;/p&gt;&#xD;
&lt;p&gt;(1) SQLite 要求你首先创建一个结构：&lt;/p&gt;&#xD;
&lt;div &gt;&#xD;
&lt;pre &gt;[StructLayoutAttribute(LayoutKind.Sequential)]&#xD;
internal struct sqlite3_tokenizer_module&#xD;
{&#xD;
	public int iVersion;&#xD;
	public sqlite3_tokenizer_module_xCreate xCreate;&#xD;
	public sqlite3_tokenizer_module_xDestroy xDestroy;&#xD;
	public sqlite3_tokenizer_module_xOpen xOpen;&#xD;
	public sqlite3_tokenizer_module_xClose xClose;&#xD;
	public sqlite3_tokenizer_module_xNext xNext;&#xD;
}&#xD;
&lt;/pre&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;p&gt;除了 iVersion 是常数之外，其余几个字段都是函数指针，分别是切词器生命周期各阶段的回调函数。其中 xNext 函数是重点，用于返回下一个切好的词。&lt;/p&gt;&#xD;
&lt;p&gt;(2) 然后将上面的这个结构体的内存地址，通过下面的 SQL 语句告诉给 SQLite：&lt;/p&gt;&#xD;
&lt;div &gt;&#xD;
&lt;pre &gt;SELECT fts3_tokenizer('demo', &amp;lt;sqlite3_tokenizer_module ptr&amp;gt;);&#xD;
&lt;/pre&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;p&gt;比如这句注册了名叫 demo 的切词器。注册之后就可以使用这个切词器了：&lt;/p&gt;&#xD;
&lt;div &gt;&#xD;
&lt;pre &gt;CREATE VIRTUAL TABLE pages USING fts3(title, body, tokenize=demo);&#xD;
&lt;/pre&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;p&gt;简单说起来只是这两步，但实现过程对于 C# 程序员来说，还是不太容易的，因为我们并不经常直接和函数指针、内存地址这些东西打交道。&lt;/p&gt;&#xD;
&lt;p&gt;实现过程中比较关键的几点是：&lt;/p&gt;&#xD;
&lt;p&gt;(1) 必须将回调函数，以及上面提到的接口 module 结构体，放到非托管内存领域。因为托管内存是 CLR 管理的，垃圾回收随时会启动，对象也可能被移动位置，回调函数和内存地址随时都会失效（尤其是切词处理时有大量数据进进出出，垃圾回收也会很频繁）。&lt;/p&gt;&#xD;
&lt;p&gt;Tip：可以先用 Marshal.AllocHGlobal 申请一段非托管内存，然后用 Marshal.StructureToPtr 将结构体写入非托管内存。但必须注意：放入非托管内存空间的结构体，一定要在使用完毕后手动释放（Marshal.FreeHGlobal）。&lt;/p&gt;&#xD;
&lt;p&gt;(2) 即便写入了非托管内存，关了程序切词器也就没了，所以每次连接到 SQLite 时，只要操作将要涉及到 FTS 虚表，都必须重新注册切词器。&lt;/p&gt;&#xD;
&lt;hr /&gt;&#xD;
&lt;p&gt;好了，下面开始上主菜～&lt;/p&gt;&#xD;
&lt;p&gt;你已经看到，这个实现过程中有大量的代码要在非托管内存进行，需要小心翼翼的处理，一不留神就会出问题。因此，有必要做一些封装，将这些实现细节隐藏起来，方便 .NET 开发者扩展新的切词器。&lt;/p&gt;&#xD;
&lt;p&gt;下面这个是我封装后的抽象基类，只贴出接口部分：&lt;/p&gt;&#xD;
&lt;div &gt;&#xD;
&lt;pre &gt;public abstract class SQLiteFtsTokenizer&#xD;
{&#xD;
	/// &amp;lt;summary&amp;gt;&#xD;
	/// 切词器名称。也就是 tokenize=**** 处写的那个名称，请重写此属性。请用英文字母。&#xD;
	/// &amp;lt;/summary&amp;gt;&#xD;
	public virtual string Name&#xD;
	{&#xD;
		get { return "custom"; }&#xD;
	}&#xD;
&#xD;
	/// &amp;lt;summary&amp;gt;&#xD;
	/// 注册切词器。参数是 SQLite 连接。&#xD;
	/// &amp;lt;/summary&amp;gt;&#xD;
	public void RegisterMe(SQLiteConnection connection) { }&#xD;
&#xD;
	/// &amp;lt;summary&amp;gt;&#xD;
	/// 切词器刚创建时的处理。（可选）&#xD;
	/// &amp;lt;/summary&amp;gt;&#xD;
	/// &amp;lt;param name="tokenizerArgument"&amp;gt;The argument for tokenizer.&amp;lt;/param&amp;gt;&#xD;
	protected virtual void OnCreate(string tokenizerArgument) { }&#xD;
&#xD;
	/// &amp;lt;summary&amp;gt;&#xD;
	/// 切词器销毁前的处理。（可选）&#xD;
	/// &amp;lt;/summary&amp;gt;&#xD;
	protected virtual void OnDestroy() { }&#xD;
&#xD;
	/// &amp;lt;summary&amp;gt;&#xD;
	/// 切词器开始工作前的初始化。&#xD;
	/// &amp;lt;/summary&amp;gt;&#xD;
	protected abstract void PrepareToStart();&#xD;
&#xD;
	/// &amp;lt;summary&amp;gt;&#xD;
	/// SQLite 传出的、需要切词的字符串（只读）。&#xD;
	/// &amp;lt;/summary&amp;gt;&#xD;
	protected string InputString&#xD;
	{&#xD;
		get { return this.inputString; }&#xD;
	}&#xD;
&#xD;
	/// &amp;lt;summary&amp;gt;&#xD;
	/// 尝试读取下一个 Token。&#xD;
	/// &amp;lt;/summary&amp;gt;&#xD;
	/// &amp;lt;returns&amp;gt;成功读取 Token 返回 true，读取结束返回 false。&amp;lt;/returns&amp;gt;&#xD;
	protected abstract bool MoveNext();&#xD;
&#xD;
	/// &amp;lt;summary&amp;gt;&#xD;
	/// 读取到的 Token。&#xD;
	/// &amp;lt;/summary&amp;gt;&#xD;
	protected string Token&#xD;
	{&#xD;
		get { return this.token; }&#xD;
		set { this.token = value; }&#xD;
	}&#xD;
&#xD;
	/// &amp;lt;summary&amp;gt;&#xD;
	/// 读取到的 Token 在 InputString 的位置（从 0 起算）。&#xD;
	/// &amp;lt;/summary&amp;gt;&#xD;
	protected int TokenIndexOfString&#xD;
	{&#xD;
		get { return this.tokenIndexOfString; }&#xD;
		set { this.tokenIndexOfString = value; }&#xD;
	}&#xD;
&#xD;
	/// &amp;lt;summary&amp;gt;&#xD;
	/// 下一次读取应该开始的位置（从 0 起算）。如果下一次读取正好在此次 Token 的后面，可以返回 -1。（目前我还未发现它的影响）&#xD;
	/// &amp;lt;/summary&amp;gt;&#xD;
	protected int NextIndexOfString&#xD;
	{&#xD;
		get { return this.nextIndexOfString; }&#xD;
		set { this.nextIndexOfString = value; }&#xD;
	}&#xD;
&#xD;
	/// &amp;lt;summary&amp;gt;&#xD;
	/// 开发测试用。返回值是切完的 Token 列表。&#xD;
	/// &amp;lt;/summary&amp;gt;&#xD;
	public List&amp;lt;string&amp;gt; TestMe(string inputString) { }&#xD;
}&#xD;
&#xD;
&lt;/pre&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;p&gt;有了这个基类，扩展出我们自己的切词器就比较容易了。我在下载压缩包里放了一个 CJKTokenizer。参考了车东为 Lucene 写的 CJKTokenizer 的做法，采用的是二元切词法，比如&amp;ldquo;清华大学&amp;rdquo;将切为&amp;ldquo;清华/华大/大学&amp;rdquo;三个 Token。&lt;/p&gt;&#xD;
&lt;p&gt;最后，看一下自定义 Tokenizer 的使用代码示例：&lt;/p&gt;&#xD;
&lt;div &gt;&#xD;
&lt;pre &gt;using (SQLiteConnection connection = new SQLiteConnection("Data Source=filename"))&#xD;
{&#xD;
    CJKTokenizer tokenizer = new CJKTokenizer();&#xD;
    connection.Open();&#xD;
    tokenizer.RegisterMe(connection); //注册切词器&#xD;
&#xD;
    //建表&#xD;
    SQLiteCommand cmd = new SQLiteCommand(connection);&#xD;
    cmd.CommandText = "CREATE VIRTUAL TABLE docs USING fts3(title, content, tokenize=cjk)";&#xD;
    cmd.ExecuteNonQuery();&#xD;
&#xD;
    //插入数据&#xD;
    cmd.CommandText = "INSERT INTO docs (title, content) VALUES (?, ?)";&#xD;
    SQLiteParameter p1 = new SQLiteParameter();&#xD;
    p1.DbType = System.Data.DbType.String;&#xD;
    p1.Value = "测试标题";&#xD;
    cmd.Parameters.Add(p1);&#xD;
    SQLiteParameter p2 = new SQLiteParameter();&#xD;
    p2.DbType = System.Data.DbType.String;&#xD;
    p1.Value = "测试内容";&#xD;
    cmd.Parameters.Add(p2);&#xD;
    cmd.ExecuteNonQuery();&#xD;
&#xD;
    //检索&#xD;
    cmd.CommandText = "SELECT docid, title, content FROM docs WHERE docs MATCH '测试'";&#xD;
    SQLiteDataReader dr = cmd.ExecuteReader();&#xD;
    while(dr.Read())&#xD;
    {&#xD;
        //...&#xD;
    }&#xD;
    dr.Close();&#xD;
&#xD;
    connection.Close();&#xD;
}&#xD;
&#xD;
&lt;/pre&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;p&gt;其实只多了两行代码：一行 new ，一行注册切词器。&lt;/p&gt;&#xD;
&lt;p&gt;现有的切词器大多针对 Lucene 开发，如果不想改动太多代码，可以采用&amp;ldquo;适配器模式&amp;rdquo;，为 Lucene Tokenizer（TokenFilter）套一个 Adapter。压缩包里有一份毛胚版的参考实现。&lt;/p&gt;&#xD;
&lt;p&gt;（此系列的下一篇将写写根据相关度排序的话题，看看有没有人捧场吧～）&lt;/p&gt;&#xD;
&lt;p&gt;&lt;a style="display: inline-block; background: yellow; padding: 10px 20px;" href="http://files.cnblogs.com/percyboy/SQLite.rar"&gt;代码下载&lt;/a&gt;&lt;/p&gt;&lt;img src="http://www.cnblogs.com/percyboy/aggbug/1950492.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/percyboy/archive/2011/02/10/1950492.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/percyboy/archive/2010/08/18/1802794.html</id><title type="text">SQLite全文检索(1)</title><summary type="text">说到全文检索，目前比较流行、也比较成熟的选择是 Lucene.net。今天给大家介绍的是 SQLite 内置的全文检索功能，以如此小的 footprint 实现全文检索功能，我想还是有一定吸引力的。国内目前涉及此领域的文章还很少，.net 圈估计本文是第一篇吧，能力有限，多多包涵。</summary><published>2010-08-18T11:52:00Z</published><updated>2010-08-18T11:52:00Z</updated><author><name>破宝</name><uri>http://www.cnblogs.com/percyboy/</uri></author><link rel="alternate" href="http://www.cnblogs.com/percyboy/archive/2010/08/18/1802794.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/percyboy/archive/2010/08/18/1802794.html"/><content type="html">&lt;p&gt;SQLite 是一款非常轻量的嵌入型数据库，没有独立的进程，非常小的 footprint，零配置，支持事务，&amp;ldquo;public domain&amp;rdquo;开源，对于客户端程序来说已经游刃有余。[&lt;a href="http://www.sqlite.org/docs.html"&gt;更多的介绍&lt;/a&gt;，&lt;a href="http://sqlite.phxsoftware.com/"&gt;System.Data.SQLite 库&lt;/a&gt;]&lt;/p&gt;&#xD;
&lt;p&gt;说到全文检索，目前比较流行、也比较成熟的选择是 &lt;a href="http://lucene.apache.org/lucene.net/"&gt;Lucene.net&lt;/a&gt;。今天给大家介绍的是 SQLite 内置的全文检索功能，以如此小的 footprint 实现全文检索功能，我想还是有一定吸引力的。国内目前涉及此领域的文章还很少，.net 圈估计本文是第一篇吧，能力有限，多多包涵。&lt;/p&gt;&#xD;
&lt;p&gt;要使用 SQLite 全文检索，首先要创建 VIRTUAL TABLE：&lt;/p&gt;&#xD;
&lt;pre &gt;CREATE VIRTUAL TABLE pages USING fts3(title, body);&lt;/pre&gt;&#xD;
&lt;p&gt;虚表 pages 包含 title, body 两个文本字段，此外还有一个 docid 整数型的内置字段（你可以自行为 docid 赋值，也可以插入 NULL 让系统自动分配）。虚表可以和其他普通表类似的操作、连接等。&lt;/p&gt;&#xD;
&lt;pre &gt;INSERT INTO pages (docid, title, body) VALUES (1, 'hello, world', '"hello, world"is my first line of data.');&#xD;
UPDATE pages SET title='hello, world !' WHERE docid=1;&#xD;
SELECT title, body FROM pages INNER JOIN pageinfo ON pageinfo.docid=pages.docid WHERE pageinfo.SiteName='sina';&#xD;
&lt;/pre&gt;&#xD;
&lt;p&gt;虽然你可以写 WHERE title=&amp;rsquo;hello, world !&amp;rsquo; 这样的查询，但这样做首先效率上比较糟糕（SQLite 将做全表扫描），而且体现不出全文检索的好处来。实际上应该是用 MATCH 运算符：&lt;/p&gt;&#xD;
&lt;pre &gt;SELECT title, body FROM pages WHERE pages MATCH 'world';&#xD;
SELECT title, body FROM pages WHERE title MATCH 'world';&lt;/pre&gt;&#xD;
&lt;p&gt;注意这两句，前一个 MATCH 左边写了表名，后一个写的是列名。后一个仅搜索 title 列，前一个是搜索全部列（docid 列以外）。&lt;/p&gt;&#xD;
&lt;p&gt;MATCH 右侧的表达式支持模糊查询、支持指定列查询、支持 AND/OR/NEAR/NOT 等运算：&lt;/p&gt;&#xD;
&lt;pre &gt;SELECT title, body FROM pages WHERE pages MATCH 'hel*';&#xD;
SELECT title, body FROM pages WHERE pages MATCH 'title:hello';&#xD;
SELECT title, body FROM pages WHERE pages MATCH 'hello AND world';&#xD;
SELECT title, body FROM pages WHERE pages MATCH '(hello NEAR world) OR (program AND language)';&#xD;
&lt;/pre&gt;&#xD;
&lt;p&gt;但要注意只能出现一次 MATCH 判断，WHERE title MATCH 'hello' AND body MATCH 'world' 是不行的，可以改作 WHERE pages MATCH 'title:hello AND body:world'。&lt;/p&gt;&#xD;
&lt;p&gt;我们一般都希望检索结果能给出一片段，并标出所查关键词，此时可以使用 snippet 函数：&lt;/p&gt;&#xD;
&lt;pre &gt;SELECT title, snippet(pages, '&amp;lt;strong&amp;gt;', '&amp;lt;/strong&amp;gt;', '...') FROM pages WHERE pages MATCH 'hello';&lt;/pre&gt;&#xD;
&lt;p&gt;将会选出一两个包含检索关键词的片段，并在关键词两边用 &amp;lt;strong&amp;gt; 标记出来； '...' 将用在片段的间隔和末尾。如果略去后三个参数，则默认是以 &amp;lt;b&amp;gt; 标签标关键词。&lt;/p&gt;&#xD;
&lt;p&gt;【提示】&lt;/p&gt;&#xD;
&lt;p&gt;1. 从sqlite.org 下载的 SQLite 版本默认是不支持全文检索功能的，需要修改编译参数重新编译。不过，.net 开发者一般直接使用从 phxsoftware.com 下载的 System.Data.SQLite.dll 版本，这个版本是默认开启全文检索功能的。&lt;/p&gt;&#xD;
&lt;p&gt;2. SQLite 内置的切词器只支持西方文字，对缺乏空格分隔的东亚文字无能为力，我们需要做一些扩展让它支持东亚文字的切词。我们下一篇就来讨论这一话题。&lt;/p&gt;&#xD;
&lt;p&gt;3. 我们希望检索结果能按照相关度排序，这需要你先研究明白 offset，matchinfo 函数的用法，然后根据 SQLite 的&lt;a href="http://www.sqlite.org/fts3.html"&gt;官方文档&lt;/a&gt;的示例，写出自定义的相关度计算函数，然后再据此进行排序。如果有机会，我再单独写一篇这个话题的内容。&lt;/p&gt;&lt;img src="http://www.cnblogs.com/percyboy/aggbug/1802794.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/percyboy/archive/2010/08/18/1802794.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/percyboy/archive/2009/12/24/zhubajie.html</id><title type="text">80块钱毁掉“猪八戒”的信誉</title><summary type="text">简单列一列“猪八戒”的罪状：* “猪八戒”贪图区区80块钱的小利，就无视雇主、投标者双方的利益。* 仅仅由于有人投诉，就做出作弊的裁断。丝毫不和当事人联系、以确认身份，仅凭投诉人一面之词就做出决定。* 不仅事前没有联系当事人，事后也不主动通知，企图蒙混过关，昧掉80块钱。* 违背雇主意愿，随意添加“入围”稿件。* 以公司内部的规定为由，拒不给出所谓的作弊证据。雇主和投标者双方的利益都得不到保障，谁还敢信任你们？“猪八戒”你要记住：好事不出门，坏事传千里！作恶一次，即使行善百次也是无法弥补的！丢了信誉，你的“威客”也就快玩完了。</summary><published>2009-12-24T00:27:00Z</published><updated>2009-12-24T00:27:00Z</updated><author><name>破宝</name><uri>http://www.cnblogs.com/percyboy/</uri></author><link rel="alternate" href="http://www.cnblogs.com/percyboy/archive/2009/12/24/zhubajie.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/percyboy/archive/2009/12/24/zhubajie.html"/></entry><entry><id>http://www.cnblogs.com/percyboy/archive/2009/06/15/2070003.html</id><title type="text">有点郁闷：MSDN文档中MidpointRounding.AwayFromZero的翻译错误</title><summary type="text">很早就知道 Math.Round 方法实际上并不是我们上学时学到的“四舍五入”，而是 IEEE 标准定义的“银行家舍入”算法，通俗说法是“四舍六入五成双”（1.5→2, 4.5→4）。当需要用到“四舍五入”算法时，.NET 1.x 中是需要自己实现(比如一种思路：正数加0.5后Math.Floor，负数减0.5后Math.Ceiling)。.NET 2.0 开始，Math.Round 方法提供了一个枚举选项 MidpointRounding.AwayFromZero 可以用来实现传统意义上的“四舍五入”。即： Math.Round(4.5, MidpointRounding.AwayFrom.</summary><published>2009-06-15T03:53:00Z</published><updated>2009-06-15T03:53:00Z</updated><author><name>破宝</name><uri>http://www.cnblogs.com/percyboy/</uri></author><link rel="alternate" href="http://www.cnblogs.com/percyboy/archive/2009/06/15/2070003.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/percyboy/archive/2009/06/15/2070003.html"/></entry><entry><id>http://www.cnblogs.com/percyboy/archive/2009/02/20/2070004.html</id><title type="text">当 ASP.net Mobile Controls 碰到“中国特色”的 CMWAP / UNIWAP</title><summary type="text"/><published>2009-02-19T16:35:00Z</published><updated>2009-02-19T16:35:00Z</updated><author><name>破宝</name><uri>http://www.cnblogs.com/percyboy/</uri></author><link rel="alternate" href="http://www.cnblogs.com/percyboy/archive/2009/02/20/2070004.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/percyboy/archive/2009/02/20/2070004.html"/></entry><entry><id>http://www.cnblogs.com/percyboy/archive/2008/09/24/2070005.html</id><title type="text">闲话“正版”：正版软件和盗版软件的区别到底是什么？</title><summary type="text"/><published>2008-09-24T00:22:00Z</published><updated>2008-09-24T00:22:00Z</updated><author><name>破宝</name><uri>http://www.cnblogs.com/percyboy/</uri></author><link rel="alternate" href="http://www.cnblogs.com/percyboy/archive/2008/09/24/2070005.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/percyboy/archive/2008/09/24/2070005.html"/></entry><entry><id>http://www.cnblogs.com/percyboy/archive/2008/09/24/2070006.html</id><title type="text">闲话“正版”：真是因为“缺钱”吗？</title><summary type="text">最近有一条争议不小的新闻：微软（中国）在国庆节期间的促销活动，Office家庭版降价到199元。这让我想到了我三年多前的一篇博文，梦呓：微软在中国的新定价策略——比例折算法。这个价格可以说比“比例折算法”还要实惠。不过遗憾的是，网上投票情况显示，半数以上的网民对此促销活动并不买账，认为只要比盗版价格高就绝不买正版。这也如实反映了国民对“正版”的认识程度。很多人拒绝正版的理由是没钱。但另一方面却发现，大家对于硬件选择相当的“阔绰”“奢侈”，全部组件都要选择最高等级，CPU要市场上最快的，硬盘要转速高容量大的，要外加两三千的专业级独立显卡，再加杜比5.1的家庭影院，呵呵，无论自己的实际需求是否真的</summary><published>2008-09-23T23:00:00Z</published><updated>2008-09-23T23:00:00Z</updated><author><name>破宝</name><uri>http://www.cnblogs.com/percyboy/</uri></author><link rel="alternate" href="http://www.cnblogs.com/percyboy/archive/2008/09/24/2070006.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/percyboy/archive/2008/09/24/2070006.html"/></entry><entry><id>http://www.cnblogs.com/percyboy/archive/2008/06/16/2070007.html</id><title type="text">又一个疑似Bug： XmlDataSource 控件的 Data 属性动态改变时，缓存不会自动失效</title><summary type="text"/><published>2008-06-16T12:16:00Z</published><updated>2008-06-16T12:16:00Z</updated><author><name>破宝</name><uri>http://www.cnblogs.com/percyboy/</uri></author><link rel="alternate" href="http://www.cnblogs.com/percyboy/archive/2008/06/16/2070007.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/percyboy/archive/2008/06/16/2070007.html"/></entry><entry><id>http://www.cnblogs.com/percyboy/archive/2008/06/10/2070008.html</id><title type="text">立此存照：System.Net.Mail 的 bug</title><summary type="text">痛苦了debug了一个多钟头，后来终于在网络上找到了这篇“救星”文章：http://columns.chicken-house.net/blogs/chicken/archive/2007/04/06/system-net-mail-bug.aspx立此存照，如果您也碰到同样问题，希望能够能比我更幸运些，更早找到问题所在。症状是：调用 SmtpClient.Send 方法后，出现 System.FormatException，英文消息为“An invalid character was found in header value.”中文消息是：“邮件标头中找到无效字符”。原因是在 SmtpCl</summary><published>2008-06-10T10:25:00Z</published><updated>2008-06-10T10:25:00Z</updated><author><name>破宝</name><uri>http://www.cnblogs.com/percyboy/</uri></author><link rel="alternate" href="http://www.cnblogs.com/percyboy/archive/2008/06/10/2070008.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/percyboy/archive/2008/06/10/2070008.html"/></entry></feed>
