做 NMPARegHub 时跑真实数据(中国政府网政策文件库,62 份文件)暴露出三个 bug。 它们都不是想出来的,是被真实数据跑出来的,且都有回归测试覆盖。
一、NULL AND x 永不为真
一个看起来完全合理的排除查询:
SELECT * FROM docs
WHERE title LIKE ? -- '药品%'
AND doc_number NOT LIKE ? -- '%化妆品%'
问题出在无发文字号的文件上:它们的 doc_number 是 NULL。
NULL NOT LIKE '%化妆品%' 求值为 NULL,而 TRUE AND NULL 是 NULL,
WHERE 只保留 TRUE,于是这些行被静默丢弃。
结果:库里 62 份文件,其中一大半没有发文字号,查询返回的比例严重偏低, 但语句没有报错,结果看起来”正常”。
修复是把它写成显式处理:
AND COALESCE(doc_number, '') NOT LIKE ?
这类 bug 在生产环境可以潜伏很久——它不产生错误,只产生一个偏小的数字, 而偏小的数字往往看起来像是”数据本来就少”。
二、FTS5 的 NOT 无法独立成立
全文检索走 FTS5 索引时,纯排除查询(只有 NOT 条件、没有正向词)恒返回空集。
原因是 FTS5 的查询语法里 NOT 是二元运算符,必须有一个左侧项;单独的 NOT x
不是合法查询。于是 药品注册 -化妆品 这种写法在 FTS 层直接失效。
对策:排除条件改用 SQL 谓词实现,不要交给 FTS。即:
WHERE docs_fts MATCH ? -- 只放正向词
AND title NOT LIKE '%化妆品%' -- 排除交给 SQL
分工明确:FTS 负责”包含”,SQL 负责”不包含”。
三、子串误匹配
分类规则里有一条把含”注册”的文件归入”药品注册”。结果**《化妆品注册备案管理 办法》被归成了药品注册** —— 因为它包含”注册”两个字。
修法是引入标题级排除规则:先看标题是否已经命中更具体的类别,命中了就短路, 不再往下走模糊规则。
这类问题的通用形态是:分层分类里,优先级顺序本身就是一条需要测试的业务规则。 不测顺序,规则集再大也会互相污染。
为什么这三个值得单独记
三个 bug 有同一个特征:都不产生错误信号。
- 第一个返回一个偏小的数字
- 第二个返回空集(这个至少”看起来像没数据”)
- 第三个返回一个看起来合理的错误分类
能暴露它们的只有一件事:拿真实、脏、不整齐的数据跑。 用自己造的、字段齐全的示例数据,永远跑不出这三个问题。
延伸:NMPARegHub —— 测试覆盖发文字号 解析(含反例)、归类排除规则、效力推断、FTS 索引同步、纯排除查询与两字词回退。