连接 [SQL]
SQL 中的 Join 子句用于基于表之间的相关列将 2 个或更多表组合在一起。 Drizzle ORM 的 joins 语法在类似 SQL 和类型安全之间取得了平衡。
联接类型
Drizzle ORM 提供了 INNER JOIN、LEFT JOIN、RIGHT JOIN 和 CROSS JOIN 的 API。
让我们快速看一下基于以下表结构的示例:
export const users = singlestoreTable('users', {
id: int('id').primaryKey().autoincrement(),
name: varchar('name', { length: 255 }).notNull(),
});
export const pets = singlestoreTable('pets', {
id: int('id').primaryKey().autoincrement(),
name: varchar('name', { length: 255 }).notNull(),
ownerId: int('owner_id').notNull().references(() => users.id),
})左联接
const result = await db.select().from(users).leftJoin(pets, eq(users.id, pets.ownerId))select ... from `users` left join `pets` on `users`.`id` = `pets`.`owner_id`// 结果类型
const result: {
user: {
id: number;
name: string;
};
pets: {
id: number;
name: string;
ownerId: number;
} | null;
}[];右联接
const result = await db.select().from(users).rightJoin(pets, eq(users.id, pets.ownerId))select ... from `users` right join `pets` on `users`.`id` = `pets`.`owner_id`// 结果类型
const result: {
user: {
id: number;
name: string;
} | null;
pets: {
id: number;
name: string;
ownerId: number;
};
}[];内联接
const result = await db.select().from(users).innerJoin(pets, eq(users.id, pets.ownerId))select ... from `users` inner join `pets` on `users`.`id` = `pets`.`owner_id`// 结果类型
const result: {
user: {
id: number;
name: string;
};
pets: {
id: number;
name: string;
ownerId: number;
};
}[];交叉联接
const result = await db.select().from(users).crossJoin(pets)select ... from `users` cross join `pets`// 结果类型
const result: {
user: {
id: number;
name: string;
};
pets: {
id: number;
name: string;
ownerId: number;
};
}[];部分选择
如果你需要选择特定的一部分字段,或者希望得到一个扁平的响应类型,Drizzle ORM
支持在联接中使用部分选择,并且会根据 .select({ ... }) 结构自动推断返回类型。
await db.select({
userId: users.id,
petId: pets.id,
}).from(user).leftJoin(pets, eq(users.id, pets.ownerId))select `users`.`id`, `pets`.`id` from `users` left join `pets` on `users`.`id` = `pets`.`owner_id`// 结果类型
const result: {
userId: number;
petId: number | null;
}[];你可能已经注意到,petId 现在可以为 null,这是因为我们使用了左联接,并且可能存在没有宠物的用户。
在使用 sql 操作符进行部分选择字段和聚合时,务必要记住这一点,
为了正确推断结果类型,你应该使用 sql<type | null>,这一点要靠你自己处理!
const result = await db.select({
userId: users.id,
petId: pets.id,
petName1: sql`upper(${pets.name})`,
petName2: sql<string | null>`upper(${pets.name})`,
//˄我们应该在类型中显式告诉 'string | null',因为我们正在对该字段进行左联接
}).from(user).leftJoin(pets, eq(users.id, pets.ownerId))select `users`.`id`, `pets`.`id`, upper(`pets`.`name`)... from `users` left join `pets` on `users`.`id` = `pets`.`owner_id`// 结果类型
const result: {
userId: number;
petId: number | null;
petName1: unknown;
petName2: string | null;
}[];为了避免在联接包含大量列的表时出现大量可空字段,我们可以利用嵌套选择对象语法, 我们的智能类型推断会让整个对象变为可空,而不是让表中的所有字段都变为可空!
await db.select({
userId: users.id,
userName: users.name,
pet: {
id: pets.id,
name: pets.name,
upperName: sql<string>`upper(${pets.name})`
}
}).from(user).leftJoin(pets, eq(users.id, pets.ownerId))select ... from `users` left join `pets` on `users`.`id` = `pets`.`owner_id`// 结果类型
const result: {
userId: number;
userName: string;
pet: {
id: number;
name: string;
upperName: string;
} | null;
}[];别名与自连接
Drizzle ORM 支持表别名,这在你需要进行自连接时非常有用。
假设你需要获取带有父级的用户:
index.ts
schema.ts
import { user } from "./schema";
const parent = alias(user, "parent");
const result = db
.select()
.from(user)
.leftJoin(parent, eq(parent.id, user.parentId));select ... from `user` left join `user` `parent` on `parent`.`id` = `user`.`parent_id`// 结果类型
const result: {
user: {
id: number;
name: string;
parentId: number;
};
parent: {
id: number;
name: string;
parentId: number;
} | null;
}[];聚合结果
Drizzle ORM 会从驱动程序中返回按名称映射的结果,而不会改变其结构。
你可以自由地以你想要的方式处理结果,下面是一个将多对一关系数据进行映射的示例:
type User = typeof users.$inferSelect;
type Pet = typeof pets.$inferSelect;
const rows = db.select({
user: users,
pet: pets,
}).from(users).leftJoin(pets, eq(users.id, pets.ownerId)).all();
const result = rows.reduce<Record<number, { user: User; pets: Pet[] }>>(
(acc, row) => {
const user = row.user;
const pet = row.pet;
if (!acc[user.id]) {
acc[user.id] = { user, pets: [] };
}
if (pet) {
acc[user.id].pets.push(pet);
}
return acc;
},
{}
);
// result type
const result: Record<number, {
user: User;
pets: Pet[];
}>;多对一示例
import { singlestoreTable, text, integer } from 'drizzle-orm/singlestore-core';
import { drizzle } from 'drizzle-orm/singlestore';
const cities = singlestoreTable('cities', {
id: int('id').primaryKey().autoincrement(),
name: varchar('name', { length: 255 }),
});
const users = singlestoreTable('users', {
id: int('id').primaryKey().autoincrement(),
name: varchar('name', { length: 255 }),
cityId: int('city_id').references(() => cities.id)
});
const db = drizzle();
const result = db.select().from(cities).leftJoin(users, eq(cities.id, users.cityId)).all();多对多示例
const users = singlestoreTable('users', {
id: int('id').primaryKey().autoincrement(),
name: varchar('name', { length: 255 }),
});
const chatGroups = singlestoreTable('chat_groups', {
id: int('id').primaryKey().autoincrement(),
name: varchar('name', { length: 255 }),
});
const usersToChatGroups = singlestoreTable('usersToChatGroups', {
userId: int('user_id').notNull().references(() => users.id),
groupId: int('group_id').notNull().references(() => chatGroups.id),
});
// 查询 id 为 1 的用户组以及所有参与者(用户)
db.select()
.from(usersToChatGroups)
.leftJoin(users, eq(usersToChatGroups.userId, users.id))
.leftJoin(chatGroups, eq(usersToChatGroups.groupId, chatGroups.id))
.where(eq(chatGroups.id, 1))
.all();