LIKE 搜索与类型统计:ArkTS 通配符查询在鸿蒙文件库的实战

📅 2026/8/17 19:17:10
LIKE 搜索与类型统计:ArkTS 通配符查询在鸿蒙文件库的实战
实例文件资料库FileLib技术LIKE 通配符、GROUP BY 统计、批量去重导入一、文件库查询的三个技术点文件资料库的查询体系引入了前面实例没有的两类技术LIKE 通配符搜索模糊匹配与 GROUP BY 类型统计分组聚合。本篇文章逐一深入重点讲LIKE 搜索与批量导入去重。二、LIKE 通配符搜索名称/标签命中搜索「需求」要命中「项目需求文档.docx」名称和 tags 含「需求」的文件标签staticasyncsearch(context:common.Context,keyword:string):PromiseFileRecord[]{conststoreawaitFileLibDao.getStore(context);constpredicatesnewrelationalStore.RdbPredicates(FileLibDao.TABLE);predicates.like(name,%${keyword}%).or().like(tags,%${keyword}%).orderByDesc(created_time);constresultawaitstore.query(predicates);returnFileLibDao.collect(result);}SELECT*FROMfile_libWHEREnameLIKE%需求%ORtagsLIKE%需求%ORDERBYcreated_timeDESC;LIKE 的两个通配符%匹配任意长度字符含 0 个——%需求%匹配「需求」在任意位置开头/中间/结尾_匹配单个字符——需求_匹配「需求文」等单字符后缀。%关键字%的语义包含匹配子串——「项目需求文档.docx」含「需求」即命中。三种模式关键字%前缀匹配「项目」开头%关键字后缀匹配「.docx」结尾%关键字%包含匹配任意位置——搜索框最常用。LIKE 的 OR 组合.like(name, ...).or().like(tags, ...)——名称或标签任一命中。为什么用 OR 不用 AND搜索语义是「找包含关键字的文件」——名称命中或标签命中都算不是两个都包含。多字段搜索的 OR 语义是搜索框的标准。LIKE 转义注意关键字本身含%或_时需转义\%——用户搜「100%」会当通配符。Demo 不做转义教学简化生产环境要replace转义关键字。「用户输入即通配符」的风险搜「%」命中所有文件——数据量小时无碍真实搜索要转义。LIKE 的性能%关键字%无法用普通索引前导通配符无法走 B 树——全表扫描。数据量小无所谓大库用全文索引FTS5或分词方案。**「LIKE %kw% 一定全表扫」**是索引知识的常识——知道即可Demo 无需优化。三、GROUP BY 类型统计COUNT SUM 分组类型筛选条的选项与统计卡需要「每类型的文件数与总大小」staticasynctypeStats(context:common.Context):PromiseTypeStat[]{conststoreawaitFileLibDao.getStore(context);constresultawaitstore.querySql(SELECT file_type, COUNT(*) AS cnt, SUM(size) AS total_size FROM${FileLibDao.TABLE}GROUP BY file_type ORDER BY cnt DESC);constlist:TypeStat[][];while(result.goToNextRow()){constitem:TypeStat{fileType:result.getString(result.getColumnIndex(file_type)),count:result.getLong(result.getColumnIndex(cnt)),totalSize:result.getLong(result.getColumnIndex(total_size)),};list.push(item);}result.close();returnlist;}SELECTfile_type,COUNT(*)AScnt,SUM(size)AStotal_sizeFROMfile_libGROUPBYfile_typeORDERBYcntDESC;-- 文档 | 4 | 15872000-- 图片 | 3 | 33554432-- 视频 | 3 | 1300234240-- 压缩包 | 4 | 389231360-- 音频 | 2 | 24117248-- 其他 | 2 | 18874368GROUP BY 的执行逻辑按 file_type 分组同类型的行归一组→ 每组 COUNT组内行数 SUM组内 size 和→ 一行结果。**「分组 → 组内聚合」**是 GROUP BY 的心智模型。COUNT(*) 与 COUNT(column)COUNT(*)计行数含 NULL 行COUNT(size)只计非 NULL——本表无 NULL两者等价。COUNT(*) 是「行数」的通用写法。ORDER BY cnt DESC组结果按文件数降序——文档4 个排第一。分组结果的排序ORDER BY 聚合列cnt允许——SQLite 支持按别名排序。与影音 AVG 的对比AVG 是「全表聚合」一行结果GROUP BY 是「分组聚合」每类一行——实例 13 的全表 AVG 升级为实例 16 的分组统计聚合能力的递进。总统计无分组staticasynctotalStats(context:common.Context):PromiseTotalStat{constresultawaitstore.querySql(SELECT COUNT(*) AS cnt, SUM(size) AS total_size FROM${FileLibDao.TABLE});// 一行cnt total_size}标题栏「18 个文件 · 共 1.7GB」——COUNT SUM 无 GROUP BY 全表聚合一行。有无 GROUP BY 的区别有分组按组分行无分组全表一行——「每类统计」vs「总统计」的 SQL 差异只在 GROUP BY 一句。四、批量导入去重路径先查后插批量导入 18 个文件路径已存在则跳过UNIQUE 去重staticasyncbatchImport(context:common.Context,files:FileRecord[]):Promisenumber{conststoreawaitFileLibDao.getStore(context);letadded0;for(constfoffiles){constpredicatesnewrelationalStore.RdbPredicates(FileLibDao.TABLE);predicates.equalTo(path,f.path);constresultawaitstore.query(predicates);constexistsresult.goToNextRow();result.close();if(exists){continue;// UNIQUE 去重路径已存在则跳过}constvalues:relationalStore.ValuesBucket{name:f.name,file_type:f.fileType,size:f.size,path:f.path,tags:f.tags,note:f.note,created_time:f.createdTime,};awaitstore.insert(FileLibDao.TABLE,values);added;}returnadded;}逐文件「先查后插」每个文件先查 path 是否存在 → 存在跳过、不存在插入——「查重-插入」循环。返回 added实际新增数页面可提示「导入 16 个跳过 2 个重复」。为什么不用 INSERT OR IGNORESQLite 有INSERT OR IGNORE冲突忽略——一条语句搞定去重。但 RdbStore 的 insert API 没有 OR IGNORE 选项需要 executeSql 手写且「先查后插」能拿到 exists 布尔值做更细控制。「应用层先查后插」是 RdbStore 生态的常用去重方案。UNIQUE 约束的双层作用应用层 batchImport 先查后插友好跳过数据库 UNIQUE 兜底即使并发绕过也会拒绝——**「软去重 硬约束」**与健康数据同日 UNIQUE 同模式。批量的性能18 个文件 18 次查询 插入——数据量小没问题。真实大量文件可用事务beginTransaction/commit包裹提速。「循环小批量」教学够用。五、技术要点对照表技术点实现方式生产价值包含匹配LIKE ‘%kw%’子串搜索多字段 ORname LIKE OR tags LIKE多字段命中分组统计GROUP BY COUNT SUM每类统计总统计COUNT SUM 无分组全库汇总批量去重路径先查后插防重复导入字节存储size INTEGER fmtSize可聚合可展示六、常见问题 FAQQ1LIKE 和 equalTo 的区别AequalTo 是精确匹配file_type 文档LIKE 是模式匹配name LIKE %需求%包含即中。精确值筛选类型、状态用 equalTo用户输入的模糊搜索用 LIKE——搜索框是 LIKE 的典型场景。Q2%关键字%和关键字%什么区别A%关键字%匹配任意位置包含「项目需求文档」中「需求」在中间也中关键字%只匹配开头「需求」开头的名称。搜索框一般用%关键字%最宽松的包含语义「按前缀找文件」用关键字%。通配符位置决定匹配语义。Q3搜索「%」会怎样ALIKE %%%匹配所有行任意字符任意次数——整个列表全显示。关键字含通配符未转义的风险。真实项目对关键字做转义keyword.replace(/%/g, \\%).replace(/_/g, \\_)。Demo 教学简化未转义读者生产环境务必转义。Q4GROUP BY 的 ORDER BY cnt 为什么能排ASQLite 允许 ORDER BY 引用聚合别名cnt——按分组统计值排序。先分组聚合再对组结果排序——「先聚合后排序」是 SQL 的求值顺序WHERE → GROUP BY → HAVING → ORDER BY。Q5typeStats 和 totalStats 能合并吗A能——SELECT file_type, COUNT(*) ... GROUP BY file_type加上ROLLUP小计行可一次返回「每类 总计」。但 RdbStore 的 querySql 返回结果需额外解析小计行代码变复杂。分离两个方法更清晰页面两次调用数据量小无性能顾虑。Q6为什么 size 不存字符串「120MB」A字符串无法聚合SUM 报错也无法排序字典序 ‘800MB’ ‘120MB’ 错误。存最小单位字节整数展示时换算——「存储最小化、展示最大化」的字段设计原则。fmtSize 在页面层换算。Q7批量导入为什么不用事务A事务beginTransaction/commit能把 18 次插入合成一次提交更快 原子性。Demo 数据量小18 个无事务也能秒级完成。「小批量循环插入」教学简化真实导入大文件列表用事务包裹。七、文章小结本篇文章深入讲解了文件库的数据层核心LIKE 通配符搜索%包含匹配 名称/标签 OR 组合 转义风险、GROUP BY 分组统计COUNT SUM 每类一行从实例 13 的全表 AVG 升级为分组聚合、批量导入去重路径先查后插 UNIQUE 硬约束兜底、字节整数存储可聚合可换算。核心心法是「LIKE 的包含匹配语义」与「分组聚合 按维度分行统计」——搜索与统计是文件管理器的两大核心功能。下一篇16-4展示 18 个文件六类种子数据让文件库一开屏就内容丰富、统计饱满。八、进阶专题通配符语义、FTS5 取舍与导入策略本专题把全文四个技术点再往深处挖一层%与_的精确语义、LIKE 与 FTS5 的性能边界、GROUP BY 的执行步骤、先查后插与 INSERT OR IGNORE 的取舍——供读者在真实项目中做决策。8.1 LIKE 通配符% 与 _ 的语义详解%匹配任意长度的字符序列含空串_匹配恰好一个字符-- % 匹配任意长度含 0 个SELECT*FROMfile_libWHEREnameLIKE%需求%;-- 含「需求」任意位置SELECT*FROMfile_libWHEREnameLIKE项目%;-- 以「项目」开头SELECT*FROMfile_libWHEREnameLIKE%.docx;-- 以 .docx 结尾-- _ 匹配恰好一个字符SELECT*FROMfile_libWHEREnameLIKE需求_;-- 「需求文」「需求书」1 个字符后缀SELECT*FROMfile_libWHEREnameLIKE____.docx;-- 4 字符文件名 .docx模式语义示例命中需求%前缀匹配需求文档.docx、需求评审.pdf%需求后缀匹配项目需求、用户需求%需求%包含匹配任意位置项目需求文档.docx、需求.docx需求_单字符后缀需求文、需求书不匹配「需求」本身_需求单字符前缀新需求、老需求关键语义边界_是「恰好一个」%是「任意个含 0」——需求%能匹配「需求」本身需求_不能必须有第 3 个字符。搜索框常用%kw%最宽松文件名补全用kw%前缀两者场景不同。8.2 LIKE 性能与全文索引FTS5对比%关键字%的前导通配符导致 B 树索引无法利用不知道从哪个 key 开始扫只能全表扫描-- 走全表扫描前导 % 无法用索引SELECT*FROMfile_libWHEREnameLIKE%需求%;-- 可能走索引前缀匹配可以范围扫描SELECT*FROMfile_libWHEREnameLIKE需求%;维度LIKE ‘%kw%’FTS5 全文索引索引利用前导 % 无法走索引全表扫描倒排索引词条定位十万行耗时数百毫秒级全表扫毫秒级词条命中匹配粒度子串任意位置分词后的词条中文需分词器排序能力无相关性排序ORDER BY rank按相关度实现成本一个 like 调用建 FTS5 虚拟表 同步维护-- FTS5 方案示意中文需 icu 分词器生产场景才值得引入CREATEVIRTUALTABLEfile_lib_ftsUSINGfts5(name,tags,contentfile_lib);SELECT*FROMfile_lib_ftsWHEREfile_lib_ftsMATCH需求;决策依据万行以内 LIKE 足够Demo 18 行秒回十万行以上且搜索是核心功能才引入 FTS5——「LIKE 是零成本方案FTS5 是重量级方案」。8.3 GROUP BY COUNT SUM 的执行逻辑GROUP BY 三步走分组 → 组内聚合 → 输出一行-- 原始行示意 3 行文档-- 项目需求文档.docx | 文档 | 2411725-- 接口设计说明.pdf | 文档 | 5242880-- 用户手册终稿.pdf | 文档 | 8388608SELECTfile_type,COUNT(*)AScnt,SUM(size)AStotal_sizeFROMfile_libGROUPBYfile_type;-- 第 1 步按 file_type 分桶文档/图片/视频/...-- 第 2 步每桶 COUNT 行数、SUM(size)-- 第 3 步每桶输出一行 → 文档 | 3 | 16043213步骤动作结果1 分组按 file_type 分桶6 个桶六类2 聚合每桶 COUNT(*) 计行、SUM(size) 求和每桶两个聚合值3 输出每桶一行file_type cnt total_size6 行结果细节COUNT(*)计行数含 NULLSUM忽略 NULL分组后ORDER BY cnt DESC对聚合结果排序。无 GROUP BY 的 COUNT/SUM 是「全表一个桶」——typeStats 与 totalStats 只是「分组/不分组的同一套聚合」。8.4 批量导入先查后插 vs INSERT OR IGNORE维度先查后插Demo 采用INSERT OR IGNORE语句数每文件 1 查 1 插每文件 1 条冲突感知能拿到 exists 布尔值只看受影响行数RdbStore 支持原生 APIquery insert需 executeSql 手写 SQL应用逻辑可做「跳过提示」「更新旧记录」只能忽略并发安全靠 UNIQUE 兜底UNIQUE 直接生效-- INSERT OR IGNORE冲突时静默忽略UNIQUE 冲突不报错INSERTORIGNOREINTOfile_lib(name,file_type,size,path)VALUES(项目需求文档.docx,文档,2411725,/docs/需求/项目需求文档.docx);-- 若 path 已存在插入被忽略返回 0 行受影响不报错结论Demo 选「先查后插」是因为 RdbStore 的 insert API 无 OR IGNORE 选项且要 exists 布尔值做友好跳过生产大量导入用事务 INSERT OR IGNORE一条 SQL 原子完成去重更快。「应用层友好提示 数据库 UNIQUE 硬约束」双层保险不变。8.5 进阶 FAQQ1%和_能混用吗A能——需求_%.docx表示「需求 至少一个字符 任意 .docx」_占一位、%占任意位可自由组合成复杂模式。Q2LIKE 对中文有效吗A有效——SQLite 按字符比较%需求%对中文子串同样成立UTF-8 下按字符语义匹配。Q3FTS5 什么时候才值得上A十万行以上 搜索是核心路径 需要相关度排序时才值得——引入分词器与同步维护成本。Demo 18 行数据用 LIKE 即可。Q4GROUP BY 能按多个字段分组吗A能——GROUP BY file_type, tags按「类型标签」组合分组每组一行统计粒度更细。Q5先查后插有并发漏洞吗A有——两个线程同时查同一 path 都不存在时会双插UNIQUE 约束会让第二个 insert 抛异常——所以数据库层硬约束兜底必不可少。