Filter API 高级过滤查询
filter API 是 Lovrabet SDK 提供的强大数据查询接口,支持复杂条件筛选、字段选择、排序和分页,是进行数据列表查询的推荐方式。
开始之前,请确保已完成 SDK 配置。推荐使用 CLI 自动生成配置。
以下示例使用别名方式 client.models.users 以便阅读,也可以使用标准方式 client.models.dataset_xxx(功能完全一致)。
从 v1.1.22 版本开始,filter API 同时支持 OpenAPI 模式 和 WebAPI 模式。
🎯 主要功能
- ✅ 复杂条件查询 - 支持多条件组合、范围查询、模糊匹配等
- ✅ 多表关联 - 支持关联表查询,自动 JOIN 关联数据
- ✅ 字段选择 - 只返回需要的字段,减少数据传输量
- ✅ 灵活排序 - 支持多字段排序
- ✅ 分页查询 - 内置分页支持
- ✅ 类型安全 - 完整的 TypeScript 类型支持
📝 API 签名
async filter<T = any>(params?: FilterParams): Promise<ListResponse<T>>
参数说明
interface FilterParams {
where?: WhereCondition; // 查询条件
select?: string[]; // 选择返回的字段
orderBy?: SortList; // 排序规则
currentPage?: number; // 当前页码(从 1 开始,默认: 1)
pageSize?: number; // 每页数量(默认: 20)
}
返回值
interface ListResponse<T> {
tableData: T[]; // 数据列表
total: number; // 总记录数
currentPage: number; // 当前页码
pageSize: number; // 每页数量
}
🔍 Where 条件详解
条件操作符
filter API 支持以下条件操作符(所有操作符都以 $ 开头):
| 操作符 | 说明 | 示例 |
|---|---|---|
$eq | 等于 | { age: { $eq: 18 } } |
$ne | 不等于 | { status: { $ne: 'deleted' } } |
$gte / $gteq | 大于等于 | { age: { $gte: 18 } } |
$lte / $lteq | 小于等于 | { age: { $lte: 65 } } |
$in | 在集合内 | { country: { $in: ['中国', '美国'] } } |
$contain | 包含(模糊匹配) | { name: { $contain: 'hello' } } |
$startWith | 以...开头 | { email: { $startWith: 'admin' } } |
$endWith | 以...结尾 | { filename: { $endWith: '.pdf' } } |
$ 开头的表达式是 filter API 的特殊操作符,用于表示查询条件类型。这是一种通用的查询语言约定,可以避免与普通字段名冲突。
逻辑连接符
支持 $and 和 $or 进行条件组合:
| 连接符 | 说明 | 示例 |
|---|---|---|
$and | 且(所有条件都满足) | { $and: [条件1, 条件2] } |
$or | 或(任一条件满足) | { $or: [条件1, 条件2] } |
💡 使用示例
基础查询
简单等值查询
// 查询年龄为 18 岁的用户
const result = await client.models.users.filter({
where: {
age: { $eq: 18 },
},
});
范围查询
// 查询年龄在 18-45 岁之间的用户
const result = await client.models.users.filter({
where: {
age: {
$gte: 18, // 大于等于 18
$lte: 45, // 小于等于 45
},
},
});
集合查询
// 查询来自中国、美国或日本的用户
const result = await client.models.users.filter({
where: {
country: {
$in: ["中国", "美国", "日本"],
},
},
});
模糊匹配
// 查询名字包含 "hello" 的用户
const result = await client.models.users.filter({
where: {
name: { $contain: "hello" },
},
});
// 查询邮箱以 "admin" 开头的用户
const result = await client.models.users.filter({
where: {
email: { $startWith: "admin" },
},
});
复合条件查询
AND 条件组合
// 查询 18-45 岁且来自中国的 VIP 用户
const result = await client.models.users.filter({
where: {
$and: [
{ age: { $gte: 18, $lte: 45 } },
{ country: { $eq: "中国" } },
{ vip: { $ne: null } },
],
},
});
OR 条件组合
// 查询年龄小于 18 或大于 65 的用户
const result = await client.models.users.filter({
where: {
$or: [{ age: { $lte: 18 } }, { age: { $gte: 65 } }],
},
});
混合逻辑
// 查询 VIP 用户或者活跃且来自中国的普通用户
const result = await client.models.users.filter({
where: {
$or: [
{ vip: { $eq: true } },
{
$and: [{ active: { $eq: true } }, { country: { $eq: "中国" } }],
},
],
},
});
字段选择
使用 select 参数只返回需要的字段,提高查询效率:
// 只返回 id、name、email 字段
const result = await client.models.users.filter({
where: {
country: { $eq: "中国" },
},
select: ["id", "name", "email"],
});
console.log(result.tableData);
// [
// { id: 1, name: '张三', email: 'zhangsan@example.com' },
// { id: 2, name: '李四', email: 'lisi@example.com' }
// ]
当数据表字段很多但只需要其中几个字段时,使用 select 可以显著减少网络传输数据量和查询时间。
排序
使用 orderBy 参数指定排序规则:
// 单字段排序:按最后登录时间降序
const result = await client.models.users.filter({
where: {
country: { $eq: "中国" },
},
orderBy: [{ lastLoginAt: "desc" }],
});
// 多字段排序:先按优先级降序,再按创建时间降序
const result = await client.models.tasks.filter({
where: {
status: { $ne: "completed" },
},
orderBy: [
{ priority: "desc" }, // 第一优先级
{ createTime: "desc" }, // 第二优先级
{ name: "asc" }, // 第三优先级
],
});
分页
使用 currentPage 和 pageSize 参数进行分页:
// 获取第 2 页,每页 50 条记录
const result = await client.models.users.filter({
where: {
country: { $eq: "中国" },
},
currentPage: 2,
pageSize: 50,
});
console.log(`总数: ${result.total}`);
console.log(`当前页: ${result.currentPage}`);
console.log(`每页数量: ${result.pageSize}`);
console.log(`数据:`, result.tableData);
🔗 多表关联查询
v1.2.0+filter API 支持多表关联查询(JOIN),可以在一次查询中获取关联表的数据,无需手动进行多次查询。
filter 多表查询基于 Lovrabet 逆向推理引擎分析出的表关联关系运行。只有被引擎识别为有关联关系的表才能进行关联查询。如果两表确实存在关联但未被识别,可在 ER 图配置界面手动添加关联关系。
关联查询特性
| 特性 | 说明 |
|---|---|
| JOIN 类型 | 统一使用 LEFT JOIN,确保主表数据不丢失 |
| 关联检测 | 基于 Lovrabet 逆向推理引擎自动分析 |
| 支持关系 | 1:1、N:1 关联关系 |
| 嵌套层级 | 最多支持 5 层嵌套 |
| 字段引用 | 使用 tableName.fieldName 格式 |
配置表关联关系
ER 图配置地址:https://app.lovrabet.com/app/{appCode}/data/er
在 ER 图配置界面可以:
- 查看系统自动识别的表关联关系
- 手动添加或修改表关联关系
- 配置关联字段和关联类型
基础用法
在 select、where、orderBy 中使用 tableName.fieldName 格式引用关联表字段:
// 查询文章及其关联的作者信息
// 主表:article(文章),关联表:profile(作者),1:1 关联
const result = await client.models.article.filter({
select: [
"id",
"title",
"content",
"create_at",
"profile.username", // 关联表字段
"profile.avatar_url" // 关联表字段
],
where: {
"status": { "$eq": "published" },
"profile.is_signed": { "$eq": true } // 过滤关联表条件
},
orderBy: [
{ "create_at": "desc" },
{ "profile.username": "desc" } // 按关联表字段排序
],
currentPage: 1,
pageSize: 20,
});
// 返回结果自动包含关联表数据
console.log(result.tableData[0]);
// {
// id: 1,
// title: "文章标题",
// content: "文章内容",
// create_at: "2024-01-15T10:30:00Z",
// profile: {
// username: "张三",
// avatar_url: "https://...",
// is_signed: true
// }
// }
关联查询场景
场景 1:订单关联客户信息
// 查询订单及其客户信息
// 主表:orders(订单),关联表:customers(客户),N:1 关联
const result = await client.models.orders.filter({
select: [
"id",
"order_no",
"total_amount",
"status",
"customer.name", // 客户名称
"customer.phone", // 客户电话
"customer.level" // 客户等级
],
where: {
"status": { "$eq": "pending" },
"customer.level": { "$eq": "VIP" } // 只查询 VIP 客户的订单
},
orderBy: [{ "create_time": "desc" }],
currentPage: 1,
pageSize: 20,
});
console.log(result.tableData[0]);
// {
// id: 1,
// order_no: "ORD20240115001",
// total_amount: 1000,
// status: "pending",
// customer: {
// name: "张三",
// phone: "138****8000",
// level: "VIP"
// }
// }
场景 2:员工关联部门信息
// 查询员工及其部门信息
// 主表:employees(员工),关联表:departments(部门),N:1 关联
const result = await client.models.employees.filter({
select: [
"id",
"name",
"position",
"dept.name", // 部门名称
"dept.location" // 部门位置
],
where: {
"status": { "$eq": "active" },
"dept.location": { "$eq": "北京" } // 只查询北京地区的员工
},
orderBy: [{ "dept.name": "asc" }, { "name": "asc" }],
currentPage: 1,
pageSize: 50,
});
场景 3:多层关联查询
// 查询订单及其客户、客户的销售代表信息
// 主表:orders → customers → users(销售代表)
// 支持最多 5 层嵌套
const result = await client.models.orders.filter({
select: [
"id",
"order_no",
"total_amount",
"customer.name", // 客户名称
"customer.sales.name", // 销售代表姓名
"customer.sales.department" // 销售代表所属部门
],
where: {
"status": { "$eq": "completed" },
"customer.sales.department": { "$eq": "华北区" }
},
orderBy: [{ "create_time": "desc" }],
currentPage: 1,
pageSize: 20,
});
关联查询 + 复合条件
// 复杂场景:查询活跃客户的高额订单
const result = await client.models.orders.filter({
select: [
"id",
"order_no",
"total_amount",
"customer.name",
"customer.level",
"customer.phone"
],
where: {
$and: [
{ "total_amount": { "$gte": 10000 } }, // 订单金额 >= 10000
{ "create_time": { "$gte": "2024-01-01" } }, // 今年订单
{ "customer.level": { "$in": ["VIP", "SVIP"] } }, // VIP 或 SVIP 客户
{ "customer.status": { "$eq": "active" } } // 客户状态为活跃
]
},
orderBy: [
{ "total_amount": "desc" },
{ "customer.level": "desc" }
],
currentPage: 1,
pageSize: 50,
});
注意事项
-
表名引用:使用数据库表名(如
profile),而不是数据集编码(如dataset_xxx) -
关联关系:系统会自动检测表之间的关联关系(基于外键等),无需手动指定
-
数据结构:返回的关联表数据会嵌套在对应的对象中,如
customer.name会返回{ customer: { name: "..." } } -
性能考虑:
- 关联查询会增加数据库负担,按需使用
- 使用
select只选择需要的字段 - 合理设置
pageSize避免返回过多数据
-
嵌套限制:最多支持 5 层嵌套关联
🎨 完整示例
示例 1:用户管理系统
import { createClient } from "@lovrabet/sdk";
const client = createClient();
// 查询活跃的中国 VIP 用户,年龄在 18-45 岁之间
// 只返回必要字段,按最后登录时间倒序排列
const result = await client.models.users.filter({
where: {
$and: [
{ age: { $gte: 18, $lte: 45 } },
{ country: { $in: ["中国", "美国", "日本"] } },
{ vip: { $ne: null } },
{ active: { $eq: true } },
{ name: { $contain: "hello" } },
],
},
select: ["id", "name", "age", "country", "lastLoginAt", "email"],
orderBy: [{ lastLoginAt: "desc" }, { name: "asc" }],
currentPage: 1,
pageSize: 20,
});
console.log(`找到 ${result.total} 个符合条件的用户`);
result.tableData.forEach((user) => {
console.log(`${user.name} (${user.age}岁) - ${user.email}`);
});
示例 2:订单查询
// 查询最近 30 天内,金额大于 100 且状态为待支付或处理中的订单
const thirtyDaysAgo = new Date();
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
const result = await client.models.orders.filter({
where: {
$and: [
{ createTime: { $gte: thirtyDaysAgo.toISOString() } },
{ amount: { $gte: 100 } },
{
$or: [
{ status: { $eq: "pending" } },
{ status: { $eq: "processing" } },
],
},
],
},
select: ["id", "orderNo", "amount", "status", "createTime", "customerName"],
orderBy: [{ amount: "desc" }, { createTime: "desc" }],
currentPage: 1,
pageSize: 50,
});
示例 3:搜索功能
// 实现一个搜索功能:在标题或内容中包含关键词
async function searchArticles(keyword: string, page: number = 1) {
return await client.models.articles.filter({
where: {
$or: [
{ title: { $contain: keyword } },
{ content: { $contain: keyword } },
],
},
select: ["id", "title", "summary", "author", "publishTime"],
orderBy: [{ publishTime: "desc" }],
currentPage: page,
pageSize: 10,
});
}
// 使用搜索
const results = await searchArticles("人工智能", 1);
console.log(`搜索到 ${results.total} 篇文章`);
示例 4:React 组件集成
import { useState, useEffect } from "react";
import { client } from "./api/client";
function UserList() {
const [users, setUsers] = useState([]);
const [total, setTotal] = useState(0);
const [currentPage, setCurrentPage] = useState(1);
const [loading, setLoading] = useState(false);
// 查询条件
const [filters, setFilters] = useState({
country: "中国",
minAge: 18,
maxAge: 65,
vipOnly: false,
});
useEffect(() => {
loadUsers();
}, [currentPage, filters]);
const loadUsers = async () => {
setLoading(true);
try {
const result = await client.models.users.filter({
where: {
$and: [
{ age: { $gte: filters.minAge, $lte: filters.maxAge } },
{ country: { $eq: filters.country } },
...(filters.vipOnly ? [{ vip: { $ne: null } }] : []),
],
},
select: ["id", "name", "age", "email", "country", "vip"],
orderBy: [{ vip: "desc" }, { age: "asc" }],
currentPage,
pageSize: 20,
});
setUsers(result.tableData);
setTotal(result.total);
} catch (error) {
console.error("加载用户失败:", error);
} finally {
setLoading(false);
}
};
return (
<div>
<h1>用户列表 (共 {total} 人)</h1>
{/* 过滤条件 */}
<div className="filters">
<input
type="number"
value={filters.minAge}
onChange={(e) =>
setFilters({ ...filters, minAge: Number(e.target.value) })
}
placeholder="最小年龄"
/>
<input
type="number"
value={filters.maxAge}
onChange={(e) =>
setFilters({ ...filters, maxAge: Number(e.target.value) })
}
placeholder="最大年龄"
/>
<label>
<input
type="checkbox"
checked={filters.vipOnly}
onChange={(e) =>
setFilters({ ...filters, vipOnly: e.target.checked })
}
/>
仅显示 VIP
</label>
</div>
{/* 用户列表 */}
{loading ? (
<div>加载中...</div>
) : (
<table>
<thead>
<tr>
<th>ID</th>
<th>姓名</th>
<th>年龄</th>
<th>邮箱</th>
<th>VIP</th>
</tr>
</thead>
<tbody>
{users.map((user) => (
<tr key={user.id}>
<td>{user.id}</td>
<td>{user.name}</td>
<td>{user.age}</td>
<td>{user.email}</td>
<td>{user.vip ? "✓" : ""}</td>
</tr>
))}
</tbody>
</table>
)}
{/* 分页 */}
<div className="pagination">
<button
disabled={currentPage === 1}
onClick={() => setCurrentPage(currentPage - 1)}
>
上一页
</button>
<span>第 {currentPage} 页</span>
<button
disabled={currentPage * 20 >= total}
onClick={() => setCurrentPage(currentPage + 1)}
>
下一页
</button>
</div>
</div>
);
}
⚠️ 注意事项
1. 操作符大小写
所有操作符都必须以小写的 $ 开头,大小写敏感:
// ✅ 正确
{
age: {
$eq: 18;
}
}
// ❌ 错误
{
age: {
$EQ: 18;
}
} // 大写不被识别
{
age: {
eq: 18;
}
} // 缺少 $
2. 条件类型
确保条件值的类型与字段类型匹配:
// ✅ 正确 - 数字字段用数字值
{
age: {
$gte: 18;
}
}
// ❌ 错误 - 数字字段用字符串值
{
age: {
$gte: "18";
}
}
// ✅ 正确 - 日期字段用 ISO 字符串
{
createTime: {
$gte: "2025-01-01T00:00:00.000Z";
}
}
3. 性能优化
- 使用
select减少返回字段 - 合理设置
pageSize,避免一次返回过多数据 - 为常用查询字段添加索引(需联系平台管理员)
📚 相关文档
- 多表关联查询 - filter 多表查询与自定义 SQL 完整指南
- 销售报表:自定义 SQL - 自定义 SQL 查询示例
- API 参考手册 - 完整 API 文档
- 快速开始 - 5 分钟快速上手
- 实际应用示例 - 更多实战示例
- 故障排查 - 常见问题解决
有问题?查看 故障排查文档 或联系技术支持。