【mongoDB】配列から不要な要素を取り除いて取得する

# 概要

mongoDBで以下のようなデータがあるときに、`files`配列の`delete`フラグが立っていないものだけを抽出した状態で元の構造のまま取得したいというような場合のクエリを紹介します。

↓元のデータ

```json
[
  {
    "name": "testFile001",
    "createDate": ISODate("2022-08-01T12:34:56Z"),
    "createUser": "dora-chan",
    "files": [
      {
        "name": "file101",
        "delete": true,
        
      },
      {
        "name": "file102",
        
      },
      {
        "name": "file103",
        
      },
      
    ],
    
  },
  {
    "name": "testFile002",
    "createDate": ISODate("2022-08-02T12:34:56Z"),
    "createUser": "nobita",
    "files": [
      {
        "name": "file201",
        
      },
      {
        "name": "file202",
        "delete": true,
        
      },
      {
        "name": "file203",
        "delete": true,
        
      },
      
    ],
    
  },
  {
    "name": "testFile003",
    "createDate": ISODate("2022-08-03T12:34:56Z"),
    "createUser": "sizu-chan",
    "files": [
      {
        "name": "file301",
        "delete": true,
        
      },
      {
        "name": "file302",
        "delete": true,
        
      },
      {
        "name": "file303",
        "delete": true,
        
      },
      
    ],
    
  }
]
```

↓取得したいデータ

```json
[
  {
    "_id": ObjectId("5a934e000102030405000000"),
    "createDate": "createDate",
    "creteUser": "dora-chan",
    "files": [
      {
        "name": "file102"
      },
      {
        "name": "file103"
      }
    ],
    "name": "testFile001"
  },
  {
    "_id": ObjectId("5a934e000102030405000001"),
    "createDate": "createDate",
    "creteUser": "nobita",
    "files": [
      {
        "name": "file201"
      }
    ],
    "name": "testFile002"
  }
]
```


# 方法

[playGround](https://mongoplayground.net/p/2XX7_YFhOWx) にサンプルを記載しています。

[$unwind](https://www.mongodb.com/docs/manual/reference/operator/aggregation/unwind/)で配列を展開後、[$match](https://www.mongodb.com/docs/manual/reference/operator/aggregation/match/)で条件の指定をし、[$group](https://www.mongodb.com/docs/manual/reference/operator/aggregation/group/)及び[$push](https://www.mongodb.com/docs/manual/reference/operator/aggregation/push/#mongodb-group-grp.-push)で再度配列に詰め込みます。


```js
db.collection.aggregate([
  {
    $unwind: "$files" // 一旦files配列を展開する
  },
  {
    $match: {  // 展開した項目に対して条件指定
      "files.delete": {
        $ne: true
      }
    }
  },
  {
    $group: { // グルーピング
      _id: "$_id",
      createDate: {
        $first: "$createDate"
      },
      creteUser: {
        $first: "$createUser"
      },
      files: {
        $push: "$files" // 配列に詰め込む
      }
    }
  }
])
```


# 備考

## [allowDiskUse](https://www.mongodb.com/docs/manual/reference/command/aggregate/#mongodb-dbcommand-dbcmd.aggregate)オプションについて

<details><summary>原文</summary><div>

>Use this option to override 
>allowDiskUseByDefault
> for a specific query. You can use this option to either:
>
>Prohibit disk use on a system where disk use is allowed by default.
>
>Allow disk use on a system where disk use is prohibited by default.
>
>Starting in MongoDB 6.0, if allowDiskUseByDefault is set to true and the server requires more than 100 megabytes of memory for a pipeline execution stage, MongoDB automatically writes temporary files to disk unless the query specifies { allowDiskUse: false }.
>
>For details, see allowDiskUseByDefault.
>
>Starting in MongoDB 4.2, the profiler log messages and diagnostic log messages includes a usedDisk indicator if any aggregation stage wrote data to temporary files due to memory restrictions.
</div></details>


DeepL翻訳
→

>このオプションを使用すると
>allowDiskUseByDefaultを上書きします。
>を上書きします。このオプションを使用すると、次のいずれかを実行できます。
>
>ディスクの使用がデフォルトで許可されているシステムで、ディスクの使用を禁止する。
>
>デフォルトでディスクの使用が禁止されているシステムで、ディスクの使用を許可する。
>
>MongoDB 6.0 以降では、もし allowDiskUseByDefaultが true に設定されていて、サーバーがパイプラインの実行ステージに 100 メガバイト以上のメモリを必要とする場合、 クエリで { allowDiskUse: false } を指定しない限り MongoDB は自動的に一時ファイルをディスクに書き出します。
>
>詳しくは
>allowDiskUseByDefault を参照ください。
>
>MongoDB 4.2 以降、プロファイラのログメッセージと診断ログメッセージには、 集約段階でメモリ制限のためにデータを一時ファイルに書き込んだ場合に usedDisk というインジケータが表示されるようになりました。

PlayGroundの方は記載していませんが、私の開発環境はMongoDB4.4を使用しており、大量のデータを投入した環境であったため、このオプションを指定しない場合、以下のエラーが発生したため、オプションを追加することでクエリが通るようになりました。
MongoDB6.0以上の場合デフォルトで`{ allowDiskUse: true }`となっているようなので、不要かと思います。(未検証))

```json
{
	"message" : "Exceeded memory limit for $group, but didn't allow external sort. Pass allowDiskUse:true to opt in.",
	"ok" : 0,
	"code" : 292,
	"codeName" : "QueryExceededMemoryLimitNoDiskUseAllowed"
}
```

```
db.swp_clip.aggregate(
[
    {
        pipeline
    }
],
{
     allowDiskUse: true
}
)
```


## `$first`について

グルーピングをする際に

```js
  {
    $group: {
      _id: "$_id",
      createDate: "$createDate",
      creteUser: "$createUser",
      files: {
        $push: "$files"
      }
    }
  }
```

としたところ以下のようなエラーが発生しました。

```
query failed: (Location40234) The field 'creteUser' must be an accumulator object
```

グルーピングするのに単一の値にする必要があるため$firstを使用して先頭の値(実際は同じものなのでどれでもいい)を取得するようにしています。)

詳しくは[Group Accumulator Operators](https://www.mongodb.com/docs/v3.2/reference/operator/aggregation-group/)を参照