跳到主要内容

Filter API 高级过滤查询

v1.1.22+

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" }, // 第三优先级
],
});

分页

使用 currentPagepageSize 参数进行分页:

// 获取第 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 图配置界面可以:

  • 查看系统自动识别的表关联关系
  • 手动添加或修改表关联关系
  • 配置关联字段和关联类型

基础用法

selectwhereorderBy 中使用 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,
});

注意事项

  1. 表名引用:使用数据库表名(如 profile),而不是数据集编码(如 dataset_xxx

  2. 关联关系:系统会自动检测表之间的关联关系(基于外键等),无需手动指定

  3. 数据结构:返回的关联表数据会嵌套在对应的对象中,如 customer.name 会返回 { customer: { name: "..." } }

  4. 性能考虑

    • 关联查询会增加数据库负担,按需使用
    • 使用 select 只选择需要的字段
    • 合理设置 pageSize 避免返回过多数据
  5. 嵌套限制:最多支持 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,避免一次返回过多数据
  • 为常用查询字段添加索引(需联系平台管理员)

📚 相关文档


有问题?查看 故障排查文档 或联系技术支持。