$in Query on Array Field Mongo db - arrays

I have a doc like this
{
"_id" : "oidfi",
"users": [
{
"_id": "q",
"tags": ["a", "b", "c"],
"age": 20
},
{
"_id": "q",
"tags": ["x", "y", "z"],
"age": 30
}
],
"type": "repo"
}
I want to filter the users array with several fields ( I'm able to do that with the below query )
{
"$match": {
"$and": [
{
"_id": "a506f8af6f510f616bea14715276d474d2b363f0aef10443cc0f11559b139147"
}
]
}
},
{
"$project": {
"users": {
"$filter": {
"input": "$users",
"as": "users",
"cond": {
"$and": [
{
"$gte": [
"$$user.age",
15
]
}
]
}
}
}
}
}
]
is there a way for me to add condition where I give a list of tags items and get the users who are matching at least one item from their tags array
$$user.tags.containsAny(["a", "x"])
Thanks

You can do the followings in an aggregation pipeline:
$setIntersection to find out the intersection of the users.tags array and your input array i.e. ["a", "x"] in your example
$size to find the size of array in step 1
$match by the size of step 2 larger than 0
"anyTagsMatched": {
$gt: [
{
$size: {
"$setIntersection": [
"$users.tags",
[
"a",
"x"
]
]
}
},
0
]
}
Here is the Mongo playground for your reference.

You may have like following
db.collection.aggregate([
{
"$project": {
"users": {
"$filter": {
"input": "$users",
"cond": {
"$and": [
{ "$gte": [ "$$this.age", 15 ] },
{
$gt: [
{
$size: {
"$setIntersection": [
"$$this.tags",
[ "a", "x" ]
]
}
},
0
]
}
]
}
}
}
}
}
])
Working Mongo playground

Related

Join collection with array object field with another collection in MongoDB

I'm working in MongoDB and getting stuck at one aggregation case. Let me show you my collection.
First collection (data):
[
{
"_id": "8e7b3fa0-4230-448c-8f70-1d7300632834",
"data": [
{
"animal" : "7d44251a-b308-4deb-875a-33ef0a69fe2b",
"place": "Chennai"
},
{
"animal" : "fcfdd527-5885-48b0-a91f-03f72f78528f",
"place": "Kolkata"
}
]
}
]
Second collection (Animal):
[
{
"_id": "7d44251a-b308-4deb-875a-33ef0a69fe2b",
"name": "Dog"
},
{
"_id": "7d44251a-b308-4deb-875a-33ef0a69fe2b",
"name": "Cat"
}
]
I'm using this query:
db.data.aggregate([
{
"$lookup": {
"from": "animal",
"localField": "data.animal",
"foreignField": "_id",
"as": "doc"
}
},
{
"$unwind": "$doc"
},
{
"$project": {
"_id": 1,
"data.animal": "$doc.name",
"data.place": 1
}
}
])
and it result me this
[
{
"_id": "8e7b3fa0-4230-448c-8f70-1d7300632834",
"data": [
{
"animal": "Dog",
"place": "Chennai"
},
{
"animal": "Dog",
"place": "Kolkata"
}
]
},
{
"_id": "8e7b3fa0-4230-448c-8f70-1d7300632834",
"data": [
{
"animal": "Cat",
"place": "Chennai"
},
{
"animal": "Cat",
"place": "Kolkata"
}
]
}
]
Where I'm expecting like this
[
{
"_id": "8e7b3fa0-4230-448c-8f70-1d7300632834",
"data": [
{
"animal": "Dog",
"place": "Chennai"
},
{
"animal": "Cat",
"place": "Kolkata"
}
]
}
]
Mongo Playground
Also sharing this question in Mongo playgroud. Thanks in advance!!
Solution 1
$unset - Deconstruct the data array into multiple documents.
$lookup - Perform join with animal collection.
$project - Decorate the output document. For data.animal field, get the first value via $first.
$group - Group by _id. Push the data document into the data array.
db.data.aggregate([
{
"$unwind": "$data"
},
{
"$lookup": {
"from": "animal",
"localField": "data.animal",
"foreignField": "_id",
"as": "doc"
}
},
{
"$project": {
"_id": 1,
"data.animal": {
$first: "$doc.name"
},
"data.place": 1
}
},
{
$group: {
_id: "$_id",
data: {
$push: "$data"
}
}
}
])
Demo Solution 1 # Mongo Playground
Solution 2
$lookup
$set - Set data field.
2.1. $map - Iterate the data array and returns a new array.
2.1.1. $mergeObjects - Merge current iterated document with place field and the document from 2.1.1.1.
2.1.1.1. $first - Get the first document from the filtered doc arrays by matching the ids via $filter.
$unset - Remove _id and animals._id fields.
db.data.aggregate([
{
"$lookup": {
"from": "animal",
"localField": "data.animal",
"foreignField": "_id",
"as": "doc"
}
},
{
$set: {
data: {
$map: {
input: "$data",
as: "data",
in: {
$mergeObjects: [
{
place: "$$data.place"
},
{
$first: {
$filter: {
input: "$doc",
cond: {
$eq: [
"$$this._id",
"$$data.animal"
]
}
}
}
}
]
}
}
}
}
},
{
$unset: [
"doc",
"data._id"
]
}
])
Demo Solution 2 # Mongo Playground

MongoDB - Match the key

I want to $match in a MongoDB, the number of documents in thousands, so looking for dynamic sol:
$doc.k equal to $info.data.k after k, $match then we have a document that contains only information that $matchs, see the expected output document. If you have any questions let me know.
[
{
"doc": {
"k": "ABC",
"v": {
"sec": 0
}
},
"info": [
{
"data": [
{
"k": "XYZ",
"v": {
"know": "alpha"
}
},
{
"k": "ABC",
"v": {
"know": "alpha"
}
}
]
}
]
}
]
The expected output document will look like this
[
{
"doc": {
"k": "ABC",
"v": {
"sec": 0
}
},
"info": [
{
"data": [
{
"k": "ABC",
"v": {
"know": "alpha"
}
}
]
}
]
}
]
$set - Update info field.
1.1. $map - As info is an array, need to iterate each document in the info array and return a new array.
1.1.1. $filter - Filter the document by doc.k and current iterate k value.
db.collection.aggregate([
{
$set: {
info: {
$map: {
input: "$info",
in: {
data: {
$filter: {
input: "$$this.data",
cond: {
$eq: [
"$doc.k",
"$$this.k"
]
}
}
}
}
}
}
}
}
])
Sample Mongo Playground

How to preserve the order of mongoID in an array after a mongo query

Im trying to query an array of mongoIds and return data in the same order, however whenever I try to do this the order is not the same as declared below.
Sample code:
let id = [
"620d323b8d0273004c8993a4",
"620d32498d0273004c8993a5",
"61e730a745171f002d85df4b",
"620cfe708d0273004c89933a"
]
let test = await User.find({'_id': {$in: id}}, 'id text')
console.log(test)
After logging test the output returned as:
[
{"_id": "61e730a745171f002d85df4b"},
{"_id": "620cfe708d0273004c89933a"},
{"_id": "620d323b8d0273004c8993a4"},
{"_id": "620d32498d0273004c8993a5"}
]
I want to return this as the first code snippet, any help would be appreciated.
db.collection.aggregate([
{
"$match": {
_id: {
"$in": [
"620d323b8d0273004c8993a4",
"620d32498d0273004c8993a5",
"61e730a745171f002d85df4b",
"620cfe708d0273004c89933a"
]
}
}
},
{
"$group": {
"_id": null,
"docs": {
"$push": "$$ROOT"
}
}
},
{
"$set": {
"docs": {
"$map": {
"input": [
"620d323b8d0273004c8993a4",
"620d32498d0273004c8993a5",
"61e730a745171f002d85df4b",
"620cfe708d0273004c89933a"
],
"as": "s",
"in": {
"$filter": {
"input": "$docs",
"as": "d",
"cond": {
"$eq": [
"$$d._id",
"$$s"
]
}
}
}
}
}
}
},
{
"$unwind": "$docs"
},
{
"$replaceWith": {
"$first": "$docs"
}
}
])
mongoplayground

Split arrays in set mongodb

I've been trying to make a set of strings < or arrays > but my main issue is that I can't use $unwind because of the other data.
To make it more clear I'll show an example:
This is how part of my documents look like:
{
"key": [
"a",
"b",
"c"
]
},
{
"key": [
"b",
"d"
]
},
{
"key": [
"e"
]
}
And I want to get an array like this:
{
"keys": [
"a",
"b",
"c",
"d",
"e"
]
}
or this:
{
"keys": [
["a"],
["b"],
["c"],
["d"],
["e"]
]
}
I just need unique values in there.
It would be so easy if I could use $unwind and then $addToSet. But as I said before I can't because of the other data that I need.
I was trying to use $cond inside the $addToSet but I don't know how to iterate through an array.
This pipeline doesn't work ->
db.collection.aggregate([
{
"$group": {
"_id": null,
"keys": {
"$addToSet": {
"$concatArrays": [
"$key",
"$this"
]
}
},
}
}
])
Maybe something like this:
{
"$group": {
"_id": null,
"keys": {
"$addToSet": {
"$cond": {
if: {
"$gt": [
{"$size": "$key"},
1
]
},
then: "<SPLIT THE ARRAY IN THE AMOUNT OF ELEMENTS>",
else: "$key"
}
}
}
}
}
I tried this but it doesn't work either.
db.collection.aggregate([
{
"$project": {
"keys": {
"$map": {
"input": {
"$range": [
0,
{
"$size": "$key"
},
1
]
},
as: "index",
in: {
$slice: [
"$key",
"$$index",
1
]
}
}
}
}
},
{
"$group": {
"_id": null,
"keys": {
"$addToSet": {
"$concatArrays": [
"$keys"
]
}
},
}
}
])
Here is my code snippet: https://mongoplayground.net/p/sMENXyrbQgI
Could someone please guide me! I'll appreciate it so much.
So the idea is to use $setUnion along with $reduce to pick unique values from the array:
Solution in MongoPlayground
Try this query:
db.collection.aggregate([
{
$group: {
_id: null,
"keys": { $push: "$key" }
}
},
{
$addFields: {
"keys": {
$reduce: {
input: "$keys",
initialValue: [],
in: {
$setUnion: ["$$value", "$$this"]
}
}
}
}
}
])
Output:
{
"_id" : null,
"keys" : [
"a",
"b",
"c",
"d",
"e"
]
}
Test data in collection:
[
{
"key": [
"a",
"b",
"c"
]
},
{
"key": [
"b",
"d",
"a",
]
},
{
"key": [
"e",
"b"
]
}
]

Multiply Varying number of Values Inside array: Mongodb

I have arranged my data so that the documents belonging to the same customer id are aggregated into a single collection. The data format is as follows.
{
"items": [
{
"stock_code": [
"22617",
"22768",
"20749"
],
"description": [
"DESIGN",
"FAMILY PHOTO FRAME",
"ASSORTED CASES"
],
"quantity": [
18,
12,
84
],
"unit_price": [
4.95,
9.95,
6.35
]
}
],
"_id": 581485,
"customer_id": 17389,
"country": "United Kingdom"
}
I need to multiply the values of array quantity with corresponding unit_price and get a total for multiple documents in a new field. I have tried using the $reduce function and $map function to get the output but both of them result in "error"
Multiply only supports numeric types, and not arrays
Could you please suggest how should i go about accomplishing this.
Codes tried:
"$addFields": {"order_total" :
{
"$sum": {
"$map": {
"input": "$items",
"as": "items",
"in": { "$multiply": [
{ "$ifNull": [ "$$items.quantity", 0 ] },
{ "$ifNull": [ "$$items.unit_price", 0 ] }
]}
}
}
}
}
Second:
"order_total" : {
"$reduce" : {
"input" : "$items",
"initialValue" : Decimal128("0.00"),
"in": {
"$sum" : [
"$$value",
{"$multiply" : [ "$$this.quantity", "$$this.unit_price" ] }
]}
}
}
The expected result needs to add a new field of "total" by multiplying the corresponding entries of unit_price with quantity. The error message is that of multiply only supports numeric types and not arrays.
I would decompose the problem into solvable chunks beginning with the smallest unit and start with the two arrays quantity and unit_price.
So given just a document with the structure
{
"quantity": [ 18, 12, 84 ],
"unit_price": [ 4.95, 9.95, 6.35 ]
}
We can add another field with the totals for each element in both arrays i.e.
{
"quantity": [ 18, 12, 84 ],
"unit_price": [ 4.95, 9.95, 6.35 ],
"total": [ 89.1, 119.4, 533.4 ]
}
This field can be computed using $range within $map as
{
"total": {
"$map": {
"input": { "$range": [ 0, { "$size": "$quantity" } ] },
"as": "idx",
"in": {
"$let": {
"vars": {
"qty": { "$arrayElemAt": [ "$quantity", "$$idx" ] },
"price": {
"$ifNull": [
{ "$arrayElemAt": [ "$unit_price", "$$idx" ] },
0
]
}
},
"in": { "$multiply": [ "$$qty", "$$price" ] }
}
}
}
}
}
This then becomes basis for calculating the order_total field with two pipeline stages for clarity (although can be composed into a single pipeline with $reduce for brevity)
var totalMapExpression = {
"$map": {
"input": { "$range": [ 0, { "$size": "$$item.quantity" } ] },
"as": "idx",
"in": {
"$let": {
"vars": {
"qty": { "$arrayElemAt": [ "$$item.quantity", "$$idx" ] },
"price": {
"$ifNull": [
{ "$arrayElemAt": [ "$$item.unit_price", "$$idx" ] },
0
]
}
},
"in": { "$multiply": [ "$$qty", "$$price" ] }
}
}
}
};
db.collection.aggregate([
{ "$addFields": {
"items": {
"$map": {
"input": "$items",
"as": "item",
"in": {
"quantity": "$$item.quantity",
"unit_price": "$$item.unit_price",
"stock_code": "$$item.stock_code",
"description": "$$item.description",
"total": { "$sum": totalMapExpression }
}
}
}
} },
{ "$addFields": {
"order_total": { "$sum": "$items.total" }
} }
])
Try the below query:
db.collection.aggregate(
[{ $unwind: { path: "$items",} },
{ $unwind: { path: "$items.quantity",} },
{ $unwind: { path: "$items.unit_price",} },
{ $addFields: { 'total': {$multiply: ["$items.quantity", "$items.unit_price"] }} }])

Resources