1. 为什么 rawQuery() 值得单独拎出来讲SQLiteDatabase.rawQuery()是 Android 本地开发里最直接的查询入口你写一条select语句它返回一个Cursor然后你从游标里一行一行把数据取出来。它不像query()那样把表名、列名、条件拆成一堆参数而是让你把完整 SQL 交给 SQLite 去执行适合复杂联表、聚合、group by、having这类用拼装 API 写起来别扭的场景。这篇面向的是刚接触 Android 本地存储、或者写过query()但一遇到复杂 SQL 就卡住的开发者。核心就三件事建库建表、用rawQuery()执行select、正确遍历Cursor取出行数据。同时我会把调试期用到的 AI 辅助配置统一到 TaoToken 的 Key/API 通道上避免在多个工具之间来回换 Key、换地址让“写 SQL—跑查询—看结果”这条链路一次走通。需要先明确一个边界rawQuery()只负责执行select它不负责建表、不负责插入。建表和插入要用execSQL()。很多人第一次用报no such table不是rawQuery()写错了而是表根本没建。2. TaoToken 前置把调试期的 Key 和通道统一起来在本地开发阶段我经常一边写 SQL 一边让 AI 帮忙检查语句、生成测试数据、解释Cursor的列索引。如果每个工具都单独配一套 Key 和接口地址改起来很烦。TaoToken 在这里的作用是提供一个统一的 API 通道官网入口是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 基址是 https://taotoken.net/api 这个地址不加 UTM。你需要先拿到 Key再去配置具体工具。拿 Key 的入口在控制台的 API Keys 页面https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 。拿到之后如果你只是想验证模型能不能正常对话用模型对话页最快https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 。如果你是要长期做 Android 编码、让 Agent 帮你改代码那更适合用 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里面写了不同客户端的配置方式。如果你用的是 Claude Code 这类工具对应的 Anthropic 接入说明在 https://taotoken.net/doc/claudecode-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaudecode_anthropicutm_campaignrewrite 。注意TaoToken 是 AI 辅助调试的通道不是数据库工具。它不会替你执行 SQLSQL 还是在 Android 工程里跑。它的价值是让你在写 SQL、排查Cursor问题时有一个稳定的 AI 辅助入口。配置的时候有个小坑API 基址和官网地址不要混用。基址用https://taotoken.net/api不要在后面拼 UTM 参数否则某些客户端会把整串当成路径。3. 可复制配置建库建表 rawQuery 调用骨架3.1 建库建表 SQL先建一张person表字段包括自增主键、姓名、年龄。用execSQL()执行public class DbHelper extends SQLiteOpenHelper { private static final String DB_NAME demo.db; private static final int DB_VERSION 1; public DbHelper(Context context) { super(context, DB_NAME, null, DB_VERSION); } Override public void onCreate(SQLiteDatabase db) { String createSql create table person ( id integer primary key autoincrement, name text not null, age integer not null); db.execSQL(createSql); } Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL(drop table if exists person); onCreate(db); } }建表之后插入几条测试数据同样用execSQL()SQLiteDatabase db helper.getWritableDatabase(); db.execSQL(insert into person (name, age) values (?, ?), new Object[]{iteedu, 4}); db.execSQL(insert into person (name, age) values (?, ?), new Object[]{tom, 18}); db.execSQL(insert into person (name, age) values (?, ?), new Object[]{jerry, 25});这里execSQL()的第二个参数是占位符的值数组和rawQuery()的第二个参数用法一致都是按顺序替换?。3.2 rawQuery 调用骨架最简单的无占位符查询SQLiteDatabase db helper.getReadableDatabase(); Cursor cursor db.rawQuery(select * from person, null);带占位符的查询第二个参数传String[]Cursor cursor db.rawQuery( select * from person where name like ? and age ?, new String[]{%iteedu%, 4});注意占位符的值统一用字符串数组传即使age是整数列SQLite 也会做类型转换。如果你传null表示这条 SQL 没有占位符。3.3 Cursor 遍历骨架拿到Cursor之后标准遍历流程是先判断moveToFirst()是否成功再用do-while或while逐行读取最后close()。if (cursor ! null cursor.moveToFirst()) { do { int id cursor.getInt(cursor.getColumnIndexOrThrow(id)); String name cursor.getString(cursor.getColumnIndexOrThrow(name)); int age cursor.getInt(cursor.getColumnIndexOrThrow(age)); Log.d(rawQueryDemo, id id , name name , age age); } while (cursor.moveToNext()); } if (cursor ! null) { cursor.close(); }getColumnIndexOrThrow()比getColumnIndex()更安全列名写错会直接抛异常而不是返回 -1 导致后面取值错位。实测下来这个习惯能省掉很多“数据读出来是空的”这类排查时间。4. 验证请求跑通查询并正确取出行数据4.1 完整可运行示例把上面的片段拼成一个完整方法放在 Activity 或测试类里public void queryPerson() { DbHelper helper new DbHelper(this); SQLiteDatabase db helper.getReadableDatabase(); Cursor cursor null; try { cursor db.rawQuery( select id, name, age from person where age ? order by age asc, new String[]{4}); if (cursor.moveToFirst()) { do { int id cursor.getInt(cursor.getColumnIndexOrThrow(id)); String name cursor.getString(cursor.getColumnIndexOrThrow(name)); int age cursor.getInt(cursor.getColumnIndexOrThrow(age)); Log.d(rawQueryDemo, id id , name name , age age); } while (cursor.moveToNext()); } else { Log.d(rawQueryDemo, no rows matched); } } finally { if (cursor ! null) { cursor.close(); } db.close(); } }4.2 预期输出按前面插入的三条数据age 4且按年龄升序日志应该是id1, nameiteedu, age4 id2, nametom, age18 id3, namejerry, age25如果你看到的是no rows matched先检查插入数据有没有真的执行成功再检查where条件。rawQuery()本身不会报“条件没匹配”它只是老老实实返回一个空游标。4.3 用 AI 辅助核对 SQL写复杂 SQL 的时候我会把语句贴到模型对话里让它帮我检查括号、占位符数量、列名是否和建表语句一致。模型对话入口是 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 。比如你写了一条带group by的语句可以让它帮你确认select里的非聚合列是否都在group by中避免 SQLite 静默返回不确定的结果。这一步不是必须的但在调试期确实能减少来回改代码的次数。Key 和通道统一之后你不需要为每个辅助工具单独记一套配置。5. 本篇常见错排查5.1 no such table: person原因基本只有一个表没建。rawQuery()不会自动建表。检查SQLiteOpenHelper.onCreate()有没有被调用。如果你改了DB_VERSION但没改onUpgrade()旧库不会重建表。调试期可以直接卸载应用重装或者手动删掉/data/data/包名/databases/demo.db。5.2 CursorIndexOutOfBoundsException这个异常通常来自两个地方一是没调用moveToFirst()就直接getString()二是getColumnIndex()返回 -1然后拿 -1 去取值。解决办法就是前面说的用getColumnIndexOrThrow()并且严格按moveToFirst()→ 循环 →moveToNext()的顺序来。5.3 占位符数量对不上rawQuery()的第二个参数数组长度必须和 SQL 里?的数量一致。多一个少一个都会抛SQLiteBindOrColumnIndexOutOfRangeException。写带占位符的语句时我习惯先把?数一遍再对照数组长度。5.4 忘记 close 导致资源泄漏Cursor和SQLiteDatabase都要关。Cursor不关长时间运行会报CursorWindowAllocationException。用try-finally包起来是最稳的写法别指望 GC 帮你收。5.5 列名大小写和别名问题SQLite 列名默认不区分大小写但如果你在select里用了别名比如select name as user_name那Cursor里取的时候要用别名user_name不能用原列名name。这个坑在联表查询里特别常见。6. 把 Key 和查询链路都固定下来rawQuery()的用法本身不复杂一条select一个Cursor一次遍历。真正容易出问题的是建表和插入没做、占位符对不上、游标没关这些细节。把建表 SQL、插入语句、查询骨架、遍历模板都固定成可复制的片段后面再写复杂查询就是在这个骨架上加where、join、group by。调试期的 AI 辅助配置也一样固定成一个 Key、一个 API 通道就够了。需要拿 Key 去 https://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 验证模型去 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 长期编码用 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 接入细节看 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。这样你排查 SQL 问题的时候注意力就只在 SQL 和Cursor上不用再分心去换配置。
企业数字化 ERP 产品动态
相关推荐
超声波风速风向测量原理与工程实践 1. 项目概述:为什么声波时差法成了高精度风速风向监测的“隐形冠军”你有没有注意过,气象站里那个安静转动的风杯,或者屋顶上微微摆动的风标,它们背后其实藏着一套精密到毫秒级的物理测量逻辑?今天聊的这个“超声波风速… · 2026/9/26 15:05:56
ChatGPT、Codex实战:AI写完代码后,Review为什么成了最耗时的环节? /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 15:05:56
RubricRL实战:用显式评分标准替代奖励模型,降低LLM强化学习成本 1. 从“打分”到“训练信号”:RubricRL到底在解决什么问题 大语言模型做强化学习,最让人头疼的从来不是算法本身,而是 奖励信号从哪来 。传统RLHF那套流程,先训一个奖励模型,再用PPO去优化,中间涉及四个模… · 2026/9/26 15:05:49
Valheim模组开发必学:BepInEx部署与Unity版本匹配原理 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 15:35:05
SQL Server 2019 Express 安装与混合模式实操指南 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 15:34:59
NAPI机制深度解析:从中断到轮询的Linux收包路径优化实践 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 15:34:59
配色工具全流程指南:从灵感采集到工程落地的场景化分类 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 15:34:59
数据库课后习题答案别硬背:当测试用例集刷,效率翻倍 简介:万常选版《数据库原理与设计》课后习题答案资源,覆盖第2至6章及第9章,适合正在学习关系模型、数据库建模、关系数据理论与模式求精的本科生、自学者作为复习与自测材料。压缩包共7个文件,含3个doc参考答案、2个sql示例脚本、… · 2026/9/26 0:00:21
OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 0:00:40
向下兼容与向上兼容:接口设计中的兼容性策略与工程实践 一次版本升级事故,是很多团队绕不过去的坎。线上环境里,服务端明明已经上线了新版接口,老的移动端还在照着旧文档传参数。请求一到网关,校验直接拒绝,用户操作失败,客服群炸了锅,开发群里开始互… · 2026/9/26 0:00:46