联表查询
在 MongoDB 中,联表查询不像关系型数据库(如 MySQL)那样通过 JOIN
语句实现,而是通过 聚合操作(Aggregation)来模拟类似的功能。MongoDB 是一个非关系型数据库,主要通过 嵌套文档 和 引用文档 来处理复杂的数据结构。
1. 嵌套文档与引用文档的概念
- 嵌套文档:MongoDB 支持在一个文档内嵌套其他文档,这样可以将相关的信息存储在一个文档中,避免了跨表查询。例如,在一个订单文档中,可以直接嵌套客户信息。
- 引用文档:在 MongoDB 中,有时需要将文档通过引用(
ObjectId
)的方式关联,而不是直接嵌套。这样一个文档存储另一个文档的引用,类似于关系型数据库的外键。
2. 使用聚合实现联表查询:
尽管 MongoDB 没有关系型数据库那种直接的 JOIN
操作,但可以通过聚合操作中的 $lookup
来模拟 左外连接,将不同集合的数据合并到一起。
示例 1:嵌套文档查询
假设有一个 orders
集合,每个订单文档中包含了客户的信息,如下:
1{
2 "_id": 1,
3 "orderId": "A123",
4 "product": "Laptop",
5 "customer": {
6 "customerId": "C001",
7 "name": "John Doe",
8 "email": "john@example.com"
9 }
10}
在这个例子中,客户信息直接嵌套在订单文档中。可以在查询订单时,直接访问嵌套的客户信息。
MongoDB 查询代码:
1async function getOrderWithCustomer() {
2 const client = await MongoClient.connect("mongodb://localhost:27017", { useNewUrlParser: true, useUnifiedTopology: true });
3 const db = client.db("storeDB");
4 const orders = db.collection("orders");
5
6 const result = await orders.find({}).toArray();
7 console.log(result);
8 await client.close();
9}
10
11getOrderWithCustomer();
说明:
在这个查询中,直接从订单集合中获取数据,其中包括嵌套的客户信息。由于数据已经嵌套,因此无需进行额外的联表查询。
示例 2:引用文档查询
假设有两个集合:orders
和 customers
。orders
集合存储订单信息,customers
集合存储客户信息。订单文档中只有客户的 customerId
引用。
orders
集合:
1{
2 "_id": 1,
3 "orderId": "A123",
4 "product": "Laptop",
5 "customerId": "C001"
6}
customers
集合:
1{
2 "_id": "C001",
3 "name": "John Doe",
4 "email": "john@example.com"
5}
在这个例子中,orders
集合中的 customerId
引用了 customers
集合中的文档。为了获得订单的完整信息,需要通过 $lookup
聚合操作将两个集合连接起来,类似关系型数据库中的 JOIN
。
MongoDB 聚合代码:
1async function getOrderWithCustomerDetails() {
2 const client = await MongoClient.connect("mongodb://localhost:27017", { useNewUrlParser: true, useUnifiedTopology: true });
3 const db = client.db("storeDB");
4 const orders = db.collection("orders");
5
6 const result = await orders.aggregate([
7 {
8 $lookup: {
9 from: "customers", // 引用的集合名称
10 localField: "customerId", // 当前集合的字段(引用字段)
11 foreignField: "_id", // 目标集合的字段(被引用的字段)
12 as: "customerDetails" // 将连接结果存放到一个新的字段中
13 }
14 }
15 ]).toArray();
16
17 console.log(result);
18 await client.close();
19}
20
21getOrderWithCustomerDetails();
说明:
$lookup
:这个操作符用于将两个集合的数据连接起来。它的功能类似 SQL 中的JOIN
,用于从目标集合(customers
)中获取与源集合(orders
)相关联的文档。from
:目标集合的名称,这里是customers
。localField
:源集合中要匹配的字段,这里是customerId
。foreignField
:目标集合中用于匹配的字段,这里是customers
集合的_id
字段。as
:指定新字段的名称,结果将存储在customerDetails
字段中。
执行上述代码后,你会看到订单信息与客户详细信息合并后的结果。例如:
1[
2 {
3 "_id": 1,
4 "orderId": "A123",
5 "product": "Laptop",
6 "customerId": "C001",
7 "customerDetails": [
8 {
9 "_id": "C001",
10 "name": "John Doe",
11 "email": "john@example.com"
12 }
13 ]
14 }
15]
示例 3:多级联表查询
如果有更多的集合需要连接(例如,orders
-> customers
-> products
),也可以通过多个 $lookup
来实现多级联表查询。
假设在 orders
集合中也存储了产品的 productId
,并且有一个 products
集合记录了产品的详细信息。
MongoDB 多级联表代码示例:
1async function getOrderWithCustomerAndProduct() {
2 const client = await MongoClient.connect("mongodb://localhost:27017", { useNewUrlParser: true, useUnifiedTopology: true });
3 const db = client.db("storeDB");
4 const orders = db.collection("orders");
5
6 const result = await orders.aggregate([
7 {
8 $lookup: {
9 from: "customers", // 第一个连接:从 customers 集合获取客户信息
10 localField: "customerId",
11 foreignField: "_id",
12 as: "customerDetails"
13 }
14 },
15 {
16 $lookup: {
17 from: "products", // 第二个连接:从 products 集合获取产品信息
18 localField: "productId",
19 foreignField: "_id",
20 as: "productDetails"
21 }
22 }
23 ]).toArray();
24
25 console.log(result);
26 await client.close();
27}
28
29getOrderWithCustomerAndProduct();
说明:
- 在上述代码中,使用了两个
$lookup
操作符,首先将orders
集合与customers
集合连接,接着将orders
集合与products
集合连接,形成多级联表查询。
总结:
- 嵌套文档:适合存储关联较紧密的数据,查询时不需要联表,可以直接嵌套在一个文档内。
- 引用文档:适合存储关联较松散的数据,通过
ObjectId
引用其他集合中的数据。使用$lookup
聚合操作可以模拟类似 SQL 中的JOIN
操作来联接不同集合的数据。 - 聚合管道中的
$lookup
:允许在不同集合之间进行联表查询,支持左外连接的功能。
通过理解 嵌套文档 和 引用文档,以及如何使用 聚合管道 实现联表查询,可以更好地应对 MongoDB 中复杂数据结构的处理。