Hello members of stackoverflow, I'm totally new to mongoDB hence I'm having trouble with formulating some queries in it. I've been trying to do it for quite some time but have failed to do so. Please refer to the following code:
#create database
use db_m_1
#create collection (all tables in database)
db.createCollection('articles')
#this is your article in noSQL
#change this according to your info
article = {
_id: '1',
title: 'article1',
date_published: ISODate("2014-09-17T23:25:56.314Z"),
written_by: [{
_id: '1',
name: 'Osama'
}],
comments: [{
_id: '1',
content: 'This is very good article',
date: ISODate("2014-09-17T23:25:56.314Z"),
comment_by: {
_id: '1',
name: 'Osama'
password: 'pass'
}
}]
}
#this is used to insert
db.articles.insert(article)
#queries
#1-- know which comments were made on article 'a beginning' and by whom
#create query
let q1 = {
title: 'a beginning'
}
#create projection
db.articles.find(q1, {"Comments.username": 1, "Comments.comment_content": 1})
#2-- which staff members were working on article 'sample'
let q2 = {
title: 'sample'
}
let p2 = {
written_by: 1
}
db.articles.find(q2, p2);
#5-- which articles were written in 2014
let q5 = {
date_published: new Date("2014-10-01T00:00:00.000Z")}
}
q5 = {date_published: new Date("2014-05-13T00:00:00.000+00:00")}
let p5 = {
title: 1,
written_by: 1
}
I need help with query 3 and 4.
3-- how many articles has each staff member worked on?
4-- which staff members have worked on more than one article with maximum number of article writing staff on top
Thank you. :)
3-- how many articles has each staff member worked on?
4-- which staff members have worked on more than one article with
maximum number of article writing staff on top
These two aggregations solve the queries 3 and 4 respectively.
# 3
db.articles.aggregate( [
{
$unwind: "$written_by"
},
{
$group: {
_id: "$written_by.name",
count: { $sum: 1 }
}
},
{
$project: {
name: "$_id",
_id: 0,
count: 1
}
}
] )
# 4
db.articles.aggregate( [
{
$unwind: "$written_by"
},
{
$group: {
_id: "$written_by.name",
count: { $sum: 1 }
}
},
{
$match: {
count: { $gt: 1 }
}
},
{
$project: {
name: "$_id",
_id: 0,
count: 1
}
},
{
$sort: { count: -1 }
}
] )
Some test data:
article1 = {
_id: '1',
title: 'article1',
date_published: ISODate("2014-09-17T23:25:56.314Z"),
written_by: [ {
_id: '1',
name: 'Osama'
} ],
comments: [ {
_id: '1',
content: 'This is very good article',
date: ISODate("2014-09-17T23:25:56.314Z"),
comment_by: {
_id: '1',
name: 'Osama',
password: 'pass'
}
} ]
}
article2 = {
_id: '2',
title: 'article2',
written_by: [ {
_id: '9',
name: 'Krish'
},
{
_id: '1',
name: 'Osama'
} ],
comments: "..."
}
the data structure you use isn't proper for this kind of queries. I recommend you to have one collection per your entities, for this example we need 3 collections with these schema:
// article doc schema
{
_id: int,
title: String,
date_published: ISODate,
};
// writer doc schema
{
_id: int,
name: String,
articles_ids: [int]
};
// comment doc schema
{
_id: int,
content: String,
date: ISODate,
by_id: int,
article_id: int
};
by this way you can make complex queries, and you have to use aggregate query for your need. here is some examples:
// Query 03
// how many articles has each staff member worked on?
let piplines = [
{
// separate writers by their names
$group: {
_id: "$name",
totalArticles: { $size: "$articles_ids" }
}
}
];
let arrayOfStaffStates = writerCollection.aggregate(piplines);
// Query 04
// which staff members have worked on more than one article
let piplines = [
{
// separate writers by their names
$group: {
_id: "$name",
totalArticles: { $size: "$articles_ids" }
},
// match they who have more than 1 articles
$match: {
totalArticles: { $gte: 1 }
}
}
];
let stuffs = writerCollection.aggregate(piplines);
Related
I have a database of a the employees of a company that looks like this:
{
_id: 7698,
name: 'Blake',
job: 'manager',
manager: 7839,
hired: ISODate("1981-05-01T00:00:00.000Z"),
salary: 2850,
department: {name: 'Sales', location: 'Chicago'},
missions: [
{company: 'Mac Donald', location: 'Chicago'},
{company: 'IBM', location: 'Chicago'}
]
}
I have an exercise in which I need to write the MongoDb command that returns all them employees who did all their missions in Chicago. I struggle with the all because I cannot find a way to check that all the locations of the missions array are equal to 'Chicago'.
I was thinking about doing it in two time: first find the total number of missions the employee has and then compare it to the number of mission he has in Chicago (that how I would do in SQL I guess). But I cannot found the number of mission the employee did in Chicago. Here is what I tried:
db.employees.aggregate([
{
$match: { "missions": { $exists: true } }
},
{
$project: {
name: 1,
nbMissionsChicago: {
$sum: {
$cond: [
{
$eq: [{
$getField: {
field: { $literal: "$location" },
input: "$missions"
}
}, "Chicago"]
}, 1, 0
]
}
}
}
}
])
Here is the result :
{ _id: 7698, name: 'Blake', nbMissionsChicago: 0 }
{ _id: 7782, name: 'Clark', nbMissionsChicago: 0 }
{ _id: 8000, name: 'Smith', nbMissionsChicago: 0 }
{ _id: 7902, name: 'Ford', nbMissionsChicago: 0 }
{ _id: 7499, name: 'Allen', nbMissionsChicago: 0 }
{ _id: 7654, name: 'Martin', nbMissionsChicago: 0 }
{ _id: 7900, name: 'James', nbMissionsChicago: 0 }
{ _id: 7369, name: 'Smith', nbMissionsChicago: 0 }
First of all, is there a better method to check that all the locations of the missions array respect the condition? And why does this commands returns only 0 ?
Thanks!
If all you need is the agents who had all their missions in "Chicago" then you don't need an aggregation pipeline for it, specifically the approach of filtering the array as part of the aggregation can't utilize an index and will make performance even worse.
A simple query should suffice here:
db.collection.find({
$and: [
{
"missions": {
$exists: true
}
},
{
"missions.location": {
$not: {
$gt: "Chicago"
}
}
},
{
"missions.location": {
$not: {
$lt: "Chicago"
}
}
}
]
})
Mongo Playground
This way we can build an index on the missions field and utilize it properly, any documents with a different value other then "Chigaco" will not match as they will fail the $gt or $lt comparion.
Note that an empty array also matches the condition, you can change the generic "missions" exists condition key into "missions.0": {$exists: true}, this will also require at least one mission.
You are unable to get the correct result as it is not the correct way to iterate the element in an array field.
Instead, you need to work with $size operator to get the size of an array and the $filter operator to filter the document.
Updated: You can directly compare the filtered array with the original array.
db.employees.aggregate([
{
$match: {
"missions": {
$exists: true
}
}
},
{
$project: {
name: 1,
nbMissionsChicago: {
$eq: [
{
$filter: {
input: "$missions",
cond: {
$eq: [
"$$this.location",
"Chicago"
]
}
}
},
"$missions"
]
}
}
}
])
Demo # Mongo Playground
I have a mongoDB database structure like this
{
_id: ObjectId,
name: string,
scheduledDate: ISOString
}
I want to return all scheduledDates that repeat the same scheduledDate day 2 times or more across all the database
Example:
{
_id: ObjectId,
name: 'example1',
scheduledDate: "2022-04-15T05:44:00.000Z"
},
{
_id: ObjectId,
name: 'example1',
scheduledDate: "2022-04-15T07:44:00.000Z"
},
{
_id: ObjectId,
name: 'example1',
scheduledDate: "2022-04-18T02:44:00.000Z"
},
{
_id: ObjectId,
name: 'example1',
scheduledDate: "2022-04-18T02:20:00.000Z"
},
{
_id: ObjectId,
name: 'example1',
scheduledDate: "2022-04-18T02:44:00.000Z"
},
{
_id: ObjectId,
name: 'example1',
scheduledDate: "2022-04-10T05:44:00.000Z"
}
In this example 2022-04-15 repeats 2 times and 2022-04-18 repeat 3 times, so both match the criteria (2 times or more) so I want to return both date day
Is this possible?
Like this:
{
scheduledDate:"2022-04-15T00:00:00.000Z"
},
{
scheduledDate:"2022-04-18T00:00:00.000Z"
}
And one more question, is possible to do the same with hours? A list of specific hours of scheduledDate that repeat across all database X times
Use $group with $date and $dateTrunc
db.collection.aggregate([
{
$group: {
_id: {
$dateTrunc: {
date: { $toDate: "$scheduledDate" },
unit: "day"
}
},
count: { $sum: 1 }
}
},
{
$match: {
count: { $gt: 1 }
}
},
{
$project: {
_id: 0,
scheduledDate: "$_id"
}
}
])
mongoplayground
I'm using node.js and mongodb, I have an array of objects which holds the names of an id. Let's say below is my array
let names = [
{ value: 1, text: 'One' },
{ value: 2, text: 'Two' },
{ value: 3, text: 'Three' },
{ value: 4, text: 'Gour' }
]
And this is my query result of a collection using $group which gives me the distinct values.
[
{ _id: { code: '1', number: 5 } },
{ _id: { code: '2', number: 5 } },
{ _id: { code: '3', number: 2 } },
{ _id: { code: '4', number: 22 } },
]
$lookup let's us to join the data from a different collection, but in my case I have an array which holds the text value for each of the codes which I got from the query.
Is there a way we can map the text from the array to the results from mongodb?
Any help will be much appreciated.
EDIT
MongoDB query which I was trying
db.collection.aggregate([
{
$match: {
_Id: id
}
},
{
$lookup: {
localField: "code",
from: names,
foreignField: "value",
as: "renderedNames"
}
},
{
"$group" : {
"_id": {
code: "$code",
number: "$number"
}
}
}
]);
Local variable lives in nodejs app, and mongodb knows nothing about it.
It looks like it belongs to representation layer, where you want to show codes as meaningful names. The mapping should be done there. I believe find is the most suitable here:
names.find(name => name.code === doc._id.code).text
If the names are not truly variable but quite constant, you can move it to own collection, e.g. codeNames:
db.codeNames.insert([
{ _id: "1", text: 'One' },
{ _id: "2", text: 'Two' },
{ _id: "3", text: 'Three' },
{ _id: "4", text: 'Gour' }
]);
and use $lookup as following:
db.collection.aggregate([
{
$match: {
_Id: id
}
},
{
"$group" : {
"_id": {
code: "$code",
number: "$number"
}
}
},
{
$lookup: {
localField: "_id.code",
from: "codeNames",
foreignField: "_id",
as: "renderedNames"
}
}
]);
If none of the above suit your usecase, you can pass the names to the database in each request to map names db-side, but you must be really really sure you cannot use 2 previous options:
db.collection.aggregate([
{
$match: {
_Id: id
}
},
{
"$group" : {
"_id": {
code: "$code",
number: "$number"
}
}
},
{
$project: {
renderedNames: { $filter: {
input: [
{ value: "1", text: 'One' },
{ value: "2", text: 'Two' },
{ value: "3", text: 'Three' },
{ value: "4", text: 'Gour' }
],
as: "name",
cond: { $eq: [ "$$name.value", "$_id.code" ] }
}
}
}
},
]);
As a side note, I find $match: {_Id: id} quite confusing, especially followed by $group. If _Id is _id, it is unique. You can have no more than 1 document after this stage, so there is not too much to group really.
I have a Model in MongoDB which include an array(category) which has an array(picture) inside it.
Shirt Model looks like this:
{ _id: 100, category: [ { name: 'CatName', _id: 101, picture: [Object] } ] }
And my Picture Array looks like this:
[ { path: 'p1', _id: 102 },
{ path: 'p2', _id: 103 } ] }
I Pass 3 variables
main array id which is 100 (var1)
category name which is CatName (var2)
picture id which is 102 (var3)
I want to GET an array which looks like:
{ _id: 100, category: [ { name: 'CatName', _id: 101,
picture: [ { path: 'p1', _id: 102 }]
} ]
}
What I have tried is this:
Shirt.find( {_id: var1} ,
{category: { $and:[ { name: var2 } , { picture: {$elemMatch: {_id: var3}}} ] }} )
.exec(function(err, product) {
console.log('Result ' + product );
res.jsonp(product);
});
But the Result I receive is Undefined for First Code
Second Code That I tried:
Shirt.find( {_id: var1} ,
{category: {$elemMatch: { name: var2,picture: {$elemMatch: {_id: var3} }}} } )
And Result from second code filter the array for var1 and var2
But it contain the whole picture array which means it does not filter var3
What is the correct code to find what I want?
Is this a correct approach for a Shopping Website database Or you have a better suggestion?
Am I applying Parent and Child Database Correctly?
Thanks!
Following mongo query will give you expected result:
db.collection.find({
"category": {
$elemMatch: {
"name": "CatName",
"picture": {
$elemMatch: {
"_id": 102
}
}
}
}
})
You need to convert it in node.js format.
Following format may help you. Not Tested
collection.find({
_id: "" // add your match Id here} ,
{
category: {
"$elemMatch": {
"name": "CatName",
"picture": {
$elemMatch: {
"_id": 102
}
}
}
}
}
})
.exec(function(err, product) {
console.log('Result ' + product);
res.jsonp(product);
});
AFTER EDIT QUESTION
You should use mongo aggregation to get required values. Query will be like following -
db.collection.aggregate({
$unwind: '$category'
}, {
$unwind: '$category.picture'
}, {
$match: {
_id: 100, // add here your var1 variable instead of 100
'category.name': 'CatName', // add here your var2 variable instead of 'CatName'
'category._id': 102, //add your match id here if any otherwise remove this condition
'category.picture._id': 102 //add your var3 instead of 102
}
}, {
$group: {
_id: '$category.name',
'name': {
$first: '$category.name'
},
'picture': {
'$push': '$category.picture'
}
}
})
Lets say that I have the following document in the books collection:
{
_id:0 ,
item: "TBD",
stock: 0,
info: { publisher: "1111", pages: 430 },
tags: [ "technology", "computer" ],
ratings: [ { _id:id1, by: "ijk", rating: 4 }, {_id:id2 by: "lmn", rating: 5 } ],
reorder: false
}
I would like to update the value of ratings[k].rating and all I know is the id of the collection and the _id of the objects existing in the array ratings.
The tutorial of mongoDB has the following example that uses the position of the object inside the array but I suppose that if the update can only be done by knowing the position, this means that I firstly have to find the position and then proceed with the update? Can I do the update with only one call and if so how I can do that?
db.books.update(
{ _id: 1 },
{
$inc: { stock: 5 },
$set: {
item: "ABC123",
"info.publisher": "2222",
tags: [ "software" ],
"ratings.1": { by: "xyz", rating: 3 }
}
}
)
Sorry for late answer; I think this is what you want to do with mongoose.
Books.findOneAndUpdate({
_id: 1,
'ratings._id': id1
},
{
$set: {
'ratings.$.rating' : 3
}
}, function(err, book){
// Response
});
Positional operator may help you:
db.books.update(
// find book by `book_id` with `rating_id` specified
{ "_id": book_id, "ratings._id": rating_id },
// set new `value` for that rating
{ $set: { 'ratings.$.rating': value }}
);
$ will save position of matched document.