Drizzle 查询

WARNING

此页面讲解的是 drizzle 版本 1.0.0-beta.1 及更高版本中可用的概念。

npm
yarn
pnpm
bun
npm i drizzle-orm@beta
npm i drizzle-kit@beta -D

PostgreSQL
SQLite
MySQL
SingleStore

Drizzle ORM 设计为在 SQL 之上提供一个薄的类型层。 我们坚信自己设计了从 TypeScript 操作 SQL 数据库的最佳方式,现在是让它变得更好的时候了。

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

它是现有模式定义和查询构建器的一个扩展。 你可以根据需要选择使用它。 我们确保你同时拥有最佳的开发者体验和性能。

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

const db = drizzle({ relations });

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

关系查询是 Drizzle 原始 查询生成器 的一个扩展。 你需要在初始化 drizzle() 时提供全部的 tablesrelations,然后使用 db.query API 即可。

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

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

const db = drizzle({ 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 会使用 核心类型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 中导入它们,或者使用回调语法:

const users = await db.query.users.findMany({
	where: {
		id: 1
	}
});
select * from users where 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: "",
      ilike: "",
      notLike: "",
      notIlike: "",
      isNull: true,
      isNotNull: true,
      arrayOverlaps: [1, 2],
      arrayContained: [1, 2],
      arrayContains: [1, 2]
    },
},

示例

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

关系过滤器

使用 Drizzle 关系查询,你不仅可以按查询的表过滤,还可以按查询中包含的任何表过滤。

示例: 获取 ID 大于 10 并且至少有一条内容以 “M” 开头的帖子的 users

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

示例: 只获取至少有一条帖子的用户及帖子:

const response = 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 现在也可以用于关联表中!

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

查询带评论的帖子,从第 5 条到第 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",
  },
});

同时使用升序和降序排序:

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

你还可以在排序语句中使用自定义 sql

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

包含自定义字段

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

IMPORTANT

目前 extras 中不支持聚合,请使用 核心查询 实现聚合。

import { sql } from 'drizzle-orm';

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

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

IMPORTANT

如果你对 extras 中的字段指定了 .as("<别名>") ,Drizzle 会忽略该操作。

用关系查询构建器获取所有用户及其群组,并包含 fullName 字段(由 firstName 和 lastName 拼接):

const res = await db.query.users.findMany({
	extras: {
		fullName: (users, { sql }) => sql<string>`concat(${users.name}, " ", ${users.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: {
		contentLength: (table, { sql }) => sql<number>`length(${table.content})`,
	},
	with: {
		comments: {
			extras: {
				commentSize: (table, { sql }) => sql<number>`length(${table.content})`,
			},
		},
	},
});
// 结果类型
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;
	}[];
};

包含子查询

你也可以在关系查询中使用子查询,以利用自定义 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", "posts"."r" as "posts", 
((select count(*) from "posts" where "posts"."author_id" = "d0"."id")) as "totalPostsCount" 
from "users" as "d0" 
left join lateral(
  select coalesce(json_agg(row_to_json("t".*)), '[]') as "r" 
  from (select "d1"."id" as "id", "d1"."content" as "content", "d1"."author_id" as "authorId" from "posts" as "d1" where "d0"."id" = "d1"."author_id") as "t"
) as "posts" on true

预处理语句

预处理语句旨在大幅提升查询性能 — 详情见此处

本节介绍如何使用 Drizzle 关系查询构建器定义占位符并执行预处理语句。

where 中的占位符
PostgreSQL
MySQL
SQLite
const prepared = db.query.users.findMany({
    where: { id: { eq: sql.placeholder("id") } },
    with: {
      posts: {
        where: { 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: sql.placeholder("limit"),
      },
    },
  }).prepare("query_name");

const usersWithPosts = await prepared.execute({ limit: 1 });
offset 中的占位符
PostgreSQL
MySQL
SQLite
const prepared = db.query.users.findMany({
	offset: sql.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: 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("query_name");

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