Orchid ORM 查询方法

116 阅读4分钟

查询方法

不可变性原则

所有查询方法均返回新的查询实例不会修改原始查询链。条件调用需通过重新赋值生效:

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) :无匹配时返回 undefined

    const 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()模拟空结果、清空表数据

通过合理组合上述方法,可高效构建类型安全、语义清晰的复杂数据库查询,同时保持良好的可维护性和性能。