6. SELECT查询与结果集处理:基本查询、WHERE条件、ORDER BY排序、LIMIT分页、遍历结果集
数据库编程里,最核心的操作就是查询。说白了,你写再多代码,最终目的就是把数据拿出来用。SELECT 语句就是干这个的。我做了这么多年嵌入式数据库开发,可以说 80% 的调试时间都花在写查询上。今天咱们就把这块彻底讲透。
本章核心知识点:
- 基本 SELECT 查询语法
- WHERE 条件过滤
- ORDER BY 排序
- LIMIT 分页
- 结果集遍历与处理
6.1 基本查询:从简单开始
最基本的 SELECT 查询,就是「给我看看这张表里有什么」。语法很简单:
SELECT 列名1, 列名2, ... FROM 表名;
如果你想看所有列,用星号 * 就行。我个人习惯在调试阶段先用 SELECT * 看看全貌,正式代码里再改成具体列名——这样性能更好,也更容易维护。
举个例子,假设我们有个 sensors 表:
-- 查询所有列
SELECT * FROM sensors;
-- 查询指定列
SELECT id, name, temperature FROM sensors;
我的经验:嵌入式设备内存有限,能用具体列名就别用 *。我在一个智能家居项目里,就因为用了 SELECT * 拉回了一堆不需要的 BLOB 数据,结果内存直接爆了。从那以后,我写 SELECT 都会明确指定列名。
6.2 WHERE 条件:精准筛选
光查全部数据不够用。你想想看,一张表里可能有几万条记录,你总得挑出你想要的。WHERE 子句就是干这个的。
SELECT 列名 FROM 表名 WHERE 条件表达式;
常用的条件运算符:
| 运算符 | 说明 | 示例 |
|---|---|---|
| = | 等于 | WHERE id = 100 |
| > / < | 大于 / 小于 | WHERE temperature > 30 |
| >= / <= | 大于等于 / 小于等于 | WHERE humidity <= 60 |
| != 或 <> | 不等于 | WHERE status != 0 |
| LIKE | 模糊匹配 | WHERE name LIKE 'temp%' |
| IN | 在集合中 | WHERE id IN (1,2,3) |
| AND / OR | 逻辑组合 | WHERE a > 10 AND b < 20 |
来看个实际例子:
-- 查询温度大于25度的传感器
SELECT id, name, temperature
FROM sensors
WHERE temperature > 25;
-- 多条件组合:温度大于25且湿度小于60
SELECT * FROM sensors
WHERE temperature > 25 AND humidity < 60;
-- 模糊匹配:名字以"temp"开头的传感器
SELECT * FROM sensors
WHERE name LIKE 'temp%';
注意:我曾经在一个项目中,用 WHERE name = 'temp_sensor' 死活查不到数据。后来发现是字符串末尾有个不可见字符。嵌入式设备的数据经常有这种「脏数据」,建议用 TRIM() 函数处理一下。
6.3 ORDER BY 排序:让数据有序
查出来的数据默认是按插入顺序排列的。但很多时候,你需要按某个字段排序。比如按温度从高到低排列。
SELECT 列名 FROM 表名 ORDER BY 列名 [ASC | DESC];
ASC:升序(默认,从小到大)DESC:降序(从大到小)
看例子:
-- 按温度升序排列(默认)
SELECT id, name, temperature
FROM sensors
ORDER BY temperature;
-- 按温度降序排列
SELECT id, name, temperature
FROM sensors
ORDER BY temperature DESC;
-- 多字段排序:先按温度降序,温度相同再按湿度升序
SELECT * FROM sensors
ORDER BY temperature DESC, humidity ASC;
我的习惯:多字段排序时,我会把最关键的排序字段放前面。比如查报警记录,先按严重级别降序,再按时间升序。这样最严重的报警永远在最前面。
6.4 LIMIT 分页:控制数据量
嵌入式设备的内存很金贵。一次查几万条数据回来,内存可能直接撑爆。LIMIT 就是用来限制返回行数的。
SELECT 列名 FROM 表名 LIMIT 行数 OFFSET 偏移量;
或者用简写形式:
SELECT 列名 FROM 表名 LIMIT 偏移量, 行数;
分页的核心公式:
-- 第N页,每页M条数据
SELECT * FROM sensors
LIMIT M OFFSET (N-1) * M;
-- 比如第2页,每页10条
SELECT * FROM sensors
LIMIT 10 OFFSET 10;
重要提醒:分页查询一定要配合 ORDER BY 使用!否则每次查出来的数据顺序可能不一样,分页就乱套了。我见过有人没加 ORDER BY,结果第一页和第二页的数据有重叠——排查了半天才发现是这个问题。
6.5 遍历结果集:C语言实战
好了,SQL 语句写好了,怎么在 C 代码里拿到数据?这就涉及到结果集遍历了。以 SQLite 为例,核心流程是三步:
- 准备语句:
sqlite3_prepare_v2() - 逐行遍历:
sqlite3_step()循环 - 读取列值:
sqlite3_column_xxx()系列函数
来看完整代码:
#include <stdio.h>
#include <sqlite3.h>
void query_sensors(sqlite3 *db, int page, int page_size) {
sqlite3_stmt *stmt;
const char *sql;
int rc;
int offset = (page - 1) * page_size;
// 1. 准备SQL语句
sql = "SELECT id, name, temperature, humidity "
"FROM sensors "
"WHERE temperature > ? "
"ORDER BY temperature DESC "
"LIMIT ? OFFSET ?";
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
if (rc != SQLITE_OK) {
printf("准备语句失败: %s\n", sqlite3_errmsg(db));
return;
}
// 2. 绑定参数
sqlite3_bind_double(stmt, 1, 25.0); // 温度大于25度
sqlite3_bind_int(stmt, 2, page_size); // 每页条数
sqlite3_bind_int(stmt, 3, offset); // 偏移量
// 3. 遍历结果集
printf("=== 第%d页 传感器数据 ===\n", page);
printf("ID\t名称\t\t温度\t湿度\n");
printf("----------------------------------------\n");
while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) {
int id = sqlite3_column_int(stmt, 0);
const unsigned char *name = sqlite3_column_text(stmt, 1);
double temp = sqlite3_column_double(stmt, 2);
double hum = sqlite3_column_double(stmt, 3);
printf("%d\t%s\t%.1f\t%.1f\n", id, name, temp, hum);
}
if (rc != SQLITE_DONE) {
printf("遍历出错: %s\n", sqlite3_errmsg(db));
}
// 4. 清理
sqlite3_finalize(stmt);
}
关键函数说明:
| 函数 | 作用 |
|---|---|
| sqlite3_prepare_v2() | 编译SQL语句,生成预处理语句 |
| sqlite3_bind_xxx() | 绑定参数到占位符 ? |
| sqlite3_step() | 执行一步,返回 SQLITE_ROW 表示有数据 |
| sqlite3_column_xxx() | 读取当前行的列值(int, double, text 等) |
| sqlite3_finalize() | 销毁预处理语句,释放资源 |
避坑指南:我曾经在遍历结果集时忘了调用 sqlite3_finalize(),结果内存泄漏了。嵌入式设备跑几天就崩了。记住:每个 sqlite3_prepare 都必须对应一个 sqlite3_finalize,就像 malloc 和 free 一样。
6.6 综合实战:带分页的传感器查询
把上面所有知识点串起来,写一个完整的查询函数:
void list_sensors_paginated(sqlite3 *db, int page) {
sqlite3_stmt *stmt;
int rc;
int page_size = 5;
int offset = (page - 1) * page_size;
// 带参数的分页查询
const char *sql =
"SELECT id, name, temperature, humidity, status "
"FROM sensors "
"WHERE status = 1 "
"ORDER BY temperature DESC, id ASC "
"LIMIT ? OFFSET ?";
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
if (rc != SQLITE_OK) {
printf("SQL错误: %s\n", sqlite3_errmsg(db));
return;
}
sqlite3_bind_int(stmt, 1, page_size);
sqlite3_bind_int(stmt, 2, offset);
printf("\n--- 传感器列表 (第%d页) ---\n", page);
printf("ID\t名称\t\t温度\t湿度\t状态\n");
printf("----------------------------------------\n");
int count = 0;
while (sqlite3_step(stmt) == SQLITE_ROW) {
int id = sqlite3_column_int(stmt, 0);
const char *name = (const char *)sqlite3_column_text(stmt, 1);
double temp = sqlite3_column_double(stmt, 2);
double hum = sqlite3_column_double(stmt, 3);
int status = sqlite3_column_int(stmt, 4);
printf("%d\t%-12s\t%.1f\t%.1f\t%s\n",
id, name, temp, hum,
status ? "在线" : "离线");
count++;
}
if (count == 0) {
printf("没有更多数据了。\n");
}
sqlite3_finalize(stmt);
}
我的建议:实际项目中,我一般会把分页参数(页码、每页条数)封装成结构体,这样代码更清晰。另外,记得在遍历前先查一下总记录数,这样前端才知道总共有多少页。
好了,SELECT 查询这块的核心内容就这些。从基本查询到条件过滤,从排序到分页,再到 C 代码里的结果集遍历——每一步都是实战中会反复用到的。你写代码的时候,多想想「这个查询会不会返回太多数据?」「排序字段选对了没?」「参数绑定有没有漏掉?」。养成这些习惯,你的数据库编程水平会提升很快。
公众号:蓝海资料掘金营,微信deep3321