MongoDB - Set field to hash of another field - javascript

Is there a way to run this query so that instead of { $toString: "$_id" } I can take the hash of the image field value for that document and set the field to that hash value?
So instead of this:
db.getCollection("test_products").updateMany( {_id: {$in: [ ObjectId('6340eae3bb965cdee37b8b30'), ObjectId('6340eae3bb965cdee37b8b33') ]}}, [
{ $set : { image : { $concat : [ "https://www.example.com/products/", { $toString: "$_id" }, ".png" ] } } }
] );
I would want something like this:
db.getCollection("test_products").updateMany( {_id: {$in: [ ObjectId('6340eae3bb965cdee37b8b30'), ObjectId('6340eae3bb965cdee37b8b33') ]}}, [
{ $set : { image : { $concat : [ "https://www.example.com/products/", { $hex_md5: "$image" }, ".png" ] } } }
] );

You will need to use $function to use js functionality for the md5 sum computation.
db.collection.update({},
[
{
"$addFields": {
"image": {
"$function": {
"body": "function(image){return \"https://www.example.com/products/\"+hex_md5(image)+\".png\"}",
"args": [
"$image"
],
"lang": "js"
}
}
}
}
],
{
multi: true
})
Mongo Playground

Related

Mongoose/MongoDB: How can I $inc only the first value I get from an array?

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.

Remove Records from MongoDB Collection based on the Individual User Pairs

I have a set of documents (messages) in MongoDB collection as below. I want to just preserve the latest 500 records for individual user pairs. Users are identified as sentBy and sentTo.
/* 1 */
{
"_id" : ObjectId("5f1c1b00c62e9b9aafbe1d6c"),
"sentAt" : ISODate("2020-07-25T11:44:00.004Z"),
"readAt" : ISODate("1970-01-01T00:00:00.000Z"),
"msgBody" : "dummy text",
"msgType" : "text",
"sentBy" : ObjectId("54d6732319f899c704b21ef7"),
"sentTo" : ObjectId("54d6732319f899c704b21ef5"),
}
/* 2 */
{
"_id" : ObjectId("5f1c1b3cc62e9b9aafbe1d6d"),
"sentAt" : ISODate("2020-07-25T11:45:00.003Z"),
"readAt" : ISODate("1970-01-01T00:00:00.000Z"),
"msgBody" : "dummy text",
"msgType" : "text",
"sentBy" : ObjectId("54d6732319f899c704b21ef9"),
"sentTo" : ObjectId("54d6732319f899c704b21ef8"),
}
/* 3 */
{
"_id" : ObjectId("5f1c1b78c62e9b9aafbe1d6e"),
"sentAt" : ISODate("2020-07-25T11:46:00.003Z"),
"readAt" : ISODate("1970-01-01T00:00:00.000Z"),
"msgBody" : "dummy text",
"msgType" : "text",
"sentBy" : ObjectId("54d6732319f899c704b21ef6"),
"sentTo" : ObjectId("54d6732319f899c704b21ef8"),
}
/* 4 */
{
"_id" : ObjectId("5f1c1c2e1449dd9bbef28575"),
"sentAt" : ISODate("2020-07-25T11:49:02.012Z"),
"readAt" : ISODate("1970-01-01T00:00:00.000Z"),
"msgBody" : "dummy text",
"msgType" : "text",
"sentBy" : ObjectId("54cfcf93e2b8994c25077924"),
"sentTo" : ObjectId("54d6732319f899c704b21ef5"),
}
/* and soon... assume it to be 10k+ */
Algo that came to my mind is -
Grouping first based on the OR operator
Sorting the records in descending order on a timely basis
Limit it to 500
Get the array of _id that should be preserved
Pass the ID(s) to new mongo query .deleteMany() with $nin condition
Please help I struggled a lot on this, and have not got any success. Many Thanks :)
Depending on scale I would do one of the two following:
Assuming scale is somewhat low and you can actually group the entire collection in a reasonable time I would do something similar to what you suggjested:
db.collection.aggregate([
{
$sort: {
sentAt: 1
}
},
{
$group: {
_id: {
$cond: [
{$gt: ["$sentBy", "$sentTo"]},
["$sendBy", "$sentTo"],
["$sentTo", "$sendBy"],
]
},
roots: {$push: "$$ROOT"}
}
},
{
$project: {
roots: {$slice: ["$roots", -500]}
}
},
{
$unwind: "$roots"
},
{
$replaceRoot: {
newRoot: "$roots"
}
},
{
$out: "this_collection"
}
])
The sort stage has to come first as you can't sort an inner array post group, the $cond in the group stage simulates the $or operator logic which can't be used there. finally instead of retrieving the result than using deleteMany with $nin you can just use $out to rewrite the current collection.
If scale is way too big to support this then you should just iterate user by user and do what you suggested at first, here is a quick example:
let userIds = await db.collection.distinct("sentBy");
let done = [1];
for (let i = 0; i < userIds.length; i++) {
let matches = await db.collection.aggregate([
{
$match: {
$and: [
{
$or: [
{
"sentTo": userIds[i]
},
{
"sendBy": userIds[i]
}
]
},
{ // this is not necessary it's just to avoid running on ZxY and YxZ
$or: [
{
sendTo: {$nin: done}
},
{
sendBy: {$nin: done}
}
]
}
]
}
},
{
$sort: {
sentAt: 1
}
},
{
$group: {
_id: {
$cond: [
{$eq: ["$sentBy", userIds[i]]},
"$sendTo",
"$sentBy"
]
},
roots: {$push: "$$ROOT"}
}
},
{
$project: {
roots: {$slice: ["$roots", -500]}
}
},
{
$unwind: "$roots"
},
{
$group: {
_id: null,
keepers: {$push: "$roots._id"}
}
}
]).toArray();
if (matches.length) {
await db.collection.deleteMany(
{
$and: [
{
$or: [
{
"sentTo": userIds[i]
},
{
"sendBy": userIds[i]
}
]
},
{ // this is only necessary if you used it above.
$or: [
{
sendTo: {$nin: done}
},
{
sendBy: {$nin: done}
}
]
},
{
_id: {$nin: matches[0].keepers}
}
]
}
)
}
done.push(userIds[i])
}

Find duplicate values inside an array in Mongo DB but it can be present outside object

{
"_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

How to use $filter in nested child array with mongodb?

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

How to update subset of a string in MongoDB?

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/

Categories

Resources