[MongoDB]: Changing the type of values in all array fields - mongodb

I have the authors and books test collections which have a many-to-many relationship between them.
> db.books.find()
[
{
_id: ObjectId("60a676f24312c6d8ea7bd6ec"),
title: '300 years of peanut juggling: A longitudinal analysis.',
inPrint: true,
authors: [ '60a673c44312c6d8ea7bd6e9', '60a673c44312c6d8ea7bd6ea' ]
},
{
_id: ObjectId("60a676f24312c6d8ea7bd6ed"),
title: "Mystery Overflow: murder and chaos on the Web's biggest developer Q & A platform.",
inPrint: true,
authors: [ '60a673c44312c6d8ea7bd6eb' ],
edition: 2
}
]
> db.authors.find()
[
{
_id: ObjectId("60a673c44312c6d8ea7bd6e9"),
name: 'Jason Filippou',
age: 33,
nationalities: [ 'GRC, CND' ],
books: [ '60a676f24312c6d8ea7bd6ec' ]
},
{
_id: ObjectId("60a673c44312c6d8ea7bd6ea"),
name: 'Mary Chou',
age: 39,
nationalities: [ 'USA' ],
books: [ '60a676f24312c6d8ea7bd6ec' ]
},
{
_id: ObjectId("60a673c44312c6d8ea7bd6eb"),
name: 'Max Schwarz',
age: 42,
job: 'pilot',
books: [ '60a676f24312c6d8ea7bd6ed' ]
}
]
I implement the relationship externally, as can be seen by the authors and books fields. However, I have made the mistake of having the arrays of references be raw strings, instead of ObjectId types. This means that my joins as required by, e.g, $lookup()) fail.
I tried to mass update all the strings to make them ObjectIds using the command:
db.books.find({}).forEach(book => book.authors.forEach(id => ObjectId(id)))
While the command worked, the original data did not change:
> db.books.find({}).forEach(book => book.authors.forEach(id => ObjectId(id)))
> db.books.find()
[
{
_id: ObjectId("60a676f24312c6d8ea7bd6ec"),
title: '300 years of peanut juggling: A longitudinal analysis.',
inPrint: true,
authors: [ '60a673c44312c6d8ea7bd6e9', '60a673c44312c6d8ea7bd6ea' ]
},
{
_id: ObjectId("60a676f24312c6d8ea7bd6ed"),
title: "Mystery Overflow: murder and chaos on the Web's biggest developer Q & A platform.",
inPrint: true,
authors: [ '60a673c44312c6d8ea7bd6eb' ],
edition: 2
}
]
> db.authors.find()
[
{
_id: ObjectId("60a673c44312c6d8ea7bd6e9"),
name: 'Jason Filippou',
age: 33,
nationalities: [ 'GRC, CND' ],
books: [ '60a676f24312c6d8ea7bd6ec' ]
},
{
_id: ObjectId("60a673c44312c6d8ea7bd6ea"),
name: 'Mary Chou',
age: 39,
nationalities: [ 'USA' ],
books: [ '60a676f24312c6d8ea7bd6ec' ]
},
{
_id: ObjectId("60a673c44312c6d8ea7bd6eb"),
name: 'Max Schwarz',
age: 42,
job: 'pilot',
books: [ '60a676f24312c6d8ea7bd6ed' ]
}
]
What is my mistake here?

If you decide to update all books string to ObjectId, u can use update-documents-with-aggregation-pipeline
db.authors.updateMany({},[
{
"$addFields": {
"books": {
"$map": {
"input": "$books",
"in": {
"$toObjectId": "$$this"
}
}
}
}
}
])

Related

Mongodb: making a "groupBy" inside an aggregation and $project

My first question. Tried to find the answer but couldn't.
I have a dataset like the following:
{
_id: "6294d59455be467837cb6262",
text: 'Primeiro post',
image: '15bee21f99c0c879ecee4633f25eaa91',
width: 327,
height: 245,
location: 'Porto Alegre, Rio Grande do Sul',
poster: { name: 'J R', avatar: 'c633219c7fbacd09db9a9d54986b36b9' },
reactions: [
{ filename: 'image1', userId: 23 },
{ filename: 'image2', userId: 12 },
]
},
{
_id: "6294d59455be656589cb62825",
text: 'Segundo post',
image: '15bee21f99c0c879ecee4633f25423sgf',
width: 327,
height: 245,
location: 'Porto Alegre, Rio Grande do Sul',
poster: { name: 'J R', avatar: 'c633219c7fbacd09db9a9d54986b36b9' },
reactions: [
{ filename: 'image3', userId: 23 },
{ filename: 'image2', userId: 12 },
{ filename: 'image2', userId: 56 },
{ filename: 'image7', userId: 9 },
]
},
...
I want to retrieve all the fields, along with 2 things:
if the userId passed to the query is on the userId of the array 'reactions' - that's done in the reactionsByMe field I created with a $filter (query below);
group by 'filename' with the quantity on each of the registers that are returned;
I have the following working aggregate query:
[
{ $match: { 'createdBy.companyId': req.userInfo.companyId, type: 'post' } },
{
$project: {
reactionsByMe: {
$filter: {
input: '$reactions',
as: 'reaction',
cond: { $eq: ['$$reaction.userId', req.userInfo.userId] }
},
},
text: 1,
image: 1,
width: 1,
height: 1,
location: 1,
poster: 1,
}
},
]
I just don't know how to make a group inside the $project.
Any help appreciated! Thanks.
Edit: the expected output:
{
_id: "6294d59455be467837cb6262",
text: 'Primeiro post',
image: '15bee21f99c0c879ecee4633f25eaa91',
width: 327,
height: 245,
location: 'Porto Alegre, Rio Grande do Sul',
poster: { name: 'J R', avatar: 'c633219c7fbacd09db9a9d54986b36b9' },
reactions: [
{ _id: 'image1', count: 3 },
{ _id: 'image2', count: 5 },
]
}
With the "reactions" property giving me the total of each image in each document

mongo push to array in aggregation

I have the following code:
const newArray = [];
companies.items.forEach(async (item) => {
if (item.parentCompanyID) {
newArray.push({
updateOne: {
filter: { id: item?.parentCompanyID },
update: [
{
$push: {
branches: {
id: item?.id,
name: item.companyName,
parentId: item?.parentCompanyID,
type: item.companyType,
active: item.isActive,
number: item.companyNumber,
newlyAdded: { $eq: [{ $type: '$newlyAdded' }, 'missing'] },
},
},
},
],
upsert: true,
},
});
} else {
newArray.push({
updateOne: {
filter: { id: item?.id },
update: [
{
$set: {
id: item?.id,
name: item.companyName,
parentId: item?.parentCompanyID,
type: item.companyType,
active: item.isActive,
number: item.companyNumber,
newlyAdded: { $eq: [{ $type: '$newlyAdded' }, 'missing'] },
},
},
],
upsert: true,
},
});
}
});
await Company.bulkWrite(newArray);
This will go through company.items and for each will add updateOne into newArray which will the goes to bulkWrite.
My problem lies with $push as this needs to be in aggregation pipeline, and when i add the brackets around update it will break with MongooseError: Invalid update pipeline operator: "$push"
Iam sure the script could be simplified but iam still fairly new to mongoDB. What i need is this to insert to Company if the item hasnt got parentCompanyID, if it does have than push to branches array for the relevant Company with id of parentCompanyID.
Sample data from company.items array:
{
id: 5,
name: "Sports"
parentCompanyID: null
},
{
id: 51,
name: "Football"
parentCompanyID: 5
}
And MongoDB for COmpany should look like this:
{
id: 5,
name: "Sports",
parentCompanyID: null,
branches: [{
id: 51,
name: "Football",
parentCompanyID: 5
}]
}
Hope this makes sense. ANy help would be appreciated. I could not find any similar issue and only one i came accross was to use $concatArrays but this wouldnt work either.
Thank you
EDIT:
as per #Takis_ answer thsi now sort of works. Only problem is when $concatArrays does it jobs its not pushing into array as expected from $push. this is the result as of now, insted of branches being one array it has nested arrays. if there are more branches it follows same patter and it could end up with many nested arrays rather than 1 array of objects. any ideas?
{
"id": 29683585,
"name": "123",
"parentId": null,
"newlyAdded": true,
"branches": [
[
null,
{
"id": 29693873,
"name": "245",
"parentId": 29683585
}
],
{
"id": 29695646,
"name": "789",
"parentId": 29683585
}
]
}
This has now been sorted. Thanks to Takis and his pointing to $concatArrays i was able to make this works.
Working code is below:
newArray.push({
updateOne: {
filter: { id: item?.parentCompanyID },
update: [
{
$set: {
branches: {
$concatArrays: [
{ $ifNull: ['$branches', []] },
[
{
id: item?.id,
name: item.companyName,
parentId: item?.parentCompanyID,
type: item.companyType,
active: item.isActive,
number: item.companyNumber,
newlyAdded: { $ne: ['$newlyAdded', null] },
},
],
],
},
},
},
],
upsert: true,
},
});

Using $pull with Deployd method

I have a Deployd API which exposes a json structure like this:
[
{id: "1"
username: "john",
password: " ..... ",
email: "example#gmail.com",
coins: 60,
badges: [ ],
courses:[
{ id: "123456",
title: "Animals",
grades_per_module: [ [30], [28, 26], [25, 24]]
.....
},
{ id: "112233",
title: "Food",
grades_per_module: [ [20, 25, 27], [22]]
.....
}
]
},
{id: "2"
username: "mark",
password: " ..... ",
email: "ex#gmail.com",
coins: 40,
badges: [ ],
courses:[
{ id: "123456",
title: "Animals",
grades_per_module: [ [27], [21, 30], [30, 30]]
.....
}
]
}
]
Then I need to remove the intere course with id="112233" of the user "john" using an angular code.
So I use this code, but it doesn't work:
this.http.put('http://localhost:2403/users/1',
{ "courses": { $pull: { "id": 112233 } }
}).subscribe( ..... )
Deployd API returns me a positive message, but the course is not really removed from the user object.
Can anyone help me?

Mongoose findbyid / find({id: "..."}) returning Null

I have seen this asked several times, but I haven't found a solution that has worked. I am trying to query a MongoDB using Mongoose .findById and am not getting the expected results. find({id: "..."}) is also not returning anything.
Using find without any parameters displays all of the expected results including id key-value pairs, but I cannot use the id value in the query.
Using mongoose: 5.4.9
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/mongo-exercises', { useNewUrlParser: true });
const courseSchema = new mongoose.Schema({
name: String,
author: String,
tags: [String],
date: { type: Date, default: Date.now },
isPublished: Boolean,
price: Number
});
const Course = mongoose.model('Course', courseSchema);
async function getCourses() {
return await Course
.find()
}
async function run() {
const result = await getCourses();
console.log(result);
}
run();
//Return
[ { tags: [ 'react', 'frontend' ],
_id: 5a68fdf95db93f6477053ddd,
date: 2018-01-24T21:43:21.589Z,
name: 'React Course',
author: 'Mosh',
isPublished: false,
__v: 0 },
{ tags: [ 'aspnet', 'backend' ],
_id: 5a68fde3f09ad7646ddec17e,
date: 2018-01-24T21:42:59.605Z,
name: 'ASP.NET MVC Course',
author: 'Mosh',
isPublished: true,
price: 15,
__v: 0 },
{ tags: [ 'node', 'backend' ],
_id: 5a68fe2142ae6a6482c4c9cb,
date: 2018-01-24T21:44:01.075Z,
name: 'Node.js Course by Jack',
author: 'Jack',
isPublished: true,
price: 12,
__v: 0 },
{ tags: [ 'node', 'backend' ],
_id: 5a68fdd7bee8ea64649c2777,
date: 2018-01-24T21:42:47.912Z,
name: 'Node.js Course',
author: 'Mosh',
isPublished: true,
price: 20,
__v: 0 },
{ tags: [ 'node', 'backend' ],
_id: 5a68ff090c553064a218a547,
date: 2018-01-24T21:47:53.128Z,
name: 'Node.js Course by Mary',
author: 'Mary',
isPublished: false,
price: 12,
__v: 0 },
{ tags: [ 'angular', 'frontend' ],
_id: 5a6900fff467be65019a9001,
date: 2018-01-24T21:56:15.353Z,
name: 'Angular Course',
author: 'Mosh',
isPublished: true,
price: 15,
__v: 0 },
{ tags: [ 'express', 'backend' ],
_id: 5a68fdc3615eda645bc6bdec,
date: 2018-01-24T21:42:27.388Z,
name: 'Express.js Course',
author: 'Mosh',
isPublished: true,
price: 10,
__v: 0 } ]
That code verifies I'm connected to the correct database and retrieving real ids. When I modify the getCourses function as shown below, I get null or an empty array depending on whether I use findById or find({id: "..."}).
async function getCourses() {
return await Course
.findById('5a68fdf95db93f6477053ddd')
}
//null
async function getCourses() {
return await Course
.find({ id: '5a68fdf95db93f6477053ddd' })
}
// []
async function getCourses() {
return await Course
.find({ _id: '5a68fdf95db93f6477053ddd' })
}
// []
Any help would be greatly appreciated. Thank you!
EDIT: Showing full find() response.
Upon our discussion as your importing the data from JSON file, So its inserting the _id as string, While finding the document by _id, moognoose automatically converting string to Object.
// Original JSON
{
"_id": "5a68fde3f09ad7646ddec17e",
"tags": [
"aspnet",
"backend"
],
"date": "2018-01-24T21:42:59.605Z",
"name": "ASP.NET MVC Course",
"author": "Mosh",
"isPublished": true,
"price": 15,
"__v": 0
}
Solution 1 :
To insert the _id as in the JSON, change your _id field with $oid as below,
{
"_id": { "$oid": "5a68fde3f09ad7646ddec17e" },
"tags": [
"aspnet",
"backend"
],
"date": "2018-01-24T21:42:59.605Z",
"name": "ASP.NET MVC Course",
"author": "Mosh",
"isPublished": true,
"price": 15,
"__v": 0
}
Solution 2 :
Remove _id from your JSON, MongoDB will generate a _id automatically
{
"tags": [
"aspnet",
"backend"
],
"date": "2018-01-24T21:42:59.605Z",
"name": "ASP.NET MVC Course",
"author": "Mosh",
"isPublished": true,
"price": 15,
"__v": 0
}

Get desired output using mongoDB find

I have following documents in my collection
{
_id: ObjectId("5166fefbc482c31052000002"),
contact: [
{
home: 7735734105
},
{
office: 9583866301
}
],
user_name: "moti",
reportsTo: "bikram",
technology: [
{
name: ".net",
rating: 5
},
{
name: "JavaScript",
rating: 2
}
],
project: [
"Agile School",
"Draftmate"
],
type: "developer",
email: "motiranjan.pradhan#ajatus.co.in"
}
and
{
_id: ObjectId("5166fe90c482c31052000001"),
contact: [
{
home: 7735734103
},
{
office: 9583866901
}
],
user_name: "ganesh",
reportsTo: "bikram",
technology: [
{
name: "JavaScript",
rating: 3
},
{
name: ".net",
rating: 4
}
],
project: [
"SLBC",
"Draftmate"
],
type: "developer",
email: "ganesh.patra#ajatus.co.in"
}
Now I need to find the rating of the people who know only JavaScript.Currently if I run
db.users.find(
{
technology: {
$elemMatch: {
name: 'JavaScript'
}
}
},{user_name:1,'technology.name':1,_id:0}
).pretty()
I am getting names of all technologies(.net & JavaScript) and their corresponding ratings. I need only user names,and their respective ratings in JavaScript only. Do I need to use any aggregation techniques?
The positional operator '$' can be used to limit query results to the first matching element. To use in your query above you would change it to:
db.users.find( { technology: { $elemMatch: { name: 'JavaScript' } } },{user_name:1,'technology.$.name':1,_id:0} )