MongoDB sort documents by array elements

2019-01-07 17:32发布

问题:

I have document structure like :

{
    map: 'A',
    points: [
        {
            type: 'type1',
            distanceToSpawn: 110
        },
        {
            type: 'type4',
            distanceToSpawn: 40
        },
        {
           type: 'type6',
           distanceToSpawn: 30
        }
    ]
},
{
    map: 'B',
    points: [
        {
            type: 'type1',
            distanceToSpawn: 100
        },
        {
            type: 'type2',
            distanceToSpawn: 60
        },
        {
            type: 'type3',
            distanceToSpawn: 25
        }
    ]
},
{
    map: 'C',
    points: [
        {
            type: 'type2',
            distanceToSpawn: 90
        },
        {
            type: 'type3',
            distanceToSpawn: 1
        },
        {
            type: 'type6',
            distanceToSpawn: 76
        }
    ]
}

I want to get all maps having point type type1 sorted by the distanceToSpawn in ascending order.

Expected result :

{
    map: 'B',
    points: [
        {
            type: 'type1',
            distanceToSpawn: 100
        }
    ]
},
{
    map: 'A',
    points: [
        {
            type: 'type1',
            distanceToSpawn: 110
        }
    ]
}

I've tried something like :

db.maps.find({'points.type': {$eq : 'type1'}}, {map: 1, 'points.$':1}).sort({'points.distanceToSpawn': 1}).limit(10)

But this thing not sorting maps by ascending order.

Thanks.

回答1:

You cannot do that with arrays, and the main problem here is because you wan the "sort" to happen on the matched element. If you want to sort results like this then you need to use .aggregate() instead. Either as:

For modern MongoDB versions:

db.maps.aggregate([
    { "$match": { "points.type": "type1" }},
    { "$addFields": {
        "order": {
            "$filter": {
              "input": "$points",
              "as": "p",
              "cond": { "$eq": [ "$$p.type", "type1" ] }
            }
        }
    }},
    { "$sort": { "order": 1 } }
])

For MongoDB 2.6 to 3.0

db.maps.aggregate([
    { "$match": { "points.type": "type1" }},
    { "$project": {
        "points": { 
            "$setDifference": ]
                { "$map": {
                    "input": "$points",
                    "as": "p",
                    "in": { "$cond": [
                        { "$eq": [ "$$p.type", "$type1" ] },
                        "$$p",
                        false
                    ]}
                }},
                [false]   
            ]
        }
    }},
    { "$sort": { "points.distanceToSpawn": 1 } }
])

Or less efficiently in versions prior to MongoDB 2.6:

db.maps.aggregate([
    { "$match": { "points.type": "type1" }},
    { "$unwind": "$points" },
    { "$match": { "points.type": "type1" }},
    { "$group": {
        "_id": "$id",
        "points": { "$push": "$points" }
    }},
    { "$sort": { "points.ditanceToSpawn": 1 } }         
])

That's the only way to match the correct elements and have them considered in a "sort" operation. The default cursor sort will just otherwise consider the values for the field in the array elements that do not match your selected "type" instead.