i have some data structured like this :
"location_identifiers": [
{
"permalink": "olivette-missouri",
"uuid": "e1774b1c-634d-4ea4-1414-cd8be15df631",
"location_type": "city",
"entity_def_id": "location",
"value": "Olivette"
},
{
"permalink": "missouri-united-states",
"uuid": "51a065b8-05d5-1a28-3fcd-1ad143f1f725",
"location_type": "region",
"entity_def_id": "location",
"value": "Missouri"
}
i want to restructure it to look like this, i am using mongodb compass:
"location_identifiers": [
{
"city": "Olivette",
"region": "Missouri"
}
]
i have tried unwind and project query but i am stuck, any help would be greatly appreciated.
Use $arrayToObject
db.collection.aggregate([
{
"$match": {}
},
{
"$set": {
"location_identifiers": [
{
"$arrayToObject": {
"$map": {
"input": "$location_identifiers",
"as": "item",
"in": {
k: "$$item.location_type",
v: "$$item.value"
}
}
}
}
]
}
}
])
mongoplayground
Related
Need help with mongo db query
Mondo db query - search for parents with state good and children with state bad or missing. output should be an array of all the children with state bad or missing from parents with good state
Below is the JSON list
[
{
"name": "parent-a",
"status": {
"state": "good"
},
"children": [
"child-1",
"child-2"
]
},
{
"name": "child-1",
"state": "good",
"parent": "parent-a"
},
{
"name": "child-2",
"state": {},
"parent": "parent-a"
},
{
"name": "parent-b",
"status": {
"state": "good"
},
"children": [
"child-3",
"child-4"
]
},
{
"name": "child-3",
"state": "good",
"parent": "parent-b"
},
{
"name": "child-4",
"state": "bad",
"parent": "parent-b"
},
{
"name": "parent-c",
"status": {
"state": "bad"
},
"children": [
"child-5",
"child-6"
]
},
{
"name": "child-5",
"state": "good",
"parent": "parent-c"
},
{
"name": "child-6",
"state": "bad",
"parent": "parent-c"
}
]
Expected output
"children": [
{
"name": "child-2",
"state": {}
},
{
"name": "child-4",
"state": "bad"
}
]
Any inputs would be appreciated. Thanks in advance :)
One option is to use $lookup* for this:
db.collection.aggregate([
{$match: {state: {$in: ["bad", {}]}}},
{$lookup: {
from: "collection",
localField: "parent",
foreignField: "name",
pipeline: [
{$match: {"status.state": "good"}}
],
as: "hasGoodParent"
}},
{$match: {"hasGoodParent.0": {$exists: true}}},
{$project: {name: 1, state: 1, _id: 0}}
])
See how it works on the playground example
*If your mongoDB version is lower than 5.0 you need to change the syntax a bit. Drop the localField and foreignField of the $lookup and replace with let and equality match on the pipeline
Here is an approach doing this all without a "$lookup" stage as performance usually suffers when involved. Basically we match all relevant children and parents and we group by the child id. if it has a parent (which means the parent has a "good" state, and a "child" which means the child has a "bad/{}" state then it's matched).
You should make sure you have the appropriate indexes to support the initial query.
Additionally I would personally recommend adding a boolean field on each document to mark wether it's a parent or a child. right now we have to use the field structure based on your input to mark this type but I would consider this a bad practice.
Another thing we did not discuss which doesn't seem possible from the current structure is recursion, can a child have children of it's own? Just some things to consider
db.collection.aggregate([
{
$match: {
$or: [
{
$and: [
{
"status.state": "good"
},
{
parent: {
$exists: false
}
},
{
"children.0": {
$exists: true
}
}
]
},
{
$and: [
{
"state": {
$in: [
"bad",
null,
{}
]
}
},
{
parent: {
$exists: true
}
}
]
}
]
}
},
{
$unwind: {
path: "$children",
preserveNullAndEmptyArrays: true
}
},
{
$addFields: {
isParent: {
$cond: [
{
$eq: [
null,
{
$ifNull: [
"$parent",
null
]
}
]
},
1,
0
]
}
}
},
{
$group: {
_id: {
$cond: [
"$isParent",
"$children",
"$name"
]
},
hasParnet: {
$sum: "$isParent"
},
hasChild: {
$sum: {
$subtract: [
1,
"$isParent"
]
}
},
state: {
"$mergeObjects": {
$cond: [
"$isParent",
{},
{
state: "$state"
}
]
}
}
}
},
{
$match: {
hasChild: {
$gt: 0
},
hasParnet: {
$gt: 0
}
}
},
{
$group: {
_id: null,
children: {
$push: {
name: "$_id",
state: "$state.state"
}
}
}
}
])
Mongo Playground
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
How to use projection to view only the below part from all docs of the collection?
Conditions:
I need to fetch only for "type": "DEBIT" and below 2 lines, NOT all other keys in the same type.
I dont want to view other types like Account, Deposit.
{
"key": "Call",
"enabled": true,
}
Sample Docs which i have in the below structure.
{
"_id": "1",
"menu": [
{
"type": "ACCOUNT",
"scope": "ACCOUNT",
"items": [
{
"key": "Call",
"enabled": true,
},
{
"key": "Work",
"enabled": true,
}
]
},
{
"type": "DEPOSIT",
"scope": "DEPOSIT",
"items": [
{
"key": "Call",
"enabled": true,
},
{
"key": "Work",
"enabled": true,
}
]
},
{
"type": "DEBIT",
"scope": "DEBIT",
"items": [
{
"key": "Call",
"enabled": true,
},
{
"key": "Work",
"enabled": true,
}
]
},
]
}
first, you need to $unwind the menu
then $match type debit
and filter array items and then group to create the final result
db.collection.aggregate([
{
"$unwind": "$menu"
},
{
$match: {
"menu.type": "DEBIT"
}
},
{
"$project": {
_id: 1,
"menu.items": {
"$filter": {
"input": "$menu.items",
"as": "s",
"cond": {
$and: [
{
$eq: [
"$$s.enabled",
true
]
},
{
$eq: [
"$$s.key",
"Call"
]
}
]
}
}
}
}
},
{
"$group": {
"_id": "$_id",
"menu": {
"$push": "$menu"
}
}
}
])
https://mongoplayground.net/p/kRChgF9rLsI
use $filter
db.collection.aggregate([
{
"$match": {
"menu.type": "DEBIT"
}
},
{
"$set": {
"menu": {
"$filter": {
"input": "$menu",
"as": "m",
"cond": {
$eq: [
"$$m.type",
"DEBIT"
]
}
}
}
}
}
])
mongoplayground
I have the following document structure:
{
"user_id": "102",
"roles": [{
"name": "superuser"
}, {
"name": "admin"
}, {
"name": "account_admin"
}]
}
And I need to unwind into the following result:
{
"user_id": "102290863723817866607",
"roles": [
0: "superuser",
1: "gsuite_admin",
2: "account_admin"
]
}
I can't quite figure out how to stage the aggregation pipeline to get this result
You can use a $projectcompined with a $map to select the name from your role`s object:
db.collection.aggregate([
{
"$project": {
"roles": {
"$map": {
"input": "$roles",
"as": "role",
"in": "$$role.name"
}
}
}
}
])
I have an object like this:
{
"_id": {
"$oid": "5f0047f02fd3fc048aab9ee9"
},
"array": [
{
"_id": {
"$oid": "5f00dcc23e12b8721e4f3672"
},
"name": "NAME",
"sub_array": [
{
"sub_array2": [
{
"$oid": "5f00e367f7b8747beddc6d31"
},
{
"$oid": "5f00f26c1facd18c5158d1d3"
}
],
"_id": {
"$oid": "5f00de99a8802e767885e72b"
},
"week_day": 1
},
{
"sub_array2": [
{
"$oid": "5f00e367f7b8747beddc6d31"
}
],
"_id": {
"$oid": "5f00f2501facd18c5158d1d2"
},
"week_day": 3
}
]
},
{
"_id": {
"$oid": "5f00f2401facd18c5158d1d1"
},
"name": "NAME1",
"sub_array": []
}
]
}
I want to replace sub_array ids with objects from another collection but that results converting array and sub_array to objects and losing all of the data like week_day.
Lookup:
'$lookup': {
'from': 'sati',
'localField': 'array.sub_array.sub_array2',
'foreignField': '_id',
'as': 'array.sub_array.sub_array2'
}
Result:
{
"_id": {
"$oid": "5f0047f02fd3fc048aab9ee9"
},
"array": {
"sub_array": {
"sub_array2": [
{
"_id": {
"$oid": "5f00e367f7b8747beddc6d31"
},
"endTime": "2020-07-03T12:06:50+0000",
"startTime": "2020-07-03T12:05:50+0000",
"data1": {
"$oid": "5f005e63ab1cbf2374d5163f"
}
},
{
"_id": {
"$oid": "5f00e367f7b8747beddc6d31"
},
"endTime": "2020-07-03T12:06:50+0000",
"startTime": "2020-07-03T12:05:50+0000",
"data1": {
"$oid": "5f005e63ab1cbf2374d5163f"
}
},
{
"_id": {
"$oid": "5f00e367f7b8747beddc6d31"
},
"endTime": "2020-07-03T12:06:50+0000",
"startTime": "2020-07-03T12:05:50+0000",
"data1": {
"$oid": "5f005e63ab1cbf2374d5163f"
}
}
]
}
}
}
Is there a way to "replace" the individual ids without converting entire arrays to objects and removing other fields. I know mongoose can do that but I'm not permitted to use it. None of the other questions helped (example).
It will override entire object key:value with $lookup result. Instead, store the lookup result in the sati variable and add an extra stage like shown below.
$map allows use iterate over an array and transform each item.
db.collection.aggregate([
{
"$lookup": {
"from": "sati",
"localField": "array.sub_array.sub_array2",
"foreignField": "_id",
"as": "sati"
}
},
{
$project: {
array: {
$map: {
input: "$array",
as: "array",
in: {
_id: "$$array._id",
name: "$$array.name",
sub_array: {
$map: {
input: "$$array.sub_array",
as: "sub_array",
in: {
_id: "$$sub_array._id",
week_day: "$$sub_array.week_day",
sub_array2: {
$filter: {
input: "$sati",
as: "sati_item",
cond: {
$in: [
"$$sati_item._id",
"$$sub_array.sub_array2"
]
}
}
}
}
}
}
}
}
}
}
}
])
MongoPlayground | Altenative with $mergeObjects