sql
当需要使用原始 SQL 时,可使用从 BaseTable 文件导出的 sql 函数,该函数也可通过查询对象直接调用,以方便在查询构建中嵌入原生 SQL 片段。
指定结果类型
在使用自定义 SQL 进行查询选择时,可通过 <Generic> 语法显式指定结果类型,确保类型安全:
import { sql } from './baseTable';
// 声明返回结果中 `num` 字段为 number 类型
const result: { num: number }[] = await db.table.select({
num: sql<number>`random() * 100`
});
列类型解析
若需对查询结果进行类型解析(如将数据库时间戳转为 Date 对象),可通过 type 方法为列指定解析器:
const result: { timestamp: Date }[] = await db.table.select({
// 通过 type 方法指定 timestamp 列为时间戳类型(需配合列类型覆盖使用)
timestamp: sql`now()`.type((t) => t.timestamp()),
});
动态类型与条件操作
在 from 等高级查询场景中,通过回调函数动态设置列类型,可使生成的 SQL 支持特定类型的条件操作:
const subQuery = db.someTable.select({
// 定义 `sum` 列为十进制类型,允许后续数值比较
sum: (q) => sql`$a + $b`.type((t) => t.decimal()).values({ a: 1, b: 2 }),
});
// 基于数值类型条件进行过滤
const result = await db.$from(subQuery).where({ sum: { gte: 5 } });
SQL 模板字面量安全插值
框架提供一系列以 Sql 结尾的查询方法(如 whereSql、orderSql),支持直接传递 SQL 模板字面量,且内置参数转义机制,完全避免 SQL 注入风险:
const { value } = req.params;
// 安全的参数插值,自动处理引号转义
await db.table.whereSql`column = ${value}`;
列引用最佳实践
直接通过字符串拼接 SQL 时需避免手动插入列名或值,推荐使用 column 或 ref 方法引用列(会自动添加表名前缀,避免多表连接时的歧义):
// 多表连接场景下的列引用,自动添加表名前缀
db.table.join(db.otherTable, 'id', 'other.otherId').where`
${db.table.column('id')} = 1 AND ${db.otherTable.ref('id')} = 2
`;
原始 SQL 安全参数注入
若需在 sql({ raw: '...' }) 中注入参数,可通过 $ 标识占位符,并通过 values 对象传递实际值( `` 用于转义列名或表名):
await db.table.where( sql<boolean>({ raw: '
column = random() * $value', // $转义为数据库列名,value 为参数占位符
values: { column: 'someColumn', value: 100 }, // 参数对象
}),
);
用法总结
sql`key = ${value}`; // 基础模板字面量插值
sql<boolean>`key = ${value}`; // 显式指定结果类型
sql`key = ${value}`.type((t) => t.boolean()); // 为 SELECT 列指定类型解析器
sql({ raw: 'random()' }); // 纯原始 SQL 字符串(禁止手动插值)
sql({ raw: `$$column = $value`, values: { column: 'col', value: 123 } }); // 带转义与参数的原始 SQL
column
column 方法用于引用当前表的列,支持在原始 SQL 片段或表达式构建中使用,生成带表名前缀的列引用。
await db.table.select({
// 生成 SQL:("table"."id" = 1 OR "table"."name" = 'name') AS "one"
one: (q) => sql<boolean>`${q.column('id')} = ${1} OR ${q.column('name')} = ${'name'}`,
// 等价的表达式构建方式,避免原始 SQL 拼接
two: (q) => q.column('id').equals(1).or(q.column('name').equals('name')),
});
ref
ref 方法与 column 类似,但支持引用连接表的列或动态定义的列,适用于多表关联场景。
await db.table.join('otherTable').select({
// 引用连接表列,生成 SQL:("otherTable"."id" = 1 OR "otherTable"."name" = 'name') AS "one"
one: (q) => sql<boolean>`${q.ref('otherTable.id')} = ${1} OR ${q.ref('otherTable.name')} = ${'name'}`,
// 表达式构建方式,链式调用实现条件组合
two: (q) => q.ref('otherTable.id').equals(1).or(q.ref('otherTable.name').equals('name')),
});
fn
fn 方法用于调用任意 SQL 函数,支持函数参数传递、返回类型声明及聚合操作。
示例:调用数学函数
const q = await User.select({
// 调用 PostgreSQL 的 sqrt 函数,指定返回类型为 number
sqrt: (q) => q.fn<number>('sqrt', ['numericColumn']),
}).take();
// 类型安全:q.sqrt 为 number 类型
q.sqrt;
示例:聚合函数与类型约束
const q = await User.select({
sqrtIsGreaterThan5: (q) =>
q.fn('sqrt', ['numericColumn']) // 调用平方根函数
.type((t) => t.float()) // 指定列类型为浮点数,以支持数值比较
.gt(5), // 生成 SQL 条件:sqrt(numericColumn) > 5
}).take();
// 返回值为布尔值(可空,取决于列是否允许 null)
q.sqrtIsGreaterThan5;