30 原始 SQL 查询与聚合统计:querySql 实战 30 原始 SQL 查询与聚合统计querySql 实战前言图30 原始 SQL 查询与聚合统计querySql 实战 运行效果截图HarmonyOS NEXT虽然RdbPredicates覆盖了大部分查询场景但当需要聚合统计COUNT、SUM、AVG、GROUP BY 分组、SQL 函数或JOIN 多表关联时直接使用原始 SQL 更为简单高效。鸿蒙 RDB 提供了store.querySql(sql, bindArgs)方法支持原始 SQL 执行。本文以鹿鹿·笔迹心理分析项目中 HandwritingDao 和 ReportDao 的多个聚合查询方法为例深入解析 querySql 的使用模式。鸿蒙官方·RdbStore SQL 执行developer.huawei.com项目源码仓库harmony-app GitHub图querySql 的四大使用场景——聚合统计、GROUP BY、JOIN、SQL 函数store.querySql聚合统计COUNT / SUM / AVG分组查询GROUP BY HAVING多表联接LEFT JOIN / INNER JOINSQL 函数strftime / substr / COALESCE参数化绑定防止 SQL 注入一、querySql 基础1.1 方法签名querySql(sql:string,bindArgs?:Arraynumber|string|boolean):PromiseResultSet参数类型说明sqlstring完整的 SQL 查询语句bindArgsArraynumber|string|boolean可选参数化绑定的值1.2 最简单的查询// SELECT * 全部返回constrsawaitstore.querySql(SELECT COUNT(*) AS cnt FROM handwriting)// 参数化查询使用 ? 占位符constrsawaitstore.querySql(SELECT COUNT(*) AS cnt FROM handwriting WHERE archive_id ?,[archiveId])安全提示始终使用?参数化绑定不要拼接字符串。querySql的第二个参数bindArgs会自动处理转义防止 SQL 注入。二、COUNT 计数查询2.1 简单计数// HandwritingDao.countAllstaticasynccountAll():Promisenumber{conststoreHandwritingDao.getStore()constrsawaitstore.querySql(SELECT COUNT(*) AS cnt FROM${TABLE})letcnt0if(rs.goToFirstRow()){cntrs.getLong(rs.getColumnIndex(cnt))}rs.close()returncnt}2.2 带条件的计数// HandwritingDao.countByArchivestaticasynccountByArchive(archiveId:number):Promisenumber{conststoreHandwritingDao.getStore()constrsawaitstore.querySql(SELECT COUNT(*) AS cnt FROM${TABLE}WHERE archive_id ?,[archiveId])letcnt0if(rs.goToFirstRow()){cntrs.getLong(rs.getColumnIndex(cnt))}rs.close()returncnt}2.3 去重计数// HandwritingDao.countDistinctActiveDays// 统计有记录的不同天数staticasynccountDistinctActiveDays():Promisenumber{conststoreHandwritingDao.getStore()constrsawaitstore.querySql(SELECT COUNT(DISTINCT (created_at / 86400000)) AS cnt FROM${TABLE})letcnt0if(rs.goToFirstRow()){cntrs.getLong(rs.getColumnIndex(cnt))}rs.close()returncnt}去重技巧created_at是毫秒时间戳除以86400000一天的毫秒数后同一天的所有记录映射为相同的整数COUNT(DISTINCT ...)汇总出实际有记录的天数。三、LIMIT 限制3.1 带 LIMIT 的查询// HandwritingDao.listRecentstaticasynclistRecent(limit:number):PromiseHandwritingEntity[]{conststoreHandwritingDao.getStore()constrsawaitstore.querySql(SELECT * FROM${TABLE}ORDER BY created_at DESC LIMIT ?,[limit]// limit 作为参数化值)constresult:HandwritingEntity[][]while(rs.goToNextRow()){result.push(HandwritingDao.parseRow(rs))}rs.close()returnresult}SQL 语句生成示例// 如果 limit 5// → SELECT * FROM handwriting ORDER BY created_at DESC LIMIT 5// 返回最近 5 条笔迹记录3.2 带条件 排序 LIMIT// ReportDao.findLatestByArchivestaticasyncfindLatestByArchive(archiveId:number):PromiseReportEntity|null{conststoreReportDao.getStore()constrsawaitstore.querySql(SELECT * FROM${TABLE}WHERE archive_id ? ORDER BY created_at DESC LIMIT 1,[archiveId])letresult:ReportEntity|nullnullif(rs.goToFirstRow()){resultReportDao.parseRow(rs)}rs.close()returnresult}四、聚合查询汇总4.1 项目中使用的聚合 SQLDAOSQL用途参数HandwritingDaoCOUNT(*) FROM ${TABLE}总笔迹数无HandwritingDaoCOUNT(*) FROM ${TABLE} WHERE archive_id ?某档案笔迹数archiveIdHandwritingDaoCOUNT(DISTINCT (created_at / 86400000))活跃天数无ReportDaoCOUNT(*) FROM ${TABLE}总报告数无ReportDaoSELECT * ... WHERE archive_id ? ORDER BY created_at DESC LIMIT 1最新报告archiveId4.2 扩展更多聚合模式// 场景 1AVG 平均能量值未来可能需要constrsawaitstore.querySql(SELECT AVG(energy_score) AS avg_energy FROM handwriting WHERE archive_id ?,[archiveId])// 场景 2GROUP BY 分组统计constrsawaitstore.querySql(SELECT source, COUNT(*) AS cnt FROM handwriting GROUP BY source)// 场景 3MAX/MIN 数值范围constrsawaitstore.querySql(SELECT MAX(word_count) AS max_words, MIN(word_count) AS min_words FROM handwriting)// 场景 4JOIN 两表笔迹报告constrsawaitstore.querySql(SELECT h.id, h.ocr_text, r.personality_type, r.radar_json FROM handwriting h JOIN report r ON h.id r.handwriting_id WHERE h.archive_id ? ORDER BY h.created_at DESC,[archiveId])五、querySql vs query(Predicates) 对比5.1 选择策略场景使用理由等值查询 排序RdbPredicates类型安全可读性好聚合函数querySqlRdbPredicates 不支持LIMIT 限制querySqlRdbPredicates.limit() 可用JOIN 多表querySql唯一选择简单查询RdbPredicates代码更简洁5.2 同一个查询的两种写法// 写法 ARdbPredicates推荐constpredicatesnewrelationalStore.RdbPredicates(TABLE)predicates.equalTo(archive_id,archiveId)predicates.orderByDesc(created_at)constrsawaitstore.query(predicates,COLS)// 写法 BquerySql等效constrsawaitstore.querySql(SELECT * FROM${TABLE}WHERE archive_id ? ORDER BY created_at DESC,[archiveId])5.3 代码量对比查询类型RdbPredicates行数querySql行数简单等值3 行1 行但 SQL 字符串长排序条件4 行1 行SQL 字符串更长COUNT❌ 不支持✅ 1 行JOIN❌ 不支持✅ 1 行六、SQL 安全性6.1 参数化绑定// ✅ 安全参数化绑定awaitstore.querySql(SELECT * FROM handwriting WHERE archive_id ?,[archiveId])// ❌ 危险字符串拼接awaitstore.querySql(SELECT * FROM handwriting WHERE archive_id ${archiveId}// 注入风险)6.2 表名与列名拼接注意事项// ⚠️ 表名和列名不能参数化需要直接拼接constTABLEhandwritingconstsqlSELECT * FROM${TABLE}ORDER BY created_at DESC LIMIT ?// ⚠️ 但是值必须参数化// ✅ ${TABLE}表名固定安全// ✅ ?值可变参数化安全// ❌ ${archiveId}用户输入危险七、querySql 返回结果的解析7.1 聚合结果的解析// 聚合查询的 ResultSet 解析constrsawaitstore.querySql(SELECT COUNT(*) AS cnt FROM handwriting)// 聚合结果只有 1 行letcnt0if(rs.goToFirstRow()){cntrs.getLong(rs.getColumnIndex(cnt))// 通过别名获取}rs.close()returncnt7.2 聚合字段的命名// 聚合字段推荐使用 AS 别名// 便于在代码中通过 getColumnIndex(alias) 读取SELECT COUNT(*) AS cnt ...→getColumnIndex(cnt)SELECT AVG(energy_score) AS avg ...→getColumnIndex(avg)SELECT MAX(word_count) AS max ...→getColumnIndex(max)八、模板字符串的合理使用// ✅ 在 DAO 中使用 ${TABLE} 常量项目做法constTABLEhandwritingconstrsawaitstore.querySql(SELECT COUNT(*) AS cnt FROM${TABLE})// ⚠️ 注意${TABLE} 是常量不是用户输入所以安全// 如果表名需要动态传入应做好白名单校验总结本文完整解析了鸿蒙 RDB 中原始 SQL 查询的使用方法querySql(sql, args)支持完整的 SQL 语法参数化绑定防注入COUNT 计数COUNT(*)、条件计数、COUNT(DISTINCT ...)去重LIMIT 限制配合ORDER BY DESC获取最新 N 条聚合扩展AVG、GROUP BY、MAX/MIN、JOIN 的 SQL 模板安全原则值用?参数化表名/列名用常量与 RdbPredicates 互补简单查询用 Predicates复杂统计用 querySql下一篇文章将深入数据更新与级联操作——ArchiveDao 中 delete 时的级联清理。如果这篇文章对你有帮助欢迎点赞、收藏⭐、关注你的支持是我持续创作的动力参考资源RdbStore.querySql 方法[HandwritingDao 项目源码](file:///Users/fiona/Downloads/bijixinli/harmony-app/entry/src/main/ets/features/data/HandwritingDao.ets)[ReportDao 项目源码](file:///Users/fiona/Downloads/bijixinli/harmony-app/entry/src/main/ets/features/data/ReportDao.ets)SQLite 聚合函数参数化绑定防注入鸿蒙数据持久化指南HarmonyOS 开发文档七、高级 SQL 技巧窗口函数与 CTE鸿蒙 RDB 基于 SQLite 3.35支持窗口函数和公共表表达式CTE适用于更复杂的统计场景。7.1 窗口函数排名统计-- 按日期对情绪分数排名使用窗口函数SELECTcreated_at,score,RANK()OVER(ORDERBYscoreDESC)asscore_rankFROMhandwritingWHEREuser_id?constsqlSELECT created_at, score, RANK() OVER (ORDER BY score DESC) as score_rank FROM handwriting WHERE user_id ?constrsawaitstore.querySql(sql,[userId])7.2 CTE递归层级查询-- 使用 CTE 简化复杂统计WITHmonthly_statsAS(SELECTstrftime(%Y-%m,created_at)asmonth,COUNT(*)astotal,AVG(score)asavg_scoreFROMhandwritingWHEREuser_id?GROUPBYmonth)SELECT*FROMmonthly_statsORDERBYmonthDESC7.3 JSON 字段的 SQL 查询对于存储为 JSON 的字段可以使用 SQLite 的json_extract函数// 查询 keywords JSON 数组中包含特定关键词的记录constsqlSELECT * FROM report WHERE json_extract(keywords, $[0]) ? OR json_extract(keywords, $[1]) ?constrsawaitstore.querySql(sql,[keyword,keyword])7.4 SQL 注入防护原则操作安全做法危险做法参数绑定querySql(sql, [param])\WHERE id ${id}动态表名使用白名单校验直接拼接用户输入LIKE 通配LIKE ?% val %LIKE %${val}%如果这篇文章对你有帮助欢迎点赞、收藏⭐、关注你的支持是我持续创作的动力

相关新闻

最新新闻

STM32H563ZI上NetX Duo UDP无回包?三层排查与修复实录

STM32H563ZI上NetX Duo UDP无回包?三层排查与修复实录

上周我把一个原本跑在 STM32F407 上的小网络应用迁到 STM32H563ZI 上,CubeMX 里选了 ThreadX NetX Duo,顺手用它的 UDP Echo Server 示例生成了工程。编译下载后,板子能 ping 通主机,串口也打印了 Link Up,但网络调试…

2026/8/29 18:27:09
BlueNRG-LP定时器实战:TIMER与LPTIM的低功耗设计要点

BlueNRG-LP定时器实战:TIMER与LPTIM的低功耗设计要点

去年做一款基于BlueNRG-LP的无线温湿度记录仪,项目功能本身不复杂,真正耗时间的反而是这颗芯片的定时器模块。功能验证阶段一切正常,一进入低功耗调试就开始碰壁:LPTIM配好后休眠电流多出几个微安,PWM驱动蜂鸣器时偶尔…

2026/8/29 18:27:09
贝叶斯优化+LSTM+GPR:新能源汽车销量预测混合框架

贝叶斯优化+LSTM+GPR:新能源汽车销量预测混合框架

简介:在时间序列预测中,如何融合深度学习与概率模型来提升精度与可靠性,是工业界持续关注的问题。LSTM擅长捕捉非线性时序特征,但小样本下易过拟合且无法量化不确定性;高斯过程回归(GPR)提供概率…

2026/8/29 18:27:09
ReRAM取代Flash,Cortex-M23内核MCU如何重塑嵌入式存储

ReRAM取代Flash,Cortex-M23内核MCU如何重塑嵌入式存储

半导体行业这些年有个趋势我一直很关注:MCU正在逐步摆脱对传统Flash存储的依赖。Nuvoton最新推出的M2L31系列,就是这股浪潮里很有代表性的一颗料。它把片上非易失存储从Flash换成了ReRAM(阻变存储器,也就是常说的忆阻器&#xff0…

2026/8/29 18:27:09
光纤急停与信号系统在OEM控制器中的设计、调试与故障排查指南

光纤急停与信号系统在OEM控制器中的设计、调试与故障排查指南

2. 为什么急停和信号系统要上光纤 说起急停和信号传感器,绝大多数人第一反应还是传统的硬接线方案——急停按钮串在安全继电器回路里,传感器走IO模块,信号靠铜缆传。这套做法在普通车间里确实没毛病,但在某些特殊工况下&#xff0…

2026/8/29 18:27:09
StarOps智能运维管家:基于银河麒麟的多模态大模型根因定位与自动化修复实践

StarOps智能运维管家:基于银河麒麟的多模态大模型根因定位与自动化修复实践

简介:在传统运维体系中,监控告警平台往往只能给出“哪里异常”的提示,却难以回答“为什么异常”。当告警风暴来袭,分散的日志、指标、拓扑与配置数据让根因定位变得异常困难。智能运维(AIOps)的核心价值在于…

2026/8/29 18:22:08