B0ltz
B0ltz

Reputation: 425

MongoDB advanced count query, depending of document's content

I've got an issue with an advanced count query with MongoDB.

I'm tring to count object inside a document field, but only if a previous element matching a query exist and is older in the array.

Let me explain....

I have got a container document, lookin' like:

{
 "_id" : ...,
 [...]
 "objects" : [ ]
}

Inside the objects fields:
I've got object document lookin' like:

[
  {
    "_id" : ...,
    "name" : "foo",
    "properties" = [ ],
    "time" = 000000042
  }

  {
    "_id" : ...,
    "name" : "bar",
    "properties" = [ ],
    "time" = 000000424
  }

  {
    "_id" : ...,
    "name" : "baz",
    "properties" = [ ],
    "time" = 000004242
  }

For now I count how many container documents contain:
count of container with object 1 (foo),
count of container with object 1 and 2 (foo and bar),
count of container with object 1, 2 and 3 (foo, bar, baz)

But now I want to count foo and bar only if bar is older than foo (using the time field)...
count of container with object 1 (foo),
count of container with object 1 and 2 (foo and bar), AND foo.time < bar.time
count of container with object 1, 2 and 3 (foo, bar, baz) AND foo.time < bar.time < baz.time

The probleme is that the time field needs to change for each container. With other words: How can I use dynamic query per document

here a sample of code:

foreach ($COUNTER[ARRAY_FIELDS_NAME_TO_COUNT] as $key => $value)
{
// Build the query (Name & properties)
$match[$key] = array('$elemMatch' => array('name' => $value['name']));
foreach ($value['properties'] as $propertyName => $propertyValue)
  $match[$key]['$elemMatch']["properties.$propertyName"] = $propertyValue;

// Time checking
if ($key > 0)
{
  //FIXME with a... dynamics query searching inside current doc??
  //           or   a special var set to the previous object matched...  or MapReduce..
}


// Make the query
$query = array('objects' => array('$all' => $match));

$result[$key]['count'] = $db->person->count($query);
}

I'm new in MongoDB, and I really don't know what could be the best practice for doing that efficiently!

Regards!

Upvotes: 2

Views: 966

Answers (1)

Asya Kamsky
Asya Kamsky

Reputation: 42352

Here is how you can do this with Aggregation Framework in the shell (javascript).

Note that I've split this up into multiple lines for readability, the actual query is the last line. I've used foo bar and baz but of course you want to adjust to something more appropriate.

match = { "$match" : { "objects.name" : "foo" } };
unwind = { "$unwind" : "$objects" };
projectDates = {"$project" : {
        "_id" : 1,
        "objects" : 1,
        "fooDate" : {
            "$cond" : [
                {
                    "$eq" : [
                        "$objects.name",
                        "foo"
                    ]
                },
                "$objects.time",
                0
            ]
        },
        "barDate" : {
            "$cond" : [
                {
                    "$eq" : [
                        "$objects.name",
                        "bar"
                    ]
                },
                "$objects.time",
                0
            ]
        },
        "bazDate" : {
            "$cond" : [
                {
                    "$eq" : [
                        "$objects.name",
                        "baz"
                    ]
                },
                "$objects.time",
                0
            ]
        }
    }
};
group = {"$group" : {
        "_id" : "$_id",
        "objects" : {
            "$push" : "$objects"
        },
        "maxFooDate" : {
            "$max" : "$fooDate"
        },
        "maxBarDate" : {
            "$max" : "$barDate"
        },
        "maxBazDate" : {
            "$max" : "$bazDate"
        }
    }
};
projectCount = {"$project" : {
        "_id" : 1,
        "foos" : {
            "$add" : [
                1,
                0
            ]
        },
        "foosAndBars" : {
            "$cond" : [
                {
                    "$lt" : [
                        "$maxFooDate",
                        "$maxBarDate"
                    ]
                },
                1,
                0
            ]
        },
        "foosBarsAndBazs" : {
            "$cond" : [
                {
                    "$and" : [
                        {
                            "$lt" : [
                                "$maxBarDate",
                                "$maxBazDate"
                            ]
                        },
                        {
                            "$lt" : [
                                "$maxFooDate",
                                "$maxBarDate"
                            ]
                        }
                    ]
                },
                1,
                0
            ]
        }
    }
};
countFoos = {"$group" : {
        "_id" : "FoosBarsBazs",
        "Foos" : {
            "$sum" : "$foos"
        },
        "FBs" : {
            "$sum" : "$foosAndBars"
        },
        "FBBs" : {
            "$sum" : "$foosBarsAndBazs"
        }
    }
};


db.collection.aggregate([match, unwind, projectDates, projectCount, countFoos]).result

Upvotes: 1

Related Questions