Increment a field conditioned by a WHERE - node.js

I can't seem to figure out how to do this in Sequelize. I have an instance from findOne, and I want to increment one of its fields using an expression, and only under certain conditions. Something such as:
UPDATE Account SET balance = balance - 10 WHERE balance >= 10;
I want the db to calculate the expression, as this isn't happening in a transaction. So I can't do a SET balance = 32. (I could do SET balance = 32 WHERE balance = 42, but that's not as effective.) I don't want to put a CHECK in there, as there are other places where I do want to allow a negative balance.
(Our Sequelize colleague has left, and I can't figure out how to do this).
I see the instance.increment and decrement, but it doesn't look like they take a where object.
I don't see how to express the setting of balance = balance - 10, nor of expressing the expression in the where object.

You are probably looking for Model.decrement instead of instance.decrement. instance.decrement is for updating the specific record so where doesn't make sense.
Model.decrement: https://sequelize.org/master/class/lib/model.js~Model.html#static-method-decrement
The example in the link shows similar scenario as yours.
============================================
Update:
This translates to your example.
const Op = require('sequelize').Op;
Account.decrement('balance', {
by: 10,
where: {
balance: {
[Op.gte]: 10
}
}
});

Based on #Emma comments, here's what I have working. amountToAdd is just that; cash_balance is the field I'm incrementing. The check on cash_balance ensures that if I'm decrementing (that is amount_to_add is < 0), the balance doesn't go below 0. I need to muck around with that some.
const options = {
where: {
user_id: userId,
cash_balance: {
[ Op.gte ]: amountToAdd,
},
},
by: amountToAdd
};
const incrResults = await models.User.increment( 'cash_balance', options );

Related

mongodb pull is not removing all items

I'm working on a project in nodejs using mongodb as my database. I'm trying to get rid of elements within my array that have dates before today. The problem that I'm having is that at most 5 elements are being deleted. I want all elements that meet this criteria to be deleted. Also, when I don't have user.possible.pull(items._id) const result = await user.save() all elements that meet this criteria are shown in my deletePossible array. However, when I do have user.possible.pull(items._id) const result = await user.save() at most 5 are being shown as well.
In my database, my User document looks like:
_id: '',
name: '',
possible: Array
0 Object
date: "Tues Jan 10 2023",
_id: "63c0b169b6fa12ac49874a13"
1 Object
date: "Wed Jan 11 2023",
_id: "63c0b172b6fa12ac49874a32"
...
My code:
const user = await User.findById(args.userId)
const deletePossible = [];
for (var items of user.possible) {
if (+new Date(items.date) < +new Date().setHours) {
deletePossible.push(items._id)
user.possible.pull(items._id)
const result = await user.save()
}
}
`
console.log(deletePossible)
I've tried a number of things such as:
for (var item of deletePossible) {
user.possible.pull(item)
const result = await user.save()
}
following deletePossible.push(items._id), and
const userInfo = await User.updateOne( { _id: args.userId}, {possible:{$pull:[...deletePossible] }} )
which removes all of the arrays from possible regardless of if it's contained within deletePossible and then adds a random _id. Nothing I have tried seems to work. Does anyone have any idea why this is happening and how to get this to work properly? I would really appreciate any help or advice. Thank you!
You can simply filter user.possible and save the updated User:
const user = await User.findById(args.userId);
if (!user) return;
// Change the condition based on your needs
user.possible = user.possible.filter(p => new Date(p.date) >= new Date());
await user.save();
The core of the issue appears to not be related to Mongo or Mongoose really, but is rather just a standard algorithmic logic problem.
Consider the following code, which iterates over an array, logs each element, and removes the third element when it arrives at it:
const array = [0, 1, 2, 3, 4];
for (const element of array) {
console.log(element);
if (element === 2) {
array.splice(2, 1); // remove the element at index 2 from the array
}
}
This code outputs:
0
1
2
4
Notice anything interesting? 3 has been skipped.
This happens because deleting an element from an array causes everything in front of it to move up a position. So if you're looking at 2 and you delete it, then 3 moves into 2's place, and 4 moves into 3's place. So then if you look at the next position, you're now looking at 4, not 3. The code never sees the 3.
This is why you should never change an array while iterating over it. A lot of languages won't even allow you to (if you're using iterators), they'll throw some sort of "underlying collection was modified during iteration" error. You can make it work if you know what you're doing (often just by iterating over the array backwards), but there are usually better solutions anyway, like using Array.prototype.filter().
One easy solution is to iterate over a copy of the array, so that when you do the delete, the array you're iterating over (the copy) isn't changed. That would look like this:
for (const item of [...user.possible]) {
if (/* some condition */) {
user.possible.pull(item._id);
}
}
Another problem with your code: +new Date().setHours will always evaluate to NaN since setHours is a function and converting a function to a number always results in NaN. I suspect this is just a typo you introduced while struggling with the original issue.
The suggestion to use filter() is even better.

Increment the Sort Key dynamoDB node.js

I am new to dynamodb.
I want to increment the Sort Key
If the id=0 the next id=1 and so on,
If the user(Partition key), id(Sort Key) add items the next add items the id increment 1.
The code use on PutItem with dynamodb.
Is possible to do that?
I did not want use the UUID( unique Key)
Most situations don't need an auto-incrementing attribute and DynamoDB doesn't provide this feature out of the box. This is considered to be an anti-pattern in distributed systems.
But, see How to autoincrement in DynamoDB if you really need to.
I understand that you may need this number because it is a legal obligation to have incremental invoice numbers for example.
One way would be to create a table to store your number sequences.
Add fields like:
{
name: "invoices",
prefix: "INV",
numberOfDigits: 5,
leasedValue: 1,
appliedValue: 1,
lastUpdatedTime: '2022-08-05'
},
{
name: "deliveryNotes",
prefix: "DN",
numberOfDigits: 5,
leasedValue: 1,
appliedValue: 1,
lastUpdatedTime: '2022-08-05'
}
You need 2 values (a lease and an applied value), to make sure you never skip a beat, even when things go wrong.
That check-lease-apply-release/rollback logic looks as follows:
async function useSequence(name: string, cb: async (uniqueNumber: string) => void) {
// 1. GET THE SEQUENCE FROM DATABASE
const sequence = await getSequence("invoices");
this.validateSequence(sequence);
// 2. INCREASE THE LEASED VALUE
const oldValue = sequence.appliedValue;
const leasedValue = oldValue + 1;
sequence.leasedValue = leasedValue;
await saveSequence(sequence);
try {
// 3. CREATE AND SAVE YOUR DOCUMENT
await cb(format(leasedValue));
// 4. INCREASE THE APPLIED VALUE
sequence.appliedValue++;
await saveSequence(sequence);
} catch(err) {
// 4B. ROLLBACK WHEN THINGS ARE BROKEN
console.err(err)
try {
const sequence = await getSequence(name);
sequence.leasedValue--;
this.validateSequence(sequence);
await saveSequence(sequence);
} catch (err2) {
console.error(err2);
}
throw err;
}
}
function validateSequence(sequence) {
// A CLEAN STATE, MEANS THAT THE NUMBERS ARE IN SYNC
if (sequence.leasedValue !== sequence.appliedValue) {
throw new Error("sequence is broken.");
}
}
Then, whenever you need a unique number you can use the above function to work in a protected scope, where the number will be rollbacked when something goes wrong.
const details = ...;
await useSequence("invoice", async (uniqueNumber) => {
const invoiceData = {...details, id: uniqueNumber};
const invoice = await this.createInvoice(invoiceData);
await this.saveInvoice(invoice);
})
Can it scale? Can it run on multiple instances? No, it can't. It never will be, because in most countries it's just not legal to do so. You're not allowed to send out invoice 6 before invoice 5 or to cancel invoice 5 after you've send invoice 6.
The only exception being, if you have multiple sequences. e.g. in some cases you're allowed to have a sequence per customer, or a sequence per payment system, ... Hence, you want them in your database.

How do I get json data with retrhinkdb

(I'm not good eng.)
I use a group to include dates. I want to get the information out in a row. What do i need to do
.group(r.row('time_create').dayOfWeek())
json export
[
{
group: 1,
reduction: [
{
detail: "no",
id: "37751c10-97ea-4a3a-b2c9-3e8b39383b79",
order_id: "15",
status: "Not_Delivery",
time_create: "2018-09-23T15:25:13.141Z"
}
]
}
]
i want change data json to
{
"date":
{
"Sun": [
{
detail: "no",
order_id: "15",
status: "Not_Delivery",
time_create: "2018-09-28 15:25:13"
}
]
}
}
Do i have to give the information out as i want.
Looks like you tried but didn't manage to transform the data from your previous question. ;)
Here is a proposition, this is not the only way of doing it.
First, it seems you want to remove the id field. You may do that in your ReQL using without:
.group(r.row('time_create').dayOfWeek()).without('id')
(You may apply without('id') before group, it should work the same, see this for more details.)
Then, to transform the result array (let's call it queryResult) into an object (let's call it output):
// prepare the skeleton of the output
let output = {
date: {}
};
// walk the result, filling the output in the process
queryResult.forEach((groupData) => {
let key = groupData.group;
if (!output[key]) {
output[key] = [];
}
output.date[key].push(...groupData.reduction);
})
Now you almost have your desired structure in output, the only thing is that day keys are still numbers and not a short day name. In my opinion, this should be handled by the front-end, since you may want to have different languages implemented for your front-end. But anyway, the idea is always the same: having a translation table that maps Rethink's day numbers with human-readable day names:
const translationTable = {
1: 'Mon',
2: 'Tue',
// ...
7: 'Sun'
};
Now if you do that in your front-end, you just replace the data's keys on the fly, when displaying is needed (or retrieve the key from the day name, depending on how you display stuff). Otherwise, if you go for a back-end implementation (which, again, is clearly not the best solution), you can change one line in the code above (assuming you declared translationTable already):
let key = groupData.group;
// becomes
let key = translationTable[groupData.group];
Feel free to ask in comments if there's something you don't understand!

Establishing the number of times element appears in mongodb collection before mapreduce

Thanks for the help in advance.
I have the following in a mongodb collection
{
id: 12345,
userid: 9876,
CompName: PC-537,
LogonTime: 2015-07-1,
SessionName: Session1
}
What i am trying to do is Mapreduce the database down to where i can see how many sessions were allocated to each comp name, so instead of the comp name appearing multiple times in the collection, it will appear once, with the sessions associated with it appearing as values
Can someone please give me some pointers? I know how to mapreduce when using numbers (for example rainfall: 9mm) and having that tally up, but when its text how can i find reduce it so the PC's only appear once, and also either list all the sessions they had and the total sessions they had.
What I have tried so far is the following
var mapFunc = function() {
emit(this.CompName, this.SessionName);
};
var reduceFunc = function(keyComp, keySession) {
return Array.sum(keySession);
};
db.compcollection.mapReduce(
mapFunc,
reduceFunc,
{ out: "comp2" }
)
This gave me a result of the database reducing down to the session name not the CompName and also did not give a numeric count. So basically it gave me all the machines associated with a sessionname, not the reverse (which is what i want)
Can someone please help :)
In case anyone needs this in the future i have figured this out, see code below
var mapper = function() {
emit(this.CompName, 1);
};
var reducer = function(compname, counter) {
return Array.sum(count);
};
db.compcollection.mapReduce(mapper, reducer, { out: "comp15" }
)

CouchDB view map function that doesn't segregate reduce keys

Here is the doc "schema":
{
type: "offer",
product: "xxx",
price: "14",
valid_from: [2012, 7, 1, 0, 0, 0]
}
There are a lot of such documents with many valid dates in the past and the future and a lot of times two or three offers in the same month. I can't find a way to make the following view: Given a date, give me a list of the products and their running offer for that date.
I think I need to emit the valid_date field in order to set the endkey of the query to the given date and then I need to reduce on the max of this field, which means i can't emit it.
Have I got it wrong? I am totally new to the map/reduce concept. Any suggestions on how to do it?
I'm really thrown by your comments about wanting to reduce, based on your requirements you want just a map function - no reduce. Here's the map function based on what you asked for:
function(d) {
if( d.type === 'offer' ) {
var dd = d.valid_from;
dd[1] = ( '0' + ( dd[1] + 1 )).slice(-2); // remove the +1 if 7 is July not August
dd[2] = ( '0' + dd[2] ).slice(-2);
emit( dd.slice(0,3).join('-') );
}
}
Then to show all offers valid for a given day you'd query this view with params like:
endkey="2012-08-01"&include_docs=true

Resources