from
子句中的子查询join
子句中的子查询limit
和 offset
分页eq(left, right)
gt(left, right)
, gte(left, right)
, lt(left, right)
, lte(left, right)
inArray(value, array)
like(value, pattern)
, ilike(value, pattern)
and(...conditions)
or(...conditions)
not(condition)
upper(value)
, lower(value)
length(value)
concat(...values)
add(left, right)
coalesce(...values)
count(value)
sum(value)
avg(value)
min(value)
, max(value)
TanStack DB 提供了一个强大、类型安全的查询系统,允许您使用类似 SQL 的流畅 API 从集合中获取、过滤、转换和聚合数据。所有查询默认都是**实时**的,这意味着当底层数据发生变化时,它们会自动更新。
查询系统围绕着一个与 Kysely 或 Drizzle 等 SQL 查询构建器类似的 API 构建,您可以通过链式调用方法来组合查询。查询构建器不会按照方法调用的顺序执行操作,而是将您的查询组合成一个优化的增量管道,然后高效地编译和执行。每个方法都会返回一个新的查询构建器,允许您链式调用操作。
实时查询解析为集合,当底层数据发生变化时,这些集合会自动更新。您可以订阅更改、迭代结果并使用所有标准的集合方法。
import { createCollection, liveQueryCollectionOptions, eq } from '@tanstack/db'
const activeUsers = createCollection(liveQueryCollectionOptions({
query: (q) =>
q
.from({ user: usersCollection })
.where(({ user }) => eq(user.active, true))
.select(({ user }) => ({
id: user.id,
name: user.name,
email: user.email,
}))
}))
import { createCollection, liveQueryCollectionOptions, eq } from '@tanstack/db'
const activeUsers = createCollection(liveQueryCollectionOptions({
query: (q) =>
q
.from({ user: usersCollection })
.where(({ user }) => eq(user.active, true))
.select(({ user }) => ({
id: user.id,
name: user.name,
email: user.email,
}))
}))
结果类型会根据您的查询结构自动推断,提供完整的 TypeScript 支持。当您使用 select 子句时,结果类型会匹配您的投影。如果没有 select,您将获得完整的架构,并带有正确的连接可选性。
要创建实时查询集合,您可以使用 liveQueryCollectionOptions 结合 createCollection,或者使用便捷函数 createLiveQueryCollection。
创建实时查询的基本方法是使用 liveQueryCollectionOptions 结合 createCollection。
import { createCollection, liveQueryCollectionOptions, eq } from '@tanstack/db'
const activeUsers = createCollection(liveQueryCollectionOptions({
query: (q) =>
q
.from({ user: usersCollection })
.where(({ user }) => eq(user.active, true))
.select(({ user }) => ({
id: user.id,
name: user.name,
}))
}))
import { createCollection, liveQueryCollectionOptions, eq } from '@tanstack/db'
const activeUsers = createCollection(liveQueryCollectionOptions({
query: (q) =>
q
.from({ user: usersCollection })
.where(({ user }) => eq(user.active, true))
.select(({ user }) => ({
id: user.id,
name: user.name,
}))
}))
为了获得更多控制,您可以指定其他选项
const activeUsers = createCollection(liveQueryCollectionOptions({
id: 'active-users', // Optional: auto-generated if not provided
query: (q) =>
q
.from({ user: usersCollection })
.where(({ user }) => eq(user.active, true))
.select(({ user }) => ({
id: user.id,
name: user.name,
})),
getKey: (user) => user.id, // Optional: uses stream key if not provided
startSync: true, // Optional: starts sync immediately
}))
const activeUsers = createCollection(liveQueryCollectionOptions({
id: 'active-users', // Optional: auto-generated if not provided
query: (q) =>
q
.from({ user: usersCollection })
.where(({ user }) => eq(user.active, true))
.select(({ user }) => ({
id: user.id,
name: user.name,
})),
getKey: (user) => user.id, // Optional: uses stream key if not provided
startSync: true, // Optional: starts sync immediately
}))
选项 | 类型 | 描述 |
---|---|---|
id | string (可选) | 实时查询的可选唯一标识符。如果未提供,将自动生成。这对于调试和日志记录很有用。 |
query | QueryBuilder 或函数 | 查询定义,这可以是 Query 实例或返回 Query 实例的函数。 |
getKey | (item) => string | number (可选) | 一个函数,用于从每一行提取唯一键。如果未提供,将使用流的内部键。对于简单情况,这是父集合的键,但在连接的情况下,自动生成的键将是父键的组合。使用 getKey 在您希望使用父集合中的特定键作为结果集合的键时非常有用。 |
schema | Schema (可选) | 可选的验证模式 |
startSync | boolean (可选) | 是否立即开始同步。默认为 true。 |
gcTime | number (可选) | 垃圾回收时间(毫秒)。默认为 5000(5 秒)。 |
对于更简单的情况,您可以使用 createLiveQueryCollection 作为快捷方式
import { createLiveQueryCollection, eq } from '@tanstack/db'
const activeUsers = createLiveQueryCollection((q) =>
q
.from({ user: usersCollection })
.where(({ user }) => eq(user.active, true))
.select(({ user }) => ({
id: user.id,
name: user.name,
}))
)
import { createLiveQueryCollection, eq } from '@tanstack/db'
const activeUsers = createLiveQueryCollection((q) =>
q
.from({ user: usersCollection })
.where(({ user }) => eq(user.active, true))
.select(({ user }) => ({
id: user.id,
name: user.name,
}))
)
在 React 中,您可以使用 useLiveQuery 钩子
import { useLiveQuery } from '@tanstack/react-db'
function UserList() {
const activeUsers = useLiveQuery((q) =>
q
.from({ user: usersCollection })
.where(({ user }) => eq(user.active, true))
)
return (
<ul>
{activeUsers.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)
}
import { useLiveQuery } from '@tanstack/react-db'
function UserList() {
const activeUsers = useLiveQuery((q) =>
q
.from({ user: usersCollection })
.where(({ user }) => eq(user.active, true))
)
return (
<ul>
{activeUsers.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)
}
有关框架集成的更多详细信息,请参阅 React 和 Vue 适配器文档。
每个查询的基础是 from 方法,它指定源集合或子查询。您可以使用对象语法为源设置别名。
from({
[alias]: Collection | Query,
}): Query
from({
[alias]: Collection | Query,
}): Query
参数
从一个集合中选择所有记录的简单查询开始
const allUsers = createCollection(liveQueryCollectionOptions({
query: (q) => q.from({ user: usersCollection })
}))
const allUsers = createCollection(liveQueryCollectionOptions({
query: (q) => q.from({ user: usersCollection })
}))
结果包含所有用户及其完整架构。您可以迭代结果或按键访问它们
// Get all users as an array
const users = allUsers.toArray
// Get a specific user by ID
const user = allUsers.get(1)
// Check if a user exists
const hasUser = allUsers.has(1)
// Get all users as an array
const users = allUsers.toArray
// Get a specific user by ID
const user = allUsers.get(1)
// Check if a user exists
const hasUser = allUsers.has(1)
使用别名使您的查询更具可读性,尤其是在处理多个集合时
const users = createCollection(liveQueryCollectionOptions({
query: (q) => q.from({ u: usersCollection })
}))
// Access fields using the alias
const userNames = createCollection(liveQueryCollectionOptions({
query: (q) =>
q
.from({ u: usersCollection })
.select(({ u }) => ({
name: u.name,
email: u.email,
}))
}))
const users = createCollection(liveQueryCollectionOptions({
query: (q) => q.from({ u: usersCollection })
}))
// Access fields using the alias
const userNames = createCollection(liveQueryCollectionOptions({
query: (q) =>
q
.from({ u: usersCollection })
.select(({ u }) => ({
name: u.name,
email: u.email,
}))
}))
使用 where 子句根据条件过滤数据。您可以链式调用多个 where 调用——它们使用 and 逻辑组合。
where 方法接受一个回调函数,该函数接收一个包含您的表别名的对象,并返回一个布尔表达式。您可以使用 eq()、gt() 等比较函数以及 and() 和 or() 等逻辑运算符来构建这些表达式。这种声明式方法允许查询系统高效地优化您的过滤器。这些将在 表达式函数参考 部分有更详细的描述。这与您使用 Kysely 或 Drizzle 构建查询的方式非常相似。
需要注意的是,where 方法不是一个在每一行或结果上执行的函数,它是一种描述将要执行的查询的方式。这种声明式方法适用于几乎所有用例,但如果您需要使用更复杂的条件,则有一个函数式变体 fn.where,它将在 函数式变体 部分进行描述。
where(
condition: (row: TRow) => Expression<boolean>
): Query
where(
condition: (row: TRow) => Expression<boolean>
): Query
参数
按简单条件过滤用户
import { eq } from '@tanstack/db'
const activeUsers = createCollection(liveQueryCollectionOptions({
query: (q) =>
q
.from({ user: usersCollection })
.where(({ user }) => eq(user.active, true))
}))
import { eq } from '@tanstack/db'
const activeUsers = createCollection(liveQueryCollectionOptions({
query: (q) =>
q
.from({ user: usersCollection })
.where(({ user }) => eq(user.active, true))
}))
链式调用多个 where 调用来实现 AND 逻辑
import { eq, gt } from '@tanstack/db'
const adultActiveUsers = createCollection(liveQueryCollectionOptions({
query: (q) =>
q
.from({ user: usersCollection })
.where(({ user }) => eq(user.active, true))
.where(({ user }) => gt(user.age, 18))
}))
import { eq, gt } from '@tanstack/db'
const adultActiveUsers = createCollection(liveQueryCollectionOptions({
query: (q) =>
q
.from({ user: usersCollection })
.where(({ user }) => eq(user.active, true))
.where(({ user }) => gt(user.age, 18))
}))
使用逻辑运算符构建复杂条件
import { eq, gt, or, and } from '@tanstack/db'
const specialUsers = createLiveQueryCollection((q) =>
q
.from({ user: usersCollection })
.where(({ user }) =>
and(
eq(user.active, true),
or(
gt(user.age, 25),
eq(user.role, 'admin')
)
)
)
)
import { eq, gt, or, and } from '@tanstack/db'
const specialUsers = createLiveQueryCollection((q) =>
q
.from({ user: usersCollection })
.where(({ user }) =>
and(
eq(user.active, true),
or(
gt(user.age, 25),
eq(user.role, 'admin')
)
)
)
)
查询系统提供了几种比较运算符
import { eq, gt, gte, lt, lte, like, ilike, inArray, and, or, not } from '@tanstack/db'
// Equality
eq(user.id, 1)
// Comparisons
gt(user.age, 18) // greater than
gte(user.age, 18) // greater than or equal
lt(user.age, 65) // less than
lte(user.age, 65) // less than or equal
// String matching
like(user.name, 'John%') // case-sensitive pattern matching
ilike(user.name, 'john%') // case-insensitive pattern matching
// Array membership
inArray(user.id, [1, 2, 3])
// Logical operators
and(condition1, condition2)
or(condition1, condition2)
not(condition)
import { eq, gt, gte, lt, lte, like, ilike, inArray, and, or, not } from '@tanstack/db'
// Equality
eq(user.id, 1)
// Comparisons
gt(user.age, 18) // greater than
gte(user.age, 18) // greater than or equal
lt(user.age, 65) // less than
lte(user.age, 65) // less than or equal
// String matching
like(user.name, 'John%') // case-sensitive pattern matching
ilike(user.name, 'john%') // case-insensitive pattern matching
// Array membership
inArray(user.id, [1, 2, 3])
// Logical operators
and(condition1, condition2)
or(condition1, condition2)
not(condition)
有关所有可用函数的完整参考,请参阅 表达式函数参考 部分。
使用 select 指定要在结果中包含哪些字段并转换数据。如果没有 select,您将获得完整的架构。
与 where 子句类似,select 方法接受一个回调函数,该函数接收一个包含您的表别名的对象,并返回一个包含您要在结果中包含的字段的对象。这些可以与 表达式函数参考 部分的函数结合使用来创建计算字段。您还可以使用展开运算符来包含表中的所有字段。
select(
projection: (row: TRow) => Record<string, Expression>
): Query
select(
projection: (row: TRow) => Record<string, Expression>
): Query
参数
从数据中选择特定字段
const userNames = createLiveQueryCollection((q) =>
q
.from({ user: usersCollection })
.select(({ user }) => ({
id: user.id,
name: user.name,
email: user.email,
}))
)
/*
Result type: { id: number, name: string, email: string }
```ts
for (const row of userNames) {
console.log(row.name)
}
```
*/
const userNames = createLiveQueryCollection((q) =>
q
.from({ user: usersCollection })
.select(({ user }) => ({
id: user.id,
name: user.name,
email: user.email,
}))
)
/*
Result type: { id: number, name: string, email: string }
```ts
for (const row of userNames) {
console.log(row.name)
}
```
*/
重命名结果中的字段
const userProfiles = createLiveQueryCollection((q) =>
q
.from({ user: usersCollection })
.select(({ user }) => ({
userId: user.id,
fullName: user.name,
contactEmail: user.email,
}))
)
const userProfiles = createLiveQueryCollection((q) =>
q
.from({ user: usersCollection })
.select(({ user }) => ({
userId: user.id,
fullName: user.name,
contactEmail: user.email,
}))
)
使用表达式创建计算字段
import { gt, length } from '@tanstack/db'
const userStats = createLiveQueryCollection((q) =>
q
.from({ user: usersCollection })
.select(({ user }) => ({
id: user.id,
name: user.name,
isAdult: gt(user.age, 18),
nameLength: length(user.name),
}))
)
import { gt, length } from '@tanstack/db'
const userStats = createLiveQueryCollection((q) =>
q
.from({ user: usersCollection })
.select(({ user }) => ({
id: user.id,
name: user.name,
isAdult: gt(user.age, 18),
nameLength: length(user.name),
}))
)
使用内置函数转换数据
import { concat, upper, gt } from '@tanstack/db'
const formattedUsers = createCollection(liveQueryCollectionOptions({
query: (q) =>
q
.from({ user: usersCollection })
.select(({ user }) => ({
...user, // Include all user fields
displayName: upper(concat(user.firstName, ' ', user.lastName)),
isAdult: gt(user.age, 18),
}))
}))
/*
Result type:
{
id: number,
name: string,
email: string,
displayName: string,
isAdult: boolean,
}
*/
import { concat, upper, gt } from '@tanstack/db'
const formattedUsers = createCollection(liveQueryCollectionOptions({
query: (q) =>
q
.from({ user: usersCollection })
.select(({ user }) => ({
...user, // Include all user fields
displayName: upper(concat(user.firstName, ' ', user.lastName)),
isAdult: gt(user.age, 18),
}))
}))
/*
Result type:
{
id: number,
name: string,
email: string,
displayName: string,
isAdult: boolean,
}
*/
有关可用函数的完整列表,请参阅 表达式函数参考 部分。
使用 join 将多个集合的数据合并。Join 默认为 left Join 类型,并且只支持相等条件。
TanStack DB 中的 Join 是合并多个集合数据的途径,在概念上与 SQL Join 非常相似。当两个集合被 Join 时,结果是一个新的集合,其中包含合并后的数据作为单行。新集合是一个实时查询集合,并且当底层数据发生变化时会自动更新。
没有 select 的 join 将返回被 Join 集合别名命名的行对象。
Join 的结果类型将考虑 Join 类型,并且 Join 字段的可选性将由 Join 类型决定。
注意
我们正在开发一个 include 系统,该系统将允许 Join 将结果投影到层次结构对象。例如,一个 issue 行可能有一个 comments 属性,该属性是 comment 行的数组。有关更多详细信息,请参阅 此 issue。
join(
{ [alias]: Collection | Query },
condition: (row: TRow) => Expression<boolean>, // Must be an `eq` condition
joinType?: 'left' | 'right' | 'inner' | 'full'
): Query
join(
{ [alias]: Collection | Query },
condition: (row: TRow) => Expression<boolean>, // Must be an `eq` condition
joinType?: 'left' | 'right' | 'inner' | 'full'
): Query
参数
Join 用户与其帖子
const userPosts = createLiveQueryCollection((q) =>
q
.from({ user: usersCollection })
.join({ post: postsCollection }, ({ user, post }) =>
eq(user.id, post.userId)
)
)
/*
Result type:
{
user: User,
post?: Post, // post is optional because it is a left join
}
```ts
for (const row of userPosts) {
console.log(row.user.name, row.post?.title)
}
```
*/
const userPosts = createLiveQueryCollection((q) =>
q
.from({ user: usersCollection })
.join({ post: postsCollection }, ({ user, post }) =>
eq(user.id, post.userId)
)
)
/*
Result type:
{
user: User,
post?: Post, // post is optional because it is a left join
}
```ts
for (const row of userPosts) {
console.log(row.user.name, row.post?.title)
}
```
*/
将 Join 类型指定为第三个参数
const activeUserPosts = createLiveQueryCollection((q) =>
q
.from({ user: usersCollection })
.join(
{ post: postsCollection },
({ user, post }) => eq(user.id, post.userId),
'inner', // `inner`, `left`, `right` or `full`
)
)
const activeUserPosts = createLiveQueryCollection((q) =>
q
.from({ user: usersCollection })
.join(
{ post: postsCollection },
({ user, post }) => eq(user.id, post.userId),
'inner', // `inner`, `left`, `right` or `full`
)
)
或者使用别名 leftJoin, rightJoin, innerJoin 和 fullJoin 方法
// Left join - all users, even without posts
const allUsers = createLiveQueryCollection((q) =>
q
.from({ user: usersCollection })
.leftJoin(
{ post: postsCollection },
({ user, post }) => eq(user.id, post.userId),
)
)
/*
Result type:
{
user: User,
post?: Post, // post is optional because it is a left join
}
*/
// Left join - all users, even without posts
const allUsers = createLiveQueryCollection((q) =>
q
.from({ user: usersCollection })
.leftJoin(
{ post: postsCollection },
({ user, post }) => eq(user.id, post.userId),
)
)
/*
Result type:
{
user: User,
post?: Post, // post is optional because it is a left join
}
*/
// Right join - all posts, even without users
const allPosts = createLiveQueryCollection((q) =>
q
.from({ user: usersCollection })
.rightJoin(
{ post: postsCollection },
({ user, post }) => eq(user.id, post.userId),
)
)
/*
Result type:
{
user?: User, // user is optional because it is a right join
post: Post,
}
*/
// Right join - all posts, even without users
const allPosts = createLiveQueryCollection((q) =>
q
.from({ user: usersCollection })
.rightJoin(
{ post: postsCollection },
({ user, post }) => eq(user.id, post.userId),
)
)
/*
Result type:
{
user?: User, // user is optional because it is a right join
post: Post,
}
*/
// Inner join - only matching records
const activeUserPosts = createLiveQueryCollection((q) =>
q
.from({ user: usersCollection })
.innerJoin(
{ post: postsCollection },
({ user, post }) => eq(user.id, post.userId),
)
)
/*
Result type:
{
user: User,
post: Post,
}
*/
// Inner join - only matching records
const activeUserPosts = createLiveQueryCollection((q) =>
q
.from({ user: usersCollection })
.innerJoin(
{ post: postsCollection },
({ user, post }) => eq(user.id, post.userId),
)
)
/*
Result type:
{
user: User,
post: Post,
}
*/
// Full join - all users and all posts
const allUsersAndPosts = createLiveQueryCollection((q) =>
q
.from({ user: usersCollection })
.fullJoin(
{ post: postsCollection },
({ user, post }) => eq(user.id, post.userId),
)
)
/*
Result type:
{
user?: User, // user is optional because it is a full join
post?: Post, // post is optional because it is a full join
}
*/
// Full join - all users and all posts
const allUsersAndPosts = createLiveQueryCollection((q) =>
q
.from({ user: usersCollection })
.fullJoin(
{ post: postsCollection },
({ user, post }) => eq(user.id, post.userId),
)
)
/*
Result type:
{
user?: User, // user is optional because it is a full join
post?: Post, // post is optional because it is a full join
}
*/
在单个查询中链式调用多个 Join
const userPostComments = createLiveQueryCollection((q) =>
q
.from({ user: usersCollection })
.join({ post: postsCollection }, ({ user, post }) =>
eq(user.id, post.userId)
)
.join({ comment: commentsCollection }, ({ post, comment }) =>
eq(post.id, comment.postId)
)
.select(({ user, post, comment }) => ({
userName: user.name,
postTitle: post.title,
commentText: comment.text,
}))
)
const userPostComments = createLiveQueryCollection((q) =>
q
.from({ user: usersCollection })
.join({ post: postsCollection }, ({ user, post }) =>
eq(user.id, post.userId)
)
.join({ comment: commentsCollection }, ({ post, comment }) =>
eq(post.id, comment.postId)
)
.select(({ user, post, comment }) => ({
userName: user.name,
postTitle: post.title,
commentText: comment.text,
}))
)
子查询允许您将一个查询的结果作为另一个查询的输入,它们嵌入在查询本身中,并被编译成一个单一的查询管道。它们非常类似于在单个操作中执行的 SQL 子查询。
请注意,子查询与在新查询的 from 或 join 子句中使用实时查询结果不同。当您这样做时,中间结果将被完全计算并可供您访问,子查询是其父查询的内部部分,本身不会物化为集合,因此效率更高。
有关在新的 from 或 join 子句中使用实时查询结果的更多详细信息,请参阅 缓存中间结果 部分。
使用子查询作为主源
const activeUserPosts = createCollection(liveQueryCollectionOptions({
query: (q) => {
// Build the subquery first
const activeUsers = q
.from({ user: usersCollection })
.where(({ user }) => eq(user.active, true))
// Use the subquery in the main query
return q
.from({ activeUser: activeUsers })
.join({ post: postsCollection }, ({ activeUser, post }) =>
eq(activeUser.id, post.userId)
)
}
}))
const activeUserPosts = createCollection(liveQueryCollectionOptions({
query: (q) => {
// Build the subquery first
const activeUsers = q
.from({ user: usersCollection })
.where(({ user }) => eq(user.active, true))
// Use the subquery in the main query
return q
.from({ activeUser: activeUsers })
.join({ post: postsCollection }, ({ activeUser, post }) =>
eq(activeUser.id, post.userId)
)
}
}))
与子查询结果 Join
const userRecentPosts = createCollection(liveQueryCollectionOptions({
query: (q) => {
// Build the subquery first
const recentPosts = q
.from({ post: postsCollection })
.where(({ post }) => gt(post.createdAt, '2024-01-01'))
.orderBy(({ post }) => post.createdAt, 'desc')
.limit(1)
// Use the subquery in the main query
return q
.from({ user: usersCollection })
.join({ recentPost: recentPosts }, ({ user, recentPost }) =>
eq(user.id, recentPost.userId)
)
}
}))
const userRecentPosts = createCollection(liveQueryCollectionOptions({
query: (q) => {
// Build the subquery first
const recentPosts = q
.from({ post: postsCollection })
.where(({ post }) => gt(post.createdAt, '2024-01-01'))
.orderBy(({ post }) => post.createdAt, 'desc')
.limit(1)
// Use the subquery in the main query
return q
.from({ user: usersCollection })
.join({ recentPost: recentPosts }, ({ user, recentPost }) =>
eq(user.id, recentPost.userId)
)
}
}))
当同一个子查询在查询中多次使用时,它会被自动去重并只执行一次
const complexQuery = createCollection(liveQueryCollectionOptions({
query: (q) => {
// Build the subquery once
const activeUsers = q
.from({ user: usersCollection })
.where(({ user }) => eq(user.active, true))
// Use the same subquery multiple times
return q
.from({ activeUser: activeUsers })
.join({ post: postsCollection }, ({ activeUser, post }) =>
eq(activeUser.id, post.userId)
)
.join({ comment: commentsCollection }, ({ activeUser, comment }) =>
eq(activeUser.id, comment.userId)
)
}
}))
const complexQuery = createCollection(liveQueryCollectionOptions({
query: (q) => {
// Build the subquery once
const activeUsers = q
.from({ user: usersCollection })
.where(({ user }) => eq(user.active, true))
// Use the same subquery multiple times
return q
.from({ activeUser: activeUsers })
.join({ post: postsCollection }, ({ activeUser, post }) =>
eq(activeUser.id, post.userId)
)
.join({ comment: commentsCollection }, ({ activeUser, comment }) =>
eq(activeUser.id, comment.userId)
)
}
}))
在此示例中,activeUsers 子查询使用了两次,但只执行了一次,提高了性能。
构建具有多个嵌套级别的复杂查询
import { count } from '@tanstack/db'
const topUsers = createCollection(liveQueryCollectionOptions({
query: (q) => {
// Build the post count subquery
const postCounts = q
.from({ post: postsCollection })
.groupBy(({ post }) => post.userId)
.select(({ post }) => ({
userId: post.userId,
count: count(post.id),
}))
// Build the user stats subquery
const userStats = q
.from({ user: usersCollection })
.join({ postCount: postCounts }, ({ user, postCount }) =>
eq(user.id, postCount.userId)
)
.select(({ user, postCount }) => ({
id: user.id,
name: user.name,
postCount: postCount.count,
}))
.orderBy(({ userStats }) => userStats.postCount, 'desc')
.limit(10)
// Use the user stats subquery in the main query
return q.from({ userStats })
}
}))
import { count } from '@tanstack/db'
const topUsers = createCollection(liveQueryCollectionOptions({
query: (q) => {
// Build the post count subquery
const postCounts = q
.from({ post: postsCollection })
.groupBy(({ post }) => post.userId)
.select(({ post }) => ({
userId: post.userId,
count: count(post.id),
}))
// Build the user stats subquery
const userStats = q
.from({ user: usersCollection })
.join({ postCount: postCounts }, ({ user, postCount }) =>
eq(user.id, postCount.userId)
)
.select(({ user, postCount }) => ({
id: user.id,
name: user.name,
postCount: postCount.count,
}))
.orderBy(({ userStats }) => userStats.postCount, 'desc')
.limit(10)
// Use the user stats subquery in the main query
return q.from({ userStats })
}
}))
使用 groupBy 对数据进行分组并应用聚合函数。当您在 select 中使用聚合而没有 groupBy 时,整个结果集将被视为一个组。
groupBy(
grouper: (row: TRow) => Expression | Expression[]
): Query
groupBy(
grouper: (row: TRow) => Expression | Expression[]
): Query
参数
按部门分组用户并计数
import { count, avg } from '@tanstack/db'
const departmentStats = createCollection(liveQueryCollectionOptions({
query: (q) =>
q
.from({ user: usersCollection })
.groupBy(({ user }) => user.departmentId)
.select(({ user }) => ({
departmentId: user.departmentId,
userCount: count(user.id),
avgAge: avg(user.age),
}))
}))
import { count, avg } from '@tanstack/db'
const departmentStats = createCollection(liveQueryCollectionOptions({
query: (q) =>
q
.from({ user: usersCollection })
.groupBy(({ user }) => user.departmentId)
.select(({ user }) => ({
departmentId: user.departmentId,
userCount: count(user.id),
avgAge: avg(user.age),
}))
}))
注意
在 groupBy 查询中,您的 select 子句中的属性必须是
您不能选择既不是聚合也不是分组的属性。
通过从回调函数返回数组来按多列分组
const userStats = createCollection(liveQueryCollectionOptions({
query: (q) =>
q
.from({ user: usersCollection })
.groupBy(({ user }) => [user.departmentId, user.role])
.select(({ user }) => ({
departmentId: user.departmentId,
role: user.role,
count: count(user.id),
avgSalary: avg(user.salary),
}))
}))
const userStats = createCollection(liveQueryCollectionOptions({
query: (q) =>
q
.from({ user: usersCollection })
.groupBy(({ user }) => [user.departmentId, user.role])
.select(({ user }) => ({
departmentId: user.departmentId,
role: user.role,
count: count(user.id),
avgSalary: avg(user.salary),
}))
}))
使用各种聚合函数来汇总数据
import { count, sum, avg, min, max } from '@tanstack/db'
const orderStats = createCollection(liveQueryCollectionOptions({
query: (q) =>
q
.from({ order: ordersCollection })
.groupBy(({ order }) => order.customerId)
.select(({ order }) => ({
customerId: order.customerId,
totalOrders: count(order.id),
totalAmount: sum(order.amount),
avgOrderValue: avg(order.amount),
minOrder: min(order.amount),
maxOrder: max(order.amount),
}))
}))
import { count, sum, avg, min, max } from '@tanstack/db'
const orderStats = createCollection(liveQueryCollectionOptions({
query: (q) =>
q
.from({ order: ordersCollection })
.groupBy(({ order }) => order.customerId)
.select(({ order }) => ({
customerId: order.customerId,
totalOrders: count(order.id),
totalAmount: sum(order.amount),
avgOrderValue: avg(order.amount),
minOrder: min(order.amount),
maxOrder: max(order.amount),
}))
}))
有关可用的聚合函数的完整列表,请参阅 聚合函数 部分。
使用 having 过滤聚合结果——这类似于 where 子句,但是在聚合执行后应用的。
having(
condition: (row: TRow) => Expression<boolean>
): Query
having(
condition: (row: TRow) => Expression<boolean>
): Query
参数
const highValueCustomers = createLiveQueryCollection((q) =>
q
.from({ order: ordersCollection })
.groupBy(({ order }) => order.customerId)
.select(({ order }) => ({
customerId: order.customerId,
totalSpent: sum(order.amount),
orderCount: count(order.id),
}))
.having(({ order }) => gt(sum(order.amount), 1000))
)
const highValueCustomers = createLiveQueryCollection((q) =>
q
.from({ order: ordersCollection })
.groupBy(({ order }) => order.customerId)
.select(({ order }) => ({
customerId: order.customerId,
totalSpent: sum(order.amount),
orderCount: count(order.id),
}))
.having(({ order }) => gt(sum(order.amount), 1000))
)
当您在没有 groupBy 的情况下使用聚合时,整个结果集将被分组
const overallStats = createLiveQueryCollection((q) =>
q
.from({ user: usersCollection })
.select(({ user }) => ({
totalUsers: count(user.id),
avgAge: avg(user.age),
maxSalary: max(user.salary),
}))
)
const overallStats = createLiveQueryCollection((q) =>
q
.from({ user: usersCollection })
.select(({ user }) => ({
totalUsers: count(user.id),
avgAge: avg(user.age),
maxSalary: max(user.salary),
}))
)
这相当于将整个集合分组为一个组。
可以通过组键访问分组结果
const deptStats = createCollection(liveQueryCollectionOptions({
query: (q) =>
q
.from({ user: usersCollection })
.groupBy(({ user }) => user.departmentId)
.select(({ user }) => ({
departmentId: user.departmentId,
count: count(user.id),
}))
}))
// Access by department ID
const engineeringStats = deptStats.get(1)
const deptStats = createCollection(liveQueryCollectionOptions({
query: (q) =>
q
.from({ user: usersCollection })
.groupBy(({ user }) => user.departmentId)
.select(({ user }) => ({
departmentId: user.departmentId,
count: count(user.id),
}))
}))
// Access by department ID
const engineeringStats = deptStats.get(1)
注意:分组结果的键不同,具体取决于分组方式
- 单列分组:按实际值键(例如,deptStats.get(1))
- 多列分组:按分组值的 JSON 字符串键(例如,userStats.get('[1,"admin"]'))
使用 orderBy、limit 和 offset 来控制结果的顺序和分页。排序是增量执行的,以获得最佳性能。
orderBy(
selector: (row: TRow) => Expression,
direction?: 'asc' | 'desc'
): Query
limit(count: number): Query
offset(count: number): Query
orderBy(
selector: (row: TRow) => Expression,
direction?: 'asc' | 'desc'
): Query
limit(count: number): Query
offset(count: number): Query
参数
按单列对结果进行排序
const sortedUsers = createLiveQueryCollection((q) =>
q
.from({ user: usersCollection })
.orderBy(({ user }) => user.name)
.select(({ user }) => ({
id: user.id,
name: user.name,
}))
)
const sortedUsers = createLiveQueryCollection((q) =>
q
.from({ user: usersCollection })
.orderBy(({ user }) => user.name)
.select(({ user }) => ({
id: user.id,
name: user.name,
}))
)
按多列排序
const sortedUsers = createLiveQueryCollection((q) =>
q
.from({ user: usersCollection })
.orderBy(({ user }) => user.departmentId, 'asc')
.orderBy(({ user }) => user.name, 'asc')
.select(({ user }) => ({
id: user.id,
name: user.name,
departmentId: user.departmentId,
}))
)
const sortedUsers = createLiveQueryCollection((q) =>
q
.from({ user: usersCollection })
.orderBy(({ user }) => user.departmentId, 'asc')
.orderBy(({ user }) => user.name, 'asc')
.select(({ user }) => ({
id: user.id,
name: user.name,
departmentId: user.departmentId,
}))
)
使用 desc 进行降序排序
const recentPosts = createLiveQueryCollection((q) =>
q
.from({ post: postsCollection })
.orderBy(({ post }) => post.createdAt, 'desc')
.select(({ post }) => ({
id: post.id,
title: post.title,
createdAt: post.createdAt,
}))
)
const recentPosts = createLiveQueryCollection((q) =>
q
.from({ post: postsCollection })
.orderBy(({ post }) => post.createdAt, 'desc')
.select(({ post }) => ({
id: post.id,
title: post.title,
createdAt: post.createdAt,
}))
)
使用 offset 跳过结果
const page2Users = createLiveQueryCollection((q) =>
q
.from({ user: usersCollection })
.orderBy(({ user }) => user.name, 'asc')
.limit(20)
.offset(20) // Skip first 20 results
.select(({ user }) => ({
id: user.id,
name: user.name,
}))
)
const page2Users = createLiveQueryCollection((q) =>
q
.from({ user: usersCollection })
.orderBy(({ user }) => user.name, 'asc')
.limit(20)
.offset(20) // Skip first 20 results
.select(({ user }) => ({
id: user.id,
name: user.name,
}))
)
通过组合更小的、可重用的部分来构建复杂的查询。这种方法使您的查询更易于维护,并通过缓存实现更好的性能。
基于运行时条件构建查询
import { Query, eq } from '@tanstack/db'
function buildUserQuery(options: { activeOnly?: boolean; limit?: number }) {
let query = new Query().from({ user: usersCollection })
if (options.activeOnly) {
query = query.where(({ user }) => eq(user.active, true))
}
if (options.limit) {
query = query.limit(options.limit)
}
return query.select(({ user }) => ({
id: user.id,
name: user.name,
}))
}
const activeUsers = createLiveQueryCollection(buildUserQuery({ activeOnly: true, limit: 10 }))
import { Query, eq } from '@tanstack/db'
function buildUserQuery(options: { activeOnly?: boolean; limit?: number }) {
let query = new Query().from({ user: usersCollection })
if (options.activeOnly) {
query = query.where(({ user }) => eq(user.active, true))
}
if (options.limit) {
query = query.limit(options.limit)
}
return query.select(({ user }) => ({
id: user.id,
name: user.name,
}))
}
const activeUsers = createLiveQueryCollection(buildUserQuery({ activeOnly: true, limit: 10 }))
实时查询集合的结果本身就是一个集合,并且在底层数据发生变化时会自动更新。这意味着您可以使用实时查询集合的结果作为另一个实时查询集合的源。这种模式对于构建复杂的查询很有用,您希望缓存中间结果以加快后续查询的速度。
// Base query for active users
const activeUsers = createLiveQueryCollection((q) =>
q
.from({ user: usersCollection })
.where(({ user }) => eq(user.active, true))
)
// Query that depends on active users
const activeUserPosts = createLiveQueryCollection((q) =>
q
.from({ user: activeUsers })
.join({ post: postsCollection }, ({ user, post }) =>
eq(user.id, post.userId)
)
.select(({ user, post }) => ({
userName: user.name,
postTitle: post.title,
}))
)
// Base query for active users
const activeUsers = createLiveQueryCollection((q) =>
q
.from({ user: usersCollection })
.where(({ user }) => eq(user.active, true))
)
// Query that depends on active users
const activeUserPosts = createLiveQueryCollection((q) =>
q
.from({ user: activeUsers })
.join({ post: postsCollection }, ({ user, post }) =>
eq(user.id, post.userId)
)
.select(({ user, post }) => ({
userName: user.name,
postTitle: post.title,
}))
)
您可以使用 Query 类来创建可重用的查询定义。这对于构建希望在应用程序中多次重用相同查询构建器实例的复杂查询很有用。
import { Query, eq } from '@tanstack/db'
// Create a reusable query builder
const userQuery = new Query()
.from({ user: usersCollection })
.where(({ user }) => eq(user.active, true))
// Use it in different contexts
const activeUsers = createLiveQueryCollection({
query: userQuery.select(({ user }) => ({
id: user.id,
name: user.name,
}))
})
// Or as a subquery
const userPosts = createLiveQueryCollection((q) =>
q
.from({ activeUser: userQuery })
.join({ post: postsCollection }, ({ activeUser, post }) =>
eq(activeUser.id, post.userId)
)
)
import { Query, eq } from '@tanstack/db'
// Create a reusable query builder
const userQuery = new Query()
.from({ user: usersCollection })
.where(({ user }) => eq(user.active, true))
// Use it in different contexts
const activeUsers = createLiveQueryCollection({
query: userQuery.select(({ user }) => ({
id: user.id,
name: user.name,
}))
})
// Or as a subquery
const userPosts = createLiveQueryCollection((q) =>
q
.from({ activeUser: userQuery })
.join({ post: postsCollection }, ({ activeUser, post }) =>
eq(activeUser.id, post.userId)
)
)
使用 Ref<MyType> 创建可重用的回调函数
import { Ref, eq, gt, and } from '@tanstack/db'
// Create reusable callbacks
const isActiveUser = (user: Ref<User>) => eq(user.active, true)
const isAdultUser = (user: Ref<User>) => gt(user.age, 18)
// Use them in queries
const activeAdults = createCollection(liveQueryCollectionOptions({
query: (q) =>
q
.from({ user: usersCollection })
.where(({ user }) => and(isActiveUser(user), isAdultUser(user)))
.select(({ user }) => ({
id: user.id,
name: user.name,
age: user.age,
}))
}))
import { Ref, eq, gt, and } from '@tanstack/db'
// Create reusable callbacks
const isActiveUser = (user: Ref<User>) => eq(user.active, true)
const isAdultUser = (user: Ref<User>) => gt(user.age, 18)
// Use them in queries
const activeAdults = createCollection(liveQueryCollectionOptions({
query: (q) =>
q
.from({ user: usersCollection })
.where(({ user }) => and(isActiveUser(user), isAdultUser(user)))
.select(({ user }) => ({
id: user.id,
name: user.name,
age: user.age,
}))
}))
您还可以创建接受整个行的回调,并将它们直接传递给 where
// Callback that takes the whole row
const isHighValueCustomer = (row: { user: User; order: Order }) =>
row.user.active && row.order.amount > 1000
// Use directly in where clause
const highValueCustomers = createCollection(liveQueryCollectionOptions({
query: (q) =>
q
.from({ user: usersCollection })
.join({ order: ordersCollection }, ({ user, order }) =>
eq(user.id, order.userId)
)
.where(isHighValueCustomer)
.select(({ user, order }) => ({
userName: user.name,
orderAmount: order.amount,
}))
}))
// Callback that takes the whole row
const isHighValueCustomer = (row: { user: User; order: Order }) =>
row.user.active && row.order.amount > 1000
// Use directly in where clause
const highValueCustomers = createCollection(liveQueryCollectionOptions({
query: (q) =>
q
.from({ user: usersCollection })
.join({ order: ordersCollection }, ({ user, order }) =>
eq(user.id, order.userId)
)
.where(isHighValueCustomer)
.select(({ user, order }) => ({
userName: user.name,
orderAmount: order.amount,
}))
}))
这种方法使您的查询逻辑更具模块化和可测试性。
查询系统提供了一套全面的函数,用于过滤、转换和聚合数据。
相等比较
eq(user.id, 1)
eq(user.name, 'John')
eq(user.id, 1)
eq(user.name, 'John')
数字、字符串和日期比较
gt(user.age, 18)
gte(user.salary, 50000)
lt(user.createdAt, new Date('2024-01-01'))
lte(user.rating, 5)
gt(user.age, 18)
gte(user.salary, 50000)
lt(user.createdAt, new Date('2024-01-01'))
lte(user.rating, 5)
检查一个值是否在数组中
inArray(user.id, [1, 2, 3])
inArray(user.role, ['admin', 'moderator'])
inArray(user.id, [1, 2, 3])
inArray(user.role, ['admin', 'moderator'])
字符串模式匹配
like(user.name, 'John%') // Case-sensitive
ilike(user.email, '%@gmail.com') // Case-insensitive
like(user.name, 'John%') // Case-sensitive
ilike(user.email, '%@gmail.com') // Case-insensitive
使用 AND 逻辑组合条件
and(
eq(user.active, true),
gt(user.age, 18),
eq(user.role, 'user')
)
and(
eq(user.active, true),
gt(user.age, 18),
eq(user.role, 'user')
)
使用 OR 逻辑组合条件
or(
eq(user.role, 'admin'),
eq(user.role, 'moderator')
)
or(
eq(user.role, 'admin'),
eq(user.role, 'moderator')
)
否定一个条件
not(eq(user.active, false))
not(eq(user.active, false))
转换大小写
upper(user.name) // 'JOHN'
lower(user.email) // 'john@example.com'
upper(user.name) // 'JOHN'
lower(user.email) // 'john@example.com'
获取字符串或数组长度
length(user.name) // String length
length(user.tags) // Array length
length(user.name) // String length
length(user.tags) // Array length
连接字符串
concat(user.firstName, ' ', user.lastName)
concat('User: ', user.name, ' (', user.id, ')')
concat(user.firstName, ' ', user.lastName)
concat('User: ', user.name, ' (', user.id, ')')
将两个数字相加
add(user.salary, user.bonus)
add(user.salary, user.bonus)
返回第一个非空值
coalesce(user.displayName, user.name, 'Unknown')
coalesce(user.displayName, user.name, 'Unknown')
计算非空值的数量
count(user.id) // Count all users
count(user.postId) // Count users with posts
count(user.id) // Count all users
count(user.postId) // Count users with posts
对数值求和
sum(order.amount)
sum(user.salary)
sum(order.amount)
sum(user.salary)
计算平均值
avg(user.salary)
avg(order.amount)
avg(user.salary)
avg(order.amount)
查找最小值和最大值
min(user.salary)
max(order.amount)
min(user.salary)
max(order.amount)
函数可以组合和链接
// Complex condition
and(
eq(user.active, true),
or(
gt(user.age, 25),
eq(user.role, 'admin')
),
not(inArray(user.id, bannedUserIds))
)
// Complex transformation
concat(
upper(user.firstName),
' ',
upper(user.lastName),
' (',
user.id,
')'
)
// Complex aggregation
avg(add(user.salary, coalesce(user.bonus, 0)))
// Complex condition
and(
eq(user.active, true),
or(
gt(user.age, 25),
eq(user.role, 'admin')
),
not(inArray(user.id, bannedUserIds))
)
// Complex transformation
concat(
upper(user.firstName),
' ',
upper(user.lastName),
' (',
user.id,
')'
)
// Complex aggregation
avg(add(user.salary, coalesce(user.bonus, 0)))
函数式变体 API 提供了标准 API 的替代方案,为复杂的转换提供了更大的灵活性。通过函数式变体,回调函数包含实际的代码,这些代码将被执行以执行操作,从而为您提供 JavaScript 的全部强大功能。
警告
函数式变体 API 不能被查询优化器优化,也不能使用集合索引。它用于标准 API 不够用的罕见情况。
使用 fn.select() 进行具有 JavaScript 逻辑的复杂转换
const userProfiles = createLiveQueryCollection((q) =>
q
.from({ user: usersCollection })
.fn.select((row) => ({
id: row.user.id,
displayName: `${row.user.firstName} ${row.user.lastName}`,
salaryTier: row.user.salary > 100000 ? 'senior' : 'junior',
emailDomain: row.user.email.split('@')[1],
isHighEarner: row.user.salary > 75000,
}))
)
const userProfiles = createLiveQueryCollection((q) =>
q
.from({ user: usersCollection })
.fn.select((row) => ({
id: row.user.id,
displayName: `${row.user.firstName} ${row.user.lastName}`,
salaryTier: row.user.salary > 100000 ? 'senior' : 'junior',
emailDomain: row.user.email.split('@')[1],
isHighEarner: row.user.salary > 75000,
}))
)
使用 fn.where() 进行复杂的过滤逻辑
const specialUsers = createLiveQueryCollection((q) =>
q
.from({ user: usersCollection })
.fn.where((row) => {
const user = row.user
return user.active &&
(user.age > 25 || user.role === 'admin') &&
user.email.includes('@company.com')
})
)
const specialUsers = createLiveQueryCollection((q) =>
q
.from({ user: usersCollection })
.fn.where((row) => {
const user = row.user
return user.active &&
(user.age > 25 || user.role === 'admin') &&
user.email.includes('@company.com')
})
)
使用 fn.having() 进行复杂的聚合过滤
const highValueCustomers = createLiveQueryCollection((q) =>
q
.from({ order: ordersCollection })
.groupBy(({ order }) => order.customerId)
.select(({ order }) => ({
customerId: order.customerId,
totalSpent: sum(order.amount),
orderCount: count(order.id),
}))
.fn.having((row) => {
return row.totalSpent > 1000 && row.orderCount >= 3
})
)
const highValueCustomers = createLiveQueryCollection((q) =>
q
.from({ order: ordersCollection })
.groupBy(({ order }) => order.customerId)
.select(({ order }) => ({
customerId: order.customerId,
totalSpent: sum(order.amount),
orderCount: count(order.id),
}))
.fn.having((row) => {
return row.totalSpent > 1000 && row.orderCount >= 3
})
)
函数式变体在复杂数据转换方面表现出色
const userProfiles = createLiveQueryCollection((q) =>
q
.from({ user: usersCollection })
.fn.select((row) => {
const user = row.user
const fullName = `${user.firstName} ${user.lastName}`.trim()
const emailDomain = user.email.split('@')[1]
const ageGroup = user.age < 25 ? 'young' : user.age < 50 ? 'adult' : 'senior'
return {
userId: user.id,
displayName: fullName || user.name,
contactInfo: {
email: user.email,
domain: emailDomain,
isCompanyEmail: emailDomain === 'company.com'
},
demographics: {
age: user.age,
ageGroup: ageGroup,
isAdult: user.age >= 18
},
status: user.active ? 'active' : 'inactive',
profileStrength: fullName && user.email && user.age ? 'complete' : 'incomplete'
}
})
)
const userProfiles = createLiveQueryCollection((q) =>
q
.from({ user: usersCollection })
.fn.select((row) => {
const user = row.user
const fullName = `${user.firstName} ${user.lastName}`.trim()
const emailDomain = user.email.split('@')[1]
const ageGroup = user.age < 25 ? 'young' : user.age < 50 ? 'adult' : 'senior'
return {
userId: user.id,
displayName: fullName || user.name,
contactInfo: {
email: user.email,
domain: emailDomain,
isCompanyEmail: emailDomain === 'company.com'
},
demographics: {
age: user.age,
ageGroup: ageGroup,
isAdult: user.age >= 18
},
status: user.active ? 'active' : 'inactive',
profileStrength: fullName && user.email && user.age ? 'complete' : 'incomplete'
}
})
)
函数式变体保持完整的 TypeScript 支持
const processedUsers = createLiveQueryCollection((q) =>
q
.from({ user: usersCollection })
.fn.select((row): ProcessedUser => ({
id: row.user.id,
name: row.user.name.toUpperCase(),
age: row.user.age,
ageGroup: row.user.age < 25 ? 'young' : row.user.age < 50 ? 'adult' : 'senior',
}))
)
const processedUsers = createLiveQueryCollection((q) =>
q
.from({ user: usersCollection })
.fn.select((row): ProcessedUser => ({
id: row.user.id,
name: row.user.name.toUpperCase(),
age: row.user.age,
ageGroup: row.user.age < 25 ? 'young' : row.user.age < 50 ? 'adult' : 'senior',
}))
)
当您需要以下功能时,请使用函数式变体
函数式变体中的回调是实际执行的 JavaScript 函数,与使用声明式表达式的标准 API 不同。这使您可以完全控制逻辑,但代价是优化机会减少。
但是,如果可能,请优先使用标准 API,因为它提供了更好的性能和优化机会。
您的每周 JavaScript 资讯。每周一免费发送给超过 10 万开发者。