mongodb $lookup has_many association from embedded document - json

I have a boards collection, a lists collection, and a cards collection. An array of lists is embedded in a board document. I am trying to get an output that looks like this:
{
_id: 1,
title: "a board",
lists: [
{
_id: 1,
title: "a list",
cards: [ { _id: 1, title: "a card", list_id: 1 }, { _id: 2, title: "another card", list_id: 1 } ]
},
...
]
}
I want to nest the cards in the list it belongs to. The card document has a list_id field. I tried this:
db.boards.aggregate([
{ '$match' => { _id: 1 } },
{ '$lookup' => {
from: "cards",
localField: "lists._id",
foreignField: "list_id",
as: "cards"
} },
])
But that resulted in:
{
_id: 1,
title: "a board",
lists: [ { _id: 1, title: "a list" } ],
cards: [ { _id: 1, title: "a card", list_id: 1 }, { _id: 2, title: "another card", list_id: 1 } ]
}
I know that I have to use $unwind to get the result I want, but I can't get it to work

You need one additional aggregation pipeline step to "merge" these two lists and you can achieve it by running $map with embedded $filter. It simply works as a "join" operation on two arrays:
{
$project: {
_id: 1,
title: 1,
lists: {
$map: {
input: "$lists",
as: "list",
in: {
$mergeObjects: [
"$$list",
{
cards: {
$filter: {
input: "$cards",
cond: { $eq: [ "$$this.list_id", "$$list._id" ] }
}
}
}
]
}
}
}
}
}
Mongo Playground

Related

how to query mongodb nested query three level?

I have three collections (docs example struct see below :)
my mongodb version is 4.4
section
{
_id: ObjectId('62b131211331e0e9ba284187'),
name: 'name1',
},
{
_id: ObjectId('62b131211331e0e9ba284187'),
name: 'name2',
}
...
2.problems
{
_id: ObjectId('62b13d5f1331e0e9ba2841e6'),
title: 'title1',
score: 10,
section_id: ObjectId('62b131211331e0e9ba284188')
},
{
_id: ObjectId('62b13d5f1331e0e9ba2841e6'),
title: 'title2',
score: 20,
section_id: ObjectId('62b131211331e0e9ba284188')
},
{
_id: ObjectId('62b13d5f1331e0e9ba2841e6'),
title: 'title3',
score: 30,
section_id: ObjectId('62b131211331e0e9ba284188')
}
...
3.choices
{
_id: ObjectId('62b164ae1331e0e9ba284236'),
text: 'text1',
value: 0,
checked: false,
problem_id: ObjectId('62b13d5f1331e0e9ba2841ed')
},
{
_id: ObjectId('62b164ae1331e0e9ba284236'),
text: 'text2',
value: 0,
checked: false,
problem_id: ObjectId('62b13d5f1331e0e9ba2841ed')
},
{
_id: ObjectId('62b164ae1331e0e9ba284236'),
text: 'text3',
value: 0,
checked: false,
problem_id: ObjectId('62b13d5f1331e0e9ba2841ed')
},
...
the relationship of those collections
sections has many problems
problems has many choices
My question is :
Now I have an array of sections's id like below:
[
ObjectId('62b131211331e0e9ba284188'),
ObjectId('62b131211331e0e9ba28418a'),
ObjectId('62b131211331e0e9ba28418c')
]
I want to perform one query get the result like below:
[
{
_id: ObjectId('62b131211331e0e9ba284187'),
name: 'name1',
problems: [
{
_id: ObjectId('62b13d5f1331e0e9ba2841e6'),
title: 'title1',
score: 10,
section_id: ObjectId('62b131211331e0e9ba284188'),
choices: [
{}, // choice doc
]
},
{
_id: ObjectId('62b13d5f1331e0e9ba2841e6'),
title: 'title2',
score: 10,
section_id: ObjectId('62b131211331e0e9ba284188'),
choices: [
{}, // choice doc
]
},
...
]
},
{
_id: ObjectId('62b131211331e0e9ba284187'),
name: 'name1',
problems: [
{},
]
},
{
_id: ObjectId('62b131211331e0e9ba284187'),
name: 'name1',
problems: [
{},
]
}
]
I have try run this query in my mongoexpress but it didn't work:
// match stage (ps: start with sections collection)
{
"_id": {
$in: [
ObjectId('62b131211331e0e9ba284188'),
ObjectId('62b131211331e0e9ba28418a'),
ObjectId('62b131211331e0e9ba28418c')
]
}
// lookup stage
{
from: 'problems',
let: {"sid": "$_id"},
pipeline: [
{
"$match": {
"$expr": {
"$eq": ["$section_id", "$$sid"]
},
},
},
{
"$lookup": {
from: "choices",
let: {"chid": "$_id"},
pipeline: [
{
"$match": {
"$expr": {},
},
},
],
as: "choices"
},
},
],
as: "problems"
}

How to output object of an array in the root of the document in MongoDB using aggregation?

I have this document :
{"_id":"1", "elem":"ok",
"arrayOfObjects":[
{"type":"k","fieldx":"lol"},
{"type":"SndObject","fieldy":"foo"},
{"type":"Object1","fieldx":"bob"}
]
}
what is the aggregation to have this output:
{"_id":"1", "elem":"ok",
"Object1":[
{"type":"Object1","fieldx":"lol"},
{"type":"Object1","fieldx":"bob"}
],
"SndObject":[{"type":"SndObject","fieldy":"foo"}]
}
I found a way out, but it need me to know all the type i have:
{
"$addFields" : {
"Object1" : {
"$filter": {
"input": "$arrayOfObjects",
"as": "types",
"cond": {
"$and": [{ "$eq": [ "$$types.type", "Object1" ] }]
}
}
}
}
}
It would be best if i can loop over my arrayOfObjects and get the same result without pre knowledge of the type.
Might be there would be more easy option than this,
$unwind deconstruct arrayOfObjects array
$group by _id, type and elem, construct array of arrayOfObjects
$arrayToObject convert k and v from array to object
$group by _id and merge objects in root
db.collection.aggregate([
{ $unwind: "$arrayOfObjects" },
{
$group: {
_id: {
type: "$arrayOfObjects.type",
_id: "$_id"
},
elem: { $first: "$elem" },
arrayOfObjects: { $push: "$arrayOfObjects" }
}
},
{
$group: {
_id: "$_id._id",
elem: { $first: "$elem" },
arrayOfObjects: {
$mergeObjects: {
$arrayToObject: [[
{
k: "$_id.type",
v: "$arrayOfObjects"
}
]]
}
}
}
}
])
Playground

MongoDb: Retrieve all documents higher than the average value of a column

I have created a collection using this piece of code.
db.createCollection("item", {
validator: {
$jsonSchema: {
bsonType: "object",
required: ["_id", "item_name", "unit_price"],
properties: {
_id: {
bsonType: "string",
description: "must be a string and is required",
minLength: 3,
maxLength: 5,
pattern: "I[0-9]*$"
},
item_name: {
bsonType: "string",
description: "must be a string and is required"
},
unit_price: {
bsonType: "double"
}
}
}
},
validationLevel: "moderate"
})
and I have inserted records in the Item collection. Now I wish to list the items whose "unit_price" is less than the average price of all items.
What I have tried is
db.item.aggregate([{
$group: {
_id: null,
averageUnitPrice: {
$avg: "$unit_price"
}
}
}])
I have tried to use the above piece of code but I am not able to figure out how to take this average and use it to retrieve the documents higher than averageUnitPrice. Any help is highly appreciated!! Thanks a tonn!!!.
So I finally figured it out. This query will give me the average and the list of items which are less than the average value.
db.item.aggregate([{
$group: {
_id: null,
avg_price: {
$avg: "$unit_price"
},
unit_price: {
"$addToSet": "$unit_price"
}
},
},{
$project: {
avg_price: "$avg_price",
unit_price: {
$filter: {
input: "$unit_price",
as: "unit_prices",
cond: {
$lt: ["$$unit_prices", "$avg_price"]
}
}
}
}
}
])

Map mongo aggregation result to array

I have a mongo query that looks like this:
db.myCollection.aggregate([ {
$group: {
_id: null,
price: {
$sum: "$price"
},
inventory: {
$sum: "$inventory"
}
}
}])
Which returns
{
"_id" : null,
"price" : 26,
"inventory" : 5,
}
I would like a query which would rather return something like this:
[
{
name: "price",
amount: 26
},
{
name: "inventory",
amount: 5
}
]
EDIT:
How would I write this in Java with Spring Data? I can group and sum but don't know how to project it?
Aggregation aggregation = newAggregation(
group("$id")
.sum("price").as("price")
.sum("inventory").as("inventory")
);
You'll need to use $project. It allows us to define which fields will be returned, as well as their format.
db.myCollection.aggregate([{
$group: {
_id: null,
price: {
$sum: "$price"
},
inventory: {
$sum: "$inventory"
}
}
},
{
$project: {
_id: 0 //removes _id from result
something: [
{name: "price", amount: "$price"},
{name: "inventory", amount: "$inventory"}
]
}
}])
That will give you:
{
"something" : [
{
"name" : "price",
"amount" : 26
},
{
"name" : "inventory",
"amount" : 5
}
]
}
You can use below aggregation
db.collection.aggregate([
{ "$project": {
"data": {
"$map": {
"input": { "$objectToArray": "$$ROOT" },
"in": { "name": "$$this.k", "amount": "$$this.v" }
}
}
}},
{ "$unwind": "$data" },
{ "$replaceRoot": { "newRoot": "$data" }},
{ "$match": { "amount": { "$ne": null }}
}
])

Elasticsearch: how to store all json objects dynamically

I'm trying to store all Json objects through elasticsearch.
client.create({
index: 'index',
type: 'type',
id:"1"
body:result[0]
},function (error,response)
{
if (error)
{
console.log('elasticsearch cluster is down!');
}
else
{
console.log('All is well');
}
});
In this result[0] I'm getting my first value of a Json object but I need to store all Json objects dynamically.
The output which i'm getting is:
-> POST http://localhost:9200/index/type/1?op_type=create
{
"Name": "Martin",
"Age": "43",
"Address": "trichy"
}
<- 201
{
"_index": "index",
"_type": "type",
"_id": "1",
"_version": 4,
"created": true
}
But I need an output like this:
-> POST http://localhost:9200/index/type/1?op_type=create
{
"Name": "Martin",
"Age": "43",
"Address": "trichy"
},
{
"Name": "vel",
"Age": "23",
"Address": "chennai"
},
{
"Name": "ajay",
"Age": "23",
"Address": "chennai"
}
<- 201
{
"_index": "index",
"_type": "type",
"_id": "1",
"_version": 4,
"created": true
}
What you need is to use the bulk endpoint in order to send many documents at the same time.
The body contains two rows per document, the first row contains the index, type and id of the document and the document itself is in the next row. Rinse and repeat for each document.
client.bulk({
body: [
// action description
{ index: { _index: 'index', _type: 'type', _id: 1 } },
// the document to index
{ Name: 'Martin', Age: 43, Address: 'trichy' },
{ index: { _index: 'index', _type: 'type', _id: 2 } },
{ Name: 'vel', Age: 23, Address: 'chennai' },
{ index: { _index: 'index', _type: 'type', _id: 3 } },
{ Name: 'ajay', Age: 23, Address: 'chennai' }
]
}, function (err, resp) {
// ...
});
I suspect your result array is the JSON you get from your other question from yesterday. If so, then you can build the bulk body dynamically, like this:
var body = [];
result.forEach(function(row, id) {
body.push({ index: { _index: 'index', _type: 'type', _id: (id+1) } });
body.push(row);
});
Then you can use the body in your bulk call like this:
client.bulk({
body: body
}, function (err, resp) {
// ...
});