查询方法
不可变性原则
所有查询方法均返回新的查询实例,不会修改原始查询链。条件调用需通过重新赋值生效:
let query = db.table.select('id', 'name');
// 错误:未重新赋值,条件不生效
if (params.name) query.where({ name: params.name }); // ❌ 原查询未被修改
// 正确:通过赋值应用条件
if (params.name) query = query.where({ name: params.name }); // ✅ 生成新查询实例
可变方法(谨慎使用)
以 _ 开头的方法(如 _where)会直接修改查询实例,仅用于底层逻辑,应用层开发不推荐使用:
const query = db.table.select('id', 'name');
if (params.name) query._where({ name: params.name }); // ⚠️ 直接修改实例,易引发不可控状态
单条记录查询与空结果处理
| 方法 | 未找到时行为 | 核心功能 |
|---|---|---|
take() | 抛出 NotFoundError | 添加 LIMIT 1,强制返回单条记录,无匹配时抛错 |
takeOptional() | 返回 undefined | 添加 LIMIT 1,无匹配时返回 undefined |
find(id) | 抛出 NotFoundError | 通过单主键查找,表必须定义主键(复合主键不可用) |
findOptional(id) | 返回 undefined | 通过主键查找,无匹配时返回 undefined |
findBy(uniqueCond) | 抛出 NotFoundError | 通过唯一条件(主键 / 唯一索引)查找,支持复合唯一约束 |
findByOptional(uniqueCond) | 返回 undefined | 同上,无匹配时返回 undefined |
findBySql(sql) | 抛出 NotFoundError | 通过原始 SQL 查找(支持模板字面量安全插值) |
findBySqlOptional(sql) | 返回 undefined | 同上,无匹配时返回 undefined |
示例:
//使用 `take` 来“获取”单条记录。它添加 `LIMIT 1`,未找到时抛出 `NotFoundError`。
const taken: TableType = await db.table.where({ key: 'value' }).take();
//使用 `takeOptional` 来“获取”单条记录。它添加 `LIMIT 1`,未找到时返回 `undefined`。
const takenOptional: TableType | undefined = await db.table
.where({ key: 'value' })
.takeOptional();
// 通过主键(id)查找单条记录,未找到时抛出 NotFoundError
const result: TableType = await db.table.find(1);
// 通过主键(id)查找单条记录,未找到时返回 undefined
const result: TableType | undefined = await db.table.findOptional(1);
// 查找单条唯一记录,未找到时抛出 NotFoundError
await db.table.findBy({ key: 'value' });
// 查找单条唯一记录,未找到时返回 undefined
await db.table.findByOptional({ key: 'value' });
// 使用原始 SQL 查找单条记录,未找到时抛出 NotFoundError
await db.user.findBySql`
age = ${age} AND
name = ${name}
`;
// 使用原始 SQL 查找单条记录,未找到时返回 `undefined`。
await db.user.findBySqlOptional`
age = ${age} AND
name = ${name}
`;
结果处理与数据提取
基础结果处理
-
get(columnOrSql):提取单值(列值或 SQL 表达式结果),无匹配时抛错const userName = await db.user.find(1).get('name'); // 获取单个字段值 const randomValue = await db.user.get(sql`floor(random() * 100)`); // 执行 SQL 表达式 -
getOptional(columnOrSql):无匹配时返回undefinedconst maybeValue = await db.user.findOptional(999).getOptional('name'); -
rows():返回无字段名的二维数组(列值按顺序排列)const rows = await db.user.select('id', 'name').rows(); // [[1, 'Alice'], [2, 'Bob']] -
pluck(column):提取单个列值的数组const allIds = await db.user.pluck('id'); // [1, 2, 3] -
exec():执行查询但不解析结果(用于INSERT/UPDATE/DELETE等操作)await db.user.where({ id: 1 }).update({ active: false }).exec(); // 仅执行更新,不返回数据 -
all():返回所有匹配的记录(对象数组),覆盖前序限制方法(如take()、limit())
// 示例:获取所有激活用户(即使之前有 .take(),也返回完整数组)
const activeUsers = db.user.where({ active: true }).take().all();
-
none(): 强制查询返回空结果或计数0,用于模拟数据不存在或快速测试:await db.user.none(); // 返回空数组 [](不执行实际查询) await db.user.insert({ name: 'test' }).none(); // 不执行插入,返回受影响行数 0
高级查询构建
表别名与数据源
-
as(alias):设置表别名,简化复杂查询中的列引用const users = db.user.as('u').select('u.id', 'u.name'); // 别名 `u` 用于列引用 // 连接时使用别名避免列名冲突 await db.user .join(db.profile.as('p'), 'p.userId', 'u.id') .select('u.name', 'p.email'); -
from(source):指定数据源(表名、子查询、WITH别名)// 子查询作为数据源 const subQuery = db.order.select('userId').where({ status: 'paid' }); const paidUsers = db.user.from(subQuery).select('id', 'name'); // 使用 WITH 表达式 db.user .with('recent_orders', subQuery) .from('recent_orders') .select('userId'); -
fromSql(sql):通过原始 SQL 自定义FROM子句(需手动处理安全插值)import { sql } from './baseTable'; const tableName = 'users_prod'; const data = await db.user.fromSql`${sql.identifier(tableName)}`; // 安全拼接表名
表继承控制:only([enable])
在 PostgreSQL 表继承场景中,强制仅查询当前表(而非子表):
// 仅查询父表自身记录(忽略子表)
const parentData = db.parentTable.only().select('id');
// 恢复默认行为(查询父表及所有子表)
const allData = db.parentTable.only(false).select('id');
分页查询:offset(n) / limit(n)
// 分页:第 2 页,每页 20 条(跳过前 20 条,取接下来 20 条)
const page = 2;
const pageSize = 20;
const users = await db.user.offset((page - 1) * pageSize).limit(pageSize);
清空表数据:truncate(options)
// 简单截断(不可恢复,谨慎使用)
await db.user.truncate();
// 重启自增主键(如 PostgreSQL 的 SERIAL 类型)
await db.user.truncate({ restartIdentity: true });
// 级联截断依赖表(外键关联的子表)
await db.parentTable.truncate({ cascade: true });
克隆查询实例:clone()
创建查询链副本,独立修改而不影响原始实例:
const baseQuery = db.user.select('id', 'name').where({ active: true });
const adminQuery = baseQuery.clone().where({ role: 'admin' }); // 克隆后独立添加条件
const activeUsers = await baseQuery; // 原始查询不受影响
const admins = await adminQuery;
结果转换与聚合
数据转换
-
map(fn):转换单条记录(空结果不调用)const usersWithFullName = await db.user.map((user) => ({ ...user, fullName: `${user.firstName} ${user.lastName}`, })); -
transform(fn):转换整个结果集(处理null值,需最后调用)// 聚合结果为 null 时设为 0 const totalAmount = await db.order.sum('amount').transform((sum) => sum ?? 0);
聚合与分组
// 按类别统计产品数量并筛选平均评分 > 4 的类别
const categoryStats = await db.product
.group('category')
.select('category')
.selectAvg('rating', { as: 'avg_rating' })
.having((q) => q.avg('rating').gt(4));
底层操作与调试
原始 SQL 执行:$query
// 安全插值(模板字面量)
const result = await db.$query<{ count: number }>`
SELECT COUNT(*) AS count FROM users WHERE age > ${18}
`;
// 手动构造 SQL 对象(适用于动态参数)
import { sql } from './baseTable';
const { rows } = await db.$query(sql`SELECT $1 AS value`, [123]);
获取执行 SQL:toSQL()
const { text, values } = db.user.where({ age: 25 }).toSQL();
console.log('SQL:', text); // SELECT "user".* FROM "user" WHERE "user"."age" = $1
console.log('参数:', values); // [25]
最佳实践
-
不可变性优先:始终通过
query = query.method()应用条件,避免可变方法(_xxx)带来的副作用。 -
明确结果预期:
- 要求记录必须存在时使用
take()/find()/findBy() - 允许记录不存在时使用
takeOptional()/findOptional()/findByOptional()
- 要求记录必须存在时使用
-
类型安全:通过
select()显式指定所需列,利用 TypeScript 推断避免运行时类型错误。 -
复杂查询拆分:通过中间变量分解复杂逻辑,提高可读性:
const baseQuery = db.user.where({ active: true }); const adminQuery = baseQuery.where({ role: 'admin' }); const regularQuery = baseQuery.where({ role: 'user' }).limit(10);
方法速查表
| 分类 | 方法 | 功能描述 |
|---|---|---|
| 基础查询 | take(), takeOptional() | 限制单条记录,处理空结果 |
find(), findBy() | 通过主键或唯一条件查找,强制返回或抛错 | |
| 结果处理 | get(), pluck() | 提取单值或单列数组 |
map(), transform() | 转换记录或结果集 | |
all(), none() | all():默认行为,返回所有匹配的记录数组(对象形式),覆盖前序 take/limit 等限制方法; none():强制查询无结果(返回空数组 [] 或计数 0),不执行实际数据库查询(用于模拟空数据或快速校验) | |
| 高级构建 | as(), from() | 设置表别名、指定数据源 |
group(), having() | 分组与聚合过滤 | |
offset(), limit() | 分页查询 | |
| 底层操作 | $query(), toSQL() | 执行原始 SQL、获取执行语句 |
| 特殊控制 | truncate() | 模拟空结果、清空表数据 |
通过合理组合上述方法,可高效构建类型安全、语义清晰的复杂数据库查询,同时保持良好的可维护性和性能。