Query Builder
O Repository.find() cobre a maioria dos casos. Quando você precisa de GROUP BY, HAVING, JOINs manuais, agregações ou expressões SQL arbitrárias, use o QueryBuilder.
const queryBuilder = conn.getRepository(Post).createQueryBuilder();Métodos Disponíveis
Seção intitulada “Métodos Disponíveis”| Método | Retorno | Descrição |
|---|---|---|
select(keys) | this | Colunas a selecionar |
where(condition) | this | Condição WHERE (sobrescreve chamadas anteriores) |
andWhere(sql, params?) | this | Acrescenta condição WHERE com SQL raw |
leftJoin(relation, alias) | this | LEFT JOIN por chave de relação |
groupBy(columns) | this | GROUP BY |
having(condition) | this | HAVING |
orderBy(options) | this | ORDER BY |
limit(n) | this | LIMIT |
offset(n) | this | OFFSET |
getMany() | Promise<T[]> | Executa e retorna entidades hidratadas |
getRaw() | Promise<Record[]> | Executa e retorna objetos simples |
getCount() | Promise<number> | Executa COUNT(*) |
build() | { sql, params } | Gera o SQL sem executar |
explain() | Promise<string> | Retorna o EXPLAIN ANALYZE (PostgreSQL) |
Consulta Básica
Seção intitulada “Consulta Básica”const posts = await conn.getRepository(Post) .createQueryBuilder() .where({ status: 'published' }) .orderBy({ createdAt: 'DESC' }) .limit(10) .getMany();As chaves em where e orderBy são propriedades da entidade — o QueryBuilder mapeia automaticamente para os nomes de coluna no banco:
.where({ viewCount: MoreThan(100) })// WHERE "view_count" > $1getMany() vs getRaw()
Seção intitulada “getMany() vs getRaw()”getMany()— retorna instâncias da entidade, com type casting e todos os mapeamentos aplicadosgetRaw()— retorna objetos JavaScript simples com os nomes das colunas do banco, sem hidratação
// getMany — entidades tipadasconst posts = await queryBuilder.getMany();// posts[0] instanceof Post === true// posts[0].viewCount === 42 (number)
// getRaw — objetos simplesconst rows = await queryBuilder.getRaw();// rows[0] = { id: 1, title: 'Olá', view_count: '42' }// ^^^^ string crua do bancoUse getRaw() quando você seleciona colunas calculadas ou agregações que não existem na entidade.
Agregações e GROUP BY
Seção intitulada “Agregações e GROUP BY”Para COUNT, SUM, AVG e similares, passe as expressões SQL diretamente no select() e use getRaw():
const stats = await conn.getRepository(Post) .createQueryBuilder() .select(['authorId', 'COUNT(*) AS total', 'SUM("view_count") AS views']) .groupBy('"author_id"') .having('COUNT(*) > 5') .orderBy({ 'COUNT(*)': 'DESC' }) .getRaw();
// stats = [{ author_id: 1, total: '12', views: '3400' }, ...]Para apenas contar registros com um filtro, use getCount():
const total = await conn.getRepository(Post) .createQueryBuilder() .where({ status: 'published' }) .getCount();// SELECT COUNT(*) FROM "posts" WHERE "status" = $1Condições WHERE
Seção intitulada “Condições WHERE”Operadores
Seção intitulada “Operadores”Todos os operadores do find() funcionam no QueryBuilder:
import { MoreThan, Like, In, IsNull } from 'mirror-orm';
await conn.getRepository(Post) .createQueryBuilder() .where({ viewCount: MoreThan(100), title: Like('%mirror%'), status: In(['published', 'featured']), deletedAt: IsNull(), }) .getMany();Lógica OR — array de objetos
Seção intitulada “Lógica OR — array de objetos”await conn.getRepository(Post) .createQueryBuilder() .where([ { status: 'published' }, { status: 'featured', viewCount: MoreThan(1000) }, ]) .getMany();// WHERE (status = $1) OR (status = $2 AND view_count > $3)andWhere() — SQL raw adicional
Seção intitulada “andWhere() — SQL raw adicional”Para condições que os operadores não cobrem, acrescente SQL arbitrário. Os parâmetros são automaticamente reposicionados:
await conn.getRepository(Post) .createQueryBuilder() .where({ status: 'published' }) .andWhere('"view_count" > (SELECT AVG("view_count") FROM "posts")') .getMany();// WHERE "status" = $1// AND "view_count" > (SELECT AVG("view_count") FROM "posts")Com parâmetros:
await conn.getRepository(Post) .createQueryBuilder() .where({ authorId: 1 }) .andWhere('"score" BETWEEN $1 AND $2', [50, 100]) .getMany();// WHERE "author_id" = $1 AND "score" BETWEEN $2 AND $3// ^^^ reposicionado automaticamenteChamar
where()mais de uma vez sobrescreve a condição anterior. Para acumular condições, useandWhere()ou passe tudo em um único objeto.
LEFT JOIN
Seção intitulada “LEFT JOIN”O leftJoin() recebe a chave da relação na entidade (não o nome da tabela) e um alias:
const books = await conn.getRepository(Book) .createQueryBuilder() .leftJoin('author', 'author') .where({ 'author.name': Like('%Knuth%') }) .getMany();// LEFT JOIN "authors" "author" ON "books"."author_id" = "author"."id"// WHERE "author"."name" LIKE $1Use o alias com dot notation no where para filtrar pela tabela joinada:
.where({ 'author.country': 'BR', status: 'published' })// WHERE "author"."country" = $1 AND "status" = $2Apenas
leftJoin()está disponível. ParaINNER JOIN, useandWhere()com SQL raw.
Paginação
Seção intitulada “Paginação”const page = 2;const perPage = 10;
const posts = await conn.getRepository(Post) .createQueryBuilder() .where({ status: 'published' }) .orderBy({ createdAt: 'DESC' }) .limit(perPage) .offset((page - 1) * perPage) .getMany();Soft Delete
Seção intitulada “Soft Delete”Se a entidade tiver @DeletedAt, o QueryBuilder sempre acrescenta WHERE deleted_at IS NULL automaticamente — incluindo no getCount(). Esse filtro não pode ser desativado no QueryBuilder; para incluir deletados use repo.find({ withDeleted: true }).
Inspecionar o SQL — build() e explain()
Seção intitulada “Inspecionar o SQL — build() e explain()”build() — gera SQL sem executar
Seção intitulada “build() — gera SQL sem executar”Útil para debug, logging ou testes:
const { sql, params } = conn.getRepository(Post) .createQueryBuilder() .select(['id', 'title']) .where({ status: 'published' }) .orderBy({ createdAt: 'DESC' }) .limit(5) .build();
console.log(sql);// SELECT "id", "title" FROM "posts"// WHERE "status" = $1 AND "deleted_at" IS NULL// ORDER BY "created_at" DESC// LIMIT 5
console.log(params);// ['published']explain() — plano de execução (PostgreSQL)
Seção intitulada “explain() — plano de execução (PostgreSQL)”const plan = await conn.getRepository(Post) .createQueryBuilder() .where({ authorId: 1 }) .explain();
console.log(plan);// Index Scan using posts_author_id_idx on posts// (cost=0.28..8.30 rows=1 width=32)// (actual time=0.021..0.022 rows=1 loops=1)Transações
Seção intitulada “Transações”O QueryBuilder herda o contexto de transação via AsyncLocalStorage automaticamente — o mesmo mecanismo do Repository:
await conn.transaction(async (transaction) => { const drafts = await transaction.getRepository(Post) .createQueryBuilder() .where({ status: 'draft', authorId: userId }) .getMany();
for (const draft of drafts) { draft.status = 'published'; await transaction.getRepository(Post).save(draft); }});Referência de Comportamentos
Seção intitulada “Referência de Comportamentos”| Situação | Comportamento |
|---|---|
Chamar where() duas vezes | Segunda chamada sobrescreve a primeira |
Chave desconhecida em where() | Silenciosamente ignorada |
Chave desconhecida em select() | Passada como raw SQL (permite expressões) |
Chave desconhecida em orderBy() | Passada como raw SQL (permite expressões) |
Entidade com @DeletedAt | WHERE deleted_at IS NULL acrescentado automaticamente |
getCount() com soft delete | COUNT respeita o filtro de soft delete |
Parâmetros em andWhere() | Reposicionados automaticamente após os do where() |