I'm trying to use two collections(reviews and products) to find the title and description of products reviewed by reviewer “A”. I just need to display the title and description. Nothing else.
So far I have:
db.reviews.aggregate([
{$match: {reviewer : 'A'}},
{$lookup: {
from: "products",
localField: "reviewer",
foreignField: "title",
foreignField: "description",
as: "products_docs"}},
{$project: {
_id: 0,
reviewerID: 1,
title: 1,
description: 1
}}
])
There is supposed to be two products which the current output gives the ID so far but not the title or description.
/* 1 */
{
"reviewerID" : "A"
}
/* 2 */
{
"reviewerID" : "A"
}
Am I missing something?
sample docs :
review :
{
"_id" : ObjectId("5d0b70f2d7367de7f5fa1589"),
"reviewerID" : "A",
"asin" : "1",
"reviewerName" : "Bob",
"helpful" : [
0,
0
],
"reviewText" : "It was really good.",
"overall" : 1.0,
"summary" : "Brilliant",
"unixReviewTime" : 1402185600,
"reviewTime" : "06 8, 2014"
}
product :
{
"_id" : ObjectId("5d0b6d1cd7367de7f58b4906"),
"asin" : "1",
"description" : "Perfect for sunny days",
"title" : "Sunglasses",
"imUrl" : "/sunglasses.jpg",
"related" : {
"also_bought" : [
"729300236X"
]
},
"salesRank" : {
"Shoes" : 257607
},
"categories" : [
[
"Clothing, Shoes & Jewellery",
"Women",
"Accessories",
"Sunglasses & Eyewear Accessories",
"Sunglasses"
],
[
"Clothing, Shoes & Jewellery",
"Men",
"Accessories",
"Sunglasses & Eyewear Accessories",
"Sunglasses"
]
]
}
You can do that using $lookup, Try this below query :
db.review.aggregate([
/** filtering out review coll to get required doc */
{
$match: {
"reviewerID": "A"
}
},
{
$lookup: {
from: "product",
let: {
asin: "$asin" // creating local variable from review Coll's field
},
pipeline: [
{
$match: {
$expr: {
$eq: [
"$asin", // foreign field
"$$asin" // local variable created in let
]
}
}
},
/** projecting only required fields from product Coll */
{
$project: {
description: 1,
title: 1,
_id: 0
}
}
],
as: "data"
}
}
])
Test : MongoDB-Playground
Related
Using Mongo 4.4
I'm looking to to lookups across collections and add a human readable value from the target collection to the source collection using a aggregate.
This works fine for individual values, but for some lookups the ObjectIds are in objects in arrays, and I can't get that work. I can pull all the values back, but not place the individual values in the array objects.
In this test case, I have a library database with a books collection and a subscribers collection. The subscribers have a checkouts entry with is an array of objects, containing a reference to a book, and the checkout date. I want to add the book title to each object in the array.
Test Database:
books collection:
[
{
"_id" : ObjectId("63208c9f0d97eff0cfbefde6"),
"title" : "There and back again",
"author" : "Bilbo Baggins",
"publisher" : "Middle Earth Books"
},
{
"_id" : ObjectId("63208cd10d97eff0cfbeff02"),
"title" : "Two Towers",
"author" : "JRR Tolkin",
"publisher" : "Dude Books"
},
{
"_id" : ObjectId("63208cf10d97eff0cfbeffa3"),
"title" : "Dune",
"author" : "Frank Herbert",
"publisher" : "Classic Books"
},
{
"_id" : ObjectId("63208d1d0d97eff0cfbf0087"),
"title" : "Old Man's War",
"author" : "John Scalzi",
"publisher" : "Old Man Books"
}
]
subscribers collection:
[
{
"_id" : ObjectId("63208c2e0d97eff0cfbefb46"),
"name" : "Tom",
"checkouts" : [
{
"bookId" : ObjectId("63208cd10d97eff0cfbeff02"),
"checkoutDate" : ISODate("2022-01-01T21:21:20.202Z")
},
{
"bookId" : ObjectId("63208d1d0d97eff0cfbf0087"),
"checkoutDate" : ISODate("2022-01-02T21:22:20.202Z")
}
],
"address" : "123 Somewhere"
},
{
"_id" : ObjectId("63208c4e0d97eff0cfbefc1f"),
"name" : "Bob",
"checkouts" : [],
"address" : "123 Somewhere"
},
{
"_id" : ObjectId("63208c640d97eff0cfbefc9a"),
"name" : "Mary",
"checkouts" : [],
"address" : "123 Somewhere Else"
}
Desired Output for user Tom:
{
"_id" : ObjectId("63208c2e0d97eff0cfbefb46"),
"name" : "Tom",
"checkouts" : [
{
"bookId" : ObjectId("63208cd10d97eff0cfbeff02"),
"checkoutDate" : ISODate("2022-01-01T21:21:20.202Z"),
"title" : "Two Towers"
},
{
"bookId" : ObjectId("63208d1d0d97eff0cfbf0087"),
"checkoutDate" : ISODate("2022-01-02T21:22:20.202Z"),
"title" : "Old Man's War"
}
],
"address" : "123 Somewhere",
}
Using this aggregate:
db.getCollection('subscribers').aggregate([
{$match: {_id: ObjectId("63208c2e0d97eff0cfbefb46") } },
{$lookup: {from: "books", localField: "checkouts.bookId", foreignField: "_id", as: "book_tmp_field" }},
{$addFields: { "checkouts.title": "$book_tmp_field.title"}},
{$project: { book_tmp_field: 0}}
])
This is the closest I can get:
{
"_id" : ObjectId("63208c2e0d97eff0cfbefb46"),
"name" : "Tom",
"checkouts" : [
{
"bookId" : ObjectId("63208cd10d97eff0cfbeff02"),
"checkoutDate" : ISODate("2022-01-01T21:21:20.202Z"),
"title" : [
"Two Towers",
"Old Man's War"
]
},
{
"bookId" : ObjectId("63208d1d0d97eff0cfbf0087"),
"checkoutDate" : ISODate("2022-01-02T21:22:20.202Z"),
"title" : [
"Two Towers",
"Old Man's War"
]
}
],
"address" : "123 Somewhere"
}
Before performing the lookup, you should UNWIND the checkouts array. After all the processing is done, group the documents, to obtain the checkouts in the array. Finally, project your desired output document. Like this:
db.subscribers.aggregate([
{
$match: {
_id: ObjectId("63208c2e0d97eff0cfbefb46")
}
},
{
"$unwind": "$checkouts"
},
{
$lookup: {
from: "books",
localField: "checkouts.bookId",
foreignField: "_id",
as: "book_tmp_field"
}
},
{
$addFields: {
"checkouts.title": "$book_tmp_field.title"
}
},
{
$project: {
book_tmp_field: 0
}
},
{
"$group": {
"_id": {
_id: "$_id",
address: "$address",
name: "$name"
},
"checkouts": {
"$push": "$checkouts"
}
}
},
{
"$replaceRoot": {
"newRoot": {
"$mergeObjects": [
"$_id",
{
checkouts: "$checkouts"
}
]
}
}
}
])
Here's the playground link.
I am trying to aggregate data with a foreign model.
The structure I am trying to supercharge is the following:
{
"_id" : ObjectId("62b489664cbb9bc8c947f19f"),
"user_id" : ObjectId("61a775da4cbb9bc8c947edd9"),
"product_types" : [
{
"type" : NumberLong(1),
"product_id" : ObjectId("62b4890f4cbb9bc8c947e5ef"),
},
{
"type" : NumberLong(1),
"product_id" : ObjectId("62b4890f4cbb9bc8c947e5ed"),
}
]
}
I am trying to add product data from product_id, and I think I am pretty close to it, but I am adding 2 identical products in an array instead of the correct one:
Query:
db.getCollection('interests').aggregate([
{
$lookup:{
from: "products",
localField: "product_types.product_id",
foreignField: "_id",
as: "productInterestData"
}
},
{
$set: {
"product_types.product": {
$map: {
input: "$product_types",
in: {
$mergeObjects: [
"$this",
{
$arrayElemAt: [
"$productInterestData",
{$indexOfArray: ["$productInterestData.id", "$this.id"]}
]
}
]
}
}
}
}
},
{$unset: "productInterestData"}
])
Result (with an array of 2 identical products, instead of the correct one):
{
"_id" : ObjectId("62b489664cbb9bc8c947f19f"),
"user_id" : ObjectId("61a775da4cbb9bc8c947edd9"),
"product_types" : [
{
"type" : NumberLong(0),
"product_id" : ObjectId("62b4890f4cbb9bc8c947e5ef"),
"product" : [
{
"_id" : ObjectId("62b4890f4cbb9bc8c947e5ef"),
"name" : "olive",
},
{
"_id" : ObjectId("62b4890f4cbb9bc8c947e5ef"),
"name" : "olive",
}
]
},
{
"type" : NumberLong(1),
"product_id" : ObjectId("62b4890f4cbb9bc8c947e5ed"),
"product" : [
{
"_id" : ObjectId("62b4890f4cbb9bc8c947e5ef"),
"name" : "olive",
},
{
"_id" : ObjectId("62b4890f4cbb9bc8c947e5ef"),
"name" : "olive",
}
]
}
]
}
Any idea on how to fix the query to have only one product instead of an array of identical ones?
Few small adjustments on the $set phase:
product_types, not product_types.product, in order to avoid duplication of the array. In order to nest it anther product add the key product in the $mergeObjects operation.
$productInterestData._id instead of $productInterestData.id
$$this instead of $this (we need two $ here)
$$this.product_id instead of $this.id
db.interests.aggregate([
{
$lookup: {
from: "products",
localField: "product_types.product_id",
foreignField: "_id",
as: "productInterestData"
}
},
{
$set: {
product_types: {
$map: {
input: "$product_types",
in: {
$mergeObjects: [
"$$this",
{product:{
$arrayElemAt: [
"$productInterestData",
{$indexOfArray: ["$productInterestData._id", "$$this.product_id"]}
]
}}
]
}
}
}
}
},
{$unset: "productInterestData"}
])
See how it works on the playground example
Before the question, I'm extremely new to mongo DB and NoSQL.
I'm having two collections in my database:
users:
{
"_id" : ObjectId("5f1efeece50f2b25d4be2de2"),
"name" : {
"familyName" : "Doe",
"givenName" : "John"
},
"email" : "johndoe#example.com",
"threads" : [ObjectId("5f1f00f31abb0e3f107fbf93"), ObjectId("5f1f0725850eca800c70ef9e") ] }
}
threads:
{
"_id" : ObjectId("5f1f0725850eca800c70ef9e"),
"thread_participants" : [ ObjectId("5f1efeece50f2b25d4be2de2"), ObjectId("5f1eff1ae50f2b25d4be2de4") ],
"date_created" : ISODate("2020-07-27T16:25:19.702Z") }
}
I want to get all the threads which an user is involved in with the other user's info nested inside.
Something like:
{
"_id" : ObjectId("5f1f0725850eca800c70ef9e"),
"thread_participants" :
[
{
"name" : {
"familyName" : "Doe",
"givenName" : "John"
},
"email" : "johndoe#example.com",
},
{
"name" : {
"familyName" : "Doe",
"givenName" : "Monica"
},
"email" : "monicadoe#example.com",
}
],
"date_created" : ISODate("2020-07-27T16:25:19.702Z") }
},
...,
...,
...
How do I go about this?
You can use $lookup to "join" the data from both collections:
db.threads.aggregate([
{
$lookup: {
from: "$users",
let: { participants: "$thread_participants" },
pipeline: [
{
$match: {
$expr: {
$in: [ "$_id", "$$participants" ]
}
}
},
{
$project: {
_id: 1,
email: 1,
name: 1
}
}
],
as: "thread_participants"
}
}
])
Mongo Playground
I have 2 collections in MongoDB:
User: favourite_tags: array[] and Event: title: string, tags: array[], so my question is: how can i find an event title that has at least 1 of the favourites tag of a user?
For example the user "A" has in his favourites tags rock, dance and food, the events with tags are: marathon (run, sport), eating contest (food, drink) and general rock band concert (music, rock).
Excpected result after the find are:
concert, eating contest
I believe this can be done using $lookup (be sure to refer to the documentation matching your version of mongoDB).
In this answer, I used mongoDB v3.4.19
Sample data
db.getCollection('users').insert(
{
name: "bob",
favourite_tags: ["rock", "dance", "food"]
}
)
db.getCollection('events').insertMany([
{
title: "marathon",
tags: ["run", "sport"]
},
{
title: "eating contest",
tags: ["food", "drink"]
},
{
title: "general rock band concert",
tags: ["music", "rock"]
}
])
Query for events that have at least 1 matching user (includes matched users in results):
db.getCollection('events').aggregate([
{
$lookup: {
from: "users",
localField: "tags",
foreignField: "favourite_tags",
as: "users"
}
},
{
$match: {
users: {
$ne: []
}
}
}
])
Results:
{
"title" : "eating contest",
"tags" : [
"food",
"drink"
],
"users" : [
{
"name" : "bob",
"favourite_tags" : [
"rock",
"dance",
"food"
]
}
]
},
{
"title" : "general rock band concert",
"tags" : [
"music",
"rock"
],
"users" : [
{
"name" : "bob",
"favourite_tags" : [
"rock",
"dance",
"food"
]
}
]
}
Query for users that matches them to events (includes events they matched with):
db.getCollection('users').aggregate([
{
$lookup: {
from: "events",
localField: "favourite_tags",
foreignField: "tags",
as: "matchingEvents"
}
}
])
Results:
{
"name" : "bob",
"favourite_tags" : [
"rock",
"dance",
"food"
],
"matchingEvents" : [
{
"title" : "eating contest",
"tags" : [
"food",
"drink"
]
},
{
"title" : "general rock band concert",
"tags" : [
"music",
"rock"
]
}
]
}
I have am trying to perform an aggregate function on my collection but I can't seem to fit the right query for the job.
My goal is to display the top 2 fastest laps on all maps and show the associated user first name and last name.
Here is my stats collections:
{
"_id" : ObjectId("5c86674d87e8cd468c850c86"),
"lapTime" : "1:32:29",
"map" : "France",
"driver" : [
ObjectId("5c7c499b555fa13f50c9c248")
],
"date" : ISODate("2019-03-11T13:49:01.472Z"),
"__v" : 0
}
{
"_id" : ObjectId("5c8667ec87e8cd468c850c87"),
"lapTime" : "2:32:34",
"map" : "France",
"driver" : [
ObjectId("5c7c499b555fa13f50c9c248")
],
"date" : ISODate("2019-03-11T13:51:40.895Z"),
"__v" : 0
}
{
"_id" : ObjectId("5c86674x87e8Sd567c120c86"),
"lapTime" : "1:12:29",
"map" : "France",
"driver" : [
ObjectId("5c7c499b555fa13f50c9c248")
],
"date" : ISODate("2019-03-11T10:49:01.472Z"),
"__v" : 0
}
{
"_id" : ObjectId("5c8667f887e8cd468c850c88"),
"lapTime" : "1:88:29",
"map" : "Italy",
"driver" : [
ObjectId("5c7c499b555fa13f50c9c248")
],
"date" : ISODate("2019-03-11T13:51:52.727Z"),
"__v" : 0
}
{
"_id" : ObjectId("5c866970c65910291c6f2000"),
"lapTime" : "1:34:29",
"map" : "Italy",
"driver" : [
ObjectId("5c80f78ca0ecdf26c83dfc8a")
],
"date" : ISODate("2019-03-11T13:58:08.135Z"),
"__v" : 0
}
{
"_id" : ObjectId("5c868532b5c50c17b0917f9e"),
"lapTime" : "1:43:33",
"map" : "Italy",
"driver" : [
ObjectId("5c80f78ca0ecdf26c83dfc8a")
],
"date" : ISODate("2019-03-11T15:56:34.869Z"),
"__v" : 0
}
Since I am passing the driver ID by reference here:
"driver":[ObjectId("5c7c499b555fa13f50c9c248")] , I want to display the driver's attributes from my users collection.
Here is one of my user objects:
{
"_id" : ObjectId("5c7c499b555fa13f50c9c248"),
"password" : "$2a$10$L..Pf44/R7yJfNPdikIObe04aiJaY/e94VSKlFscjgYOe49Y7iwJK",
"email" : "john.smith#yahoo.com",
"firstName" : "John",
"lastName" : "Smith",
"laps" : [],
"__v" : 0,
}
Here is what I tried so far:
db.getCollection('stats').aggregate([
{ $group: {
_id: { map: "$map" }, // replace `name` here twice
laps: { $addToSet: "$lapTime" },
driver:{$addToSet: "$driver"},
count: { $sum: 1 }
} },
{$lookup:
{
from: "users",
localField: "firstName",
foreignField: "lastName",
as: "driver"
}},
{ $match: {
count: { $gte: 2 }
} },
{ $sort : { count : -1} },
{ $limit : 10 }
]);
As a result, I am getting drivers as a empty array.
What I am actually trying to achieve is something like this:
{
"_id" : {
"map" : "France"
},
"laps" : [
"Jonathan Smith":"2:32:34",
"Someone Else":"1:32:29"
],
"count" : 2.0
}
I think this should work:-
db.getCollection('stats').aggregate([
{ $unwind: "$driver" },
{$lookup:
{
from: "users",
localField: "driver",
foreignField: "_id",
as: "driver"
}},
{ $group: {
_id: { map: "$map" }, // replace `name` here twice
laps: { $addToSet:
{
lapTime: "$lapTime",
driverName: "$driver.firstName" + "$driver.lastName"
}
},
count: { $sum: 1 }
} },
{ $match: {
count: { $gte: 2 }
} },
{ $sort : { count : -1} },
{ $limit : 10 }
]);