Become a Gold Sponsor

Drizzle 查询

Drizzle ORM 旨在成为 SQL 之上的一个薄型类型层。
我们真心相信我们设计了从 TypeScript 操作 SQL 数据库的最佳方法,现在是时候使其更出色了。

关系查询旨在为您提供出色的开发者体验, 以便从 SQL 数据库中查询嵌套的关系数据,避免多个连接和复杂的数据映射。

它是现有模式定义和查询构建器的扩展。
您可以根据自己的需求选择使用它。
我们确保为您提供最优秀的开发者体验和性能。

index.ts
schema.ts
import * as schema from './schema';
import { drizzle } from 'drizzle-orm/...';

const db = drizzle({ schema });

const result = await db.query.users.findMany({
	with: {
		posts: true			
	},
});
[{
	id: 10,
	name: "Dan",
	posts: [
		{
			id: 1,
			content: "SQL 是很棒的",
			authorId: 10,
		},
		{
			id: 2,
			content: "但请检查关系查询",
			authorId: 10,
		}
	]
}]

⚠️ 如果您在多个文件中声明 SQL 模式,您可以这样做:

index.ts
schema1.ts
schema2.ts
import * as schema1 from './schema1';
import * as schema2 from './schema2';
import { drizzle } from 'drizzle-orm/...';

const db = drizzle({ schema: { ...schema1, ...schema2 } });

const result = await db.query.users.findMany({
	with: {
		posts: true			
	},
});

模式

Drizzle 关系查询总是生成一个 SQL 语句在数据库上运行,并且有一些注意事项。
为了对每种数据库提供最佳支持,我们引入了 modes

Drizzle 关系查询在底层使用子查询的侧向连接,目前 PlanetScale 不支持它们。

当使用 mysql2 驱动程序与常规 MySQL 数据库时 — 你应该指定 mode: "default", 当使用 mysql2 驱动程序与 PlanetScale 时 — 你需要指定 mode: "planetscale"

import * as schema from './schema';
import { drizzle } from "drizzle-orm/mysql2";
import mysql from "mysql2/promise";

const connection = await mysql.createConnection({
  uri: process.env.PLANETSCALE_DATABASE_URL,
});

const db = drizzle({ client: connection, schema, mode: 'planetscale' });

查询

关系查询是 Drizzle 原始 查询构建器 的一个扩展。
您需要在 drizzle() 初始化时提供来自您的模式文件/文件的所有 tablesrelations , 然后只需使用 db.query API。

drizzle 的导入路径取决于您使用的 数据库驱动程序

index.ts
schema.ts
import * as schema from './schema';
import { drizzle } from 'drizzle-orm/...';

const db = drizzle({ schema });

await db.query.users.findMany(...);
// 如果您在多个文件中有模式
import * as schema1 from './schema1';
import * as schema2 from './schema2';
import { drizzle } from 'drizzle-orm/...';

const db = drizzle({ schema: { ...schema1, ...schema2 } });

await db.query.users.findMany(...);

Drizzle 提供 .findMany().findFirst() API。

查找多个

const users = await db.query.users.findMany();
// 结果类型
const result: {
	id: number;
	name: string;
	verified: boolean;
	invitedBy: number | null;
}[];

查找第一个

.findFirst() 会在查询中添加 limit 1

const user = await db.query.users.findFirst();
// 结果类型
const result: {
	id: number;
	name: string;
	verified: boolean;
	invitedBy: number | null;
};

包括关系

With 操作符允许您从多个相关表中组合数据并正确聚合结果。

获取所有带评论的帖子:

const posts = await db.query.posts.findMany({
	with: {
		comments: true,
	},
});

获取第一个带评论的帖子:

const post = await db.query.posts.findFirst({
	with: {
		comments: true,
	},
});

您可以根据需要链式调用嵌套的 with 语句。
对于任何嵌套的 with 查询,Drizzle 将使用 Core Type API 推断类型。

获取所有用户及其帖子。每个帖子应包含评论列表:

const users = await db.query.users.findMany({
	with: {
		posts: {
			with: {
				comments: true,
			},
		},
	},
});

部分字段选择

columns 参数允许您包含或省略要从数据库中获取的列。

Drizzle 在查询级别执行部分选择,没有额外数据被从数据库中传输。

请记住 Drizzle 输出的是单个 SQL 语句

仅获取带有 idcontent 的所有帖子,并包括 comments

const posts = await db.query.posts.findMany({
	columns: {
		id: true,
		content: true,
	},
	with: {
		comments: true,
	}
});

获取没有 content 的所有帖子:

const posts = await db.query.posts.findMany({
	columns: {
		content: false,
	},
});

当同时存在 truefalse 的选择选项时,所有 false 的选项将被忽略。

如果您包含 name 字段并排除 id 字段,则 id 的排除将是多余的, 因为除了 name 的所有字段都将被排除。

在同一查询中排除和包含字段:

const users = await db.query.users.findMany({
	columns: {
		name: true,
		id: false //被忽略
	},
});
// 结果类型
const users: {
	name: string;
};

仅包含来自嵌套关系的列:

const res = await db.query.users.findMany({
	columns: {},
	with: {
		posts: true
	}
});
// 结果类型
const res: {
	posts: {
		id: number,
		text: string
	}
}[];

嵌套部分字段选择

就像 部分选择 一样,您可以包含或排除嵌套关系的列:

const posts = await db.query.posts.findMany({
	columns: {
		id: true,
		content: true,
	},
	with: {
		comments: {
			columns: {
				authorId: false
			}
		}
	}
});

选择过滤器

就像在我们的 SQL 风格查询构建器中一样,关系查询 API 允许您定义过滤器和条件, 使用我们 运算符 的列表。

您可以从 drizzle-orm 导入它们,或使用回调语法直接使用:

import { eq } from 'drizzle-orm';

const users = await db.query.users.findMany({
	where: eq(users.id, 1)
})
const users = await db.query.users.findMany({
	where: (users, { eq }) => eq(users.id, 1),
})

寻找 id=1 并且在特定日期之前创建的评论的帖子:

await db.query.posts.findMany({
	where: (posts, { eq }) => (eq(posts.id, 1)),
	with: {
		comments: {
			where: (comments, { lt }) => lt(comments.createdAt, new Date()),
		},
	},
});

限制与偏移

Drizzle ORM 为查询和嵌套实体提供 limitoffset API。

寻找 5 个帖子:

await db.query.posts.findMany({
	limit: 5,
});

查找帖子并最多获取 3 条评论:

await db.query.posts.findMany({
	with: {
		comments: {
			limit: 3,
		},
	},
});
IMPORTANT

offset 仅适用于顶层查询。

await db.query.posts.findMany({
	limit: 5,
	offset: 2, // 正确 ✅
	with: {
		comments: {
			offset: 3, // 不正确 ❌
			limit: 3,
		},
	},
});

查找帖子并获取第 5 到第 10 个帖子的评论:

await db.query.posts.findMany({
	limit: 5,
    offset: 5,
	with: {
		comments: true,
	},
});

排序

Drizzle 为关系查询构建器提供了排序 API。

您可以使用相同的排序 核心 API 或在没有导入的情况下使用回调中的 order by 运算符。

import { desc, asc } from 'drizzle-orm';

await db.query.posts.findMany({
	orderBy: [asc(posts.id)],
});
await db.query.posts.findMany({
	orderBy: (posts, { asc }) => [asc(posts.id)],
});

asc + desc 排序:

await db.query.posts.findMany({
	orderBy: (posts, { asc }) => [asc(posts.id)],
	with: {
		comments: {
			orderBy: (comments, { desc }) => [desc(comments.id)],
		},
	},
});

包括自定义字段

关系查询 API 允许您添加自定义附加字段。
这在您需要检索数据并对其应用附加函数时非常有用。

IMPORTANT

目前在 extras 中不支持聚合,请使用 核心查询 来处理。

import { sql } from 'drizzle-orm';

await db.query.users.findMany({
	extras: {
		loweredName: sql`lower(${users.name})`.as('lowered_name'),
	},
})
await db.query.users.findMany({
	extras: {
		loweredName: (users, { sql }) => sql`lower(${users.name})`.as('lowered_name'),
	},
})

lowerName 作为键将包括在返回对象的所有字段中。

IMPORTANT

您必须显式指定 .as("<name_for_column>")

要检索所有用户及其组,但包括 fullName 字段(它是 firstName 和 lastName 的连接), 您可以使用以下查询与 Drizzle 关系查询构建器。

const res = await db.query.users.findMany({
	extras: {
		fullName: sql<string>`concat(${users.name}, " ", ${users.name})`.as('full_name'),
	},
	with: {
		usersToGroups: {
			with: {
				group: true,
			},
		},
	},
});
// 结果类型
const res: {
	id: number;
	name: string;
	verified: boolean;
	invitedBy: number | null;
	fullName: string;
	usersToGroups: {
			group: {
					id: number;
					name: string;
					description: string | null;
			};
	}[];
}[];

要检索所有带评论的帖子并添加一个额外字段以计算帖子内容和每条评论内容的大小:

const res = await db.query.posts.findMany({
	extras: (table, { sql }) => ({
		contentLength: (sql<number>`length(${table.content})`).as('content_length'),
	}),
	with: {
		comments: {
			extras: {
				commentSize: sql<number>`length(${comments.content})`.as('comment_size'),
			},
		},
	},
});
// 结果类型
const res: {
	id: number;
	createdAt: Date;
	content: string;
	authorId: number | null;
	contentLength: number;
	comments: {
			id: number;
			createdAt: Date;
			content: string;
			creator: number | null;
			postId: number | null;
			commentSize: number;
	}[];
};

预编译语句

预编译语句旨在大幅提高查询性能 — 请查看此处.

在本节中,您可以学习如何定义占位符以及如何使用 Drizzle 关系查询构建器执行预编译语句。

where 中的占位符
PostgreSQL
MySQL
SQLite
const prepared = db.query.users.findMany({
	where: ((users, { eq }) => eq(users.id, placeholder('id'))),
	with: {
		posts: {
			where: ((users, { eq }) => eq(users.id, 1)),
		},
	},
}).prepare('query_name');

const usersWithPosts = await prepared.execute({ id: 1 });
limit 中的占位符
PostgreSQL
MySQL
SQLite
const prepared = db.query.users.findMany({
	with: {
		posts: {
			limit: placeholder('limit'),
		},
	},
}).prepare('query_name');

const usersWithPosts = await prepared.execute({ limit: 1 });
offset 中的占位符
PostgreSQL
MySQL
SQLite
const prepared = db.query.users.findMany({
	offset: placeholder('offset'),
	with: {
		posts: true,
	},
}).prepare('query_name');

const usersWithPosts = await prepared.execute({ offset: 1 });
多个占位符
PostgreSQL
MySQL
SQLite
const prepared = db.query.users.findMany({
	limit: placeholder('uLimit'),
	offset: placeholder('uOffset'),
	where: ((users, { eq, or }) => or(eq(users.id, placeholder('id')), eq(users.id, 3))),
	with: {
		posts: {
			where: ((users, { eq }) => eq(users.id, placeholder('pid'))),
			limit: placeholder('pLimit'),
		},
	},
}).prepare('query_name');

const usersWithPosts = await prepared.execute({ pLimit: 1, uLimit: 3, uOffset: 1, id: 2, pid: 6 });