开发者

MongoDB - Filtering the content of an internal Array in a resultset

开发者 https://www.devze.com 2023-02-04 04:49 出处:网络
I\'m new using MongoDB, and I don\'t know how to solve the next problem: I have a collection of documents like this:

I'm new using MongoDB, and I don't know how to solve the next problem:

I have a collection of documents like this:

{
 "URL": "www.stackoverflow.com",
 "TAGS": [
         {"NAME": "question", "VOTES": 3},
         {"NAME": "answer", "VOTES": 5},
         {"NAME": "problem", "VOTES": 2}
         ]
}

First of all, I wanted all the Urls that have all the tags given in a list. I have solved this by quering:

db.links.find( { "Tags.Name" : { $all: ["question","answers"] } } );

But this query return the whole correct document, insted of only the correct document with only the tag开发者_开发知识库s I have asked for.

The result I'm looking for is:

{
 "URL": "www.stackoverflow.com",
 "TAGS": [{"NAME": "question", "VOTES": 3},
         {"NAME": "answer", "VOTES": 5}]
}

and not:

{
 "URL": "www.stackoverflow.com",
 "TAGS": [{"NAME": "question", "VOTES": 3},
         {"NAME": "answer", "VOTES": 5},
         {"NAME": "problem", "VOTES": 2}]
}

Because I've only asked for the tags ["question","answers"].

I thought about using MapReduce or parsing the resultset, but I don't know if it is the correct way of solving the problem. Maybe there is a builtin function that solve it more efficiently.

Thanks!


You can use aggregation framework of MongoDB.

If you have a doc in your collection like ;

{
 "URL": "www.stackoverflow.com",
 "TAGS": [
         {"NAME": "question", "VOTES": 3},
         {"NAME": "answer", "VOTES": 5},
         {"NAME": "problem", "VOTES": 2}
         ]
}

and you want to filter some elements of the array out you can use the aggregation sample;

db.sof_table.aggregate
([
{$unwind:'$TAGS'}, 
{$match:{'TAGS.NAME':{$in:['answer','question']}}},
{$group:{_id:'$URL',TAGS:{$push:'$TAGS'}}}
])

This will result;

{
    "result" : [
        {
            "_id" : "www.stackoverflow.com",
            "TAGS" : [
                {
                    "NAME" : "question",
                    "VOTES" : 3
                },
                {
                    "NAME" : "answer",
                    "VOTES" : 5
                }
            ]
        }
    ],
    "ok" : 1
}

as your expected result.


Generally speaking any find() operation on MongoDB returns all the documents that match the query and all documents are retrieved in their entirety. If you only want a specific section of a document then you have to do that processing on the client side.

This is a fundamental difference between document databases and SQL databases. Typically in a document database a query returns all documents that match it while in an SQL database you can choose to return only portions of the table. Unless of course like you say you do a MapReduce but that kinda seems like overkill for your use case.

Not to discourage you from using MongoDB but whatever project you work on consider whether NoSQL databases actually fit the bill (do they fill a requirement that SQL cannot) or whether you'd still be better going with a traditional SQL database.


It is possible to suppress keys and array elements in the returned document, but not in the way that you want.

In your example, you can suppress the URL key with the following query, which uses the second argument to find():

db.links.find({"TAGS.NAME" : {$all : ["question","answer"]}}, {"URL" : 0})

However, I don't believe it is possible to suppress individual members of an array on the server-side with find() based on which array members were specified with $all.

You can use $slice to return only certain members of an array, but it is position-based. For example,

{$slice : [1, 2]}

skips the first element of the array and returns up to the next two.


I was just referenced to this conversation from a link to my own solution to the problem. It does work, but in hindsight the real issue was that I didn't understand MongoDB back then.

My conclusion: if you find yourself filtering embedded arrays in MongoDB, it probably means you don't understand MongoDB.

The official solution to this issue

The official recommendation is to prefer embedding data over referencing or filtering it.

Yes, this means you're expected to duplicate data. Yes, this means you're making your DB less abstract and more tailored to your specific solution.

And yes, this might feel strange coming from SQL.

Meaning...

My suggestion would be to create a new collection for the answers, and embed the correct ones into the URL entity. It's called "the embedded subset pattern".

The technical, wrong, solution

Back then before MongoDB clicked for me, I managed to filter internal arrays, server side, by overriding the property storing the array with a filtered subset of it.

You can read about it here. Again - even though I'm really fond of my hack, I do not recommend it. It's basically a monument to how much I didn't understand MongoDB back then.


Thanks Robert. I have realized that the featured I'm looking for is not implemented at this moment. Here is the link of the issue. I hope that the MongoDB cominuty implements it in a short time. Thanks!


This might help you.

The $elemMatch projection operator takes an explicit condition argument. This allows you to project based on a condition not in the query, or if you need to project based on multiple fields in the array’s embedded documents.**

https://docs.mongodb.com/manual/reference/operator/projection/elemMatch/

0

精彩评论

暂无评论...
验证码 换一张
取 消