0

How to get subdocument element's count inside an array and how to update the subdocument's key in MongoDB

For eg, following is the whole doc stored in mongodb:

{
    "CompanyCode" : "SNBN",
    "EventCode" : "ET00008352",
    "EventName" : "Sunburn Presents Avicii India Tour",
    "TktDetail" : [ 
        {
            "Type" : "Category I",
            "Qty" : {
                "10-Dec" : {
                    "value" : 58
                },
                "11-Dec" : {
                   "value" : 83
                },
                "12-Dec" : {
                   "value" : 100
                } 
            }
        }, 
        {
            "Type" : "Category II",
            "Qty" : {
                "10-Dec" : {
                   "value" : 4
                },
                "11-Dec" : {
                    "value" : 7
                },
                "12-Dec" : {
                    "value" : 8
                }
            }
        }, 
        {
            "Type" : "PRICE LEVEL 1",
            "Qty" : {
                "11-Dec" : {
                    "value" : 2
                }
            }
        }, 
        {
            "Type" : "CatIV",
            "Qty" : {
                "18-Dec" : {
                    "value" : 20
                }
            }
        }
    ],
    "TransDate" : [ 
        "10-Dec-2013", 
        "11-Dec-2013", 
        "12-Dec-2013", 
    ],
    "VenueCode" : "SNBN",
    "VenueName" : "Sunburn",
    "_id" : ObjectId("52452db273b92012c41ad612")
}

Here TktDetail is an array, inside which there is a Qty subdoc which contains multiple elements, I want to know how to get the elements count inside Qty per index?

For example, the 0th index of TktDetail array contains 1 Qty subdoc, which further has a element count of 3, whereas 3rd index has element count of 1 in Qty subdoc.

If I want to update the subdoc key, like, I want to update the date in Qty from "10-Dec" to "10-Dec-2013", how is it possible?

Thanks in advance, looking for a reply ASAP..

Neil Lunn
  • 148,042
  • 36
  • 346
  • 317
  • Someone (not me, yet) just downvoted you because you didn't format your data block like the site **asks** you to do. Also **DONT SHOUT** everything in bold. Note for future. But **now** you **might** get some help. – Neil Lunn Feb 28 '14 at 07:18
  • Now. Your question is (clarify) you want to update the values in the sub-documents under Qty. Right? – Neil Lunn Feb 28 '14 at 07:20
  • Thanks Neil for advising me for future note. I don't want to update the value field inside Qty, i want to update the key name inside Qty which is stored in array TktDetail, for eg: if it is "10-Dec", then i want to update it as "10-Dec-2013". – Suraj Mishra Feb 28 '14 at 07:28
  • As well as i want to know, how can i get the count of elements inside Qty per index, Qty of 0th index has 3 as count, Qty of 3rd index has 1 as count, in dis way? – Suraj Mishra Feb 28 '14 at 07:32

1 Answers1

0

So the first thing here is that you actually asked two questions, being "how do I get a count of the items under Qty?" and "how can I change the names?". Now while normally unrelated I'm going to treat them as the same thing.

What you need to do is change your schema and in doing so I'm going to allow you to get the count of items and I'm going to encourage you to change those field names as well. Specifically you need a schema like this:

"TktDetail" : [ 
    {
        "Type" : "Category I",
        "Qty" : [
            { "date": ISODate("2013-12-10T00:00:00.000Z") , "value" : 58  },
            { "date": ISODate("2013-12-11T00:00:00.000Z"), "value" : 83  },
            { "date": ISODate("2013-12-01T00:00:00.000Z"), "value" : 100 },
        ]
    },

All the gory details are in my answer here to a similar question. But the problem basically is that when you use sub-documents in the way you have you are ruining your chances of doing any meaningful query operations on this, as to get at each element you must specify the full path to get there.

That answer has more detail, but the case is you really want an array. The trade-off, it's a little harder to update, especially considering you have nested arrays, but it's a lot easier to add and much easier to query.

Also, and related, change your dates to be dates and not strings. The strings, are no good for comparisons inside MongoDB. With them set as proper BSON dates (noting I clipped them to the start of day) you can compare, and query ranges and do useful things. Your application code will be happy to as the driver will return a real date object, rather than something you have to manipulate "both ways".

So once you have read through, understood and implemented this, on to counting:

db.collection.aggregate([

    // Unwind the TktDetail array to de-normalize
    {"$unwind": "$TktDetail"},

    // Also Unwind the Qty array
    {"$unwind": "$Qty" },

    // Get some group information and count the entries
    {"$group": { 
        "_id": {
            "_id": "$_id,
            "EventCode": "$EventCode",
            "Type": "$TktDetail.Type"
        },
        "Qty": {"$sum": 1 }
    }},

    // Project nicely
    {"$project": { 
        "_id": 0,
        "EventCode": "$_id.EventCode",
        "Type: "$_id.Type",
        "Qty": 1,
    }},

    // Let's even sort it 
    {"$sort": { "EventCode": 1, "Qty" -1 }}

])

So that allowed us to get a count of the items in Qty for each EventCode by Type with the Qty ordered higest to lowest.

And that is not possible on your current schema without loading and traversing each document in code.

So there is the case. Now if you want to ignore this and just go about changing the sub-document key names, then you'll need to do remove the key and underlying document and replace with the new key name, using update:

db.collection.update(
    { EventCode: "ET00008352"},
    { $unset:{ "TktDetail.0.Qty.10-Dec": "" }}
)

db.collection.update(
    { EventCode: "ET00008352"},
    { $set:{ "TktDetail.0.Qty.10-Dec-2013": { value: 58 } }}
)

And you'll need to do that for every item that you have.

So you either work out that schema conversion or otherwise have a lot of work anyway in order to change the keys. For myself, I'd do it properly, and only do it once so I didn't run into the next problem later.

Community
  • 1
  • 1
Neil Lunn
  • 148,042
  • 36
  • 346
  • 317
  • I really appreciate u for ur answer, but the first solution where u have asked me to change the schema of collection, will create problem in future while updating using $(Positional operator), because it is not supported more than 1 level and Qty will be at 2nd level, this is the reason i have treated it "Qty" as a subdoc instead of an nested array. – Suraj Mishra Feb 28 '14 at 09:15
  • Second is a known solution, which i cant use because i want to traverse on Qty without knowing the key names and update it. – Suraj Mishra Feb 28 '14 at 09:21
  • @SurajMishra I am well aware of the issues with positional updates, hence what I said is that this can be somewhat difficult but not impossible. If Anything, the only item that **should** be treated as a *sub-document* in this structure would the `TktDetail` part, which makes some other operations more difficult, but removes the positional update problem. It's the least path problem, and therefore the best place. Even if you are aware of what you have to do with $set and $unset, there is **no other way**. If you need the altered structure explained better I can do that. – Neil Lunn Feb 28 '14 at 09:27