Zum Hauptinhalt springen

Auswählen mit Query Builder

Inoffizielle Beta-Übersetzung

Diese Seite wurde von PageTurner AI übersetzt (Beta). Nicht offiziell vom Projekt unterstützt. Fehler gefunden? Problem melden →

Was ist ein QueryBuilder?

QueryBuilder ist eines der leistungsstärksten Features von TypeORM - es ermöglicht Ihnen, SQL-Abfragen mit einer eleganten und komfortablen Syntax zu erstellen, sie auszuführen und automatisch transformierte Entitäten zu erhalten.

Einfaches Beispiel für QueryBuilder:

const firstUser = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.where("user.id = :id", { id: 1 })
.getOne()

Es erstellt die folgende SQL-Abfrage:

SELECT
user.id as userId,
user.firstName as userFirstName,
user.lastName as userLastName
FROM users user
WHERE user.id = 1

und gibt Ihnen eine Instanz von User zurück:

User {
id: 1,
firstName: "Timber",
lastName: "Saw"
}

Wichtiger Hinweis bei der Verwendung des QueryBuilder

Bei der Verwendung des QueryBuilder müssen Sie in Ihren WHERE-Ausdrücken eindeutige Parameter verwenden. Das funktioniert nicht:

const result = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.leftJoinAndSelect("user.linkedSheep", "linkedSheep")
.leftJoinAndSelect("user.linkedCow", "linkedCow")
.where("user.linkedSheep = :id", { id: sheepId })
.andWhere("user.linkedCow = :id", { id: cowId })

... aber das funktioniert:

const result = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.leftJoinAndSelect("user.linkedSheep", "linkedSheep")
.leftJoinAndSelect("user.linkedCow", "linkedCow")
.where("user.linkedSheep = :sheepId", { sheepId })
.andWhere("user.linkedCow = :cowId", { cowId })

Beachten Sie, dass wir :sheepId und :cowId eindeutig benannt haben, anstatt :id zweimal für verschiedene Parameter zu verwenden.

Wie erstelle und verwende ich einen QueryBuilder?

Es gibt mehrere Möglichkeiten, einen Query Builder zu erstellen:

  • Verwendung von DataSource:

    const user = await dataSource
    .createQueryBuilder()
    .select("user")
    .from(User, "user")
    .where("user.id = :id", { id: 1 })
    .getOne()
  • Verwendung des Entity Managers:

    const user = await dataSource.manager
    .createQueryBuilder(User, "user")
    .where("user.id = :id", { id: 1 })
    .getOne()
  • Verwendung des Repositories:

    const user = await dataSource
    .getRepository(User)
    .createQueryBuilder("user")
    .where("user.id = :id", { id: 1 })
    .getOne()

Es stehen 5 verschiedene QueryBuilder-Typen zur Verfügung:

  • SelectQueryBuilder - dient zum Erstellen und Ausführen von SELECT-Abfragen. Beispiel:

    const user = await dataSource
    .createQueryBuilder()
    .select("user")
    .from(User, "user")
    .where("user.id = :id", { id: 1 })
    .getOne()
  • InsertQueryBuilder - dient zum Erstellen und Ausführen von INSERT-Abfragen. Beispiel:

    await dataSource
    .createQueryBuilder()
    .insert()
    .into(User)
    .values([
    { firstName: "Timber", lastName: "Saw" },
    { firstName: "Phantom", lastName: "Lancer" },
    ])
    .execute()
  • UpdateQueryBuilder - dient zum Erstellen und Ausführen von UPDATE-Abfragen. Beispiel:

    await dataSource
    .createQueryBuilder()
    .update(User)
    .set({ firstName: "Timber", lastName: "Saw" })
    .where("id = :id", { id: 1 })
    .execute()
  • DeleteQueryBuilder - dient zum Erstellen und Ausführen von DELETE-Abfragen. Beispiel:

    await dataSource
    .createQueryBuilder()
    .delete()
    .from(User)
    .where("id = :id", { id: 1 })
    .execute()
  • RelationQueryBuilder - dient zum Erstellen und Ausführen von beziehungs-spezifischen Operationen [TBD]. Beispiel:

    await dataSource
    .createQueryBuilder()
    .relation(User, "photos")
    .of(id)
    .loadMany()

Sie können zwischen verschiedenen QueryBuilder-Typen wechseln, wobei Sie dann eine neue Instanz erhalten (im Gegensatz zu anderen Methoden).

Werte mit QueryBuilder abrufen

Um ein einzelnes Ergebnis aus der Datenbank abzurufen, z.B. einen Benutzer per ID oder Name, verwenden Sie getOne:

const timber = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.where("user.id = :id OR user.name = :name", { id: 1, name: "Timber" })
.getOne()

getOneOrFail ruft ein einzelnes Ergebnis ab, wirft aber einen EntityNotFoundError, wenn kein Ergebnis existiert:

const timber = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.where("user.id = :id OR user.name = :name", { id: 1, name: "Timber" })
.getOneOrFail()

Um mehrere Ergebnisse aus der Datenbank abzurufen, zum Beispiel alle Benutzer, verwenden Sie getMany:

const users = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.getMany()

Es gibt zwei Arten von Ergebnissen, die Sie mit dem Select Query Builder abrufen können: Entitäten oder Rohdaten. Meistens möchten Sie echte Entitäten aus Ihrer Datenbank auswählen, beispielsweise Benutzer. Dafür verwenden Sie getOne und getMany. Manchmal benötigen Sie jedoch spezifische Daten, etwa die Summe aller Benutzerfotos. Diese Daten sind keine Entität, sondern sogenannte Rohdaten. Um Rohdaten abzurufen, verwenden Sie getRawOne und getRawMany. Beispiele:

const { sum } = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.select("SUM(user.photosCount)", "sum")
.where("user.id = :id", { id: 1 })
.getRawOne()
const photosSums = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.select("user.id")
.addSelect("SUM(user.photosCount)", "sum")
.groupBy("user.id")
.getRawMany()

// result will be like this: [{ id: 1, sum: 25 }, { id: 2, sum: 13 }, ...]

Anzahl der Ergebnisse ermitteln

Sie können die Anzahl der von einer Abfrage zurückgegebenen Zeilen mit getCount() ermitteln. Diese gibt die Anzahl als Zahl statt als Entitäts-Ergebnis zurück.

const count = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.where("user.name = :name", { name: "Timber" })
.getCount()

Erzeugt folgende SQL-Abfrage:

SELECT count(*) FROM users user WHERE user.name = 'Timber'

Wozu dienen Aliase?

Wir verwendeten createQueryBuilder("user"). Doch was ist "user"? Es handelt sich um einen normalen SQL-Alias. Aliase werden überall verwendet, außer bei der Arbeit mit ausgewählten Daten.

createQueryBuilder("user") entspricht:

createQueryBuilder().select("user").from(User, "user")

Erzeugt folgende SQL-Abfrage:

SELECT ... FROM users user

In dieser SQL-Abfrage ist users der Tabellenname und user der zugewiesene Alias. Diesen Alias verwenden wir später für den Tabellenzugriff:

createQueryBuilder()
.select("user")
.from(User, "user")
.where("user.name = :name", { name: "Timber" })

Erzeugt folgende SQL-Abfrage:

SELECT ... FROM users user WHERE user.name = 'Timber'

Beachten Sie: Wir verwendeten die Tabelle "users" über den beim Erstellen des Query Builders zugewiesenen Alias user.

Ein Query Builder ist nicht auf einen Alias beschränkt – mehrere Aliase sind möglich. Jede SELECT-Klausel kann ihren eigenen Alias haben, Sie können aus mehreren Tabellen mit jeweiligen Aliasen auswählen und Joins mit eigenen Aliasen durchführen. Diese Aliase dienen dem Zugriff auf Tabellen oder ausgewählte Daten.

Parametrisierung zur Absicherung von Daten

Wir verwendeten where("user.name = :name", { name: "Timber" }). Wofür steht { name: "Timber" }? Es ist ein Parameter zur Verhinderung von SQL-Injections. Alternativ könnten wir schreiben: where("user.name = '" + name + "'), was jedoch unsicher ist und SQL-Injections ermöglicht. Die sichere Methode ist die Spezialsyntax: where("user.name = :name", { name: "Timber" }), wobei :name der Parametername ist und der Wert im Objekt { name: "Timber" } definiert wird.

.where("user.name = :name", { name: "Timber" })

ist eine Kurzform für:

.where("user.name = :name")
.setParameter("name", "Timber")

Hinweis: Verwenden Sie nicht denselben Parameternamen für unterschiedliche Werte im Query Builder. Werte werden überschrieben, wenn sie mehrfach gesetzt werden.

Sie können auch Werte-Arrays übergeben, die mittels Spezialsyntax in SQL-Wertelisten umgewandelt werden:

.where("user.name IN (:...names)", { names: [ "Timber", "Crystal", "Lina" ] })

Wird umgewandelt in:

WHERE user.name IN ('Timber', 'Crystal', 'Lina')

Hinzufügen von WHERE-Bedingungen

Das Hinzufügen einer WHERE-Bedingung ist einfach:

createQueryBuilder("user").where("user.name = :name", { name: "Timber" })

Das ergibt:

SELECT ... FROM users user WHERE user.name = 'Timber'

Hinzufügen einer AND-Bedingung zu existierendem WHERE:

createQueryBuilder("user")
.where("user.firstName = :firstName", { firstName: "Timber" })
.andWhere("user.lastName = :lastName", { lastName: "Saw" })

Das erzeugt die folgende SQL-Abfrage:

SELECT ... FROM users user WHERE user.firstName = 'Timber' AND user.lastName = 'Saw'

Hinzufügen einer OR-Bedingung zu existierendem WHERE:

createQueryBuilder("user")
.where("user.firstName = :firstName", { firstName: "Timber" })
.orWhere("user.lastName = :lastName", { lastName: "Saw" })

Das erzeugt die folgende SQL-Abfrage:

SELECT ... FROM users user WHERE user.firstName = 'Timber' OR user.lastName = 'Saw'

IN-Abfrage mit WHERE-Bedingung:

createQueryBuilder("user").where("user.id IN (:...ids)", { ids: [1, 2, 3, 4] })

Das erzeugt die folgende SQL-Abfrage:

SELECT ... FROM users user WHERE user.id IN (1, 2, 3, 4)

Sie können einen komplexen WHERE-Ausdruck mittels Brackets in eine bestehende WHERE-Bedingung einfügen.

createQueryBuilder("user")
.where("user.registered = :registered", { registered: true })
.andWhere(
new Brackets((qb) => {
qb.where("user.firstName = :firstName", {
firstName: "Timber",
}).orWhere("user.lastName = :lastName", { lastName: "Saw" })
}),
)

Das erzeugt die folgende SQL-Abfrage:

SELECT ... FROM users user WHERE user.registered = true AND (user.firstName = 'Timber' OR user.lastName = 'Saw')

Sie können einen negierten komplexen WHERE-Ausdruck in eine bestehende WHERE-Bedingung mittels NotBrackets einfügen.

createQueryBuilder("user")
.where("user.registered = :registered", { registered: true })
.andWhere(
new NotBrackets((qb) => {
qb.where("user.firstName = :firstName", {
firstName: "Timber",
}).orWhere("user.lastName = :lastName", { lastName: "Saw" })
}),
)

Das erzeugt die folgende SQL-Abfrage:

SELECT ... FROM users user WHERE user.registered = true AND NOT((user.firstName = 'Timber' OR user.lastName = 'Saw'))

Sie können beliebig viele AND- und OR-Ausdrücke kombinieren. Wenn Sie .where mehrfach verwenden, werden alle vorherigen WHERE-Ausdrücke überschrieben.

Hinweis: Seien Sie vorsichtig mit orWhere – bei komplexen Ausdrücken mit sowohl AND als auch OR werden diese ohne Vorrangregeln gestapelt. Manchmal sollten Sie stattdessen einen WHERE-String erstellen und orWhere vermeiden.

Hinzufügen eines HAVING-Ausdrucks

Das Hinzufügen eines HAVING-Ausdrucks ist einfach:

createQueryBuilder("user").having("user.name = :name", { name: "Timber" })

Welches die folgende SQL-Abfrage erzeugt:

SELECT ... FROM users user HAVING user.name = 'Timber'

Sie können AND in eine bestehende HAVING-Expression einfügen:

createQueryBuilder("user")
.having("user.firstName = :firstName", { firstName: "Timber" })
.andHaving("user.lastName = :lastName", { lastName: "Saw" })

Das erzeugt die folgende SQL-Abfrage:

SELECT ... FROM users user HAVING user.firstName = 'Timber' AND user.lastName = 'Saw'

Sie können OR in einen vorhandenen HAVING-Ausdruck einfügen:

createQueryBuilder("user")
.having("user.firstName = :firstName", { firstName: "Timber" })
.orHaving("user.lastName = :lastName", { lastName: "Saw" })

Das erzeugt die folgende SQL-Abfrage:

SELECT ... FROM users user HAVING user.firstName = 'Timber' OR user.lastName = 'Saw'

Sie können so viele AND- und OR-Ausdrücke kombinieren, wie Sie benötigen.
Wenn Sie .having mehr als einmal verwenden, überschreiben Sie alle vorherigen HAVING-Ausdrücke.

Hinzufügen eines ORDER BY-Ausdrucks

Hinzufügen eines ORDER BY-Ausdrucks

createQueryBuilder("user").orderBy("user.id")

Das ergibt:

SELECT ... FROM users user ORDER BY user.id

Dadurch wird erzeugt:

createQueryBuilder("user").orderBy("user.id", "DESC")

createQueryBuilder("user").orderBy("user.id", "ASC")

Sie können die Sortierreihenfolge von aufsteigend zu absteigend ändern (oder umgekehrt):

createQueryBuilder("user").orderBy("user.name").addOrderBy("user.id")

Sie können mehrere Order-By-Kriterien hinzufügen:

createQueryBuilder("user").orderBy({
"user.name": "ASC",
"user.id": "DESC",
})

Wenn Sie .orderBy mehr als einmal verwenden, überschreiben Sie alle vorherigen ORDER BY-Ausdrücke.

Hinzufügen des DISTINCT ON-Ausdrucks (nur Postgres)

Wenn sowohl distinct-on als auch ein order-by-Ausdruck verwendet werden, muss der distinct-on-Ausdruck mit dem äußersten links stehenden order-by übereinstimmen. Die distinct-on-Ausdrücke werden mit denselben Regeln wie order-by interpretiert. Bitte beachten Sie, dass die Verwendung von distinct-on ohne einen order-by-Ausdruck bedeutet, dass die erste Zeile jeder Gruppe unvorhersehbar ist.

Das Hinzufügen eines DISTINCT ON-Ausdrucks ist so einfach wie:

createQueryBuilder("user").distinctOn(["user.id"]).orderBy("user.id")

Das ergibt:

SELECT DISTINCT ON (user.id) ... FROM users user ORDER BY user.id

Hinzufügen eines GROUP BY-Ausdrucks

Hinzufügen eines GROUP BY-Ausdrucks

createQueryBuilder("user").groupBy("user.id")

Das erzeugt die folgende SQL-Abfrage:

SELECT ... FROM users user GROUP BY user.id

Um weitere Group-by-Kriterien hinzuzufügen, verwenden Sie addGroupBy:

createQueryBuilder("user").groupBy("user.name").addGroupBy("user.id")

Wenn Sie .groupBy mehr als einmal verwenden, überschreiben Sie alle vorherigen GROUP BY-Ausdrücke.

Hinzufügen des LIMIT-Ausdrucks

Hinzufügen eines LIMIT-Ausdrucks

createQueryBuilder("user").limit(10)

Das erzeugt die folgende SQL-Abfrage:

SELECT ... FROM users user LIMIT 10

Die resultierende SQL-Abfrage hängt von der Art der Datenbank ab (SQL, MySQL, Postgres usw.). Hinweis: LIMIT funktioniert möglicherweise nicht wie erwartet, wenn Sie komplexe Abfragen mit Joins oder Unterabfragen verwenden. Wenn Sie Paginierung verwenden, wird empfohlen, stattdessen take zu verwenden.

Hinzufügen der OFFSET-Expression

Hinzufügen eines OFFSET-Ausdrucks

createQueryBuilder("user").offset(10)

Das erzeugt die folgende SQL-Abfrage:

SELECT ... FROM users user OFFSET 10

Die resultierende SQL-Abfrage hängt von der Art der Datenbank (SQL, MySQL, Postgres, etc.) ab. Hinweis: OFFSET funktioniert möglicherweise nicht wie erwartet, wenn Sie komplexe Abfragen mit Joins oder Unterabfragen verwenden. Wenn Sie Paginierung verwenden, wird stattdessen empfohlen, skip zu verwenden.

Beziehungen verknüpfen

Verknüpfungen (Joins) von Relationen

import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from "typeorm"
import { Photo } from "./Photo"

@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number

@Column()
name: string

@OneToMany((type) => Photo, (photo) => photo.user)
photos: Photo[]
}
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from "typeorm"
import { User } from "./User"

@Entity()
export class Photo {
@PrimaryGeneratedColumn()
id: number

@Column()
url: string

@ManyToOne((type) => User, (user) => user.photos)
user: User
}

Angenommen, Sie haben die folgenden Entitäten:

const user = await createQueryBuilder("user")
.leftJoinAndSelect("user.photos", "photo")
.where("user.name = :name", { name: "Timber" })
.getOne()

Nehmen wir nun an, Sie möchten den Benutzer "Timber" mit allen seinen Fotos laden:

{
id: 1,
name: "Timber",
photos: [{
id: 1,
url: "me-with-chakram.jpg"
}, {
id: 2,
url: "me-with-trees.jpg"
}]
}

Wie Sie sehen, hat leftJoinAndSelect automatisch alle Fotos von Timber geladen. Das erste Argument ist die Relation, die Sie laden möchten, und das zweite Argument ist ein Alias, den Sie der Tabelle dieser Relation zuweisen. Diesen Alias können Sie überall im Query Builder verwenden. Laden wir beispielsweise alle Fotos von Timber, die nicht entfernt wurden.

const user = await createQueryBuilder("user")
.leftJoinAndSelect("user.photos", "photo")
.where("user.name = :name", { name: "Timber" })
.andWhere("photo.isRemoved = :isRemoved", { isRemoved: false })
.getOne()

Dies erzeugt folgende SQL-Abfrage:

SELECT user.*, photo.* FROM users user
LEFT JOIN photos photo ON photo.user = user.id
WHERE user.name = 'Timber' AND photo.isRemoved = FALSE

Sie können Bedingungen auch direkt im Join-Ausdruck hinzufügen, anstatt "where" zu verwenden:

const user = await createQueryBuilder("user")
.leftJoinAndSelect("user.photos", "photo", "photo.isRemoved = :isRemoved", {
isRemoved: false,
})
.where("user.name = :name", { name: "Timber" })
.getOne()

Dies erzeugt folgende SQL-Abfrage:

SELECT user.*, photo.* FROM users user
LEFT JOIN photos photo ON photo.user = user.id AND photo.isRemoved = FALSE
WHERE user.name = 'Timber'

Inner und Left Joins

Wenn Sie INNER JOIN statt LEFT JOIN verwenden möchten, nutzen Sie einfach innerJoinAndSelect:

const user = await createQueryBuilder("user")
.innerJoinAndSelect(
"user.photos",
"photo",
"photo.isRemoved = :isRemoved",
{ isRemoved: false },
)
.where("user.name = :name", { name: "Timber" })
.getOne()

Dies erzeugt:

SELECT user.*, photo.* FROM users user
INNER JOIN photos photo ON photo.user = user.id AND photo.isRemoved = FALSE
WHERE user.name = 'Timber'

Der Unterschied zwischen LEFT JOIN und INNER JOIN ist, dass INNER JOIN keinen Benutzer zurückgibt, wenn er keine Fotos hat. LEFT JOIN gibt Ihnen den Benutzer auch zurück, wenn er keine Fotos hat. Mehr zu verschiedenen Join-Typen erfahren Sie in der SQL-Dokumentation.

Join ohne Selektion

Sie können Daten verknüpfen, ohne sie auszuwählen. Verwenden Sie dazu leftJoin oder innerJoin:

const user = await createQueryBuilder("user")
.innerJoin("user.photos", "photo")
.where("user.name = :name", { name: "Timber" })
.getOne()

Dies erzeugt:

SELECT user.* FROM users user
INNER JOIN photos photo ON photo.user = user.id
WHERE user.name = 'Timber'

Damit wird Timber ausgewählt, wenn er Fotos hat, aber seine Fotos werden nicht zurückgegeben.

Beliebiges Entity oder Tabelle verknüpfen

Sie können nicht nur Relationen, sondern auch beliebige nicht-verknüpfte Entities oder Tabellen joinen. Beispiele:

const user = await createQueryBuilder("user")
.leftJoinAndSelect(Photo, "photo", "photo.userId = user.id")
.getMany()
const user = await createQueryBuilder("user")
.leftJoinAndSelect("photos", "photo", "photo.userId = user.id")
.getMany()

Verknüpfungs- und Mapping-Funktionalität

Fügen Sie profilePhoto zum User-Entity hinzu, dann können Sie Daten über QueryBuilder in diese Eigenschaft mappen:

export class User {
/// ...
profilePhoto: Photo
}
const user = await createQueryBuilder("user")
.leftJoinAndMapOne(
"user.profilePhoto",
"user.photos",
"photo",
"photo.isForProfile = TRUE",
)
.where("user.name = :name", { name: "Timber" })
.getOne()

Damit wird Timbers Profilfoto geladen und als user.profilePhoto gesetzt. Für einzelne Entities verwenden Sie leftJoinAndMapOne. Für mehrere Entities nutzen Sie leftJoinAndMapMany.

Generierte Abfrage abrufen

Manchmal möchten Sie die von QueryBuilder generierte SQL-Abfrage sehen. Verwenden Sie dazu getSql:

const sql = createQueryBuilder("user")
.where("user.firstName = :firstName", { firstName: "Timber" })
.orWhere("user.lastName = :lastName", { lastName: "Saw" })
.getSql()

Für Debugging-Zwecke können Sie printSql nutzen:

const users = await createQueryBuilder("user")
.where("user.firstName = :firstName", { firstName: "Timber" })
.orWhere("user.lastName = :lastName", { lastName: "Saw" })
.printSql()
.getMany()

Diese Abfrage gibt Benutzer zurück und protokolliert den SQL-Befehl in der Konsole.

Rohdaten abrufen

Es gibt zwei Ergebnisarten bei Select-QueryBuildern: Entities und Rohdaten. Meist benötigen Sie echte Entities wie Benutzer aus Ihrer Datenbank. Dafür verwenden Sie getOne und getMany. Manchmal benötigen Sie jedoch spezifische Daten wie die Summe aller Benutzerfotos. Solche Daten sind kein Entity, sondern Rohdaten. Für Rohdaten verwenden Sie getRawOne und getRawMany. Beispiele:

const { sum } = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.select("SUM(user.photosCount)", "sum")
.where("user.id = :id", { id: 1 })
.getRawOne()
const photosSums = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.select("user.id")
.addSelect("SUM(user.photosCount)", "sum")
.groupBy("user.id")
.getRawMany()

// result will be like this: [{ id: 1, sum: 25 }, { id: 2, sum: 13 }, ...]

Daten-Streaming

Sie können stream verwenden, das einen Stream zurückgibt. Streaming liefert Rohdaten, und Sie müssen die Entity-Transformation manuell durchführen:

const stream = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.where("user.id = :id", { id: 1 })
.stream()

Paginierung verwenden

In den meisten Anwendungen benötigen Sie Paginierungsfunktionalität. Dies ist nützlich für Seitenumbrüche, Slider oder Endlos-Scroll-Komponenten.

const users = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.leftJoinAndSelect("user.photos", "photo")
.take(10)
.getMany()

Das gibt die ersten 10 Benutzer mit ihren Fotos zurück.

const users = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.leftJoinAndSelect("user.photos", "photo")
.skip(10)
.getMany()

Das gibt alle Benutzer außer den ersten 10 mit ihren Fotos zurück. Sie können diese Methoden kombinieren:

const users = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.leftJoinAndSelect("user.photos", "photo")
.skip(5)
.take(10)
.getMany()

Das überspringt die ersten 5 Benutzer und nimmt dann 10 Benutzer.

take und skip wirken wie limit und offset, sind es aber nicht. limit und offset können bei komplexen Abfragen mit Joins oder Subqueries unerwartete Ergebnisse liefern. take und skip vermeiden diese Probleme.

Locking festlegen

QueryBuilder unterstützt sowohl optimistisches als auch pessimistisches Locking.

Sperrmodi

Die Unterstützung von Sperrmodi und die entsprechenden SQL-Anweisungen sind in der folgenden Tabelle aufgeführt (leere Zellen bedeuten keine Unterstützung). Wenn ein angegebener Sperrmodus nicht unterstützt wird, wird ein LockNotSupportedOnGivenDriverError-Fehler geworfen.

|                 | pessimistic_read                  | pessimistic_write       | dirty_read    | pessimistic_partial_write (Deprecated, use onLocked instead)   | pessimistic_write_or_fail (Deprecated, use onLocked instead)   | for_no_key_update   | for_key_share |
| --------------- | --------------------------------- | ----------------------- | ------------- | -------------------------------------------------------------- | -------------------------------------------------------------- | ------------------- | ------------- |
| MySQL | FOR SHARE (8+)/LOCK IN SHARE MODE | FOR UPDATE | (nothing) | FOR UPDATE SKIP LOCKED | FOR UPDATE NOWAIT | | |
| Postgres | FOR SHARE | FOR UPDATE | (nothing) | FOR UPDATE SKIP LOCKED | FOR UPDATE NOWAIT | FOR NO KEY UPDATE | FOR KEY SHARE |
| Oracle | FOR UPDATE | FOR UPDATE | (nothing) | | | | |
| SQL Server | WITH (HOLDLOCK, ROWLOCK) | WITH (UPDLOCK, ROWLOCK) | WITH (NOLOCK) | | | | |
| AuroraDataApi | LOCK IN SHARE MODE | FOR UPDATE | (nothing) | | | | |
| CockroachDB | | FOR UPDATE | (nothing) | | FOR UPDATE NOWAIT | FOR NO KEY UPDATE | |

Um pessimistisches Lesesperren zu verwenden, nutzen Sie folgende Methode:

const users = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.setLock("pessimistic_read")
.getMany()

Um pessimistisches Schreibsperren zu verwenden, nutzen Sie folgende Methode:

const users = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.setLock("pessimistic_write")
.getMany()

Um Dirty Read-Sperren zu verwenden, nutzen Sie folgende Methode:

const users = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.setLock("dirty_read")
.getMany()

Um optimistisches Sperren zu verwenden, nutzen Sie folgende Methode:

const users = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.setLock("optimistic", existUser.version)
.getMany()

Optimistisches Sperren funktioniert in Kombination mit den Dekoratoren @Version und @UpdatedDate.

Tabellen sperren

Sie können auch Tabellen mit folgender Methode sperren:

const users = await dataSource
.getRepository(Post)
.createQueryBuilder("post")
.leftJoin("post.author", "user")
.setLock("pessimistic_write", undefined, ["post"])
.getMany()

Wenn das Lock Tables-Argument angegeben wird, wird nur die in der FOR UPDATE OF-Klausel gesperrte Tabelle spezifiziert.

setOnLocked

Ermöglicht die Steuerung des Verhaltens bei gesperrten Zeilen. Standardmäßig wartet die Datenbank auf die Freigabe der Sperre. Sie können dieses Verhalten mit setOnLocked kontrollieren.

Um nicht zu warten:

const users = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.setLock("pessimistic_write")
.setOnLocked("nowait")
.getMany()

Um die Zeile zu überspringen:

const users = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.setLock("pessimistic_write")
.setOnLocked("skip_locked")
.getMany()

Datenbankunterstützung für setOnLocked basierend auf Sperrmodi:

  • Postgres: pessimistic_read, pessimistic_write, for_no_key_update, for_key_share

  • MySQL 8+: pessimistic_read, pessimistic_write

  • MySQL < 8, Maria DB: pessimistic_write

  • Cockroach: pessimistic_write (nur nowait)

Benutzerdefinierte Indizes verwenden

In bestimmten Fällen können Sie einen spezifischen Index für den Datenbankserver vorgeben. Diese Funktion wird nur von MySQL unterstützt.

const users = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.useIndex("my_index") // name of index
.getMany()

Maximale Ausführungszeit

Wir können langsame Abfragen abbrechen, um einen Serverabsturz zu vermeiden.

const users = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.maxExecutionTime(1000) // milliseconds.
.getMany()

Teilweise Auswahl

Wenn Sie nur bestimmte Eigenschaften einer Entität auswählen möchten, verwenden Sie folgende Syntax:

const users = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.select(["user.id", "user.name"])
.getMany()

Dadurch werden nur die Eigenschaften id und name von User ausgewählt.

Unterabfragen verwenden

Sie können einfach Unterabfragen erstellen. Diese werden in FROM-, WHERE- und JOIN-Ausdrücken unterstützt. Beispiel:

const qb = await dataSource.getRepository(Post).createQueryBuilder("post")

const posts = qb
.where(
"post.title IN " +
qb
.subQuery()
.select("user.name")
.from(User, "user")
.where("user.registered = :registered")
.getQuery(),
)
.setParameter("registered", true)
.getMany()

Eine elegante Alternative:

const posts = await dataSource
.getRepository(Post)
.createQueryBuilder("post")
.where((qb) => {
const subQuery = qb
.subQuery()
.select("user.name")
.from(User, "user")
.where("user.registered = :registered")
.getQuery()
return "post.title IN " + subQuery
})
.setParameter("registered", true)
.getMany()

Alternativ können Sie einen separaten Query Builder erstellen und dessen generiertes SQL verwenden:

const userQb = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.select("user.name")
.where("user.registered = :registered", { registered: true })

const posts = await dataSource
.getRepository(Post)
.createQueryBuilder("post")
.where("post.title IN (" + userQb.getQuery() + ")")
.setParameters(userQb.getParameters())
.getMany()

Unterabfragen in FROM können so erstellt werden:

const userQb = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.select("user.name", "name")
.where("user.registered = :registered", { registered: true })

const posts = await dataSource
.createQueryBuilder()
.select("user.name", "name")
.from("(" + userQb.getQuery() + ")", "user")
.setParameters(userQb.getParameters())
.getRawMany()

Oder in eleganter Syntax:

const posts = await dataSource
.createQueryBuilder()
.select("user.name", "name")
.from((subQuery) => {
return subQuery
.select("user.name", "name")
.from(User, "user")
.where("user.registered = :registered", { registered: true })
}, "user")
.getRawMany()

Verwenden Sie addFrom, um eine Unterabfrage als "zweites FROM" hinzuzufügen.

Unterabfragen können auch in SELECT-Anweisungen verwendet werden:

const posts = await dataSource
.createQueryBuilder()
.select("post.id", "id")
.addSelect((subQuery) => {
return subQuery.select("user.name", "name").from(User, "user").limit(1)
}, "name")
.from(Post, "post")
.getRawMany()

Versteckte Spalten

Wenn das abgefragte Modell eine Spalte mit select: false enthält, müssen Sie die Funktion addSelect verwenden, um diese Daten abzurufen.

Angenommen, Sie haben folgende Entität:

import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"

@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number

@Column()
name: string

@Column({ select: false })
password: string
}

Bei Verwendung von find oder Standardabfragen erhalten Sie die Eigenschaft password nicht. Wenn Sie jedoch folgendes tun:

const users = await dataSource
.getRepository(User)
.createQueryBuilder()
.select("user.id", "id")
.addSelect("user.password")
.getMany()

Erhalten Sie die Eigenschaft password in Ihrer Abfrage.

Abfragen gelöschter Zeilen

Wenn das abgefragte Modell eine Spalte mit dem Attribut @DeleteDateColumn hat, fragt der Query Builder automatisch "soft-gelöschte" Zeilen ab.

Angenommen, Sie haben folgende Entität:

import {
Entity,
PrimaryGeneratedColumn,
Column,
DeleteDateColumn,
} from "typeorm"

@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number

@Column()
name: string

@DeleteDateColumn()
deletedAt?: Date
}

Bei Verwendung einer standardmäßigen find-Methode oder Abfrage erhalten Sie keine Zeilen, die einen Wert in dieser Spalte haben. Wenn Sie jedoch Folgendes tun:

const users = await dataSource
.getRepository(User)
.createQueryBuilder()
.select("user.id", "id")
.withDeleted()
.getMany()

erhalten Sie alle Zeilen, einschließlich derjenigen, die als gelöscht markiert sind.

Gemeinsame Tabellenausdrücke (CTEs)

QueryBuilder-Instanzen unterstützen Common Table Expressions, sofern die minimal unterstützte Version Ihrer Datenbank dies ermöglicht. Gemeinsame Tabellenausdrücke werden für Oracle noch nicht unterstützt.

const users = await connection
.getRepository(User)
.createQueryBuilder("user")
.select("user.id", "id")
.addCommonTableExpression(
`
SELECT "userId" FROM "post"
`,
"post_users_ids",
)
.where(`user.id IN (SELECT "userId" FROM 'post_users_ids')`)
.getMany()

Ergebniswerte von InsertQueryBuilder oder UpdateQueryBuilder können in Postgres verwendet werden:

const insertQueryBuilder = connection
.getRepository(User)
.createQueryBuilder()
.insert({
name: "John Smith",
})
.returning(["id"])

const users = await connection
.getRepository(User)
.createQueryBuilder("user")
.addCommonTableExpression(insertQueryBuilder, "insert_results")
.where(`user.id IN (SELECT "id" FROM 'insert_results')`)
.getMany()

Time-Travel-Abfragen

Time-Travel-Abfragen werden derzeit nur in der CockroachDB-Datenbank unterstützt.

const repository = connection.getRepository(Account)

// create a new account
const account = new Account()
account.name = "John Smith"
account.balance = 100
await repository.save(account)

// imagine we update the account balance 1 hour after creation
account.balance = 200
await repository.save(account)

// outputs { name: "John Smith", balance: "200" }
console.log(account)

// load account state on 1 hour back
account = await repository
.createQueryBuilder("account")
.timeTravelQuery(`'-1h'`)
.getOneOrFail()

// outputs { name: "John Smith", balance: "100" }
console.log(account)

Standardmäßig verwendet timeTravelQuery() die Funktion follower_read_timestamp(), wenn keine Argumente übergeben werden. Für andere unterstützte Zeitstempel-Argumente und zusätzliche Informationen lesen Sie bitte die CockroachDB-Dokumentation.

Debugging

Sie können den generierten SQL-Code vom Query Builder durch Aufruf von getQuery() oder getQueryAndParameters() abrufen.

Wenn Sie nur die Abfrage benötigen, können Sie getQuery() verwenden:

const sql = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.where("user.id = :id", { id: 1 })
.getQuery()

Was zu folgendem Ergebnis führt:

SELECT `user`.`id` as `userId`, `user`.`firstName` as `userFirstName`, `user`.`lastName` as `userLastName` FROM `users` `user` WHERE `user`.`id` = ?

Oder wenn Sie sowohl die Abfrage als auch die Parameter benötigen, erhalten Sie ein Array mit getQueryAndParameters():

const queryAndParams = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.where("user.id = :id", { id: 1 })
.getQueryAndParameters()

Was zu folgendem Ergebnis führt:

;[
"SELECT `user`.`id` as `userId`, `user`.`firstName` as `userFirstName`, `user`.`lastName` as `userLastName` FROM `users` `user` WHERE `user`.`id` = ?",
[1],
]