Drizzle 查询

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

关系查询旨在为你提供出色的开发者体验,用于从 SQL 数据库中查询嵌套的关系数据,避免多次 join 和复杂的数据映射。

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

重要

在关系查询中,对表列的引用必须通过回调参数进行,而不是通过导入的表对象。 这适用于所有接受回调的子句——orderBywhere.RAWextrasextras 中的子查询。 回调会暴露当前查询作用域中带别名的表,这对于在嵌套或自引用查询中正确生成 SQL 是必需的。

示例

await db.query.posts.findMany({
  orderBy: sql`${posts.id} asc`, // <- ❌ 直接使用表
  with: {
    comments: {
      orderBy: sql`${comments.id} desc`, // <- ❌ 直接使用表
    },
  },
});
await db.query.users.findMany({
  where: {
    RAW: sql`LOWER(${users.name}) LIKE 'john%'`, // <- ❌ 直接使用表
  },
});
await db.query.users.findMany({
  extras: {
    loweredName: sql<string>`lower(${users.name})`, // <- ❌ 直接使用表
    totalPostsCount: db.$count(posts, eq(posts.authorId, users.id)),  // <- ❌ 直接使用表
  },
});

示例

await db.query.posts.findMany({
  orderBy: (t) => sql`${t.id} asc`, // <- ✅ 使用回调
  with: {
    comments: {
      orderBy: (t, { desc }) => desc(t.id), // <- ✅ 使用回调
    },
  },
});
await db.query.users.findMany({
  where: {
    RAW: (t) => sql`LOWER(${t.name}) LIKE 'john%'`, // <- ✅ 使用回调
  },
});
await db.query.users.findMany({
  extras: {
    loweredName: (t, { sql }) => sql<string>`lower(${t.name})`, // <- ✅ 使用回调
    totalPostsCount: (t) => db.$count(posts, eq(posts.authorId, t.id)), // <- ✅ 使用回调
  },
});

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

index.ts
schema.ts
relations.ts
import { relations } from "./relations";
import { drizzle } from "drizzle-orm/...";

const db = drizzle(process.env.DATABASE_URL, { relations });

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,
		}
	]
}]

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

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

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

const db = drizzle(process.env.DATABASE_URL,{ relations });

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 的所有 posts:

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

获取所有不包含 content 的 posts:

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
	}
}[];

嵌套部分字段选择

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

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

选择过滤器

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

你可以从 drizzle-orm 中导入它们,也可以使用回调语法:

const users = await db.query.users.findMany({
	where: {
		id: 1
	}
});
select
  "d0"."id" as "id",
  "d0"."name" as "name"
from
  "users" as "d0"
where
  "d0"."id" = 1

查找 id=1 的帖子,以及在特定日期之前创建的评论:

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

所有过滤器操作符列表

where: {
    OR: [],
    AND: [],
    NOT: {},
    RAW: (table) => sql`${table.id} = 1`,

    // 按关系过滤
    [relation]: {},

	  // 按列过滤
    [column]: {
      OR: [],
      AND: [],
      NOT: {},
      eq: 1,
      ne: 1,
      gt: 1,
      gte: 1,
      lt: 1,
      lte: 1,
      in: [1],
      notIn: [1],
      like: "",
      notLike: "",
      isNull: true,
      isNotNull: true,
    },
},

示例

简单 eq
使用 AND
使用 OR
使用 NOT
使用 RAW 的复杂示例
const response = await db.query.users.findMany({
  where: {
    age: 15,
  },
});
select
  "d0"."id" as "id",
  "d0"."name" as "name",
  "d0"."age" as "age"
from
  "users" as "d0"
where
  "d0"."age" = 15

关系筛选

使用 Drizzle Relations,你不仅可以按正在查询的表进行筛选,还可以按查询中包含的任何表进行筛选。

示例: 获取所有 ID>10 且至少有一篇内容以 “M” 开头的帖子用户 users

const usersWithPosts = await db.query.usersTable.findMany({
  where: {
    id: {
      gt: 10
    },
    posts: {
      content: {
        like: 'M%'
      }
    }
  },
});

示例: 获取所有带有帖子(posts)的 users,仅当用户至少有 1 篇帖子时

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

限制与偏移

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

查找 5 篇帖子:

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

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

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

offset 现在也可以在 with 表中使用了!

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

查找带有评论的帖子,评论范围从第 6 篇帖子到第 10 篇帖子:

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

排序依据

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

你可以使用相同的排序 核心 API,或者在回调中使用 order by 操作符,无需导入。

重要

当你在同一个表上使用多个 orderBy 语句时,它们会按照你添加它们的相同顺序包含在查询中

await db.query.posts.findMany({
  orderBy: {
    id: "asc",
  },
});

asc + desc 排序:

  await db.query.posts.findMany({
    orderBy: { id: "asc" },
    with: {
      comments: {
        orderBy: { id: "desc" },
      },
    },
  });

你也可以在 order by 语句中使用自定义 sql

await db.query.posts.findMany({
  orderBy: (t) => sql`${t.id} asc`,
  with: {
    comments: {
      orderBy: (t, { desc }) => desc(t.id),
    },
  },
});

包含自定义字段

关系型查询 API 允许你添加额外的自定义字段。
当你需要检索数据并对其应用额外函数时,这非常有用。

IMPORTANT

目前 extras 中不支持聚合,请改用 core queries

import { sql } from 'drizzle-orm';

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

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

IMPORTANT

如果你为任何 extras 字段指定了 .as("<alias>") - drizzle 将忽略它

要获取所有带分组的用户,并同时包含 fullName 字段(它由 firstName 和 lastName 拼接而成), 你可以使用 Drizzle 关系型查询构建器执行以下查询。

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

要获取所有带评论的帖子,并添加额外字段来计算帖子内容的大小以及每条评论内容的大小:

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

包含子查询

你也可以在关系查询中使用子查询,以利用自定义 SQL 语法的强大能力

获取有帖子且每个用户的帖子总数

import { posts } from './schema';
import { eq } from 'drizzle-orm';

await db.query.users.findMany({
  with: {
    posts: true
  },
  extras: {
    totalPostsCount: (table) => db.$count(posts, eq(posts.authorId, table.id)),
  }
});
SELECT
	"d0"."id" AS "id",
	"d0"."name" AS "name",
	"d0"."age" AS "age",
	"d0"."invited_by" AS "invitedBy",
	(
		(
			SELECT
				count(*)
			FROM
				"posts"
			WHERE
				"posts"."author_id" = "d0"."id"
		)
	) AS "totalPostsCount",
	coalesce(
		(
			SELECT
				json_group_array(json_object('id', "id", 'content', "content", 'authorId', "authorId", 'createdAt', "createdAt")) AS "r"
			FROM
				(
					SELECT
						"d1"."id" AS "id",
						"d1"."content" AS "content",
						"d1"."author_id" AS "authorId",
						"d1"."created_at" AS "createdAt"
					FROM
						"posts" AS "d1"
					WHERE
						"d0"."id" = "d1"."author_id"
				) AS "t"
		),
		jsonb_array ()
	) AS "posts"
FROM
	"users" AS "d0"

预备语句

预备语句旨在大幅提升查询性能 — 请看这里。

在本节中,你将学习如何定义占位符,并使用 Drizzle 关系查询构建器执行预备语句。

where 中的占位符
const prepared = db.query.users.findMany({
    where: { id: { eq: sql.placeholder("id") } },
    with: {
      posts: {
        where: { id: 1 },
      },
    },
}).prepare();

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

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

const usersWithPosts = await prepared.execute({ offset: 1 });
多个占位符
const prepared = db.query.users.findMany({
    limit: sql.placeholder("uLimit"),
    offset: sql.placeholder("uOffset"),
    where: {
      OR: [{ id: { eq: sql.placeholder("id") } }, { id: 3 }],
    },
    with: {
      posts: {
        where: { id: { eq: sql.placeholder("pid") } },
        limit: sql.placeholder("pLimit"),
      },
    },
}).prepare();

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