连接 [SQL]
SQL 中的 JOIN 子句用于根据表之间的相关列来合并两个或更多表。Drizzle ORM 的 JOIN 语法在 SQL 相似性和类型安全性之间取得了平衡。
连接类型
Drizzle ORM 提供了用于 INNER JOIN [LATERAL]
、FULL JOIN
、LEFT JOIN [LATERAL]
、RIGHT JOIN
、CROSS JOIN [LATERAL]
的 API。下面我们根据以下表格 Schema 快速查看示例
export const users = pgTable('users', {
id: serial('id').primaryKey(),
name: text('name').notNull(),
});
export const pets = pgTable('pets', {
id: serial('id').primaryKey(),
name: text('name').notNull(),
ownerId: integer('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"
// result type
const result: {
user: {
id: number;
name: string;
};
pets: {
id: number;
name: string;
ownerId: number;
} | null;
}[];
左连接 Lateral
const subquery = db.select().from(pets).where(gte(users.age, 16)).as('userPets')
const result = await db.select().from(users).leftJoinLateral(subquery, sql`true`)
select ... from "users" left join lateral (select ... from "pets" where "users"."age" >= 16) "userPets" on true
// result type
const result: {
user: {
id: number;
name: string;
};
userPets: {
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"
// result type
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"
// result type
const result: {
user: {
id: number;
name: string;
};
pets: {
id: number;
name: string;
ownerId: number;
};
}[];
内连接 Lateral
const subquery = db.select().from(pets).where(gte(users.age, 16)).as('userPets')
const result = await db.select().from(users).innerJoinLateral(subquery, sql`true`)
select ... from "users" inner join lateral (select ... from "pets" where "users"."age" >= 16) "userPets" on true
// result type
const result: {
user: {
id: number;
name: string;
};
userPets: {
id: number;
name: string;
ownerId: number;
};
}[];
全连接
const result = await db.select().from(users).fullJoin(pets, eq(users.id, pets.ownerId))
select ... from "users" full join "pets" on "users"."id" = "pets"."owner_id"
// result type
const result: {
user: {
id: number;
name: string;
} | null;
pets: {
id: number;
name: string;
ownerId: number;
} | null;
}[];
交叉连接
const result = await db.select().from(users).crossJoin(pets)
select ... from "users" cross join "pets"
// result type
const result: {
user: {
id: number;
name: string;
};
pets: {
id: number;
name: string;
ownerId: number;
};
}[];
交叉连接 Lateral
const subquery = db.select().from(pets).where(gte(users.age, 16)).as('userPets')
const result = await db.select().from(users).crossJoinLateral(subquery)
select ... from "users" cross join lateral (select ... from "pets" where "users"."age" >= 16) "userPets"
// result type
const result: {
user: {
id: number;
name: string;
};
userPets: {
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"
// result type
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})`,
//˄we should explicitly tell 'string | null' in type, since we're left joining that field
}).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"
// result type
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).fullJoin(pets, eq(users.id, pets.ownerId))
select ... from "users" full join "pets" on "users"."id" = "pets"."owner_id"
// result type
const result: {
userId: number | null;
userName: string | null;
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"
// result type
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 { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
import { drizzle } from 'drizzle-orm/better-sqlite3';
const cities = sqliteTable('cities', {
id: integer('id').primaryKey(),
name: text('name'),
});
const users = sqliteTable('users', {
id: integer('id').primaryKey(),
name: text('name'),
cityId: integer('city_id').references(() => cities.id)
});
const db = drizzle();
const result = db.select().from(cities).leftJoin(users, eq(cities.id, users.cityId)).all();
多对多示例
const users = sqliteTable('users', {
id: integer('id').primaryKey(),
name: text('name'),
});
const chatGroups = sqliteTable('chat_groups', {
id: integer('id').primaryKey(),
name: text('name'),
});
const usersToChatGroups = sqliteTable('usersToChatGroups', {
userId: integer('user_id').notNull().references(() => users.id),
groupId: integer('group_id').notNull().references(() => chatGroups.id),
});
// querying user group with id 1 and all the participants(users)
db.select()
.from(usersToChatGroups)
.leftJoin(users, eq(usersToChatGroups.userId, users.id))
.leftJoin(chatGroups, eq(usersToChatGroups.groupId, chatGroups.id))
.where(eq(chatGroups.id, 1))
.all();