paging subdocument in mongodb subdocument

北慕城南 提交于 2020-01-05 12:56:10

问题


I want to paging my data in Mongodb. I use slice operator but can not paging my data. I wish to bring my row but can not paging in this row.

I want to return only 2 rows of data source.

How can resolve it

My Query :

db.getCollection('forms').find({
    "_id": ObjectId("557e8c93a6df1a22041e0879"),
    "Questions._id": ObjectId("557e8c9fa6df1a22041e087b")
}, {
    "Questions.$.DataSource": {
    "$slice": [0, 2]
    },
    "_id": 0,
    "Questions.DataSourceItemCount": 1
})

My collection data :

/* 1 */
{
    "_id" : ObjectId("557e8c93a6df1a22041e0879"),
    "QuestionCount" : 2.0000000000000000,
    "Questions" : [ 
        {
            "_id" : ObjectId("557e8c9ba6df1a22041e087a"),
            "DataSource" : [],
            "DataSourceItemCount" : NumberLong(0)
        }, 
        {
            "_id" : ObjectId("557e8c9fa6df1a22041e087b"),
            "DataSource" : [ 
                {
                    "_id" : ObjectId("557e9428a6df1a198011fa55"),
                    "CreationDate" : ISODate("2015-06-15T09:00:24.485Z"),
                    "IsActive" : true,
                    "Text" : "sdf",
                    "Value" : "sdf"
                }, 
                {
                    "_id" : ObjectId("557e98e9a6df1a1a88da8b1d"),
                    "CreationDate" : ISODate("2015-06-15T09:20:41.027Z"),
                    "IsActive" : true,
                    "Text" : "das",
                    "Value" : "asdf"
                }, 
                {
                    "_id" : ObjectId("557e98eea6df1a1a88da8b1e"),
                    "CreationDate" : ISODate("2015-06-15T09:20:46.889Z"),
                    "IsActive" : true,
                    "Text" : "asdf",
                    "Value" : "asdf"
                }, 
                {
                    "_id" : ObjectId("557e98f2a6df1a1a88da8b1f"),
                    "CreationDate" : ISODate("2015-06-15T09:20:50.401Z"),
                    "IsActive" : true,
                    "Text" : "asd",
                    "Value" : "asd"
                }, 
                {
                    "_id" : ObjectId("557e98f5a6df1a1a88da8b20"),
                    "CreationDate" : ISODate("2015-06-15T09:20:53.639Z"),
                    "IsActive" : true,
                    "Text" : "asd",
                    "Value" : "asd"
                }
            ],
            "DataSourceItemCount" : NumberLong(5)
        }
    ],
    "Name" : "er"
}

回答1:


Though this is possible to do with some real wrangling you would be best off changing the document structure to "flatten" the array entries into a single array. The main reason for this is "updates" which are not atomically supported by MongoDB with respect to updating the "inner" array due to the current limitations of the positional $ operator.

At any rate, it's not easy to deal with for the reasons that will become apparent.

For the present structure you approach it like this:

db.collection.aggregate([
    // Match the required document and `_id` is unique
    { "$match": {
        "_id":  ObjectId("557e8c93a6df1a22041e0879")
    }},

    // Unwind the outer array
    { "$unwind": "$Questions" },

    // Match the inner entry
    { "$match": {
        "Questions._id": ObjectId("557e8c9fa6df1a22041e087b"),
    }},

    // Unwind the inner array
    { "$unwind": "$Questions.DataSource" }

    // Find the first element
    { "$group": {
        "_id": {
            "_id": "$_id",
            "questionId": "$Questions._id"
       },
       "firstSource": { "$first": "$Questions.DataSource" },
       "sources": { "$push": "$Questions.DataSource" }
    }},

    // Unwind the sources again
    { "$unwind": "$sources" },

    // Compare the elements to keep
    { "$project": {
        "firstSource": 1,
        "sources": 1,
        "seen": { "$eq": [ "$firstSource._id", "$sources._id" ] }
    }},

    // Filter out anything "seen"
    { "$match": { "seen": true } },

    // Group back the elements you want
    { "$group": {
        "_id": "$_id",
        "firstSource": "$firstSource",
        "secondSource": { "$first": "$sources" }
    }}
])

So that is going to give you the "first two elements" of that inner array. It's the basic process for implementing $slice in the aggregation framework, which is required since you cannot use standard projection with a "nested array" in the way you are trying.

Since $slice is not supported otherwise with the aggregation framework, you can see that doing "paging" would be a pretty horrible and "iterative" operation in order to "pluck" the array elements.

I could at this point suggest "flattening" to a single array, but the same "slicing" problem applies because even if you made "QuestionId" a property of the "inner" data, it has the same projection an selection problems for which you need the same aggregation approach.

Then there is this "seemingly" not great structure for your data ( for some query operations ) but it all depends on your usage patterns. This structure suits this type of operation:

{
    "_id" : ObjectId("557e8c93a6df1a22041e0879"),
    "QuestionCount" : 2.0000000000000000,
    "Questions" : {
        "557e8c9ba6df1a22041e087a": {
            "DataSource" : [],
            "DataSourceItemCount" : NumberLong(0)
        }, 
        "557e8c9fa6df1a22041e087b": {
            "DataSource" : [ 
                {
                    "_id" : ObjectId("557e9428a6df1a198011fa55"),
                    "CreationDate" : ISODate("2015-06-15T09:00:24.485Z"),
                    "IsActive" : true,
                    "Text" : "sdf",
                    "Value" : "sdf"
                }, 
                {
                    "_id" : ObjectId("557e98e9a6df1a1a88da8b1d"),
                    "CreationDate" : ISODate("2015-06-15T09:20:41.027Z"),
                    "IsActive" : true,
                    "Text" : "das",
                    "Value" : "asdf"
                }
            ],
            "DataSourceItemCount" : NumberLong(5)
        }
    }
}

Where this works:

db.collection.find(
    { 
        "_id": ObjectId("557e8c93a6df1a22041e0879"),
        "Questions.557e8c9fa6df1a22041e087b": { "$exists": true }
    },
    { 
        "_id": 0,
        "Questions.557e8c9fa6df1a22041e087b.DataSource": { "$slice": [0, 2] },
        "Questions.557e8c9fa6df1a22041e087b.DataSourceItemCount": 1
    }
)

Nested arrays are not great for many operations, particularly update operations since it is not possible to get the "inner" array index for update operations. The positional $ operator will only get the "first" or "outer" array index and cannot "also" match the inner array index.

Updates with a structure like you have involve "reading" the document as a whole and then manipulating in code and writing back. There is no "guarantee" that the document has not changed in the collection between those operations and it can lead to inconsistencies unless handled properly.

On the other hand, the revised structure as shown, works well for the type of query given, but may be "bad" if you need to dynamically search or "aggregate" across what you have represented as the "outer" "Questions".

Data structure with MongoDB is very subjective to "how you use it". So it is best to consider all of your usage patterns before "nailing down" a final data structure design for your application.

So you can either take note of the problems and solutions as noted, or simply live with retrieving the "outer" element via the standard "positional" match and then just "slice" in your client code.

It's all a matter of "what suits your application best".



来源:https://stackoverflow.com/questions/30843146/paging-subdocument-in-mongodb-subdocument

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!