mongodb using $in for matching array with array inside aggregate - arrays

I am trying to perform an aggregate query in MongoDB. My target is to check if any values within an array exists in another array and conditionally introduce a third variable accordingly.
For example, I have an array A => ["a", "b"] and another array B => ["a", "c", "d"]
So in this case as "a" exists in both A & B, they should match.
aggregate.push({
"$addFields": {
"canClaim": {
$cond: [{
$in: [["a", "b"], ["a", "c", "d"]]
}, 1, 0]
}
}
})

Does this help you?
db.collection.aggregate({
"$addFields": {
"c": {
"$setIntersection": [
"$a",
"$b"
]
}
}
},
{
"$match": {
"c.1": {
"$exists": true
}
}
})
Mongo Playground

Related

jq filter: ok if item not in list

(jq newbie here, sorry if this question has an obvious answer :) )
I'd like to filter json based on whether a value is not in a list.
Here's a concrete example:
Input
[
{
"n": "A",
"a": 659533330984,
"vals": {
"n2": "B",
"b": 5193941030
}
},
{
"n": "A",
"a": 659533330984,
"vals": {
"n2": "C",
"b": 4872891707
}
},
{
"n": "B",
"a": 659533330984,
"vals": {
"n2": "C",
"b": 4872891707
}
}
]
Filter
[.n, .vals.n2] not in (["A", "B"], ["B", "C"])
Hence, in jq, I tried the following commands (also based on this related question)
jq '[ .[] | select([.n, .vals.n2] as $i | (["A", "B"], ["B", "C"]) | index($i) | not )]'
and
jq '[ .[] | select([.n, .vals.n2] != (["A", "B"], ["B", "C"]))]'
However, the both commands give the output
[
{
"n": "A",
"a": 659533330984,
"vals": {
"n2": "B",
"b": 5193941030
}
},
{
"n": "A",
"a": 659533330984,
"vals": {
"n2": "C",
"b": 4872891707
}
},
{
"n": "A",
"a": 659533330984,
"vals": {
"n2": "C",
"b": 4872891707
}
},
{
"n": "B",
"a": 659533330984,
"vals": {
"n2": "C",
"b": 4872891707
}
}
]
whereas this would be the desired output -- without duplicates and with logical AND of all "blacklisted" values:
[
{
"n": "A",
"a": 659533330984,
"vals": {
"n2": "C",
"b": 4872891707
}
}
]
It makes sense that the second command does not work, since if I understood correctly, the comma operator basically means that jq evaluates the expression once for every listed element - hence the duplicates. However simply piping through unique does not help since the output should not contain any of the filter pairs.
The only other idea I have at the moment is to pipe select through select through select... for each item in the "blacklist". However, I'd like to read the blacklist as an input -- I could dynamically create the command, but I was wondering whether there is a more beautiful solution? It feels like as if there must be...
I'd be very happy to hear your input on how to approach this best.
I'm using jq version jq-1.5-1-a5b5cbe.
The expression:
([.n, .vals.n2]) not in (["A", "B"], ["B", "C"])
would be equivalent to:
([.n, .vals.n2]) != ["A", "B"] and ([.n, .vals.n2]) != ["B", "C"]
As you have it here:
select([.n, .vals.n2] != (["A", "B"], ["B", "C"]))
it's not quite the same as the comma effectively makes it an or.
You'll need to do something more like this:
select([.n, .vals.n2] as $v | $v != ["A", "B"] and $v != ["B", "C"])
or
select([.n, .vals.n2] as $v | all(["A", "B"], ["B", "C"]; $v != .))
Also if you wanted to stick with your first approach, you would have to put the values in an array and not just separated by a comma.
select([.n, .vals.n2] as $i | [["A", "B"], ["B", "C"]] | index($i) | not)
When using index to find the index of an array (say $x), you have to write:
index([$x])
(This has to do with the fact that index is designed to work in a uniform way on both JSON strings and arrays.)
An efficient solution
[["A", "B"], ["B", "C"]] as $blacklist
| map( [.n, .vals.n2] as $i
| select( $blacklist | index([$i]) | not) )
From the jq FAQ
𝑸: Given an array, A, containing an item, X, how can I find the least index of X in A? Why does [1] | index(1) return null rather than 0? Why does [1,2] | index([1,2]) return 0 rather than null?
A: The simplest uniform method for finding the least index of X in an array is to query for [X] rather than X itself, that is: index([X]).
By contrast, the filter index([1,2]) attempts to find [1,2] as a subsequence of contiguous items in the input array. This is for uniformity with the behavior of t | index(s) where s and t are strings.
If X is not an array, then index([X]) may be abbreviated to index(X).

How to perform a "NOT IN" from an array in another array in aggregate (MongoDB) [duplicate]

I have an array A in memory created at runtime and another array B saved in a mongo database. How can I efficiently get all the elements from A that are not in B?
You can assume that the array stored in mongodb is several orders of magnitude bigger than the array created at runtime, for that reason I think that obtaining the full array from mongo and computing the result would not be efficient, but I have not found any query operation in mongo that allows me to compute the result I want.
Note that the $nin operator does the opposite of what I want, i.e., it retrieves the elements from B that are not in A.
Example:
Array A, created in my appliction at runtime, is [2, 3, 4].
Array B, stored in mongodb, is [1, 3, 5, 6, 7, 10].
The result I expect is [2, 4].
The only things that "modify" the document in response are .aggregate() and .mapReduce(), where the former is the better option.
In that case you are asking for $setDifference which compares the "sets" and returns the "difference" between the two.
So representing a document with your array:
db.collection.insert({ "b": [1, 3, 5, 6, 7, 10] })
Run the aggregation:
db.collection.aggregate([{ "$project": { "c": { "$setDifference": [ [2,3,4], "$b" ] } } }])
Which returns:
{ "_id" : ObjectId("596005eace45be96e2cb221b"), "c" : [ 2, 4 ] }
If you do not want "sets" and instead want to supply an array like [2,3,4,4] then you can compare with $filter and $in instead, if you have MongoDB 3.4 at least:
db.collection.aggregate([
{ "$project": {
"c": {
"$filter": {
"input": [2,3,4,4],
"as": "a",
"cond": {
"$not": { "$in": [ "$$a", "$b" ] }
}
}
}
}}
])
Or with $filter and $anyElementTrue in earlier versions:
db.collection.aggregate([
{ "$project": {
"c": {
"$filter": {
"input": [2,3,4,4],
"as": "a",
"cond": {
"$not": {
"$anyElementTrue": {
"$map": {
"input": "$b",
"as": "b",
"in": {
"$eq": [ "$$a", "$$b" ]
}
}
}
}
}
}
}
}}
])
Where both would return:
{ "_id" : ObjectId("596005eace45be96e2cb221b"), "c" : [ 2, 4, 4 ] }
Which is of course "not a set" since the 4 was provided as input "twice" and is therefore returned "twice" as well.

Updating nested Array elements in mongodb without reputations

i have collection called 'test' in that there is a document like:
{
"_id" : 1
"letters" : [
[ "A", "B" ],
[ "C", "D" ],
[ "A", "E", "B", "F" ]
]
}
if i updated the document by using $addToSet like this:
db.getCollection('test').update({"_id" : 1}, {$addToSet:{"letters": ["A", "B"] }})
it will not inserted another value. still the document look like
{
"_id" : 1
"letters" : [
[ "A", "B" ],
[ "C", "D" ],
[ "A", "E", "B", "F" ]
]
}
if im updating like this:
db.getCollection('test').update({"_id" : 1}, {$addToSet:{"letters": ["B", "A"] }})
Now it will update the document like:
{
"_id" : 1
"letters" : [
[ "A", "B" ],
[ "C", "D" ],
[ "A", "E", "B", "F" ],
[ "B", "A" ]
]
}
my requirment is if im give like this also (["B", "A"]), it will not update that document. Because the same letters are already present in the array.
could anyone can please give the solution.
#Shubham has the right answer. You should always sort your letters before saving into the document. So your original document should have been (I changed the third array):
{
"_id" : 1,
"letters" : [
[ "A", "B" ],
[ "C", "D" ],
[ "A", "B", "C", "F" ]
]
}
Then in your application do the sort. I'm including a Mongo Shell example here.
var input = ["B", "A"];
input.sort();
db.getCollection('test').update({"_id" : 1}, {$addToSet:{"letters": input}});
Try this answer , it works.
Use $push to insert any item in the array in your case.
db.getCollection('stack').update(
{ _id: 1 },
{ $push: { "letters": ["B", "A"] } }
)
For reference about $push you can view this link -
https://docs.mongodb.com/manual/reference/operator/update/push/

Get an array of values of a field in MongoDB

I have some entries like:
{ "name":"a", "value":10 }
{ "name":"b", "value":20 }
{ "name":"c", "value":10 }
...
And I can select names from a query with db.collection.find({"value":10},{"name":1, _id: false}). It gives me the following:
{ "name" : "a" }
{ "name" : "c" }
...
However, I want it to return an array of values, not a set of { key : value } pairs. (like [ "a", "c", ... ]). Is there a way to achieve this with only MongoDB queries or should I select and put them in an array in my application?
Current Output:
{ "name" : "a" }
{ "name" : "c" }
...
Expected output:
["a", "c", ...]
If the above is not a possible output, it may be also
{ "result": ["a", "c", ...] }
db.collection.distinct( "name", { "value": 10 })
from #Sagar Reddy comment will return a set array:
["a", "c"]
This is probably what you need. Aniway, a similar more complex query can be achieved using aggregation, where you can use extra aggregation stages.
db.collection.aggregate([
{ $match: { value: 10 }},
{ $group: { _id: null, result: { $addToSet: '$name' }}},
{ $project: { _id: 0, result: 1 }}
])
In the group stage, addToSet can be replaced with push if you want the same value to appear multiple times.
Output using addToSet:
{ "result" : [ "c", "a" ] }
Ex output using push:
{ "result" : [ "a", "c", "c" ] }

Store multiple values in single key in json

I need to store many values in single key of json. e.g.
{
"number" : "1","2","3",
"alphabet" : "a", "b", "c"
}
Something like this. Any pointers?
Use arrays:
{
"number": ["1", "2", "3"],
"alphabet": ["a", "b", "c"]
}
You can the access the different values from their position in the array. Counting starts at left of array at 0. myJsonObject["number"][0] == 1 or myJsonObject["alphabet"][2] == 'c'
{
"number" : ["1","2","3"],
"alphabet" : ["a", "b", "c"]
}
{
"success": true,
"data": {
"BLR": {
"origin": "JAI",
"destination": "BLR",
"price": 127,
"transfers": 0,
"airline": "LB",
"flight_number": 655,
"departure_at": "2017-06-03T18:20:00Z",
"return_at": "2017-06-07T08:30:00Z",
"expires_at": "2017-03-05T08:40:31Z"
}
}
};

Resources