Live-Abfragen

TanStack DB Live Queries

TanStack DB bietet ein leistungsfähiges, typsicheres Abfragesystem, mit dem Sie Daten aus Sammlungen mithilfe einer SQL-ähnlichen, flüssigen API abrufen, filtern, transformieren und aggregieren können. Alle Abfragen sind standardmäßig **live**, d. h. sie aktualisieren sich automatisch, wenn sich die zugrunde liegenden Daten ändern.

Das Abfragesystem basiert auf einer API, die SQL-Abfrage-Buildern wie Kysely oder Drizzle ähnelt, bei der Sie Methoden aneinanderreihen, um Ihre Abfrage zu komponieren. Der Abfrage-Builder führt Operationen nicht in der Reihenfolge der Methodenaufrufe aus – stattdessen komponiert er Ihre Abfrage zu einer optimalen inkrementellen Pipeline, die effizient kompiliert und ausgeführt wird. Jede Methode gibt einen neuen Abfrage-Builder zurück, sodass Sie Operationen miteinander verketten können.

Live-Abfragen werden zu Sammlungen aufgelöst, die sich automatisch aktualisieren, wenn sich ihre zugrunde liegenden Daten ändern. Sie können Änderungen abonnieren, Ergebnisse durchlaufen und alle Standardmethoden für Sammlungen verwenden.

ts
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,
      }))
}))

Die Ergebnistypen werden automatisch aus Ihrer Abfragestruktur abgeleitet und bieten vollständige TypeScript-Unterstützung. Wenn Sie eine select-Klausel verwenden, entspricht der Ergebnistyp Ihrer Projektion. Ohne select erhalten Sie das vollständige Schema mit korrekter Join-Optionalität.

Inhaltsverzeichnis

Erstellen von Live-Query-Sammlungen

Um eine Live-Query-Sammlung zu erstellen, können Sie liveQueryCollectionOptions mit createCollection verwenden oder die Komfortfunktion createLiveQueryCollection verwenden.

Verwendung von liveQueryCollectionOptions

Die grundlegende Methode zum Erstellen einer Live-Abfrage ist die Verwendung von liveQueryCollectionOptions mit createCollection

ts
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,
      }))
}))

Konfigurationsoptionen

Für mehr Kontrolle können Sie zusätzliche Optionen angeben

ts
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
}))
OptionTypBeschreibung
idstring (optional)Eine optionale eindeutige Kennung für die Live-Abfrage. Wenn nicht angegeben, wird sie automatisch generiert. Dies ist nützlich für Debugging und Protokollierung.
queryQueryBuilder oder FunktionDie Abfragedefinition, dies ist entweder eine Query-Instanz oder eine Funktion, die eine Query-Instanz zurückgibt.
getKey(item) => string | number (optional)Eine Funktion, die einen eindeutigen Schlüssel aus jeder Zeile extrahiert. Wenn nicht angegeben, wird der interne Schlüssel des Streams verwendet. In einfachen Fällen ist dies der Schlüssel aus der übergeordneten Sammlung, aber im Falle von Joins ist der automatisch generierte Schlüssel eine Kombination der übergeordneten Schlüssel. Die Verwendung von getKey ist nützlich, wenn Sie einen bestimmten Schlüssel aus einer übergeordneten Sammlung für die resultierende Sammlung verwenden möchten.
schemaSchema (optional)Optionales Schema für die Validierung
startSyncboolean (optional)Ob die Synchronisierung sofort gestartet werden soll. Standardmäßig true.
gcTimenumber (optional)Garbage-Collection-Zeit in Millisekunden. Standardmäßig 5000 (5 Sekunden).

Komfortfunktion

Für einfachere Fälle können Sie createLiveQueryCollection als Abkürzung verwenden

ts
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,
    }))
)

Verwendung mit Frameworks

In React können Sie den Hook useLiveQuery verwenden

tsx
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>
  )
}

Weitere Details zur Framework-Integration finden Sie in der Dokumentation der React- und Vue-Adapter.

From-Klausel

Die Grundlage jeder Abfrage ist die Methode from, die die Quellsammlung oder Subquery angibt. Sie können die Quelle mithilfe von Objektsyntax aliasieren.

Methodensignatur

ts
from({
  [alias]: Collection | Query,
}): Query
from({
  [alias]: Collection | Query,
}): Query

Parameter

  • [alias] - Eine Collection oder Query-Instanz. Beachten Sie, dass nur eine einzelne aliasierte Sammlung oder Subquery in der from-Klausel zulässig ist.

Grundlegende Verwendung

Beginnen Sie mit einer grundlegenden Abfrage, die alle Datensätze aus einer Sammlung auswählt

ts
const allUsers = createCollection(liveQueryCollectionOptions({
  query: (q) => q.from({ user: usersCollection })
}))
const allUsers = createCollection(liveQueryCollectionOptions({
  query: (q) => q.from({ user: usersCollection })
}))

Das Ergebnis enthält alle Benutzer mit ihrem vollständigen Schema. Sie können die Ergebnisse durchlaufen oder per Schlüssel darauf zugreifen

ts
// 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)

Verwenden Sie Aliase, um Ihre Abfragen lesbarer zu machen, insbesondere bei der Arbeit mit mehreren Sammlungen

ts
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-Klauseln

Verwenden Sie where-Klauseln, um Ihre Daten basierend auf Bedingungen zu filtern. Sie können mehrere where-Aufrufe verketten – sie werden mit and-Logik kombiniert.

Die Methode where nimmt eine Callback-Funktion entgegen, die ein Objekt mit Ihren Tabellenaliassen erhält und einen booleschen Ausdruck zurückgibt. Sie erstellen diese Ausdrücke mit Vergleichsfunktionen wie eq(), gt() und logischen Operatoren wie and() und or(). Dieser deklarative Ansatz ermöglicht es dem Abfragesystem, Ihre Filter effizient zu optimieren. Diese werden im Abschnitt Referenz der Expressionsfunktionen detaillierter beschrieben. Dies ähnelt sehr der Art und Weise, wie Sie Abfragen mit Kysely oder Drizzle erstellen.

Es ist wichtig zu beachten, dass die Methode where keine Funktion ist, die für jede Zeile oder die Ergebnisse ausgeführt wird, sondern eine Möglichkeit, die auszuführende Abfrage zu beschreiben. Dieser deklarative Ansatz funktioniert für fast alle Anwendungsfälle gut, aber wenn Sie eine komplexere Bedingung verwenden müssen, gibt es die funktionale Variante als fn.where, die im Abschnitt Funktionale Varianten beschrieben wird.

Methodensignatur

ts
where(
  condition: (row: TRow) => Expression<boolean>
): Query
where(
  condition: (row: TRow) => Expression<boolean>
): Query

Parameter

  • condition - Eine Callback-Funktion, die das Zeilenobjekt mit Tabellenaliassen empfängt und einen booleschen Ausdruck zurückgibt

Grundlegende Filterung

Benutzer nach einer einfachen Bedingung filtern

ts
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))
}))

Mehrere Bedingungen

Mehrere where-Aufrufe für AND-Logik verketten

ts
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))
}))

Komplexe Bedingungen

Logische Operatoren verwenden, um komplexe Bedingungen zu erstellen

ts
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')
        )
      )
    )
)

Verfügbare Operatoren

Das Abfragesystem bietet mehrere Vergleichsoperatoren

ts
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)

Eine vollständige Referenz aller verfügbaren Funktionen finden Sie im Abschnitt Referenz der Expressionsfunktionen.

Select

Verwenden Sie select, um anzugeben, welche Felder in Ihren Ergebnissen enthalten sein sollen, und um Ihre Daten zu transformieren. Ohne select erhalten Sie das vollständige Schema.

Ähnlich wie die where-Klausel nimmt die select-Methode eine Callback-Funktion entgegen, die ein Objekt mit Ihren Tabellenaliassen erhält und ein Objekt mit den Feldern zurückgibt, die Sie in Ihren Ergebnissen einschließen möchten. Diese können mit Funktionen aus dem Abschnitt Referenz der Expressionsfunktionen kombiniert werden, um berechnete Felder zu erstellen. Sie können auch den Spread-Operator verwenden, um alle Felder aus einer Tabelle einzuschließen.

Methodensignatur

ts
select(
  projection: (row: TRow) => Record<string, Expression>
): Query
select(
  projection: (row: TRow) => Record<string, Expression>
): Query

Parameter

  • projection - Eine Callback-Funktion, die das Zeilenobjekt mit Tabellenaliassen empfängt und das Objekt der ausgewählten Felder zurückgibt

Grundlegende Selects

Bestimmte Felder aus Ihren Daten auswählen

ts
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)
}
```
*/

Feldumbenennung

Felder in Ihren Ergebnissen umbenennen

ts
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,
    }))
)

Berechnete Felder

Berechnete Felder mit Ausdrücken erstellen

ts
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),
    }))
)

Verwendung von Funktionen und Einbeziehung aller Felder

Ihre Daten mit integrierten Funktionen transformieren

ts
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,
}
*/

Eine vollständige Liste der verfügbaren Funktionen finden Sie im Abschnitt Referenz der Expressionsfunktionen.

Joins

Verwenden Sie join, um Daten aus mehreren Sammlungen zu kombinieren. Joins sind standardmäßig vom Typ left und unterstützen nur Gleichheitsbedingungen.

Joins in TanStack DB sind eine Methode, um Daten aus mehreren Sammlungen zu kombinieren, und sind konzeptionell SQL-Joins sehr ähnlich. Wenn zwei Sammlungen verbunden werden, ist das Ergebnis eine neue Sammlung, die die kombinierten Daten als einzelne Zeilen enthält. Die neue Sammlung ist eine Live-Query-Sammlung und wird automatisch aktualisiert, wenn sich die zugrunde liegenden Daten ändern.

Ein join ohne select gibt Zeilenobjekte zurück, die mit den Aliassen der verbundenen Sammlungen benannt sind.

Der Ergebnistyp eines Joins berücksichtigt den Join-Typ, wobei die Optionalität der verbundenen Felder durch den Join-Typ bestimmt wird.

Hinweis

Wir arbeiten an einem include-System, das Joins ermöglicht, die zu einem hierarchischen Objekt projizieren. Zum Beispiel könnte eine issue-Zeile eine comments-Eigenschaft haben, die ein Array von comment-Zeilen ist. Siehe dieses Issue für weitere Details.

Methodensignatur

ts
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

Parameter

  • aliases - Ein Objekt, bei dem die Schlüssel Alias-Namen und die Werte Sammlungen oder Subqueries zum Joinen sind
  • condition - Eine Callback-Funktion, die das kombinierte Zeilenobjekt empfängt und eine Gleichheitsbedingung zurückgibt
  • joinType - Optionaler Join-Typ: 'left' (Standard), 'right', 'inner' oder 'full'

Grundlegende Joins

Benutzer mit ihren Beiträgen verbinden

ts
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-Typen

Geben Sie den Join-Typ als drittes Argument an

ts
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`
    )
)

Oder verwenden Sie die Alias-Methoden leftJoin, rightJoin, innerJoin und fullJoin

Left Join

ts
// 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

ts
// 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

ts
// 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

ts
// 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
}
*/

Mehrere Joins

Mehrere Joins in einer einzigen Abfrage verketten

ts
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,
    }))
)

Subqueries

Subqueries ermöglichen es Ihnen, das Ergebnis einer Abfrage als Eingabe für eine andere zu verwenden. Sie werden in die Abfrage selbst eingebettet und zu einer einzigen Abfragepipeline kompiliert. Sie sind SQL-Subqueries sehr ähnlich, die als Teil einer einzigen Operation ausgeführt werden.

Beachten Sie, dass Subqueries nicht dasselbe sind wie die Verwendung eines Live-Query-Ergebnisses in einer from- oder join-Klausel in einer neuen Abfrage. Wenn Sie dies tun, wird das Zwischenergebnis vollständig berechnet und Ihnen zugänglich gemacht, Subqueries sind intern für ihre übergeordnete Abfrage und nicht selbst zu einer Sammlung materialisiert, und sind daher effizienter.

Weitere Details zur Verwendung von Live-Query-Ergebnissen in einer from- oder join-Klausel in einer neuen Abfrage finden Sie im Abschnitt Zwischenergebnisse cachen.

Subqueries in from-Klauseln

Eine Subquery als Hauptquelle verwenden

ts
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)
      )
  }
}))

Subqueries in join-Klauseln

Mit einem Subquery-Ergebnis verbinden

ts
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)
      )
  }
}))

Subquery-Deduplizierung

Wenn dieselbe Subquery mehrmals innerhalb einer Abfrage verwendet wird, wird sie automatisch dedupliziert und nur einmal ausgeführt

ts
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)
      )
  }
}))

In diesem Beispiel wird die Subquery activeUsers zweimal verwendet, aber nur einmal ausgeführt, was die Leistung verbessert.

Komplexe verschachtelte Subqueries

Komplexe Abfragen mit mehreren Verschachtelungsebenen erstellen

ts
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 und Aggregationen

Verwenden Sie groupBy, um Ihre Daten zu gruppieren und Aggregatfunktionen anzuwenden. Wenn Sie Aggregate in select ohne groupBy verwenden, wird der gesamte Ergebnissatz als eine einzige Gruppe behandelt.

Methodensignatur

ts
groupBy(
  grouper: (row: TRow) => Expression | Expression[]
): Query
groupBy(
  grouper: (row: TRow) => Expression | Expression[]
): Query

Parameter

  • grouper - Eine Callback-Funktion, die das Zeilenobjekt empfängt und den/die Gruppierungsschlüssel zurückgibt. Kann einen einzelnen Wert oder ein Array für die Gruppierung nach mehreren Spalten zurückgeben

Grundlegende Gruppierung

Benutzer nach ihrer Abteilung gruppieren und zählen

ts
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),
      }))
}))

Hinweis

In groupBy-Abfragen müssen die Eigenschaften in Ihrer select-Klausel entweder

  • Eine Aggregatfunktion (wie count, sum, avg)
  • Eine Eigenschaft, die in der groupBy-Klausel verwendet wurde

Sie können keine Eigenschaften auswählen, die weder aggregiert noch gruppiert sind.

Gruppierung nach mehreren Spalten

Nach mehreren Spalten gruppieren, indem ein Array aus dem Callback zurückgegeben wird

ts
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),
      }))
}))

Aggregatfunktionen

Verschiedene Aggregatfunktionen verwenden, um Ihre Daten zusammenzufassen

ts
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),
      }))
}))

Eine vollständige Liste der verfügbaren Aggregatfunktionen finden Sie im Abschnitt Aggregatfunktionen.

Having-Klauseln

Aggregierte Ergebnisse mit having filtern – dies ähnelt der where-Klausel, wird aber nach Abschluss der Aggregation angewendet.

Methodensignatur

ts
having(
  condition: (row: TRow) => Expression<boolean>
): Query
having(
  condition: (row: TRow) => Expression<boolean>
): Query

Parameter

  • condition - Eine Callback-Funktion, die das aggregierte Zeilenobjekt empfängt und einen booleschen Ausdruck zurückgibt
ts
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))
)

Implizite Einzelgruppen-Aggregation

Wenn Sie Aggregate ohne groupBy verwenden, wird der gesamte Ergebnissatz gruppiert

ts
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),
    }))
)

Dies entspricht der Gruppierung der gesamten Sammlung in eine einzige Gruppe.

Zugriff auf gruppierte Daten

Gruppierte Ergebnisse können über den Gruppenschlüssel abgerufen werden

ts
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)

Hinweis: Gruppierte Ergebnisse werden basierend auf der Gruppierung unterschiedlich geschlüsselt

  • Gruppierung nach einer Spalte: Geschlüsselt nach dem tatsächlichen Wert (z. B. deptStats.get(1))
  • Gruppierung nach mehreren Spalten: Geschlüsselt nach einem JSON-String der gruppierten Werte (z. B. userStats.get('[1,"admin"]'))

Order By, Limit und Offset

Verwenden Sie orderBy, limit und offset, um die Reihenfolge und Paginierung Ihrer Ergebnisse zu steuern. Die Sortierung erfolgt inkrementell für optimale Leistung.

Methodensignaturen

ts
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

Parameter

  • selector - Eine Callback-Funktion, die das Zeilenobjekt empfängt und den Wert zurückgibt, nach dem sortiert werden soll
  • direction - Sortierrichtung: 'asc' (Standard) oder 'desc'
  • count - Anzahl der Zeilen, die begrenzt oder übersprungen werden sollen

Grundlegende Sortierung

Ergebnisse nach einer einzelnen Spalte sortieren

ts
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,
    }))
)

Sortierung nach mehreren Spalten

Nach mehreren Spalten sortieren

ts
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,
    }))
)

Absteigende Reihenfolge

Verwenden Sie desc für absteigende Reihenfolge

ts
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,
    }))
)

Paginierung mit limit und offset

Ergebnisse mit offset überspringen

ts
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,
    }))
)

Komponierbare Abfragen

Erstellen Sie komplexe Abfragen durch Zusammensetzung kleinerer, wiederverwendbarer Teile. Dieser Ansatz macht Ihre Abfragen wartbarer und ermöglicht durch Caching eine bessere Leistung.

Bedingte Abfrageerstellung

Abfragen basierend auf Laufzeitbedingungen erstellen

ts
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 }))

Zwischenergebnisse cachen

Das Ergebnis einer Live-Query-Sammlung ist selbst eine Sammlung und wird automatisch aktualisiert, wenn sich die zugrunde liegenden Daten ändern. Das bedeutet, dass Sie das Ergebnis einer Live-Query-Sammlung als Quelle in einer anderen Live-Query-Sammlung verwenden können. Dieses Muster ist nützlich für die Erstellung komplexer Abfragen, bei denen Sie Zwischenergebnisse cachen möchten, um weitere Abfragen zu beschleunigen.

ts
// 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,
    }))
)

Wiederverwendbare Abfragedefinitionen

Sie können die Query-Klasse verwenden, um wiederverwendbare Abfragedefinitionen zu erstellen. Dies ist nützlich für die Erstellung komplexer Abfragen, bei denen Sie dieselbe Abfrage-Builder-Instanz mehrmals in Ihrer Anwendung wiederverwenden möchten.

ts
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)
    )
)

Wiederverwendbare Callback-Funktionen

Verwenden Sie Ref<MyType>, um wiederverwendbare Callback-Funktionen zu erstellen

ts
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,
      }))
}))

Sie können auch Callbacks erstellen, die die gesamte Zeile empfangen und sie direkt an where übergeben

ts
// 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,
      }))
}))

Dieser Ansatz macht Ihre Abfragelogik modularer und testbarer.

Referenz der Expressionsfunktionen

Das Abfragesystem bietet eine umfassende Reihe von Funktionen zum Filtern, Transformieren und Aggregieren von Daten.

Vergleichsoperatoren

eq(left, right)

Gleichheitsvergleich

ts
eq(user.id, 1)
eq(user.name, 'John')
eq(user.id, 1)
eq(user.name, 'John')

gt(left, right), gte(left, right), lt(left, right), lte(left, right)

Numerische, Zeichenketten- und Datumvergleiche

ts
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(value, array)

Überprüfen, ob ein Wert in einem Array vorhanden ist

ts
inArray(user.id, [1, 2, 3])
inArray(user.role, ['admin', 'moderator'])
inArray(user.id, [1, 2, 3])
inArray(user.role, ['admin', 'moderator'])

like(value, pattern), ilike(value, pattern)

Zeichenkettenmusterabgleich

ts
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

Logische Operatoren

and(...conditions)

Bedingungen mit AND-Logik kombinieren

ts
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(...conditions)

Bedingungen mit OR-Logik kombinieren

ts
or(
  eq(user.role, 'admin'),
  eq(user.role, 'moderator')
)
or(
  eq(user.role, 'admin'),
  eq(user.role, 'moderator')
)

not(condition)

Eine Bedingung negieren

ts
not(eq(user.active, false))
not(eq(user.active, false))

Zeichenkettenfunktionen

upper(value), lower(value)

Groß-/Kleinschreibung umwandeln

ts
upper(user.name)  // 'JOHN'
lower(user.email) // 'john@example.com'
upper(user.name)  // 'JOHN'
lower(user.email) // 'john@example.com'

length(value)

Länge von Zeichenketten oder Arrays abrufen

ts
length(user.name)     // String length
length(user.tags)     // Array length
length(user.name)     // String length
length(user.tags)     // Array length

concat(...values)

Zeichenketten verketten

ts
concat(user.firstName, ' ', user.lastName)
concat('User: ', user.name, ' (', user.id, ')')
concat(user.firstName, ' ', user.lastName)
concat('User: ', user.name, ' (', user.id, ')')

Mathematische Funktionen

add(left, right)

Zwei Zahlen addieren

ts
add(user.salary, user.bonus)
add(user.salary, user.bonus)

coalesce(...values)

Den ersten Nicht-NULL-Wert zurückgeben

ts
coalesce(user.displayName, user.name, 'Unknown')
coalesce(user.displayName, user.name, 'Unknown')

Aggregatfunktionen

count(value)

Nicht-NULL-Werte zählen

ts
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(value)

Numerische Werte summieren

ts
sum(order.amount)
sum(user.salary)
sum(order.amount)
sum(user.salary)

avg(value)

Durchschnitt berechnen

ts
avg(user.salary)
avg(order.amount)
avg(user.salary)
avg(order.amount)

min(value), max(value)

Minimal- und Maximalwerte finden

ts
min(user.salary)
max(order.amount)
min(user.salary)
max(order.amount)

Funktionskomposition

Funktionen können komponiert und verkettet werden

ts
// 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)))

Funktionale Varianten

Die funktionale Variantenschnittstelle bietet eine Alternative zur Standard-API und bietet mehr Flexibilität für komplexe Transformationen. Bei funktionalen Varianten enthalten die Callback-Funktionen tatsächlichen Code, der ausgeführt wird, um die Operation durchzuführen, und geben Ihnen die volle Leistung von JavaScript zur Verfügung.

Warnung

Die funktionale Variantenschnittstelle kann vom Abfrageoptimierer nicht optimiert werden und keine Sammlungsindizes verwenden. Sie ist für seltene Fälle gedacht, in denen die Standard-API nicht ausreicht.

Funktionaler Select

Verwenden Sie fn.select() für komplexe Transformationen mit JavaScript-Logik

ts
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,
    }))
)

Funktionales Where

Verwenden Sie fn.where() für komplexe Filterlogik

ts
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')
    })
)

Funktionales Having

Verwenden Sie fn.having() für komplexe Aggregationsfilterung

ts
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
    })
)

Komplexe Transformationen

Funktionale Varianten eignen sich hervorragend für komplexe Datentransformationen

ts
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'
      }
    })
)

Typinferenzen

Funktionale Varianten behalten die vollständige TypeScript-Unterstützung

ts
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',
    }))
)

Wann funktionale Varianten zu verwenden sind

Verwenden Sie funktionale Varianten, wenn Sie benötigen

  • Komplexe JavaScript-Logik, die sich nicht mit integrierten Funktionen ausdrücken lässt
  • Integration mit externen Bibliotheken oder Dienstprogrammen
  • Volle JavaScript-Power für benutzerdefinierte Operationen

Die Callbacks in funktionalen Varianten sind tatsächliche JavaScript-Funktionen, die ausgeführt werden, im Gegensatz zur Standard-API, die deklarative Ausdrücke verwendet. Dies gibt Ihnen die volle Kontrolle über die Logik, bringt aber den Kompromiss reduzierter Optimierungsmöglichkeiten mit sich.

Ziehen Sie jedoch wann immer möglich die Standard-API vor, da diese eine bessere Leistung und Optimierungsmöglichkeiten bietet.

Unsere Partner
Code Rabbit
Electric
Prisma
Bytes abonnieren

Ihre wöchentliche Dosis JavaScript-Nachrichten. Jeden Montag kostenlos an über 100.000 Entwickler geliefert.

Bytes

Kein Spam. Jederzeit kündbar.

Bytes abonnieren

Ihre wöchentliche Dosis JavaScript-Nachrichten. Jeden Montag kostenlos an über 100.000 Entwickler geliefert.

Bytes

Kein Spam. Jederzeit kündbar.