Search code examples
node.jsmongodbsortingjson-api

MongoDB sort by property in other document


In order to expand the JSON-API capabilities of my node.js application, I'm trying to sort a query based on relationships (AKA other documents), although I don't want to return them.

According to the JSON-API documentation:

a sort field of author.name could be used to request that the primary data be sorted based upon the name attribute of the author relationship.

E.g. db.collection('books').find({}) returns:

[
    {
        type: "book",
        id: "2349",
        attributes: {
            title: "My Sweet Book"
        },
        relationships: {
            author: {
                data: {
                    type: "authors",
                    id: "9"
                }
            }
        }
    },
    {} // etc ...
]

db.collection('authors').find({id: "9"}) returns:

[
    {
        type: "author",
        id: "9",
        attributes: {
            name: "Hank Moody"
        }
    }
]

Now I need some way to do something similar to e.g.:
db.collection('books').find({}).sort({"author.name": -1})

I think I need to convert the query to an aggregation so I can use the $lookup operator, but I'm not sure how to use localField and foreignField.

db.collection('books').aggregate([
    {$match: {}},
    {$lookup: {from: "authors", localField: "attributes.author.data.id", foreignField: "id", as: "temp.author"}},
    {$sort: {"$books.temp.author.name": -1}},
    {$project: {temp: false}},
])

Notes

  • This will be a global function for fetching JSON-API data.
    • This means we don't know wether a sort key is an attribute or a relationship.
  • Most servers run LTS versions and have MongoDB 3.2

Solution

  • You can try below aggregation.

    $lookup to join to authors collection followed by $unwind to flatten the book_author array for applying $sort on name field and $project with exclusion to remove book_author field ( only works starting Mongo 3.4 version ). For lower versions you have to include all the other fields you want to keep and excluding book_author field in the $project stage.

     db.collection('books').aggregate([{
        $lookup: {
            from: "authors",
            localField: "relationships.author.data.id",
            foreignField: "id",
            as: "book_author"
        }
     }, {
        $unwind: "$book_author"
     }, {
        $sort: {
            "book_author.attributes.name": -1
        }
     }, {
        $project: {
            "book_author": 0
        }
     }])