MongoDB - Query collection's array contains all element in input array - mongodb

Let's say I have those documents below:
[
{
array : ['a', 'b' , 'c'],
},
{
array : ['b', 'd' , 'e'],
},
{
array : ['d', 'e' , 'f'],
},
]
and input array for query:
["b","d","e","f"]
Expected output:
['b', 'd' , 'e'],['d', 'e' , 'f']
Which query can I use to do that?
And how to filter which element is not in the document?
Expected result:
[
{
array : ['b', 'd' , 'e'],
missingElement : ['f']
},
{
array : ['d', 'e' , 'f'],
missingElement : ['b']
},
]

$expr - Allow to use aggregation operator.
1.1. $eq - Compare the result from 1.1.1 and 1.1.2 are equal.
1.1.1. $size - Get the size of array field.
1.1.2. $size - Get the size of array from the result 1.1.2.1.
1.1.2.1. $setIntersection - Intersect array field and input array, return the intersected value(s) in array.
db.collection.find({
$expr: {
$eq: [
{
$size: "$array"
},
{
$size: {
$setIntersection: [
"$array",
[
"b",
"d",
"e",
"f"
]
]
}
}
]
}
})
Sample Mongo Playground
Updated
For Aggregation query to find missing element(s):
$match - Filter the documents (as explained in the first answer for $expr).
$project - Decorate the output documents. For missingElement field, you need $filter operator to find each value in the input array does not exist ($not and $in) in the array.
db.collection.aggregate([
{
$match: {
$expr: {
$eq: [
{
$size: "$array"
},
{
$size: {
$setIntersection: [
"$array",
[
"b",
"d",
"e",
"f"
]
]
}
}
]
}
}
},
{
$project: {
array: 1,
missingElement: {
$filter: {
input: [
"b",
"d",
"e",
"f"
],
cond: {
$not: {
$in: [
"$$this",
"$array"
]
}
}
}
}
}
}
])
Sample Mongo Playground (Aggregation query)

Related

How to use Mongo Aggregation Pipeline to format array?

I have this document as part of an Aggregation Pipeline:
[
{
"mfe_average": [
[
true,
5.352702824879613
],
[
false,
3.2361364317753383
],
[
null,
2.675027181819201
]
]
}
]
How to convert to this format? Only the true values are Valid.
[
{
"mfe_average": [
[
"Valid",
5.352702824879613
],
[
"Invalid",
3.2361364317753383
],
[
"Invalid",
2.675027181819201
]
]
}
]
The query may look complex.
$map - Iterates the element in mfe_average and returns a new array.
1.1. $concatArrays - Combine arrays into one.
1.1.1. $cond - Set the value ("Valid"/"Invalid") by getting the first item of the nested array and comparing the value. It results in an array.
1.1.2. $slice - Take all the values in the nested array except the first item.
db.collection.aggregate([
{
$set: {
mfe_average: {
$map: {
input: "$mfe_average",
as: "avg",
in: {
$concatArrays: [
[
{
$cond: {
if: {
$eq: [
{
$arrayElemAt: [
"$$avg",
0
]
},
true
]
},
then: "Valid",
else: "Invalid"
}
}
],
{
$slice: [
"$$avg",
{
$multiply: [
{
$subtract: [
{
$size: "$$avg"
},
1
]
},
-1
]
}
]
}
]
}
}
}
}
}
])
Demo # Mongo Playground
If your nested array contains only 2 items, you may not require to write the complex $slice query as above. Instead, just provide -1 to take the last item of the nested array.
{
$slice: [
"$$avg",
-1
]
}
You can use $map operator to reconstruct the array,
$map to iterate loop of mfe_average array
$arrayElemAt to get specific position's element from array
$cond to check if the first element is true then return "Valid" otherwise "Invalid"
db.collection.aggregate([
{
$addFields: {
mfe_average: {
$map: {
input: "$mfe_average",
in: [
{
$cond: [
{ $arrayElemAt: ["$$this", 0] },
"Valid",
"Invalid"
]
},
{ $arrayElemAt: ["$$this", 1] }
]
}
}
}
}
])
Playground

check if a field value exit in array - MongoDB

I'm using MongoDB and i have the following schema of person collection:
Person {
age: [number]
}
i want to check if the age of a person exist in array, for exemple [15, 20, 12, 0]. i need something like $in operator but in reverse.
schema:
initiatives : {
ressources: [
{ departement: ObjectId }
{ departement: ObjectId }
]
)
You can use $expr with $in:
Person.find({ $expr: { $in: [ "$age", [15, 20, 12, 0] ] } })
EDIT: to compare arrays you need $setIntersection and $size operators, try:
Person.find({
$expr: {
$gt: [
{
$size: {
$setIntersection: [
[
"15",
"a",
"12",
"0"
],
"$age.x"
]
}
},
0
]
}
})

Aggregate nested objects mongodb [duplicate]

This question already has answers here:
Retrieve only the queried element in an object array in MongoDB collection
(18 answers)
Closed 4 years ago.
Could you please help me to write some sort of aggregation query using mongodb.
I have next data structure.
[
{
id: 1,
shouldPay: true,
users: [
{
id: 100,
items: [{...}],
tags: ['a', 'b', 'c', 'd']
},
{
id: 100,
items: [{...}],
tags: ['b', 'c', 'd']
},
{
id: 100,
items: [{...}],
tags: ['c', 'd']
}
],
}
]
In result I want to get something like that:
[
{
id: 1,
shouldPay: true,
user: {
id: 100,
items: [{...}],
tags: ['a', 'b', 'c', 'd']
}
}
]
The main idea is to select a specific user that has "a" letter or list of letters ['a', 'b'] in tags.
You can use below aggregation
Use $match at the starting of the pipeline to filter out the documents which don't contain "a" and "b" in tags array. And then use $filter with $setIsSubset to filter out the nested array.
$arrayELemAt to return the specified element from the array.
db.collection.aggregate([
{ "$match": { "users.tags": { "$in": ["a", "b"] }}},
{ "$project": {
"users": {
"$arrayElemAt": [
{ "$filter": {
"input": "$users",
"cond": { "$setIsSubset": [["a", "b"], "$$this.tags"] }
}},
0
]
}
}}
])
You need to use $unwind along with $filter:
db.collection.aggregate([
{
$unwind: "$users"
},
{
$match: {
"users.tags": "a"
}
}
])
Result:
[
{
"_id": ObjectId("5a934e000102030405000000"),
"id": 1,
"shouldPay": true,
"users": {
"id": 100,
"tags": [
"a",
"b",
"c",
"d"
]
}
}
]

MongoDB: Ordered matching of array entries

I have a document which looks like this:
"tokens":
[
{
"index": 1,
"word": "I",
"pos": "NNP",
},
{
"index": 2,
"word": "played",
"pos": "VBZ",
},
{
"index": 3,
"word": "football",
"pos": "IN",
}
]
And my query is:
db.test.find({
$and: [
{
'tokens.word': 'I'
},
{
tokens: {
$elemMatch: {
word: /f.*/,
pos: 'IN'
}
}
}
]
})
The output of my query is the document above. But the result should be no match in this case as I'm searching for
word: "I" followed by [word: /f.*/ and pos:'IN']
which doesn't match the tokens array in the document since the token I is followed by played and then football. However, in the query the order of the filters is different as the searching is started with
word: "I"
followed by
f.*
[football in this case].
The $and operator is a purely logical one where the order of the filter conditions does not play any role.
So the following queries are absolutely equivalent from a result set point of view:
$and: [
{ "a": 1 },
{ "b": 2 }
]
$and: [
{ "b": 2 },
{ "a": 1 }
]
The documentation states:
$and performs a logical AND operation on an array of two or more
expressions (e.g. , , etc.) and selects the
documents that satisfy all the expressions in the array. The $and
operator uses short-circuit evaluation. If the first expression (e.g.
) evaluates to false, MongoDB will not evaluate the
remaining expressions.
In your example, the "index1" entry matches the first filter "tokens.word": "I" and the "index3" document matches the second $elemMatch filter. So the document has to get returned.
UPDATE:
Here is an idea - more of a starting point really - for you to get closer to what you want:
db.collection.aggregate({
$addFields: { // create a temporary field
"differenceBetweenMatchPositions": { // called "differenceBetweenMatchPositions"
$subtract: [ // which holds the difference between
{ $indexOfArray: [ "$tokens.word", "I" ] }, // the index of first element matching first condition and
{ $min: { // the lowest index of
$filter: { // a filtered array
input: { $range: [ 0, { $size: "$tokens" } ] }, // that holds the indices [ 0, 1, 2, ..., n ] where n is the number of items in the "tokens" array - 1
as: "this", // which we want to access using "$$this"
cond: {
$let: {
vars: { "elem": { $arrayElemAt: [ "$tokens", "$$this" ] } }, // get the n-th element in our "tokens" array
in: {
$and: [
{ $eq: [ "$$elem.pos", "IN" ] }, // "pos" field must be "IN"
{ $eq: [ { $substrBytes: [ "$$elem.word", 0, 1 ] }, "f" ] } // 1st character of the "word" must be "f"
]
}
}
}
}
}
}]
}
}
}, {
$match: {
"differenceBetweenMatchPositions": { $lt: 0 } // we only want documents where the first condition is matched by an item in our array before the second one gets matched, too.
}
})

Mongo $concatArrays even when null

I have a large set of documents that may have two arrays or one of the two. I want to merge them in a $project.
I am currently using $concatArrays but as the documentation says it returns null when one of the arrays is null. I can figure out how to add a condition statement in there that will either return the $concatArrays or what ever array is in there.
Example
I have:
{_id: 1, array1: ['a', 'b', 'b'], array2: ['e', 'e']}
{_id: 2, array1: ['a', 'b', 'b']}
{_id: 3, array2: ['e', 'e']}
I want:
{_id: 1, combinedArray: ['a','b', 'b', 'e', 'e']}
{_id: 2, combinedArray: ['a','b', 'b']}
{_id: 3, combinedArray: ['e', 'e']}
I tried:
$project: {
combinedArray: { '$concatArrays': [ '$array1', '$array2' ] }
}
//output (unexpected result):
{_id: 1, combinedArray: ['a','b', 'b', 'e', 'e']}
{_id: 2, combinedArray: null}
{_id: 3, combinedArray: null}
I also tried:
$project: {
combinedArray: { '$setUnion': [ '$array1', '$array2' ] }
}
//output (unexpected result):
{_id: 1, combinedArray: ['a','b', 'e']}
{_id: 2, combinedArray: ['a','b']}
{_id: 3, combinedArray: ['e']}
As documentation for $concatArrays says
If any argument resolves to a value of null or refers to a field that
is missing, $concatArrays returns null.
So we need to be sure that we are not passing arguments which refer to a missing field or null. You can do that with $ifNull operator:
Evaluates an expression and returns the value of the expression if the
expression evaluates to a non-null value. If the expression evaluates
to a null value, including instances of undefined values or missing
fields, returns the value of the replacement expression.
So just return empty array if filed expression will not evaluate to non-null value:
db.collection.aggregate([
{$project: {
combinedArray: { '$concatArrays': [
{$ifNull: ['$array1', []]},
{$ifNull: ['$array2', []]}
] }
}
}
])
You can easily achieve this with the $ifNull operator:
db.arr.aggregate([
{
$project:{
combinedArray:{
$concatArrays:[
{
$ifNull:[
"$array1",
[]
]
},
{
$ifNull:[
"$array2",
[]
]
}
]
}
}
}
])
output:
{ "_id" : 1, "combinedArray" : [ "a", "b", "b", "e", "e" ] }
{ "_id" : 2, "combinedArray" : [ "a", "b", "b" ] }
{ "_id" : 3, "combinedArray" : [ "e", "e" ] }
I tried to do this with nested $cond, answer with $ifNull is better, but still posting my answer.
db.getCollection('test').aggregate( [{
$project: {
combinedArray: { $cond: [
{ $and: [ { $isArray: ["$array1"] }, { $isArray: ["$array2"] } ] },
{ '$concatArrays': [ '$array1', '$array2' ] },
{ $cond: [
{ $isArray: ["$array1"] },
"$array1",
"$array2"
] }
] }
}
}] )