nodejs + express + mongoDB で複数のCollectionからまとめてデータを取り出す-lookup-

# はじめに 
下のデータのように、
`works`からデータを取り出し、`user_id`を基準して`users`のデータも一緒に取り出したい。
しかし、非同期処理によってデータを順番に取り出して結合するなど、
まとめてデータを取り出すとき、MongoDBはMySQLと比べて扱にくい感がある。
いろいろ調べたところ、mongoDBもMySQLの`left join`のような使い方があるようです。

# Data
```terminal:users
{
  "user_name": "king",
  "age": 20,
  "id": "admin",
  "pw": "1234",
  "role": "admin",
  "department": "A"
}
```

```terminal:works
{
  "title": "to be hero",
  "due_date": "2022-12-15",
  "create_datetime": "2022-12-03 19:59:39",
  "user_id": "admin"
}
```


# まずlookupで取り出してみる
```javascript
db.collection("works").aggregate([
    {
        $lookup: {
            from: "users",
            localField: "user_id",
            foreignField: "id",
            as: "unserInfo"
        },
    },
]).toArray((err, result) => {
    console.log(result)
    res.render('./works/works.ejs', { works: result })
})
```
`from`: 対象のコレクション
`localField`: 結合に使われる基準のコレクションのフィールド (ここでは`works`)
`foreignField`: 結合に使われる対象のコレクションのフィールド(ここでは`users`)
`as`: 対象のコレクションのドキュメントの保存名

```terminal
[
  {
    _id: new ObjectId("638b2c1b6c66bbaeee6c0aa6"),
    title: 'to be hero',
    due_date: '2022-12-15',
    create_datetime: '2022-12-03 19:59:39',
    user_id: 'admin',
    unserInfo: [ 
     {
      _id: new ObjectId("6388ca58dbdf448d85e9e159"),
      user_name: 'king',
      age: 20,
      id: 'admin',
      pw: '1234',
      role: 'admin',
      department: 'A'
    } 
    ]
  }
]

```
`work`のデータに加えて`users`の情報を`unserInfo`として取り出した。
しかし、`unserInfo`の中にlist型として`users`のobjectデータが入っている。
これだと使いにくいので、次の`$unwind`でobjectをリストから取り出す。

# リストからとりだす
```javascript
db.collection("works").aggregate([
    {
        $lookup: {
            from: "users",
            localField: "user_id",
            foreignField: "id",
            as: "unserInfo"
        },
    },
    //追加
    { $unwind: "$unserInfo" },
]).toArray((err, result) => {
    res.render('./works/works.ejs', { works: result })
})
```
```terminal
[
  {
    _id: new ObjectId("638b2c1b6c66bbaeee6c0aa6"),
    title: 'to be hero',
    due_date: '2022-12-15',
    create_datetime: '2022-12-03 19:59:39',
    user_id: 'admin',
    unserInfo: {
      _id: new ObjectId("6388ca58dbdf448d85e9e159"),
      user_name: 'king',
      age: 20,
      id: 'admin',
      pw: '1234',
      role: 'admin',
      department: 'A'
    }
  }
]
```
`$unwind`を使うことでリストから取り出した。
もっと欲を言えば、オブジェクトからも取り出したい。
`$project`をオブジェクトの中から取り出す。




# 必要なフィールドを結合する
```javascript
db.collection("works").aggregate([
    {
        $lookup: {
            from: "users",
            localField: "user_id",
            foreignField: "id",
            as: "unserInfo"
        },
    },
    { $unwind: "$unserInfo" },
    {
        //追加
        $project: {
            _id: 1,
            create_datetime: 1,
            user_id: 1,
            user_name: "$unserInfo.user_name",
            role: "$unserInfo.role",
        }
    }
]).toArray((err, result) => {
    res.render('./works/works.ejs', { works: result })
})
```

```
[
  {
    _id: new ObjectId("638b2c1b6c66bbaeee6c0aa6"),
    create_datetime: '2022-12-03 19:59:39',
    user_id: 'admin',
    user_name: 'king',
    role: 'admin'
  }
]
```
基準となる元のフィールドは`1`を指定することで表示することができる。
結合するデータは、その新・旧のフィールドを指定する必要がある。
しかし、指定なしですべてのフィールドを並べてくれる方法はまだないようです。

# おわりに
残念ながらまだmysqlのような完璧な`left join` はない模様
sql文がそのまま使えないのが、mongoDBの欠点として感じる。
しかし、inner joinのような使い方ができるのであれば、開発スピードが増す気がする。

# 関連

https://stackoverflow.com/questions/35813854/how-to-join-multiple-collections-with-lookup-in-mongodb

https://www.mongodb.com/docs/manual/reference/operator/aggregation/lookup/

https://www.mongodb.com/docs/manual/reference/operator/aggregation/project/