简体   繁体   中英

Gets documents and total count of them in single query include pagination

I'm new in mongo and use mongodb aggregation framework for my queries. I need to retrieve some records which satisfy certain conditions( include pagination+sorting) and also get total count of records.

Now, I perform next steps:

  1. Create $match operator
    { "$match" : { "year" : "2012" , "author.authorName" : { "$regex" : "au" , "$options" : "i"}}}
  2. Added sorting and pagination
    { "$sort" : { "some_field" : -1}} , { "$limit" : 10} , { "$skip" : 0}

After querying I receive the expected result: 10 documents with all fields.

For pagination I need to know the total count of records which satisfy these conditions, in my case 25.

I use next query to get count : { "$match" : { "year" : "2012" , "author.authorName" : { "$regex" : "au" , "$options" : "i"}}} , { "$group" : { "_id" : "$all" , "reviewsCount" : { "$sum" : 1}}} , { "$sort" : { "some_field" : -1}} , { "$limit" : 10} , { "$skip" : 0}

But I don't want to perform two separate queries : one for retrieving documents and second for total counts of records which satisfy certain conditions.

I want do it in one single query and get result in next format:

{
        "result" : [
                {
            "my_documets": [
                        {
                        "_id" : ObjectId("512f1f47a411dc06281d98c0"),
                        "author" : {
                                "authorName" : "author name1",
                                "email" : "email1@email.com"
                            }
                        },
                        {
                        "_id" : ObjectId("512f1f47a411dc06281d98c0"),
                        "author" : {
                                "authorName" : "author name2",
                                "email" : "email2@email.com"
                            }
                        }, .......

                    ],
                    "total" : 25
                }
        ],
        "ok" : 1
}

I tried modify the group operator : { "$group" : { "_id" : "$all" , "author" : "$author" "reviewsCount" : { "$sum" : 1}}} But in this case I got : " exception: the group aggregate field 'author' must be defined as an expression inside an object ". If add all fields in _id then reviewsCount always = 1 because all records are different.

Nobody know how it can be implement in single query ? Maybe mongodb has some features or operators for this case? Implementation with using two separate query reduces performance for querying thousand or millions records. In my application it's very critical performance issue.

I've been working on this all day and haven't been able to find a solution, so thought i'd turn to the stackoverflow community.

Thanks.

You can try using $facet in the aggregation pipeline as

db.name.aggregate([
{$match:{your match criteria}},
{$facet: {
       data: [{$sort: sort},{$skip:skip},{$limit: limit}],
       count:[{$group: {_id: null, count: {$sum: 1}}}]
}}
])

In data, you'll get your list with pagination and in the count, count variable will have a total count of matched documents.

Ok, I have one example, but I think it's really crazy query, I put it only for fun, but if this example faster than 2 query, tell us about it in the comments please.

For this question i create collection called "so", and put into this collection 25 documents like this:

{
    "_id" : ObjectId("512fa86cd99d0adda2a744cd"),
    "authorName" : "author name1",
    "email" : "email1@email.com",
    "c" : 1
}

My query use aggregation framework:

db.so.aggregate([
    { $group:
        { 
            _id: 1, 
            collection: { $push : { "_id": "$_id", "authorName": "$authorName", "email": "$email", "c": "$c" } }, 
            count: { $sum: 1 }
        }
    },
    { $unwind: 
        "$collection"
    },
    { $project: 
        { "_id": "$collection._id", "authorName": "$collection.authorName", "email": "$collection.email", "c": "$collection.c", "count": "$count" }
    },
    { $match: 
        { c: { $lte: 10 } } 
    },
    { $sort : 
        { c: -1 }
    },
    { $skip:
        2
    },
    { $limit:
        3
    },
    { $group: 
        { 
            _id: "$count", 
            my_documets: { 
                $push: {"_id": "$_id", "authorName":"$authorName", "email":"$email", "c":"$c" } 
            } 
        } 
    },
    { $project: 
        { "_id": 0, "my_documets": "$my_documets", "total": "$_id" }
    }
])

Result for this query:

{
    "result" : [
        {
            "my_documets" : [
                {
                    "_id" : ObjectId("512fa900d99d0adda2a744d4"),
                    "authorName" : "author name8",
                    "email" : "email8@email.com",
                    "c" : 8
                },
                {
                    "_id" : ObjectId("512fa900d99d0adda2a744d3"),
                    "authorName" : "author name7",
                    "email" : "email7@email.com",
                    "c" : 7
                },
                {
                    "_id" : ObjectId("512fa900d99d0adda2a744d2"),
                    "authorName" : "author name6",
                    "email" : "email6@email.com",
                    "c" : 6
                }
            ],
            "total" : 25
        }
    ],
    "ok" : 1
}

By the end, I think that for big collection 2 query (first for data, second for count) works faster. For example, you can count total for collection like this:

db.so.count()

or like this:

db.so.find({},{_id:1}).sort({_id:-1}).count()

I don't fully sure in first example, but in second example we use only cursor, which means higher speed:

db.so.find({},{_id:1}).sort({_id:-1}).explain()
{
    "cursor" : "BtreeCursor _id_ reverse",
    "isMultiKey" : false,
    "n" : 25,
    "nscannedObjects" : 25,
    "nscanned" : 25,
    "nscannedObjectsAllPlans" : 25,
    "nscannedAllPlans" : 25,
    "scanAndOrder" : false,
    !!!!!>>>  "indexOnly" : true, <<<!!!!!
    "nYields" : 0,
    "nChunkSkips" : 0,
    "millis" : 0,
    ...
}

For completeness (full discussion was on the MongoDB Google Groups ) here is the aggregation you want:

db.collection.aggregate(db.docs.aggregate( [
    {
        "$match" : {
            "year" : "2012"
        }
    },
    {
        "$group" : {
            "_id" : null,
            "my_documents" : {
                "$push" : {
                    "_id" : "$_id",
                    "year" : "$year",
                    "author" : "$author"
                }
            },
            "reviewsCount" : {
                "$sum" : 1
            }
        }
    },
    {
        "$project" : {
            "_id" : 0,
            "my_documents" : 1,
            "total" : "$reviewsCount"
        }
    }
] )

By the way, you don't need aggregation framework here - you can just use a regular find. You can get count() from a cursor without having to re-query.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM