EXISTS / NOT EXISTS 操作符
使用 EXISTS 和 NOT EXISTS 操作符可以查询关系字段是否存在满足条件的关联实体,无需加载关联数据到内存。
核心概念
什么是 EXISTS?
EXISTS 操作符用于检查关系字段中是否存在满足特定条件的关联实体:
- ✅ 高性能: SQL 层面的子查询,避免加载大量关联数据
- ✅ 类型安全: TypeScript 自动推断子查询类型
- ✅ 增量更新: 支持响应式查询的智能刷新
- ✅ 跨框架: Angular/React/Vue 统一 API
支持的关系类型
| 关系类型 | 装饰器 | EXISTS 支持 |
|---|---|---|
| 一对多 (1:N) | @OneToMany | ✅ |
| 多对一 (N:1) | @ManyToOne | ✅ |
| 一对一 (1:1) | @OneToOne | ✅ |
| 多对多 (N:M) | @ManyToMany | ✅ |
基础用法
检查关系是否存在
// 查询有订单的用户
const usersWithOrders = await firstValueFrom(
User.find({
where: {
combinator: 'and',
rules: [{ field: 'orders', operator: 'exists' }]
}
})
);
检查关系不存在
// 查询没有订单的用户
const usersWithoutOrders = await firstValueFrom(
User.find({
where: {
combinator: 'and',
rules: [{ field: 'orders', operator: 'notExists' }]
}
})
);
带条件的 EXISTS
基本条件过滤
// 查询有"已完成"订单的用户
const usersWithCompletedOrders = await firstValueFrom(
User.find({
where: {
combinator: 'and',
rules: [
{
field: 'orders',
operator: 'exists',
where: {
combinator: 'and',
rules: [{ field: 'status', operator: '=', value: 'completed' }]
}
}
]
}
})
);
复杂条件组合
// 查询有"高价值且未退款"订单的用户
const premiumUsers = await firstValueFrom(
User.find({
where: {
combinator: 'and',
rules: [
{
field: 'orders',
operator: 'exists',
where: {
combinator: 'and',
rules: [
{ field: 'totalAmount', operator: '>', value: 1000 },
{ field: 'refunded', operator: '=', value: false },
{ field: 'status', operator: '=', value: 'completed' }
]
}
}
]
}
})
);
日期范围查询
// 查询最近30天有订单的活跃用户
const thirtyDaysAgo = new Date();
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
const activeUsers = await firstValueFrom(
User.find({
where: {
combinator: 'and',
rules: [
{
field: 'orders',
operator: 'exists',
where: {
combinator: 'and',
rules: [{ field: 'createdAt', operator: '>=', value: thirtyDaysAgo.toISOString() }]
}
}
]
}
})
);
各关系类型示例
ONE_TO_MANY (一对多)
@Entity({ name: 'Menu', namespace: 'public' })
class Menu extends EntityBase {
@PrimaryColumn({ type: PropertyType.uuid })
id!: string;
@Column({ type: PropertyType.string })
title!: string;
@Column({ type: PropertyType.uuid, nullable: true })
parentId?: string | null;
@OneToMany(() => Menu, 'parentId', { eager: false })
children?: Menu[];
}
// 查询有子菜单的菜单项
const menusWithChildren = await firstValueFrom(
Menu.find({
where: {
combinator: 'and',
rules: [{ field: 'children', operator: 'exists' }]
}
})
);
// 查询有"激活"子菜单的菜单项
const menusWithActiveChildren = await firstValueFrom(
Menu.find({
where: {
combinator: 'and',
rules: [
{
field: 'children',
operator: 'exists',
where: {
combinator: 'and',
rules: [{ field: 'active', operator: '=', value: true }]
}
}
]
}
})
);
MANY_TO_ONE (多对一)
@Entity({ name: 'Order', namespace: 'shop' })
class Order extends EntityBase {
@PrimaryColumn({ type: PropertyType.uuid })
id!: string;
@Column({ type: PropertyType.uuid })
userId!: string;
@ManyToOne(() => User, 'userId', { eager: false })
user?: User;
}
// 查询有关联用户的订单(通常不为 null)
const ordersWithUser = await firstValueFrom(
Order.find({
where: {
combinator: 'and',
rules: [{ field: 'user', operator: 'exists' }]
}
})
);
// 查询关联到 VIP 用户的订单
const vipOrders = await firstValueFrom(
Order.find({
where: {
combinator: 'and',
rules: [
{
field: 'user',
operator: 'exists',
where: {
combinator: 'and',
rules: [{ field: 'vipLevel', operator: '>=', value: 3 }]
}
}
]
}
})
);
ONE_TO_ONE (一对一)
@Entity({ name: 'User', namespace: 'shop' })
class User extends EntityBase {
@PrimaryColumn({ type: PropertyType.uuid })
id!: string;
@Column({ type: PropertyType.uuid, nullable: true })
profileId?: string | null;
@OneToOne(() => Profile, 'profileId', { eager: false })
profile?: Profile;
}
// 查询有资料的用户
const usersWithProfile = await firstValueFrom(
User.find({
where: {
combinator: 'and',
rules: [{ field: 'profile', operator: 'exists' }]
}
})
);
// 查询资料已验证的用户
const verifiedUsers = await firstValueFrom(
User.find({
where: {
combinator: 'and',
rules: [
{
field: 'profile',
operator: 'exists',
where: {
combinator: 'and',
rules: [{ field: 'verified', operator: '=', value: true }]
}
}
]
}
})
);
MANY_TO_MANY (多对多)
@Entity({ name: 'Project', namespace: 'public' })
class Project extends EntityBase {
@PrimaryColumn({ type: PropertyType.uuid })
id!: string;
@ManyToMany(() => Tag, {
junctionEntity: 'ProjectTag',
junctionNamespace: 'public',
currentKey: 'projectId',
relatedKey: 'tagId',
eager: false
})
tags?: Tag[];
}
// 查询有标签的项目
const projectsWithTags = await firstValueFrom(
Project.find({
where: {
combinator: 'and',
rules: [{ field: 'tags', operator: 'exists' }]
}
})
);
// 查询带有特定标签的项目
const urgentProjects = await firstValueFrom(
Project.find({
where: {
combinator: 'and',
rules: [
{
field: 'tags',
operator: 'exists',
where: {
combinator: 'and',
rules: [{ field: 'name', operator: '=', value: 'urgent' }]
}
}
]
}
})
);
高级用法
多个 EXISTS 条件组合
// 查询同时满足多个关系条件的用户
const qualifiedUsers = await firstValueFrom(
User.find({
where: {
combinator: 'and',
rules: [
// 有已完成订单
{
field: 'orders',
operator: 'exists',
where: {
combinator: 'and',
rules: [{ field: 'status', operator: '=', value: 'completed' }]
}
},
// 有正面评价
{
field: 'reviews',
operator: 'exists',
where: {
combinator: 'and',
rules: [{ field: 'rating', operator: '>=', value: 4 }]
}
},
// 没有退款
{
field: 'refunds',
operator: 'notExists'
}
]
}
})
);
EXISTS 与普通条件混合
// 结合实体自身条件和关系条件
const targetUsers = await firstValueFrom(
User.find({
where: {
combinator: 'and',
rules: [
// 用户自身条件
{ field: 'age', operator: '>=', value: 18 },
{ field: 'active', operator: '=', value: true },
// 关系条件
{
field: 'orders',
operator: 'exists',
where: {
combinator: 'and',
rules: [{ field: 'totalAmount', operator: '>', value: 500 }]
}
}
]
}
})
);
自引用关系
// 查询有子节点的菜单项(自引用)
const parentMenus = await firstValueFrom(
Menu.find({
where: {
combinator: 'and',
rules: [{ field: 'children', operator: 'exists' }]
}
})
);
// 查询有激活子节点的菜单项
const menusWithActiveChildren = await firstValueFrom(
Menu.find({
where: {
combinator: 'and',
rules: [
{
field: 'children',
operator: 'exists',
where: {
combinator: 'and',
rules: [{ field: 'active', operator: '=', value: true }]
}
}
]
}
})
);
响应式查询支持
EXISTS 操作符完全支持响应式查询的增量更新。
自动刷新场景
场景 1: 创建关联实体
// 查询有订单的用户
const users$ = User.find({
where: {
combinator: 'and',
rules: [{ field: 'orders', operator: 'exists' }]
}
});
// 创建新订单时,查询自动更新
const order = new Order();
order.userId = 'user-123';
await order.save(); // ✅ 用户 'user-123' 自动加入结果集
场景 2: 更新关联实体
// 查询有已完成订单的用户
const users$ = User.find({
where: {
combinator: 'and',
rules: [
{
field: 'orders',
operator: 'exists',
where: {
combinator: 'and',
rules: [{ field: 'status', operator: '=', value: 'completed' }]
}
}
]
}
});
// 订单状态变更时,查询自动更新
order.status = 'completed';
await order.save(); // ✅ 订单所属用户自动加入结果集
场景 3: 删除关联实体
// 查询有订单的用户
const users$ = User.find({
where: {
combinator: 'and',
rules: [{ field: 'orders', operator: 'exists' }]
}
});
// 删除用户的最后一个订单
await order.remove(); // ✅ 用户自动从结果集移除(如果没有其他订单)
性能优化
SQL 层面优化
EXISTS 操作符在 SQL 层面使用子查询实现,性能优于传统 JOIN:
-- EXISTS 查询(推荐)
SELECT * FROM "public$User" _
WHERE EXISTS (
SELECT 1 FROM "shop$Order" child
WHERE child."userId" = _."id"
AND child."status" = 'completed'
);
-- 传统 JOIN(可能产生重复行)
SELECT DISTINCT _ .* FROM "public$User" _
LEFT JOIN "shop$Order" child ON child."userId" = _."id"
WHERE child."status" = 'completed';
性能对比:
| 场景 | EXISTS | JOIN |
|---|---|---|
| 存在性检查 | ⚡ 快 (找到第一个即返回) | 慢 (需扫描所有) |
| 内存占用 | 低 (不加载关联数据) | 高 (加载并去重) |
| 索引利用 | 高 | 中 |
索引建议
为 EXISTS 查询添加合适的索引:
@Entity({ name: 'Order', namespace: 'shop' })
@Index({ name: 'idx_order_user_status', columns: ['userId', 'status'] })
class Order extends EntityBase {
@Column({ type: PropertyType.uuid })
userId!: string;
@Column({ type: PropertyType.string })
status!: string;
}
避免的反模式
❌ 不要: 先加载关联数据再过滤
// 低效:加载所有订单到内存
const users = await User.findAll({ relations: ['orders'] });
const filtered = users.filter(u => u.orders?.some(o => o.status === 'completed'));
✅ 推荐: 使用 EXISTS 在数据库层面过滤
// 高效:数据库层面过滤
const users = await firstValueFrom(
User.find({
where: {
combinator: 'and',
rules: [
{
field: 'orders',
operator: 'exists',
where: {
combinator: 'and',
rules: [{ field: 'status', operator: '=', value: 'completed' }]
}
}
]
}
})
);
类型安全
TypeScript 自动推断子查询的类型:
// ✅ 类型安全:TypeScript 知道 where 子句应该是 Order 的条件
User.find({
where: {
combinator: 'and',
rules: [
{
field: 'orders',
operator: 'exists',
where: {
combinator: 'and',
rules: [
{ field: 'status', operator: '=', value: 'completed' }, // ✅ Order.status
{ field: 'totalAmount', operator: '>', value: 100 } // ✅ Order.totalAmount
]
}
}
]
}
});
// ❌ 编译错误:unknownField 不存在于 Order 类型
User.find({
where: {
combinator: 'and',
rules: [
{
field: 'orders',
operator: 'exists',
where: {
combinator: 'and',
rules: [
{ field: 'unknownField', operator: '=', value: 'x' } // ❌ TypeScript 报错
]
}
}
]
}
});
常见问题
Q: EXISTS 和 JOIN 有什么区别?
A:
- EXISTS: 只检查存在性,SQL 找到第一个匹配就返回,不加载关联数据
- JOIN: 加载所有匹配的关联数据,可能产生重复行,需要 DISTINCT
Q: EXISTS 支持嵌套条件吗?
A: 支持!可以在 where 子句中使用任意复杂的条件组合(and/or)。
Q: 性能如何?
A: EXISTS 通常比 JOIN 更快,特别是:
- ✅ 只需检查存在性(不需要关联数据)
- ✅ 关联表有大量记录
- ✅ 有合适的索引
Q: 如何调试 EXISTS 查询?
A:
- 检查 SQL 日志(SQLite 控制台)
- 使用 EXPLAIN QUERY PLAN 分析执行计划
- 确认关联字段有索引
Q: 响应式查询何时刷新?
A: 当以下情况发生时自动刷新:
- 关联实体被创建/更新/删除
- 关联实体的条件字段变更
- 中间表记录变更(MANY_TO_MANY)
最佳实践
- 优先使用 EXISTS 进行存在性检查,避免加载不必要的关联数据
- 添加索引 到 WHERE 子句中使用的字段
- 简化条件 避免过于复杂的嵌套子查询
- 利用类型推断 让 TypeScript 帮助你编写正确的查询
- 测试性能 在大数据集上验证查询效率