コンパイラかく語りき

import { Fun } from 'programming'

【Mongo】配列要素を populate/lookup した後に sort する

概要

Mongo/Mongoose で配列要素を populate した後に、要素のフィールドでソートする方法について。

コードサンプルは JavaScript で書いています。

サンプル

以下のようなデータを想定します。

// user
{
    id: "foo"
    comments: ["1", "2"] // comment コレクションへの参照 ID
}

// comment
{
    id: "1",
    text: "one one one",
    created_at: "2019/01/01"
}
{
    id: "2",
    text: "two two two"
    created_at: "2019/01/02"
}

populate すると

普通に populate すると以下のようなコードになると思います。

const user = User
    .find({ id: "foo" })
    .populate("comments")

実行結果は以下の通り。

{
    id: "foo"
    comments: [
    {
        id: "1",
        text: "one one one",
        created_at: "2019/01/01"
    }
    {
        id: "2",
        text: "two two two"
        created_at: "2019/01/02"
    }
    ]
}

コメントへの参照が解決されています。

配列要素をそのフィールドに応じてソート

しかしクエリ時点で配列要素をソートしたい時があります。

例えば、comments をそのフィールドである created_at の降順にソートする場合、以下のように書けます。

const user = User,aggregate[
    { $match: { id: "foo" } },
    {
        $lookup: {
            from: 'comments',
            localField: 'comments',
            foreignField: '_id',
            as: 'items',
        },
    },
    { $unwind: '$items' },
    { $sort: { 'items.created_at': 1 } },
        {
        $group: {
            _id: '$id',
            id: { $first: '$id' },
            comments: { $addToSet: '$items' },
        },
    },
]

配列要素のソートは、$aggregation の中で $unwind$sort を組み合わせると実現できます。

ただし、元の user データに差し戻すためにはいったんデータ名を $item のように置き換えておく必要がありました。そのうえで、 $group を使って最終形データを生成します。

また、 $aggregation を使ったので、id マッチングは $match を使用。

実行結果は以下の通り。配列要素 comments がそのフィールドであるcreated_at でソートされています。

{
    id: "foo"
    comments: [
    {
        id: "2",
        text: "two two two",
        created_at: "2019/01/02"
    }
    {
        id: "1",
        text: "one one one"
        created_at: "2019/01/01"
    }
    ]
}

おしまい

やりたいことは実現できましたが、かなり複雑になってしまいました。実装方法を調べましたが、なかなか見つからずに試行錯誤しているうちに上述の方法に当たりました。

実のところ $aggregation にはまだ馴染みが薄く、きちんと理解しているかは自信がありません…。ベターな方法がありましたら、コメント欄等でご一報いただけると幸いです。

参考