n b
n b

Reputation: 43

How to translate NOT (A AND B) into a mongodb query

I've built a grammar (using peg.js) to support simple search queries in a Node.js web-application; I need to translate the AST it produces into a $match parameter for a MongoDB aggregate function. Most cases are fine, but I'm having trouble figuring out what NOT (A AND B) should look like in MongoDB.

Some example data:

{ _id:123, labels:['a', 'b', 'c'], tags:['g', 'h', 'i']},
{ _id:456, labels:['a', 'e', 'f'], tags:['i', 'j', 'k']},
{ _id:789, labels:['c', 'd', 'b'], tags:['h', 'l', 'm']}

A user might type in the query NOT (labels:a AND labels:b), or NOT (labels:b AND tags:h) which are both valid queries, based on my grammar.

The intent for the first query would be to bring back all documents that do not contain both 'a' and 'b' in their labels field (so the second and third document would be returned); for the second query, it should match all documents that do not contain both 'b' in the labels field, and 'h' in the tags field.

MongoDB's $not operator doesn't seem to allow you to neatly negate a logical operation in the way I'm looking for; ideally, I'd like to do something like:

{
    $not: {
        $and: [
            { labels: a},
            { labels: b},
        ]
    }
}

or

{
    $not: {
        $and: [
            { labels: b},
            { tags: h},
        ]
    }
}

Is there some other operator I can use to do this? Also, ideally, this would be something easy to build programatically - so that I could build a potentially complex compound query inside the parentheses, and then just negate it if there was a NOT.

EDIT: Expanded the dataset, and expanded and clarified my actual requirements for an answer.

Upvotes: 4

Views: 248

Answers (1)

loonytoons
loonytoons

Reputation: 123

For one field (as per the original question) this should do it:

{
    'labels': {
        '$not': {
            '$all': [
                'a',
                'b',
            ]
        }
    }
}

For multiple fields, you'd need to break them out but this query would work:

{
    '$and': [
        {
            'labels': {
                '$not': {
                    '$all': ['b'],
                },
            },
        },
        {
            'tags': {
                '$not': {
                    '$all': ['h'],
                }
            }
        }
    ]
}

I don't think it's possible to have the not as a main root element

Upvotes: 1

Related Questions