or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

advanced-features.mddatabase-connections.mdindex.mdquery-building.mdrelational-queries.mdschema-definition.md

relational-queries.mddocs/

0

# Relational Queries

1

2

Drizzle ORM provides a powerful relational query system that allows you to define relationships between tables and perform complex queries with automatic joins and nested results.

3

4

## Defining Relations

5

6

### One-to-One Relations

7

8

```typescript { .api }

9

function one<T extends AnyTable>(

10

table: T,

11

config?: {

12

fields: AnyColumn[];

13

references: AnyColumn[];

14

relationName?: string;

15

}

16

): One<T>;

17

```

18

19

### One-to-Many Relations

20

21

```typescript { .api }

22

function many<T extends AnyTable>(

23

table: T,

24

config?: {

25

relationName?: string;

26

}

27

): Many<T>;

28

```

29

30

### Relations Configuration

31

32

```typescript { .api }

33

function relations<T extends AnyTable>(

34

table: T,

35

callback: (helpers: RelationHelpers<T>) => Record<string, Relation>

36

): Relations<T>;

37

38

interface RelationHelpers<T extends AnyTable> {

39

one: <TForeign extends AnyTable>(

40

table: TForeign,

41

config: OneConfig<T, TForeign>

42

) => One<TForeign>;

43

many: <TForeign extends AnyTable>(

44

table: TForeign,

45

config?: ManyConfig

46

) => Many<TForeign>;

47

}

48

```

49

50

## Usage Examples

51

52

### Defining Table Relations

53

54

```typescript

55

import { relations, one, many } from "drizzle-orm";

56

57

// Tables

58

export const users = pgTable("users", {

59

id: serial("id").primaryKey(),

60

name: text("name").notNull(),

61

email: text("email").unique(),

62

});

63

64

export const posts = pgTable("posts", {

65

id: serial("id").primaryKey(),

66

title: text("title").notNull(),

67

content: text("content"),

68

authorId: integer("author_id").references(() => users.id),

69

categoryId: integer("category_id").references(() => categories.id),

70

});

71

72

export const categories = pgTable("categories", {

73

id: serial("id").primaryKey(),

74

name: text("name").notNull(),

75

});

76

77

export const comments = pgTable("comments", {

78

id: serial("id").primaryKey(),

79

content: text("content").notNull(),

80

postId: integer("post_id").references(() => posts.id),

81

authorId: integer("author_id").references(() => users.id),

82

});

83

84

// Relations

85

export const usersRelations = relations(users, ({ many }) => ({

86

posts: many(posts),

87

comments: many(comments),

88

}));

89

90

export const postsRelations = relations(posts, ({ one, many }) => ({

91

author: one(users, {

92

fields: [posts.authorId],

93

references: [users.id],

94

}),

95

category: one(categories, {

96

fields: [posts.categoryId],

97

references: [categories.id],

98

}),

99

comments: many(comments),

100

}));

101

102

export const categoriesRelations = relations(categories, ({ many }) => ({

103

posts: many(posts),

104

}));

105

106

export const commentsRelations = relations(comments, ({ one }) => ({

107

post: one(posts, {

108

fields: [comments.postId],

109

references: [posts.id],

110

}),

111

author: one(users, {

112

fields: [comments.authorId],

113

references: [users.id],

114

}),

115

}));

116

```

117

118

## Relational Query Builder

119

120

### Database with Relations

121

122

```typescript { .api }

123

interface DatabaseWithRelations<TSchema extends Record<string, unknown>> {

124

query: TSchema extends Record<string, never> ? never : RelationalQueryBuilder<TSchema>;

125

}

126

127

interface RelationalQueryBuilder<TSchema> {

128

[K in keyof TSchema]: TSchema[K] extends AnyTable

129

? TableRelationalQuery<TSchema[K], TSchema>

130

: never;

131

}

132

133

interface TableRelationalQuery<T extends AnyTable, TSchema> {

134

findFirst(config?: RelationalQueryConfig<T, TSchema>): Promise<InferSelectModel<T> | undefined>;

135

findMany(config?: RelationalQueryConfig<T, TSchema>): Promise<InferSelectModel<T>[]>;

136

}

137

```

138

139

### Query Configuration

140

141

```typescript { .api }

142

interface RelationalQueryConfig<T extends AnyTable, TSchema> {

143

columns?: ColumnsSelection<T>;

144

with?: WithSelection<T, TSchema>;

145

where?: SQL;

146

orderBy?: OrderByConfig<T>;

147

limit?: number;

148

offset?: number;

149

}

150

151

type WithSelection<T extends AnyTable, TSchema> = {

152

[K in keyof InferRelations<T, TSchema>]?: boolean | RelationalQueryConfig<any, TSchema>;

153

};

154

155

type ColumnsSelection<T extends AnyTable> = {

156

[K in keyof InferSelectModel<T>]?: boolean;

157

};

158

```

159

160

## Relational Query Examples

161

162

### Basic Queries with Relations

163

164

```typescript

165

// Database instance with relations

166

const db = drizzle(client, {

167

schema: { users, posts, categories, comments, usersRelations, postsRelations, categoriesRelations, commentsRelations }

168

});

169

170

// Find user with their posts

171

const userWithPosts = await db.query.users.findFirst({

172

with: {

173

posts: true,

174

},

175

});

176

177

// Find post with author and comments

178

const postWithDetails = await db.query.posts.findFirst({

179

with: {

180

author: true,

181

comments: {

182

with: {

183

author: true,

184

},

185

},

186

},

187

});

188

```

189

190

### Advanced Relational Queries

191

192

```typescript

193

// Selective columns and nested relations

194

const result = await db.query.users.findMany({

195

columns: {

196

id: true,

197

name: true,

198

// email excluded

199

},

200

with: {

201

posts: {

202

columns: {

203

id: true,

204

title: true,

205

// content excluded

206

},

207

with: {

208

category: true,

209

},

210

},

211

},

212

where: eq(users.id, 1),

213

});

214

215

// Conditional relation loading

216

const posts = await db.query.posts.findMany({

217

with: {

218

author: true,

219

category: true,

220

comments: {

221

where: eq(comments.content, "Great post!"),

222

with: {

223

author: {

224

columns: {

225

id: true,

226

name: true,

227

},

228

},

229

},

230

},

231

},

232

orderBy: [desc(posts.id)],

233

limit: 10,

234

});

235

```

236

237

### Filtering and Ordering Relations

238

239

```typescript

240

// Filter posts by category and include comments

241

const techPosts = await db.query.categories.findFirst({

242

where: eq(categories.name, "Technology"),

243

with: {

244

posts: {

245

with: {

246

author: true,

247

comments: {

248

orderBy: [desc(comments.id)],

249

limit: 5,

250

},

251

},

252

orderBy: [desc(posts.id)],

253

},

254

},

255

});

256

257

// Complex filtering

258

const activeUsersWithRecentPosts = await db.query.users.findMany({

259

with: {

260

posts: {

261

where: gte(posts.createdAt, new Date('2023-01-01')),

262

with: {

263

category: true,

264

},

265

},

266

},

267

where: isNotNull(users.email),

268

});

269

```

270

271

## Self-Referencing Relations

272

273

### Hierarchical Data

274

275

```typescript

276

export const categories = pgTable("categories", {

277

id: serial("id").primaryKey(),

278

name: text("name").notNull(),

279

parentId: integer("parent_id").references(() => categories.id),

280

});

281

282

export const categoriesRelations = relations(categories, ({ one, many }) => ({

283

parent: one(categories, {

284

fields: [categories.parentId],

285

references: [categories.id],

286

relationName: "parent",

287

}),

288

children: many(categories, {

289

relationName: "parent",

290

}),

291

}));

292

293

// Query hierarchical data

294

const categoryWithChildren = await db.query.categories.findFirst({

295

where: isNull(categories.parentId), // Root category

296

with: {

297

children: {

298

with: {

299

children: true, // Grandchildren

300

},

301

},

302

},

303

});

304

```

305

306

## Many-to-Many Relations

307

308

### Junction Table Pattern

309

310

```typescript

311

export const users = pgTable("users", {

312

id: serial("id").primaryKey(),

313

name: text("name").notNull(),

314

});

315

316

export const roles = pgTable("roles", {

317

id: serial("id").primaryKey(),

318

name: text("name").notNull(),

319

});

320

321

export const usersToRoles = pgTable("users_to_roles", {

322

userId: integer("user_id").references(() => users.id),

323

roleId: integer("role_id").references(() => roles.id),

324

}, (table) => ({

325

pk: primaryKey({ columns: [table.userId, table.roleId] }),

326

}));

327

328

export const usersRelations = relations(users, ({ many }) => ({

329

usersToRoles: many(usersToRoles),

330

}));

331

332

export const rolesRelations = relations(roles, ({ many }) => ({

333

usersToRoles: many(usersToRoles),

334

}));

335

336

export const usersToRolesRelations = relations(usersToRoles, ({ one }) => ({

337

user: one(users, {

338

fields: [usersToRoles.userId],

339

references: [users.id],

340

}),

341

role: one(roles, {

342

fields: [usersToRoles.roleId],

343

references: [roles.id],

344

}),

345

}));

346

347

// Query many-to-many relationships

348

const userWithRoles = await db.query.users.findFirst({

349

with: {

350

usersToRoles: {

351

with: {

352

role: true,

353

},

354

},

355

},

356

});

357

```

358

359

## Performance Considerations

360

361

### Optimizing Relational Queries

362

363

```typescript

364

// Use selective column loading

365

const optimized = await db.query.posts.findMany({

366

columns: {

367

id: true,

368

title: true,

369

// Skip large content field

370

},

371

with: {

372

author: {

373

columns: {

374

id: true,

375

name: true,

376

// Skip email and other sensitive fields

377

},

378

},

379

},

380

});

381

382

// Limit nested results

383

const limited = await db.query.users.findMany({

384

with: {

385

posts: {

386

limit: 5,

387

orderBy: [desc(posts.id)],

388

with: {

389

comments: {

390

limit: 3,

391

orderBy: [desc(comments.id)],

392

},

393

},

394

},

395

},

396

});

397

```

398

399

### Query vs. Manual Joins

400

401

```typescript

402

// Relational query (convenient, automatic joins)

403

const relationalResult = await db.query.posts.findMany({

404

with: { author: true, category: true },

405

});

406

407

// Manual join (more control, potentially better performance)

408

const manualResult = await db

409

.select()

410

.from(posts)

411

.leftJoin(users, eq(posts.authorId, users.id))

412

.leftJoin(categories, eq(posts.categoryId, categories.id));

413

```

414

415

## Types

416

417

```typescript { .api }

418

type InferSelectModel<T extends AnyTable> = T extends Table<infer TConfig>

419

? TConfig['columns'] extends Record<string, Column<infer TColumnConfig>>

420

? {

421

[K in keyof TConfig['columns']]: TConfig['columns'][K] extends Column<infer C>

422

? C['data']

423

: never;

424

}

425

: never

426

: never;

427

428

type InferInsertModel<T extends AnyTable> = T extends Table<infer TConfig>

429

? TConfig['columns'] extends Record<string, Column<infer TColumnConfig>>

430

? {

431

[K in keyof TConfig['columns'] as TConfig['columns'][K] extends Column<infer C>

432

? C['notNull'] extends true

433

? C['hasDefault'] extends true

434

? K

435

: K

436

: K

437

: never

438

]?: TConfig['columns'][K] extends Column<infer C> ? C['data'] : never;

439

} & {

440

[K in keyof TConfig['columns'] as TConfig['columns'][K] extends Column<infer C>

441

? C['notNull'] extends true

442

? C['hasDefault'] extends true

443

? never

444

: K

445

: never

446

: never

447

]: TConfig['columns'][K] extends Column<infer C> ? C['data'] : never;

448

}

449

: never

450

: never;

451

452

interface One<T extends AnyTable> {

453

readonly referencedTable: T;

454

readonly config: OneConfig<any, T>;

455

}

456

457

interface Many<T extends AnyTable> {

458

readonly referencedTable: T;

459

readonly config?: ManyConfig;

460

}

461

462

interface OneConfig<T extends AnyTable, TForeign extends AnyTable> {

463

fields: ExtractTablesWithColumns<T['$inferSelect'], AnyColumn>[];

464

references: ExtractTablesWithColumns<TForeign['$inferSelect'], AnyColumn>[];

465

relationName?: string;

466

}

467

468

interface ManyConfig {

469

relationName?: string;

470

}

471

472

type Relations<T extends AnyTable> = Record<string, One<any> | Many<any>>;

473

474

interface RelationHelpers<T extends AnyTable> {

475

one: <TForeign extends AnyTable>(

476

table: TForeign,

477

config: OneConfig<T, TForeign>

478

) => One<TForeign>;

479

many: <TForeign extends AnyTable>(

480

table: TForeign,

481

config?: ManyConfig

482

) => Many<TForeign>;

483

}

484

```