I would like to createView by comparing and grouping two table in mongodb.
table1
{id: 1, item: 'apple', house: 'A'},
{id: 2, item: 'apple', house: 'A'},
{id: 3, item: 'apple', house: 'B'},
{id: 4, item: 'banana', house: 'B'},
{id: 5, item: 'banana', house: 'C'},
{id: 6, item: 'pear', house: 'A'},
{id: 7, item: 'pear', house: 'B'},
{id: 8, item: 'pear', house: 'A'},
{id: 9, item: 'pear', house: 'C'},
And in table 2, I need to compare and match
table2
{id: 1, fruits: 'apple', type: 'important'},
{id: 2, fruits: 'banana', type: 'important'},
{id: 3, fruits: 'pear', type: 'notImportant'},
The result I want to get:
houses
{id: 1, house: 'A', totalItem: 4, noOfImportant: 2 },
{id: 2, house: 'B', totalItem: 2, noOfImportant: 2},
{id: 3, house: 'C', totalItem: 2, noOfImportant: 1},
I have tried:
db.createView(
'houses',
'table1',
[
{$lookup: { from: 'table2', localField: 'fruits', foreignField: 'item', as: 'fruits'}},
{
'$group': {
house: '$house',
totalItem: {$sum:1},
noOfImportant: {$sum:'$table2.notImpotant'},
}
},
]
)
but I can't seem to get any thing out. please help me. Thank you very much in advance!
The $lookup stage will return an array, even if it only finds a single matching document.
You can use the $arrayElemAt operator to pick out the first element, or you could simply $unwind the result.
Either way, it looks like there was a typo in your group: noOfImportant: {$sum:'$table2.notImpotant'}, - there doesn't appear to be a field named "table2", I suspect you meant to check the type field of the fruits document.
The $group stage requires that there be an explicitly defined _id field, if you need that field to be called house you can to that afterward with projection.
Since the type field is not numeric, you would need to use $cond in order to count them up.
db.createView(
'houses',
'table1',
[
{$lookup: {
from: 'table2',
localField: 'item',
foreignField: 'fruits',
as: 'fruits'
}},
{$unwind: {
path: '$fruits',
preserveNullAndEmptyArrays: true
}},
{$group: {
_id: '$house',
totalItem: {$sum:1},
noOfImportant: {
$sum:{
$cond:{
if:{$eq:['$fruits.type', 'important']},
then: 1,
else: 0
}
}
},
}},
{$addFields: { house: "$_id" }},
{$project: { _id: 0 }}
]
)
The aggregate functions returns much more descriptive error messages. You should test the pipeline prior to creating the view.
Here is a MongoPlayground that shows this pipeline in action.
Related
Mongo Playgound
Lets say I have these results:
A)
[
{_id: 1, Name: 'A', Price: 10, xx:0},
{_id: 2, Name: 'B', Price: 15, xx:0},
{_id: 3, Name: 'A', Price: 100, xx:1},
{_id: 4, Name: 'B', Price: 150, xx:1},
]
B)
[
{_id: 1, Name: 'A', Price: 10, xx:0},
{_id: 2, Name: 'B', Price: 15, xx:0},
]
I want to:
If exists at least one x:1, return all x:1 only
If there is none x:1, return all x:0
Should I do a MAP & FILTER on root docs? or some kind of MATCH with conditionals? or Redact?
Results desired Ex.:
A) Removed x:0 because exists x:1, so returned only x:1
[
{_id: 3, Name: 'A', xx:1},
{_id: 4, Name: 'B', xx:1},
]
B) Returned only x:0 as there are only x:0
[
{_id: 1, Name: 'A', xx:0},
{_id: 2, Name: 'B', xx:0},
]
Group the documents by the xx field and add the grouped docs to the docs array using $push.
Sort the docs by the _id field in descending order.
Limit the result to 1.
If there are documents with both xx: 0 and xx: 1 values, only the xx: 1 group would be returned since we're sorting in descending order and limiting the result to the first group. If there are no documents with xx: 1 but documents with xx: 0 exist, the first group would be xx: 0 which gets returned.
You can then use $unwind to return a document for each grouped document and $replaceRoot to lift the document to the root level.
db.collection.aggregate([
{
$group: {
_id: "$xx",
docs: {
$push: "$$ROOT",
}
}
},
{
$sort: {
_id: -1,
}
},
{
$limit: 1,
},
{
$unwind: "$docs"
},
{
$replaceRoot: {
newRoot: "$docs"
},
}
])
MongoPlayground
If there might be docs with an xx value other than 0 and 1, you should filter those out using $match before grouping the docs using $group.
db.collection.aggregate([
{
$match: {
xx: {
$in: [
0,
1
]
}
}
},
{
$group: {
_id: "$xx",
docs: {
$push: "$$ROOT",
}
}
},
{
$sort: {
_id: -1,
}
},
{
$limit: 1,
},
{
$unwind: "$docs"
},
{
$replaceRoot: {
newRoot: "$docs"
},
}
])
MongoPlayground
I would like to perform an aggregation query, then a find query, and apply the output of the aggregation as a new field in the find results, ie:
A have dataset like this:
{id: 1, city: "Paris", comment: "...", status: "Active"},
{id: 2, city: "London", comment: "...", status: "Active"},
{id: 3, city: "Paris", comment: "...", status: "Active"},
{id: 4, city: "New York", comment: "...", status: "Active"},
{id: 5, city: "London", comment: "...", status: "Active"},
{id: 6, city: "London", comment: "...", status: "Active"},
{id: 7, city: "London", comment: "...", status: "Disabled"}
I want to get the counts for each active city:
collection.aggregate([
{$match: {status: "Active"}},
{$group: {_id: "$city", count: {$sum: 1}}}
])
But I would like to apply the count to each entry, matched according to city. It would return something like this:
{id: 1, city: "Paris", comment: "...", status: "Active", count: 2},
{id: 2, city: "London", comment: "...", status: "Active", count: 3},
{id: 3, city: "Paris", comment: "...", status: "Active", count: 2},
{id: 4, city: "New York", comment: "...", status: "Active", count: 1},
{id: 5, city: "London", comment: "...", status: "Active", count: 3},
{id: 6, city: "London", comment: "...", status: "Active", count: 3},
{id: 7, city: "London", comment: "...", status: "Disabled", count: 3}
Ideally I would like to do this in a single query so that it can be sorted and paginated according to count.
$group by city and push root object to a root field, count status that is Active only
$unwind deconstruct root array
$mergeObjects to merge $root object and count field
$replaceRoot to replace merged object to root
db.collection.aggregate([
{
$group: {
_id: "$city",
root: { $push: "$$ROOT" },
count: {
$sum: {
$cond: [{ $eq: ["$status", "Active"] }, 1, 0]
}
}
}
},
{ $unwind: "$root" },
{
$replaceRoot: {
newRoot: { $mergeObjects: ["$root", { count: "$count" }] }
}
}
])
Playground
{
name: 'product A',
min_price: 5,
max_price: 15,
stores: [
{name: 'A', price: 6},
{name: 'B', price: 4}
]
},
{
name: 'product B',
min_price: 9,
max_price: 14,
stores: [
{name: 'C', price: 12},
{name: 'B', price: 10}
]
}
How can I find product have store's price $lt min_price?
I tried:
{$expr: { $lt: [ "$min_price", "$stores.price"] }}
Seems like I am doing it wrong!
You're close, you just need to add $min into the batch:
{$expr: { $lt: [ "$min_price", {$min: "$stores.price"} ] }}
Warehouses schema:
{_id: 1, name: 'A'}
{_id: 2, name: 'B'}
{_id: 3, name: 'C'}
Stocks schema:
{_id: 11, productId: 1, instock: [{warehouse: 'A', qty: 20}, {warehouse: 'B', qty: 5}, {warehouse: 'C', qty: 8}]
{_id: 12, productId: 2, instock: [{warehouse: 'A', qty: 30}]
I am new to MongoDB, but will like to have one row per record to show products' available qty in each of A,B,C warehouses:
Desired array output:
instock: [
{_id: 11, productId: 1, warehouse: 'A', qty: 20},
{_id: 11, productId: 1, warehouse: 'B', qty: 5},
{_id: 11, productId: 1, warehouse: 'C', qty: 8},
{_id: 12, productId: 2, warehouse: 'A', qty: 30},
{_id: 12, productId: 2, warehouse: 'B', qty: 0},
{_id: 12, productId: 2, warehouse: 'C', qty: 0}
]
I read about $lookup, $unwind, $project, and tried something like below but no where near to what I want:
Warehouse.aggregate([
{
$lookup:
{
from: "stocks",
pipeline: [
{ $project: { _id: 0, instock: {qty: 1, warehouse: 1} }},
{ $replaceRoot: { newRoot: { newStock : '$instock' } } }
],
as: "instock"
}
} ,
]);
hi, Anothony Winzlet, your advise works partially, for example:
{_id: 12, productId: 2, instock: [{warehouse: 'A', qty: 30}]
From your solution:
Result show only for warehouse A:
[{_id: 12, productId: 2, warehouse: 'A', qty: 30}]
Can I get for warehouse B & C as well? (will default qty to 0 if not defined)
[{_id: 12, productId: 2, warehouse: 'A', qty: 30},
{_id: 12, productId: 2, warehouse: 'B', qty: 0},
{_id: 12, productId: 2, warehouse: 'C', qty: 0}]
Not sure if above is possible to achieve ... thank you
Solution from Anthony Winzlet:
Warehouse.aggregate([
{ "$unwind": "$instock" },
{ "$replaceRoot": { "newRoot": { "$mergeObjects": ["$$ROOT", "$instock"] } }},
{ "$project": { "instock": 0 } }
])
Following is my collection architecture.
I don't know how apply pagination and get total photo count in aggregate query. I know that it may possible but I tried a lot, still I didn't solve the issue. Guide me for following issue.
If any optimized solution for this, then please guide me.
Photo:
{_id: 1, photo_name: '1.jpg', photo_description: 'description 1', album_id: 1},
{_id: 2, photo_name: '2.jpg', photo_description: 'description 2', album_id: 1},
{_id: 3, photo_name: '3.jpg', photo_description: 'description 3', album_id: 1},
{_id: 4, photo_name: '4.jpg', photo_description: 'description 4', album_id: 2},
{_id: 5, photo_name: '5.jpg', photo_description: 'description 5', album_id: 2},
{_id: 6, photo_name: '6.jpg', photo_description: 'description 6', album_id: 2}
Album:
{_id: 1, album_name: "my album 1", album_description: "album description 1", emoji_id: 1},
{_id: 2, album_name: "my album 2", album_description: "album description 2", emoji_id: 2},
{_id: 3, album_name: "my album 3", album_description: "album description 3", emoji_id: 3},
{_id: 4, album_name: "my album 4", album_description: "album description 4", emoji_id: 4},
{_id: 5, album_name: "my album 5", album_description: "album description 5", emoji_id: 5}
Emoji:
{_id: 1, emoji_name: "1.jpg"},
{_id: 2, emoji_name: "2.jpg"},
{_id: 3, emoji_name: "3.jpg"},
{_id: 4, emoji_name: "4.jpg"},
{_id: 5, emoji_name: "5.jpg"},
{_id: 6, emoji_name: "6.jpg"},
{_id: 7, emoji_name: "7.jpg"},
{_id: 8, emoji_name: "8.jpg"}
Testing record pagination :
2
Expected output:
[
{
album_id: 1,
album_name: "my album 1",
album_emoji: "1.jpg",
total_photos: 3,(total photo counts of all photos of paritcular album)
photos: [
{
{_id: 1, photo_name: '1.jpg', photo_description: 'description 1'},
{_id: 2, photo_name: '2.jpg', photo_description: 'description 2'}
}
]
}
]
Present query:
db
.album
.aggregate([
{
$lookup:{
from:"photo",
localField:"_id",
foreignField:"album_id",
as:"photo"
}
},
{
$lookup:{
from:"emoji",
localField:"album_emoji",
foreignField:"_id",
as:"emoji"
}
},
{
$project:{
album_name:"$album_name",
album_description:"$album_description",
album_emoji:"$emoji.image_name",
photo:"$photo"
}
},
{
$match:{
_id: 1
}
}
])
.toArray();
Present output:
[{
"_id" : 1,
"album_name" : "my album 1",
"album_emoji" : [
"1.png"
],
"photo" : [
{_id: 1, photo_name: '1.jpg', photo_description: 'description 1', album_id: 1},
{_id: 2, photo_name: '2.jpg', photo_description: 'description 2', album_id: 1},
{_id: 3, photo_name: '3.jpg', photo_description: 'description 3', album_id: 1},
]
}]
You may want to check $slice and adjust your $project stage somewhat like this
...
$project:{
album_name:"$album_name",
album_description:"$album_description",
album_emoji:"$emoji.image_name",
photo: { $slice: [ "$photo", 0, 2 ] }
}
...
Just pass different values to the $slice operator for getting different pages