I want to update the following subset of a string in monogdb
Collection: Paper
Field: URL
Document Current:
Name : House
URL : www.home.com/300x300
Document Updated
Name : House
URL : www.home.com/600x600
I have already tried this but it doesn't seem to be working:
db.Paper.find({Name:"House"}).forEach(function(e,i) {
e.URL=e.URL.replace("300","600");
db.Paper.save(e);
});
Any ideas?
You can use one of the following aggregations to query and update:
db.test.aggregate( [
{
$match: {
url: { $regex: "300x300" }
}
},
{
$addFields: {
url: { $split: [ "$url", "300" ] }
}
},
{
$addFields: {
url: {
$concat: [
{ $arrayElemAt: [ "$url", 0 ] },
"600",
{ $arrayElemAt: [ "$url", 1 ] },
"600",
{ $arrayElemAt: [ "$url", 2 ] }
]
}
}
}
] ).forEach( doc => db.test.updateOne( { _id: doc._id }, { $set: { url: doc.url } } ) )
With MongoDB version 4.2+ you can specify the aggregation instead of an update operation with updateMany:
db.test.updateMany(
{
url: { $regex: "300x300" }
},
[
{
$addFields: {
url: { $split: [ "$url", "300" ] }
}
},
{
$addFields: {
url: {
$concat: [
{ $arrayElemAt: [ "$url", 0 ] },
"600",
{ $arrayElemAt: [ "$url", 1 ] },
"600",
{ $arrayElemAt: [ "$url", 2 ] }
]
}
}
}
]
)
Pretty simple, you have to use regex instead of string in the pattern of replace function as below:
> db.Paper.find({Name:"House"}).forEach(function (e, i) {e.URL = e.URL.replace(/300/g, "600"), printjson(e); db.Paper.save(e);} )
{
"_id" : ObjectId("5e016224a16075c5bd26fbe2"),
"Name" : "House",
"URL" : "www.home.com/600x600"
}
> db.Paper.find()
{ "_id" : ObjectId("5e016224a16075c5bd26fbe2"), "Name" : "House", "URL" : "www.home.com/600x600" }
>
So, there is a difference between e.URL.replace("300", "600") and e.URL.replace(/300/g, "600"). You should figureout it yourself.
For references, please go through this: https://flaviocopes.com/how-to-replace-all-occurrences-string-javascript/
Related
I have a Mongoose Schema that looks like this:
{
_id: ObjectID,
storage: [{
location: String,
storedFood: [{
code: String,
name: String,
weight: Number
}]
}]
}
And for example in storedFood can be the same Food twice. But I only want to update one of the weights of these items.
This is my code to $inc all of the items.... How can I reduce this to only one?
try {
const deletedFoodFromStorage = await User.updateOne(
{_id: user, "storage.location": location},
{ $inc: {"storage.$.storedFood.$[food].weight": -weight}},
{ arrayFilters: [ { "food.code": code } ]},
);
res.json(deletedFoodFromStorage);
} catch(err) {
res.status(400).json('Error: ' + err)
}
Should have been a simple one. Only way I found at the moment is not simple:
db.collection.update(
{_id: user, "storage.location": location},
[
{$set: {
newItem: {
$reduce: {
input: {$getField: {
input: {$first: {$filter: {
input: "$storage",
as: "st",
cond: {$eq: ["$$st.location", location]}
}}},
field: "storedFood"
}},
initialValue: [],
in: {$concatArrays: [
"$$value",
{$cond: [
{$and: [
{$eq: ["$$this.code", code]},
{$not: {$in: [code, "$$value.code"]}}
]},
[{$mergeObjects: [
"$$this",
{weight: {$subtract: ["$$this.weight", weight]}}
]}],
["$$this"]
]
}
]
}
}
}
}},
{$set: {
storage: {
$map: {
input: "$storage",
in: {$cond: [
{$eq: ["$$this.location", location]},
{$mergeObjects: ["$$this", {storedFood: "$newItem"}]},
"$$this"
]}
}
},
newItem: "$$REMOVE"
}}
])
See how it works on the playground example
Borrowing liberally from nimrod serok's answer, here's one way to do it with a single pass through all the arrays. I suspect this can be improved, at least for clarity.
db.collection.update({
_id: user,
"storage.location": location
},
[
{
"$set": {
"storage": {
"$map": {
"input": "$storage",
"as": "store",
"in": {
"$cond": [
{"$ne": ["$$store.location", location]},
"$$store",
{
"$mergeObjects": [
"$$store",
{
"storedFood": {
"$getField": {
"field": "theFoods",
"input": {
"$reduce": {
"input": "$$store.storedFood",
"initialValue": {
"incOne": false,
"theFoods": []
},
"in": {
"$cond": [
"$$value.incOne",
{
"incOne": "$$value.incOne",
"theFoods": {
"$concatArrays": [
"$$value.theFoods",
["$$this"]
]
}
},
{
"$cond": [
{"$ne": ["$$this.code", code]},
{
"incOne": "$$value.incOne",
"theFoods": {
"$concatArrays": [
"$$value.theFoods",
["$$this"]
]
}
},
{
"incOne": true,
"theFoods": {
"$concatArrays": [
"$$value.theFoods",
[
{
"$mergeObjects": [
"$$this",
{"weight": {"$add": ["$$this.weight", -weight]}}
]
}
]
]
}
}
]
}
]
}
}
}
}
}
}
]
}
]
}
}
}
}
}
])
Try it on mongoplayground.net.
Ty for your help!
The final solution for me was to restructure addStoredFood-Function to make the storedFood unique and only add weight to it instead of adding another objects.
This makes my old update-Function work aswell.
I want to group by name then find the percentage of count of fill document to total document. The data is given below,here(fill:0 means not fill):-
{"name":"Raj","fill":0}
{"name":"Raj","fill":23}
{"name":"Raj","fill":0}
{"name":"Raj","fill":43}
{"name":"Rahul","fill":0}
{"name":"Rahul","fill":23}
{"name":"Rahul","fill":0}
{"name":"Rahul","fill":43}
{"name":"Rahul","fill":43}
{"name":"Rahul","fill":43}
Result :-
{
"name":"Raj",
fillcount:2,
fillpercentagetototaldocument:50% // 2 (fill count except 0 value ) divide by 4(total document for raj)
}
{
"name":"Rahul",
fillcount:4,
fillpercentagetototaldocument:66% // 4(fill count except 0 value ) divide by 6(total document for rahul)
}
You want you use $group combined with a conditional count like so:
db.collection.aggregate([
{
$group: {
_id: "$name",
total: {
$sum: 1
},
fillcount: {
$sum: {
$cond: [
{
$ne: [
"$fill",
0
]
},
1,
0
]
}
}
}
},
{
$project: {
_id: 0,
name: "$_id",
fillcount: 1,
fillpercentagetototaldocument: {
"$multiply": [
{
"$divide": [
"$fillcount",
"$total"
]
},
100
]
}
}
}
])
Mongo Playground
You can use mongos aggregation function to achieve that.
example:
db.getCollection('CollectionName').aggregate([
{
$group: {
_id: { name: '$name'},
fillpercentagetototaldocument: { $sum: '$fill' },
fillCount:{$sum:1}
},
},
{ $sort: { fillpercentagetototaldocument: -1 } },
]);
The result will look like this afterwards:
[
{
"_id" : {
"name" : "Rahul"
},
"fillpercentagetototaldocument" : 152,
"fillCount" : 6.0
},
{
"_id" : {
"name" : "Raj"
},
"fillpercentagetototaldocument" : 66,
"fillCount" : 4.0
}
]
{
"_id" : ObjectId("15672"),
"userName" : "4567",
"library" : [
{
"serialNumber" : "Book_1"
},
{
"serialNumber" : "Book_2"
},
{
"serialNumber" : "Book_4"
}
]
},
{
"_id" : ObjectId("123456"),
"userName" : "123",
"library" : [
{
"serialNumber" : "Book_2"
}
]
},
{
"_id" : ObjectId("1835242"),
"userName" : "13526",
"library" : [
{
"serialNumber" : "Book_7"
},
{
"serialNumber" : "Book_6"
},
{
"serialNumber" : "Book_5"
},
{
"serialNumber" : "Book_4"
},
{
"serialNumber" : "Book_3"
},
{
"serialNumber" : "Book_5"
}
]
}
I want a query which will give me the username in which serialNumber values are duplicate. The serial number values in one library can be present in other username library but it should not be there in one particular username library
Try this query :
db.collection.aggregate([
/** First match stage is optional if all of your docs are of type array & not empty */
{ $match: { $expr: { $and: [{ $eq: [{ $type: "$library" }, "array"] }, { $ne: ["$library", []] }] } } },
/** Add a new field allUnique to each doc, will be false where if elements in library have duplicates */
{
$addFields: {
allUnique: {
$eq: [
{
$size:
{
$reduce: {
input: "$library.serialNumber",
initialValue: [], // start with empty array
/** iterate over serialNumber's array from library & push current value if it's not there in array, at the end reduce would produce an array with uniques */
in: { $cond: [{ $in: ["$$this", "$$value"] }, [], { $concatArrays: [["$$this"], "$$value"] }] }
}
}
},
{
$size: "$library"
}
]
}
}
},
/** get docs where allUnique: false */
{
$match: {
allUnique: false
}
},
/** Project only needed fields & remove _id which is bydefault projected */
{
$project: {
userName: 1,
_id: 0
}
}
])
Other option can be doing this through $unwind but which is not preferable on huge datasets as it explodes your collection.
Test : MongoDB-Playground
Or from answer of #Dennis in this link duplicate-entries-from-an-array , You can try as below :
db.collection.aggregate([
{
$match: {
$expr: {
$and: [
{
$eq: [
{
$type: "$library"
},
"array"
]
},
{
$ne: [
"$library",
[]
]
}
]
}
}
},
{
$addFields: {
allUnique: {
$eq: [
{
$size: {
"$setUnion": [
"$library.serialNumber",
[]
]
}
},
{
$size: "$library"
}
]
}
}
},
{
$match: {
allUnique: false
}
},
{
$project: {
userName: 1,
_id: 0
}
}
])
Test : MongoDB-Playground
My mongodb data is like this,i want to filter the memoryLine.
{
"_id" : ObjectId("5e36950f65fae21293937594"),
"userId" : "5e33ee0b4a3895a6d246f3ee",
"notes" : [
{
"noteId" : ObjectId("5e36953665fae212939375a0"),
"time" : ISODate("2020-02-02T17:24:06.460Z"),
"memoryLine" : [
{
"_id" : ObjectId("5e36953665fae212939375ab"),
"memoryTime" : ISODate("2020-02-03T17:54:06.460Z")
},
{
"_id" : ObjectId("5e36953665fae212939375aa"),
"memoryTime" : ISODate("2020-02-03T05:24:06.460Z")
}
]
}
]}
i want to get the item which memoryTime is great than now as expected like this,
"userId" : "5e33ee0b4a3895a6d246f3ee",
"notes" : [
{
"noteId" : ObjectId("5e36953665fae212939375a0"),
"time" : ISODate("2020-02-02T17:24:06.460Z"),
"memoryLine" : [
{
"_id" : ObjectId("5e36953665fae212939375ab"),
"memoryTime" : ISODate("2020-02-03T17:54:06.460Z")
},
{
"_id" : ObjectId("5e36953665fae212939375aa"),
"memoryTime" : ISODate("2020-02-03T05:24:06.460Z")
}
]
}]
so is use code as below.i use a $filter in memoryLine to filter to get the right item.
aggregate([{
$match: {
"$and": [
{ userId: "5e33ee0b4a3895a6d246f3ee"},
]
}
}, {
$project: {
userId: 1,
notes: {
noteId: 1,
time: 1,
memoryLine: {
$filter: {
input: "$memoryLine",
as: "mLine",
cond: { $gt: ["$$mLine.memoryTime", new Date(new Date().getTime() + 8 * 1000 * 3600)] }
}
}
}
}
}]).then(doc => {
res.json({
code: 200,
message: 'success',
result: doc
})
});
but i got this,memoryLine is null,why?I try to change $gt to $lt, but also got null.
"userId" : "5e33ee0b4a3895a6d246f3ee",
"notes" : [
{
"noteId" : ObjectId("5e36953665fae212939375a0"),
"time" : ISODate("2020-02-02T17:24:06.460Z"),
"memoryLine" : null <<<------------- here is not right
}]
You can use $addFields to replace existing field, $map for outer collection and $filter for inner:
db.collection.aggregate([
{
$addFields: {
notes: {
$map: {
input: "$notes",
in: {
$mergeObjects: [
"$$this",
{
memoryLine: {
$filter: {
input: "$$this.memoryLine",
as: "ml",
cond: {
$gt: [ "$$ml.memoryTime", new Date() ]
}
}
}
}
]
}
}
}
}
}
])
$mergeObjects is used to avoid repeating fields from source memoryLine object.
Mongo Playground
I have the data structure like this:
{
"_id" : ObjectId("5c4404906736bd2608e30b5e"),
"assets": [
{
"name" : "xa",
"id" : 1
},
{
"name" : "xs",
"id" : 2
}
]
},
{
"_id" : ObjectId("5c4404906736bd2608e30b5f"),
"assets": [
{
"name" : "xa",
"id" : 3
}
]
},
{
"_id" : ObjectId("5c4404906736bd2608e30b5g"),
"assets": [
{
"name" : "xa",
"id" : 4
},
{
"name" : "xd",
"id" : 5
},
{
"name" : "xs",
"id" : 6
}
]
}
Now I want to implement the MongoDB aggregation by which I got the Answer like this:
[
{
"assets": "xa",
"count": 3
},
{
"assets": "xs",
"count": 2
},
{
"assets": "xd",
"count": 1
},
]
I have to get this done by javascript but need to implement this on aggregation. My code for acheiveing with js is like this for set of array of object i.e
var arr = [
{ asset: "xa" },
{ asset: "xs" },
{ asset: "xa" },
{ asset: "xs" },
{ asset: "xa" },
{ asset: "xd" }
];
var userDict = arr.reduce((acc, el) => {
if (!acc.hasOwnProperty(el.asset)) {
acc[el.asset] = { count: 0 };
}
acc[el.asset].count++;
return acc;
}, {});
var result = Object.entries(userDict).map(([k, v]) => ({
asset: k,
count: v.count
}));
console.log(result);
Any help is really appreciated
You can $unwind assets before applying $group with count:
db.col.aggregate([
{
$unwind: "$assets"
},
{
$group: {
_id: "$assets.name",
count: { $sum: 1 }
}
},
{
$project: {
_id: 0,
asset: "$_id",
count: 1
}
}
])