MongoDB の lookup 演算子を使用して複数の条件に参加する
今日は、MongoDB の $lookup
演算子を使用して複数の条件を結合する方法を説明します。さらに、$group
ステージと $unionWidth
集約ステージの使用法を示すいくつかの例についても説明します。
MongoDB の $lookup
演算子を使用して複数の条件に参加する
MongoDB 3.6 以降を使用している場合は、$lookup
集計 pipeline
演算子を使用して複数の条件を結合できます。
このために、users
と salaries
という名前の 2つのコレクションがあります。次のコマンドを使用して作成することもできます。
コレクションを作成するためのサンプルコード:
> db.createCollection('users')
> db.createCollection('salaries')
users
コレクションにドキュメントを挿入するためのサンプルコード:
> db.users.insertMany(
[
{
username: 'userone',
age: 30,
gender: 'Female',
city: 'Lahore',
country: 'Pakistan'
},
{
username: 'usertwo',
age: 35,
gender: 'Male',
city: 'Florida',
country: 'United States'
}
]
)
salaries
コレクションにドキュメントを挿入するためのサンプルコード:
> db.salaries.insertMany(
[
{
username: 'userone',
salary: 3000
},
{
username: 'usertwo',
salary: 5000
}
]
)
users
コレクションの表示データ:
> db.users.find().pretty()
出力:
{
"_id" : ObjectId("628deb40c1e812eeeb311439"),
"username" : "userone",
"age" : 30,
"gender" : "Female",
"city" : "Lahore",
"country" : "Pakistan"
}
{
"_id" : ObjectId("628deb40c1e812eeeb31143a"),
"username" : "usertwo",
"age" : 35,
"gender" : "Male",
"city" : "Florida",
"country" : "United States"
}
salaries
コレクションの表示データ:
> db.salaries.find().pretty()
出力:
{
"_id" : ObjectId("628deb07c1e812eeeb311437"),
"username" : "userone",
"salary" : 3000
}
{
"_id" : ObjectId("628deb07c1e812eeeb311438"),
"username" : "usertwo",
"salary" : 5000
}
コレクションを作成してドキュメントを挿入した後、さまざまなシナリオを調べて複数の条件を結合できます。 $lookup
から始めましょう。
$lookup
集約パイプライン
演算子を使用する
サンプルコード:
> db.users.aggregate([
{
$lookup: {
from: 'salaries',
let: {
user_name: '$username',
user_salary: 3000
},
pipeline: [{
$match: {
$expr: {
$and: [
{ $eq: ['$username', '$$user_name'] },
{ $gte: ['$salary','$$user_salary'] }
]
}
}
}],
as: 'usersalary'
}
}
]).pretty()
出力:
{
"_id" : ObjectId("628deb40c1e812eeeb311439"),
"username" : "userone",
"age" : 30,
"gender" : "Female",
"city" : "Lahore",
"country" : "Pakistan",
"usersalary" : [
{
"_id" : ObjectId("628deb07c1e812eeeb311437"),
"username" : "userone",
"salary" : 3000
}
]
}
{
"_id" : ObjectId("628deb40c1e812eeeb31143a"),
"username" : "usertwo",
"age" : 35,
"gender" : "Male",
"city" : "Florida",
"country" : "United States",
"usersalary" : [
{
"_id" : ObjectId("628deb07c1e812eeeb311438"),
"username" : "usertwo",
"salary" : 5000
}
]
}
ここでは、2つの条件を満たすドキュメントを取得します。
username
フィールドは、users
コレクションとsalaries
コレクションで同じです。salary
フィールドの値が3000
以上です。
両方の条件を満たすドキュメントのみを取得します。usersalary
が要素の配列として表示され、各要素が salaries
コレクションのドキュメントであることに気付いたかもしれません。
次の例に示すように、$unwind
、$addFields
、および $project
を使用して、両方のコレクション(users
と salaries
)から特定のフィールドを取得し、1つのドキュメントを形成できます。
サンプルコード:
> db.users.aggregate([
{
$lookup: {
from: 'salaries',
let: {
user_name: '$username',
user_salary: 3000
},
pipeline: [{
$match: {
$expr: {
$and: [
{ $eq: ['$username', '$$user_name'] },
{ $gte: ['$salary','$$user_salary'] }
]
}
}
}],
as: 'usersalary'
}
},
{
$unwind:'$usersalary'
},
{
$addFields: {
salary: '$usersalary.salary'
}
},
{
$project: {
username: 1,
salary: 1
}
}
]).pretty()
出力:
{
"_id" : ObjectId("628deb40c1e812eeeb311439"),
"username" : "userone",
"salary" : 3000
}
{
"_id" : ObjectId("628deb40c1e812eeeb31143a"),
"username" : "usertwo",
"salary" : 5000
}
$unwind
演算子を使用する目的は、同じ名前の要素ごとに、入力ドキュメントから出力 1 ドキュメントへの配列フィールドを分解することです。
配列に要素が 1つしかない場合、$unwind
ステージ演算子は、要素自体であるオブジェクトをフラット化します。 $addFields
は、オブジェクトまたは配列の salary
フィールドをドキュメントのルートレベルに結合します。
上記の例での使用法を理解する前に、$project
フィルターステージを使用する理由に焦点を当てましょう。 $project
を使用しない場合、ドキュメントのルートレベルで salary
フィールドと usersalary
オブジェクトを取得しますが、これは不要です。
ここで、$project
フィルターステージを使用し、出力に含めるフィールドを指定します。
プロジェクト要件が $unwind
、$addFields
、$project
の使用を制限している場合は、以下に示す代替ソリューションを使用できます。
サンプルコード:
> db.users.aggregate([
{
$lookup: {
from: 'salaries',
let: {
user_name: '$username',
user_salary: 3000
},
pipeline: [{
$match: {
$expr: {
$and: [
{ $eq: ['$username', '$$user_name'] },
{ $gte: ['$salary','$$user_salary'] }
]
}
}
}],
as: 'usersalary'
}
},
{
$replaceRoot: {
newRoot: {
$mergeObjects:[
{
$arrayElemAt: [
"$usersalary", 0
]
},
{
salary: "$$ROOT.salary"
}
]
}
}
}
]
).pretty()
出力:
{
"_id" : ObjectId("628deb07c1e812eeeb311437"),
"username" : "userone",
"salary" : 3000
}
{
"_id" : ObjectId("628deb07c1e812eeeb311438"),
"username" : "usertwo",
"salary" : 5000
}
let
フィールド(オプション)を使用して、フィールドの値を変数に割り当てます。pipeline
ステージでこれらの変数にアクセスします。ここで、pipeline
を指定して、さまざまなコレクションで実行します。
また、$match
ステージを使用して、フィールドの値を比較する $expr
という名前の評価クエリ演算子を利用していることに注意してください。
さらに、$replaceRoot
は、pipeline
の最後の集約 pipeline
ステージであり、$mergeObjects
演算子を使用して、$lookup
出力を $$ROOT
ドキュメントの部分とマージします。。
条件を結合するために $and
演算子のみを使用しました。 $または
または両方の演算子を使用することもできます。
新しいコレクションを作成し、$group
集約ステージを使用して複数の条件に参加する
サンプルコード:
> db.users_salaries.insertMany(
db.users.find({}, {"_id": 0})
.toArray()
.concat(db.salaries.find({}, {"_id": 0}).toArray())
)
db.users_salaries.aggregate([
{ "$group": {
"_id": { "username": "$username" },
"salary": { "$push": "$salary" }
}}
])
出力:
{ "_id" : { "username" : "userone" }, "salary" : [ 3000 ] }
{ "_id" : { "username" : "usertwo" }, "salary" : [ 5000 ] }
このコード例では、users_salaries
という名前の新しいコレクションを作成し、users
と salaries
という名前の 2つのコレクションをマージしてから、それらのドキュメントを新しく作成したコレクションに挿入します。次に、ユーザー名
でグループ化して、目的の出力を取得します。
新しいコレクションを作成せずに、(上記のように)同じ出力を取得することもできます。そのために、2つのコレクションの結合を実行する $unionWith
集約ステージを使用します。
サンプルコード:
> db.users.aggregate([
{ $set: { username: "$username" } },
{ $unionWith: {
coll: "salaries",
pipeline: [{ $set: { salary: "$salary" } }]
}},
{ $group: {
_id: { username: "$username"},
"salary": { "$push": "$salary" }
}}
])
出力:
{ "_id" : { "username" : "userone" }, "salary" : [ 3000 ] }
{ "_id" : { "username" : "usertwo" }, "salary" : [ 5000 ] }