ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

rawQuery()方法的使用例子:用 TaoToken 统一 Key 跑通 SQLiteDatabase 查询

rawQuery()方法的使用例子:用 TaoToken 统一 Key 跑通 SQLiteDatabase 查询 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上不用再分心去换配置。
返回列表