1

I'm a MongoDB noob and I have a MongoDB collection with millions of documents, each with tens of keys (average ~ 60, large variance) from a set of around 100. I'd like to get a count of each key across the entire collection.

For example, if the documents in the collection were:

{"_id":0, "foo": 0, "bar":1, "baz":2}
{"_id":1, "foo": 0, "baz":7, "qux":11, "quux":13}
{"_id":2, "foo": 1, "bar":1, "quux":3}

then the desired output would be:

{"_id":3, "foo":3, "bar":2, "baz":2, "qux":1, "quux": 2}

I can "explode" the collection with $objectToArray, $unwind, $group, and then $count, but it's slow.

Is there something that could do this efficiently in one pass through the collection? Something like,

[notional psuedocode]
output={}
foreach document:
  foreach key:
    if output.key exists:
      output.key+=1
    else:
      output.key=1

=> output: {key1: key1_count, ...}
6
  • 1
    Dynamic fields names is usually a poor design (as you see yourself). Can you change it? Commented Jan 21, 2022 at 22:16
  • Of course you can run it by a loop, but I don't expect it would be faster. Commented Jan 21, 2022 at 22:18
  • @WernfriedDomscheit It's all dynamic sensor data of which we have no control. Analysts query the database for data mining analysis. "Of course you can run it by a loop..." How would I do that? I'd like to try it.
    – rickhg12hs
    Commented Jan 22, 2022 at 5:09
  • @WernfriedDomscheit "Of course you can run it by a loop..." I know how to do this "externally" by retrieving documents via the Python connector and aggregating in Python. Is there a way to do this completely with MongoDB?
    – rickhg12hs
    Commented Jan 22, 2022 at 6:34
  • 1
    No, the mongo shell is also a Javascript shell. I would not expect much differences between Python an Javascript. Commented Jan 22, 2022 at 12:20

2 Answers 2

0

One of the shortest one could be this one:

const counts = {}
db.collection.aggregate([
   { $project: { data: { $objectToArray: "$$ROOT" } } }
]).forEach(doc => {
   doc.data.forEach(val => {
      counts[val.k] = typeof counts[val.k] == "undefined" ? val.v : counts[val.k] + val.v;
   })
})

I tried to find solution of $objectToArray, $group but most likely it will not work, because the size of document is limited to 16 MiBytes. Having "millions of documents" this limit will certainly exceeded.

1
  • I used counts[val.k] = typeof counts[val.k] == "undefined" ? 1 : counts[val.k] + 1; and it works, but the "millions of documents" makes the wait time forever, so far. I inserted a $limit to test and counts looked reasonable. I did implement a $unwind, $group with $count, and that does work, and maybe that's the best that can be expected, but it's still slow. I don't think I'll be doing this "collection field overview" often since it's so slow.
    – rickhg12hs
    Commented Jan 25, 2022 at 15:51
0

Running a single aggregation is better option than getting an aggregate operation result and then processing further on the client (using for-loops, etc., in mongodb shell or a driver driven programming language application). The aggregation processed on the database server performs better. There are optimizations during various aggregation stages in the newer versions of MongoDB.

db.collection.aggregate([
    {
        $project: {
            _id: 0,
            r: {
                $objectToArray: "$$ROOT"
            }

        }
    },
    {
        $unwind: "$r"
    },
    {
        $group: {
            _id: "$r.k",
            count: {
                "$sum": 1
            }
        }
    }
])

This returns a result as:

[
  { _id: 'quux', count: 2 },
  { _id: 'foo', count: 3 },
  { _id: 'qux', count: 1 },
  { _id: 'bar', count: 2 },
  { _id: 'baz', count: 2 },
  { _id: '_id', count: 3 }
]

This result can be written to another collection using the $out aggregate stage (if needed).

Also see $group and Memory Restrictions, which says "If the $group stage exceeds 100 megabytes of RAM, MongoDB writes data to temporary files...".

5
  • This "answer" was mentioned in the question. Scale it up to scores of objects per document and millions of documents ... and it's slow.
    – rickhg12hs
    Commented Apr 29 at 11:30
  • @rickhg12hs Only the aggregate stages were mentioned in the question (there is no implementation). Possibly, visitors to this post would want to see the code. Also, partial processing on the database server and then using for-each in a client application to complete the process is inefficient. Such, technique is okay if the aggregation cannot handle the logic or due to some other requirements (not the current one).
    – prasad_
    Commented Apr 29 at 12:08
  • You're right. Someone might find it useful. I found it slow.
    – rickhg12hs
    Commented Apr 29 at 16:00
  • @rickhg12hs Your question doesn't state lot of important information. What version of MongoDB? What kind of server, memory, capabilities? How much data, millions - 2 or 100 millions? What processes are running while you execute your code? How slow - there are no details there either. What is your expectation? Please post these information in your question for further guidance (from the community).
    – prasad_
    Commented Apr 30 at 3:19
  • @rickhg12hs The code I had posted will be useful and potentially someone can improve over it.
    – prasad_
    Commented Apr 30 at 3:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.