Skip to content

Select_queries


// Select all columns from main table
const users = await userRepository
.createQueryBuilder('user')
.getMany();
// SQL: SELECT * FROM users user
// Select specific columns
const users = await userRepository
.createQueryBuilder('user')
.select([
'user.id',
'user.name',
'user.email',
])
.getMany();
// SQL: SELECT user.id, user.name, user.email FROM users user
// Select with custom column names
const result = await userRepository
.createQueryBuilder('user')
.select('user.name', 'userName')
.addSelect('user.email', 'userEmail')
.addSelect('COUNT(post.id)', 'postCount')
.leftJoin('user.posts', 'post')
.groupBy('user.id')
.getRawMany();
// Returns: [{ userName: 'John', userEmail: 'john@example.com', postCount: '5' }]
// Select with SQL expressions
const result = await userRepository
.createQueryBuilder('user')
.select('CONCAT(user.firstName, \' \', user.lastName)', 'fullName')
.addSelect('LOWER(user.email)', 'emailLower')
.addSelect('EXTRACT(YEAR FROM user.createdAt)', 'joinYear')
.getRawMany();
// SQL: SELECT CONCAT(user.firstName, ' ', user.lastName) AS "fullName",
// LOWER(user.email) AS "emailLower",
// EXTRACT(YEAR FROM user.createdAt) AS "joinYear"
// FROM users user
// Select distinct values
const result = await userRepository
.createQueryBuilder('user')
.select('user.role', 'role')
.distinct(true)
.getRawMany();
// SQL: SELECT DISTINCT user.role FROM users user

JOIN Types Visualized
+------------------------------------------------------------------+
| |
| Table A Table B |
| +--------+ +--------+ |
| | id | | id | |
| +--------+ +--------+ |
| | 1 | | 1 | |
| +--------+ +--------+ |
| | 2 | | 2 | |
| +--------+ +--------+ |
| | 3 | | 4 | |
| +--------+ +--------+ |
| |
| INNER JOIN: Only matching rows |
| +--------+ |
| | A.id 1 | <-- Matches B.id 1 |
| +--------+ |
| | A.id 2 | <-- Matches B.id 2 |
| +--------+ |
| |
| LEFT JOIN: All from A, matching from B |
| +--------+--------+ |
| | A.id 1 | B.id 1 | |
| +--------+--------+ |
| | A.id 2 | B.id 2 | |
| +--------+--------+ |
| | A.id 3 | NULL | <-- No match in B |
| +--------+--------+ |
| |
| RIGHT JOIN: All from B, matching from A |
| +--------+--------+ |
| | A.id 1 | B.id 1 | |
| +--------+--------+ |
| | A.id 2 | B.id 2 | |
| +--------+--------+ |
| | NULL | B.id 4 | <-- No match in A |
| +--------+--------+ |
| |
+------------------------------------------------------------------+

// Left join - includes users without posts
const users = await userRepository
.createQueryBuilder('user')
.leftJoin('user.posts', 'post')
.getMany();
// SQL: SELECT * FROM users user
// LEFT JOIN posts post ON post.authorId = user.id
// Left join with selection - loads related data
const users = await userRepository
.createQueryBuilder('user')
.leftJoinAndSelect('user.posts', 'post')
.getMany();
// Returns users with their posts loaded
// users[0].posts = [{ id: 1, title: 'Post 1' }, ...]
// Left join with additional condition
const users = await userRepository
.createQueryBuilder('user')
.leftJoinAndSelect(
'user.posts',
'post',
'post.published = :published',
{ published: true }
)
.getMany();
// SQL: LEFT JOIN posts post
// ON post.authorId = user.id AND post.published = true
const users = await userRepository
.createQueryBuilder('user')
.select(['user.id', 'user.name'])
.leftJoin('user.posts', 'post')
.addSelect(['post.id', 'post.title'])
.getMany();
// Returns users with posts containing only id and title

// Inner join - only users with posts
const users = await userRepository
.createQueryBuilder('user')
.innerJoin('user.posts', 'post')
.getMany();
// SQL: SELECT * FROM users user
// INNER JOIN posts post ON post.authorId = user.id
const users = await userRepository
.createQueryBuilder('user')
.innerJoinAndSelect('user.posts', 'post')
.getMany();
// Only returns users that have posts
const users = await userRepository
.createQueryBuilder('user')
.innerJoinAndSelect(
'user.posts',
'post',
'post.viewCount > :minViews',
{ minViews: 100 }
)
.getMany();

const users = await userRepository
.createQueryBuilder('user')
.leftJoinAndSelect('user.posts', 'post')
.leftJoinAndSelect('post.comments', 'comment')
.leftJoinAndSelect('comment.author', 'commentAuthor')
.getMany();
// Returns users with posts, comments, and comment authors
const users = await userRepository
.createQueryBuilder('user')
// All users, with their profile if exists
.leftJoinAndSelect('user.profile', 'profile')
// Only users with published posts
.innerJoinAndSelect('user.posts', 'post', 'post.published = :pub', { pub: true })
// All posts, with comments if exist
.leftJoinAndSelect('post.comments', 'comment')
.getMany();

import { User } from './user.entity';
import { Post } from './post.entity';
const result = await userRepository
.createQueryBuilder('user')
.innerJoin(Post, 'post', 'post.authorId = user.id')
.select(['user.id', 'user.name', 'post.title'])
.getRawMany();
// Join without entity relation defined
const result = await userRepository
.createQueryBuilder('user')
.innerJoin(
(qb) => qb
.select('authorId')
.from(Post, 'post')
.where('post.viewCount > :views', { views: 100 })
.groupBy('post.authorId'),
'popular_authors',
'popular_authors.authorId = user.id'
)
.getMany();

// Self join for hierarchical data
import { Category } from './category.entity';
const categories = await categoryRepository
.createQueryBuilder('category')
.leftJoinAndSelect('category.parent', 'parent')
.leftJoinAndSelect('category.children', 'children')
.getMany();
// Returns categories with parent and children

// Pagination with joins
const [users, total] = await userRepository
.createQueryBuilder('user')
.leftJoinAndSelect('user.posts', 'post')
.where('user.isActive = :active', { active: true })
.orderBy('user.createdAt', 'DESC')
.skip(0)
.take(10)
.getManyAndCount();
console.log(`Found ${total} users, showing ${users.length}`);

const result = await userRepository
.createQueryBuilder('user')
.leftJoin('user.posts', 'post')
.select('user.id', 'userId')
.addSelect('user.name', 'userName')
.addSelect('COUNT(post.id)', 'postCount')
.groupBy('user.id')
.getRawMany();
// Returns: [{ userId: 1, userName: 'John', postCount: '5' }, ...]
const result = await userRepository
.createQueryBuilder('user')
.leftJoin('user.orders', 'order')
.select('user.id', 'userId')
.addSelect('SUM(order.total)', 'totalSpent')
.groupBy('user.id')
.getRawMany();
const result = await postRepository
.createQueryBuilder('post')
.leftJoin('post.ratings', 'rating')
.select('post.id', 'postId')
.addSelect('AVG(rating.value)', 'averageRating')
.groupBy('post.id')
.getRawMany();

// Complex query with multiple joins and conditions
const result = await userRepository
.createQueryBuilder('user')
// Select user columns
.select([
'user.id AS "userId"',
'user.name AS "userName"',
'user.email AS "userEmail"',
])
// Join posts
.leftJoin('user.posts', 'post')
// Add post count
.addSelect('COUNT(DISTINCT post.id)', 'postCount')
// Join comments
.leftJoin('post.comments', 'comment')
// Add comment count
.addSelect('COUNT(comment.id)', 'commentCount')
// Join profile
.leftJoin('user.profile', 'profile')
// Add profile bio
.addSelect('profile.bio', 'bio')
// Where conditions
.where('user.isActive = :active', { active: true })
.andWhere('user.role IN (:...roles)', { roles: ['admin', 'user'] })
// Group by user
.groupBy('user.id')
// Add profile to group by
.addGroupBy('profile.id')
// Having condition
.having('COUNT(post.id) > :minPosts', { minPosts: 0 })
// Order by post count
.orderBy('postCount', 'DESC')
// Limit
.limit(10)
// Get raw results
.getRawMany();
// Generated SQL:
// SELECT user.id AS "userId",
// user.name AS "userName",
// user.email AS "userEmail",
// COUNT(DISTINCT post.id) AS "postCount",
// COUNT(comment.id) AS "commentCount",
// profile.bio AS "bio"
// FROM users user
// LEFT JOIN posts post ON post.authorId = user.id
// LEFT JOIN comments comment ON comment.postId = post.id
// LEFT JOIN profiles profile ON profile.userId = user.id
// WHERE user.isActive = true AND user.role IN ('admin', 'user')
// GROUP BY user.id, profile.id
// HAVING COUNT(post.id) > 0
// ORDER BY postCount DESC
// LIMIT 10

JOIN Performance Tips
+------------------------------------------------------------------+
| |
| 1. Use innerJoin when you don't need NULL values |
| - Faster than leftJoin |
| - Smaller result set |
| |
| 2. Select only needed columns |
| - Reduces data transfer |
| - Better performance |
| |
| 3. Add indexes on join columns |
| - Foreign keys should be indexed |
| - Speeds up join operations |
| |
| 4. Limit result set |
| - Use skip/take for pagination |
| - Avoid loading all data |
| |
| 5. Use getRawMany() for aggregates |
| - Avoids entity hydration overhead |
| - Faster for simple queries |
| |
+------------------------------------------------------------------+

JOIN Quick Reference
+------------------------------------------------------------------+
| |
| Join Type | Method | Result |
| -------------------|---------------------------|---------------|
| LEFT JOIN | leftJoin() | All + matches |
| LEFT JOIN SELECT | leftJoinAndSelect() | + loaded data |
| INNER JOIN | innerJoin() | Matches only |
| INNER JOIN SELECT | innerJoinAndSelect() | + loaded data |
| RIGHT JOIN | rightJoin() | All B + match |
| RIGHT JOIN SELECT | rightJoinAndSelect() | + loaded data |
| |
+------------------------------------------------------------------+

Chapter 18: Where Clauses & Conditions


Last Updated: February 2026