Drizzle 查询

Drizzle ORM 的设计目标是在 SQL 之上提供一个轻量的类型层。 我们真心相信,我们已经设计出了从 TypeScript 操作 SQL 数据库的最佳方式,现在是时候让它变得更好了。

关系型查询旨在为你提供卓越的开发体验,用于查询 SQL 数据库中的嵌套关系数据,避免多次连接和复杂的数据映射。

它是对现有模式定义和查询构建器的扩展。 你可以根据需要选择是否启用它。 我们确保你既能获得一流的开发体验,也能获得出色的性能。

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

const db = drizzle({ schema });

const result = await db._query.users.findMany({
	with: {
		posts: true			
	},
});
[{
	id: 10,
	name: "Dan",
	posts: [
		{
			id: 1,
			content: "SQL is awesome",
			authorId: 10,
		},
		{
			id: 2,
			content: "But check relational queries",
			authorId: 10,
		}
	]
}]

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

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

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

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

查询

关系查询是对 Drizzle 原始 查询构建器 的扩展。
你需要在 drizzle() 初始化时从你的 schema 文件/文件中提供所有 tablesrelations,然后直接使用 db._query API。

drizzle 的导入路径取决于你正在使用的 数据库驱动

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

const db = drizzle({ schema });

await db._query.users.findMany(...);
// 如果你将 schema 放在多个文件中
import * as schema1 from './schema1';
import * as schema2 from './schema2';
import { drizzle } from 'drizzle-orm/node-mssql';

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: bit;
	invitedBy: number | null;
}[];

查找第一个

.findFirst() 将会为查询添加 limit 1

const user = await db._query.users.findFirst();
// 结果类型
const result: {
	id: number;
	name: string;
	verified: bit;
	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 //ignored
	},
});
// result type
const users: {
	name: string;
};

仅包含嵌套关系中的列:

const res = await db._query.users.findMany({
	columns: {},
	with: {
		posts: true
	}
});
// result type
const res: {
	posts: {
		id: number,
		text: string
	}
}[];

嵌套部分字段选择

就像使用 partial select 一样,你可以包含或排除嵌套关联的列:

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

选择过滤器

就像在我们的类 SQL 查询构建器中一样,
关系查询 API 允许你使用我们的 operators 列表来定义过滤器和条件。

你可以从 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,
		},
	},
});

查找带有评论的帖子,从第 6 篇到第 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 不支持聚合,请使用 core queries

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: (table, { sql }) => ({
    loweredName: sql`lower(${table.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: {
			columns: {},
			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 中的占位符
const prepared = db._query.users.findMany({
	where: ((users, { eq }) => eq(users.id, sql.placeholder('id'))),
	with: {
		posts: {
			where: ((posts, { eq }) => eq(posts.id, sql.placeholder('pid'))),
		},
	},
}).prepare();

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

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

const usersWithPosts = await prepared.execute({ offset: 1 });
多个占位符
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();

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