介绍
在本实验中,你将学习如何使用 MongoDB 投影(projection)来控制查询结果中显示的字段。投影是一项强大的功能,它允许你选择要返回的特定字段,这可以减少网络传输的数据量并提高查询性能。你将练习包含和排除字段、投影嵌套文档中的字段以及使用聚合框架(aggregation framework)来重塑输出。完成本实验后,你将对如何在 MongoDB 查询中有效使用投影有扎实的理解。
在本实验中,你将学习如何使用 MongoDB 投影(projection)来控制查询结果中显示的字段。投影是一项强大的功能,它允许你选择要返回的特定字段,这可以减少网络传输的数据量并提高查询性能。你将练习包含和排除字段、投影嵌套文档中的字段以及使用聚合框架(aggregation framework)来重塑输出。完成本实验后,你将对如何在 MongoDB 查询中有效使用投影有扎实的理解。
在本实验的第一步中,你将通过仅选择查询结果中希望看到的特定字段来学习投影的基础知识。这被称为“包含性”投影。
首先,打开 MongoDB Shell。这个交互式命令行界面是你在此实验中运行所有数据库命令的地方。
mongosh
你现在已进入 MongoDB Shell。你的终端提示符会发生变化,表明你已连接。让我们切换到一个名为 projectlab_database 的新数据库并插入一些示例数据。如果数据库不存在,MongoDB 会在你首次存储数据时为你创建它。
use projectlab_database
接下来,将三个文档插入一个名为 users 的集合中。
db.users.insertMany([
{
name: "John Doe",
age: 30,
email: "john.doe@example.com",
city: "New York",
job: "Software Engineer"
},
{
name: "Jane Smith",
age: 28,
email: "jane.smith@example.com",
city: "San Francisco",
job: "Data Scientist"
},
{
name: "Mike Johnson",
age: 35,
email: "mike.johnson@example.com",
city: "Chicago",
job: "Product Manager"
}
]);
现在,让我们查询这个集合。要返回所有文档的 name 和 age 字段,你需要将一个投影文档作为 find() 方法的第二个参数。在投影文档中,你使用 1 指定要包含的字段。默认情况下,_id 字段始终会被返回。你可以通过将其值设置为 0 来显式排除它。
db.users.find({}, { name: 1, age: 1, _id: 0 });
你应该会看到以下输出,其中仅包含每个用户的 name 和 age 字段:
[
{ "name": "John Doe", "age": 30 },
{ "name": "Jane Smith", "age": 28 },
{ "name": "Mike Johnson", "age": 35 }
]
这种技术对于仅检索必要数据非常有用,尤其是在处理大型文档时更为重要。
除了选择要包含的字段外,你还可以指定要排除的字段。这被称为“排除性”投影,当你希望看到大部分字段但隐藏少数几个(例如敏感或大型字段)时非常有用。
你应该仍然处于 mongosh shell 中,并已选择 projectlab_database。
要排除字段,请在投影文档中将其值设置为 0。让我们运行一个查询来获取所有用户数据,但排除 email 和 city 字段。
db.users.find({}, { email: 0, city: 0 });
输出将包含除 email 和 city 之外的所有字段。请注意,_id 字段默认仍然包含在内。
[
{
_id: ObjectId("..."),
name: 'John Doe',
age: 30,
job: 'Software Engineer'
},
{
_id: ObjectId("..."),
name: 'Jane Smith',
age: 28,
job: 'Data Scientist'
},
{
_id: ObjectId("..."),
name: 'Mike Johnson',
age: 35,
job: 'Product Manager'
}
]
投影的一个关键规则是,你不能在同一个投影文档中混合包含(1)和排除(0)。此规则的唯一例外是 _id 字段。即使在包含其他字段时,你也可以显式排除 _id 字段(_id: 0),就像你在上一步中所做的那样。在排除性投影中也可以排除它。
例如,要排除 email、city 和 _id,你可以运行以下命令:
db.users.find({}, { email: 0, city: 0, _id: 0 });
此查询返回的文档仅包含 name、age 和 job 字段。
现实世界的数据通常以嵌套文档和数组的形式进行组织。MongoDB 的投影功能允许你精确地选择字段,即使它们深埋在文档结构中。
首先,让我们清空现有的 users 集合,并插入包含嵌套 contact 对象和 skills 数组的新文档。
db.users.deleteMany({});
现在,插入新数据。
db.users.insertMany([
{
name: "John Doe",
contact: {
email: "john.doe@example.com",
phone: {
mobile: "123-456-7890",
work: "987-654-3210"
}
},
skills: ["JavaScript", "MongoDB", "React"]
},
{
name: "Jane Smith",
contact: {
email: "jane.smith@example.com",
phone: {
mobile: "234-567-8901",
work: "876-543-2109"
}
},
skills: ["Python", "Data Science", "Machine Learning"]
}
]);
要投影嵌套文档中的字段,你需要使用“点表示法”(dot notation)。例如,要选择用户的 name 和他们的手机号码,你需要指定 "contact.phone.mobile": 1。让我们尝试投影 name、email 和 mobile 电话号码。
db.users.find(
{},
{
name: 1,
"contact.email": 1,
"contact.phone.mobile": 1,
_id: 0
}
);
结果将保留所选字段的嵌套结构:
[
{
"name": "John Doe",
"contact": {
"email": "john.doe@example.com",
"phone": { "mobile": "123-456-7890" }
}
},
{
"name": "Jane Smith",
"contact": {
"email": "jane.smith@example.com",
"phone": { "mobile": "234-567-8901" }
}
}
]
你还可以使用 $slice 操作符投影数组的一部分。要获取用户的 name 和前两个技能,请运行以下命令:
db.users.find({}, { name: 1, skills: { $slice: 2 }, _id: 0 });
输出将显示每个用户,其 skills 数组仅包含两个元素:
[
{ "name": "John Doe", "skills": ["JavaScript", "MongoDB"] },
{ "name": "Jane Smith", "skills": ["Python", "Data Science"] }
]
对于更高级的转换,例如重命名字段或创建新的计算字段,find() 方法的投影功能是不够的。你可以使用聚合框架,特别是 $project 阶段。
让我们为这一步准备数据。清空集合并插入带有薪资信息的用户。
db.users.deleteMany({});
db.users.insertMany([
{
name: "John Doe",
age: 30,
salary: 75000,
department: "Engineering"
},
{
name: "Jane Smith",
age: 28,
salary: 85000,
department: "Data Science"
},
{
name: "Mike Johnson",
age: 35,
salary: 95000,
department: "Management"
}
]);
聚合管道中的 $project 阶段可以通过添加新字段、重命名现有字段或删除字段来重塑文档。要使用它,你需要将阶段数组传递给 aggregate() 方法。
要重命名字段,你需要定义一个新字段名,并将其值赋为现有字段,该字段名前面加上 $ 符号。让我们将 name 重命名为 fullName,将 age 重命名为 yearsOld。
db.users.aggregate([
{
$project: {
fullName: "$name",
yearsOld: "$age",
_id: 0
}
}
]);
你还可以根据计算创建新字段。让我们通过将年薪除以 12 来创建一个 monthlySalary 字段,并使用 $switch 操作符基于条件逻辑创建一个 salaryTier 字段。
db.users.aggregate([
{
$project: {
name: 1,
monthlySalary: { $divide: ["$salary", 12] },
salaryTier: {
$switch: {
branches: [
{ case: { $lt: ["$salary", 80000] }, then: "Junior" },
{ case: { $gte: ["$salary", 80000] }, then: "Senior" }
],
default: "Unknown"
}
},
_id: 0
}
}
]);
输出将是你自己在 $project 阶段定义的全新文档结构:
[
{ "name": "John Doe", "monthlySalary": 6250, "salaryTier": "Junior" },
{
"name": "Jane Smith",
"monthlySalary": 7083.333333333333,
"salaryTier": "Senior"
},
{
"name": "Mike Johnson",
"monthlySalary": 7916.666666666667,
"salaryTier": "Senior"
}
]
$project 阶段让你能够完全控制查询结果的最终形状,从而在数据库内部实现强大的数据转换。
在本实验中,你学会了如何使用投影有效地控制 MongoDB 查询的输出。你从使用 find() 方法包含和排除字段的基础知识开始。然后,你进阶到更高级的主题,例如使用点表示法(dot notation)投影嵌套文档中的字段,以及使用 $slice 操作符返回数组的子集。最后,你探索了聚合框架的 $project 阶段的强大功能,通过重命名字段、创建新的计算字段和应用条件逻辑来完全重塑文档。这些技能对于编写高效且精确的 MongoDB 查询至关重要。