Related
I have documents with a field containing an array of values which can be duplicated. I want to transform these documents with an extra field corresponding to unique values of this array. I tried aggregate + addToSet without success.
Data:
{..., "random_integers" : [1, 1, 2, 2, 3, 3]},
{..., "random_integers" : [2, 3, 4, 4, 5, 6]},
{..., "random_integers" : [9, 9, 8, 8, 7, 7]}
Expecting:
{
...
"random_integers" : [1, 1, 2, 2, 3, 3],
"unique_integers" : [1, 2, 3],
},
{
...
"random_integers" : [2, 3, 4, 4, 5, 6],
"unique_integers" : [2, 3, 4, 5, 6],
},
{
...
"random_integers" : [9, 9, 8, 8, 7, 7],
"unique_integers" : [7, 8, 9],
}
Try with aggregate + addToSet():
# Query
db.getCollection().aggregate([
{
$group: {
_id: '$_id',
unique_integers: {$addToSet: '$random_integers' }
}
}
])
# Results
{..., "unique_integers" : [[1, 1, 2, 2, 3, 3]]},
{..., "unique_integers" : [[2, 3, 4, 4, 5, 6]]},
{..., "unique_integers" : [[9, 9, 8, 8, 7, 7]]}
$addToSet add the whole list into a set, instead of each element of the array. I tried to combine $addToSet with $each but it is not recognize by mongo on a group:
# Query
db.getCollection().aggregate([
{
$group: {
_id: '$_id',
unique_integers: {$addToSet: { $each: '$random_integers' }}
}
}
])
# Error
Error: command failed: {
"ok" : 0,
"errmsg" : "Unrecognized expression '$each'",
"code" : 168,
"codeName" : "InvalidPipelineOperator"
} : aggregate failed
db.ints.aggregate( [
{ $project: {
random_integers: 1,
unique_integers: { $setIntersection: [ "$random_integers", "$random_integers" ] },
_id: 0
} }
] )
Suppose in collection I have following documents:
[
{"title": "t1", "fingerprint":[1, 2, 3]},
{"title": "t2", "fingerprint":[4, 5, 6]}
]
I want to query documents in which at least one element in fingerprint at given position is equal to my querying array.
For example:
query([1, 7, 9]) should return [{"title": "t1", "fingerprint":[1, 2, 3]}]
query([1, 5, 9]) should return [{"title": "t1", "fingerprint":[1, 2, 3]}, {"title": "t2", "fingerprint":[4, 5, 6]}]
but query([5,1,9]) should return none records, because neither of records have same value at any of the positions in fingerprint array.
How to write given query?
When you are trying to match only documents with arrays where the sequence [ 1 2, 3 ] appears in values field and only in that exact order, you can do it this way:
db.testcol.find()
{ "_id" : "first", "value" : [ 1, 2, 3 ] }
{ "_id" : "second", "value" : [ 4, 5, 6 ] }
{ "_id" : "third", "value" : [ 1, 12, 13 ] }
{ "_id" : "fourth", "value" : [ 3, 2, 1 ] }
{ "_id" : "fifth", "value" : [ 1, 12, 13, 2, 3 ] }
{ "_id" : "sixth", "value" : [ 3, 2, 1, 2, 3 ] }
> db.testcol.aggregate([{$addFields:{
cmp: {$in:[
{$literal:[1,2,3]},
{$map: {
input:{$range:[0, {$subtract:[{$size:"$value"},2]}]},
as:"l",
in: {$slice: [ "$value", "$$l", 3] }
}}
]}
}}])
{ "_id" : "first", "value" : [ 1, 2, 3 ], "cmp" : true }
{ "_id" : "second", "value" : [ 4, 5, 6 ], "cmp" : false }
{ "_id" : "third", "value" : [ 1, 12, 13 ], "cmp" : false }
{ "_id" : "fourth", "value" : [ 3, 2, 1 ], "cmp" : false }
{ "_id" : "fifth", "value" : [ 1, 12, 13, 2, 3 ], "cmp" : false }
{ "_id" : "sixth", "value" : [ 3, 2, 1, 2, 3 ], "cmp" : true }
What the $addFields stage does is checks if [1,2,3] appears in a list of three element arrays starting at position 0 of value array and moving forward till two positions before the end.
As you can see, it's now trivial to add a $match stage to filter out documents where cmp is not true.
You can use the .$index notation to perform such a search.
Example for your query([1, 7, 9])
db.coll.find({$or: [{"fingerprint.0": 1}, {"fingerprint.1": 7 }, {"fingerprint.2": 9}]})
{ "_id" : ObjectId("59170da907e34e73c0c93a9b"), "title" : "t1", "fingerprint" : [ 1, 2, 3 ] }
And query([1, 5, 9])
db.coll.find({$or: [{"fingerprint.0": 1}, {"fingerprint.1": 5 }, {"fingerprint.2": 9}]})
{ "_id" : ObjectId("59170da907e34e73c0c93a9b"), "title" : "t1", "fingerprint" : [ 1, 2, 3 ] }
{ "_id" : ObjectId("59170da907e34e73c0c93a9c"), "title" : "t2", "fingerprint" : [ 4, 5, 6 ] }
$in operator is used to match a value against list of values.
According to above mentioned description please try executing following query in MongoDB shell
db.collection.find({fingerprint:{$in:[1,7,9]}})
I was going through mongo db indexes and found this when i create index on multi key field and try to sort the result the behavior is strange.
For example:
> db.testIndexes.find();
{ "_id" : ObjectId("584e6ca8d23d3b48f9cb819d"), "type" : "depart", "item" : "aaa", "ratings" : [ 5, 8, 9 ] }
{ "_id" : ObjectId("584e6cb2d23d3b48f9cb819e"), "type" : "depart", "item" : "aaa", "ratings" : [ 2, 3, 4 ] }
{ "_id" : ObjectId("584e6cbdd23d3b48f9cb819f"), "type" : "depart", "item" : "aaa", "ratings" : [ 10, 6, 1 ] }
db.testIndexes.createIndex({ratings:1});
Now if i sue these queries :
db.testIndexes.find().sort({ratings:1}).pretty();
Result is like this
{
"_id" : ObjectId("584e6cbdd23d3b48f9cb819f"),
"type" : "depart",
"item" : "aaa",
"ratings" : [
10,
6,
1
]
}
{
"_id" : ObjectId("584e6cb2d23d3b48f9cb819e"),
"type" : "depart",
"item" : "aaa",
"ratings" : [
2,
3,
4
]
}
{
"_id" : ObjectId("584e6ca8d23d3b48f9cb819d"),
"type" : "depart",
"item" : "aaa",
"ratings" : [
5,
8,
9
]
}
and for query
db.testIndexes.find().sort({ratings:-1}).pretty();
Results are:
{
"_id" : ObjectId("584e6cbdd23d3b48f9cb819f"),
"type" : "depart",
"item" : "aaa",
"ratings" : [
10,
6,
1
]
}
{
"_id" : ObjectId("584e6ca8d23d3b48f9cb819d"),
"type" : "depart",
"item" : "aaa",
"ratings" : [
5,
8,
9
]
}
{
"_id" : ObjectId("584e6cb2d23d3b48f9cb819e"),
"type" : "depart",
"item" : "aaa",
"ratings" : [
2,
3,
4
]
}
As results does not seems to follow and order so can anyone help how mongo is sorting these results.
Thanks
Virendra
Well it does seem like the results are not following any order but actually they are. In your first sort {ratings:1}, what's happening here is the results are ordered by the smallest element in ratings. Since these are your lists:
[ 10, 6, 1 ] [ 2, 3, 4 ] [ 5, 8, 9 ]
So the list [ 10, 6, 1 ] smallest element is 1, the list [ 2, 3, 4 ] smallest element is 2 and the list [ 5, 8, 9 ] smallest element is 5. So the results are ordered in that way.
When you sort by descending, the same order happens but by maximum element in ratings.
Hope this helps.
I have mongodb documents like this:
[{ "_id" : 5, "type" : "food", "item" : "aaa", "ratings" : [ 5, 8, 9 ] },
{ "_id" : 7, "type" : "food", "item" : "bbb", "ratings" : [ 9, 8, 7 ] }]
I want to get only the field "rating" with its elements to be limited using $slice.
I am able to apply both of the operation individually like as given below:
a) for getting only rating field:
>db.test.find( { _id: 5 }, { ratings: 1} )
{ "_id" : 5, "ratings" : [ 5, 8, 9 ] }
b) for slicing the number of sub-records in rating array:
>db.test.find( { _id: 5 }, { ratings: { $slice: 2 } } )
{ "_id" : 5, "type" : "food", "item" : "aaa", "ratings" : [ 5, 8 ] }
My desired result is :
{ "_id" : 5, "ratings" : [ 5, 8] }
How to combine these two operations in an efficient way in a single query?
Thanks in advance.
there are two ways.
use agrregate with $project and $slice.
As shown by Chridam. in a find command you can tell which field you want to show.
when you do db.test.find({_id:5}) it is like -> select * from test where _id = 5.
but when you do db.test.find({_id:5},{type:0, item:0}) that make it selective on columns you want to view -> select ratings from test where _id = 5.
type:0 and item:0 means you are not interested in fetching these fields.
I have documents like
{
foo : [1, 2]
}
{
foo : [2, 3]
}
Given an array like
[2, 3, 4]
How would I select only the second document? i.e. select only the documents where all the values in foo match values in a given array.
Basically there are some ways to match array. There is no exact solution for your need.
Considering you have documents like :
{ "_id" : ObjectId("51b05a712961f4704684d901"), "x" : [ 6, 7, 8, 9 ] }
{ "_id" : ObjectId("51b05a712961f4704684d902"), "x" : [ 7, 8, 9, 10 ] }
{ "_id" : ObjectId("51b05a712961f4704684d903"), "x" : [ 8, 9, 10, 11 ] }
You can use query1 like:
db.collection.find({x:[3,4,5,6]})
The result is exact match only for arrays like x
result1:
{ "_id" : ObjectId("51b05a712961f4704684d8fe"), "x" : [ 3, 4, 5, 6 ] }
query1 will not match :
{ "_id" : ObjectId("51b05a712961f4704684d8fe"), "x" : [ 3, 4, 5] }
{ "_id" : ObjectId("51b05a712961f4704684d8fe"), "x" : [ 3, 4, 5, 6, 7] }
You can use : query2 like:
db.t.find({x:{$all:[3,4]}})
result2 can be:
{ "_id" : ObjectId("51b05a722961f4704684daf1"), "x" : [ 3, 4, 5, 6 ] }
{ "_id" : ObjectId("51b05c332961f4704684dce4"), "x" : [ 3, 4, 5 ] }
{ "_id" : ObjectId("51b05c772961f4704684dce5"), "x" : [ 3, 4, 5, 6, 7 ] }
You can use : query3 like:
db.t.find({x:{$in:[3,4]}})
Result3 would look like:
{ "_id" : ObjectId("51b05a722961f4704684daf1"), "x" : [ 3, 4, 5, 6 ] }
{ "_id" : ObjectId("51b05a722961f4704684daf2"), "x" : [ 4, 5, 6, 7 ] }
See this question also : mongodb array matching
So there is an open/unresolved ticket for a $subset operator which does what you likely to.