☰
rawQuery()方法的使用例子:用 TaoToken 统一 Key 跑通 SQLiteDatabase 查询
2026/9/26 15:05:53 网站建设 项目流程

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_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 基址是 https://taotoken.net/api (这个地址不加 UTM)。

你需要先拿到 Key,再去配置具体工具。拿 Key 的入口在控制台的 API Keys 页面:https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite 。拿到之后,如果你只是想验证模型能不能正常对话,用模型对话页最快:https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=models&utm_campaign=rewrite 。如果你是要长期做 Android 编码、让 Agent 帮你改代码,那更适合用 Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite 。

接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,里面写了不同客户端的配置方式。如果你用的是 Claude Code 这类工具,对应的 Anthropic 接入说明在 https://taotoken.net/doc/claudecode-anthropic?utm_source=taotoken_aicg_blog_end&utm_content=claudecode_anthropic&utm_campaign=rewrite 。

注意:TaoToken 是 AI 辅助调试的通道,不是数据库工具。它不会替你执行 SQL,SQL 还是在 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且按年龄升序,日志应该是:

id=1, name=iteedu, age=4 id=2, name=tom, age=18 id=3, name=jerry, age=25

如果你看到的是no rows matched,先检查插入数据有没有真的执行成功,再检查where条件。rawQuery()本身不会报“条件没匹配”,它只是老老实实返回一个空游标。

4.3 用 AI 辅助核对 SQL

写复杂 SQL 的时候,我会把语句贴到模型对话里让它帮我检查括号、占位符数量、列名是否和建表语句一致。模型对话入口是 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=models&utm_campaign=rewrite 。比如你写了一条带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_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite ,验证模型去 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=models&utm_campaign=rewrite ,长期编码用 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite ,接入细节看 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 。这样你排查 SQL 问题的时候,注意力就只在 SQL 和Cursor上,不用再分心去换配置。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询