How to update entities in a table from an array? - node.js

I'm trying to figure out how to update many elements at once. Suppose I have the following array:
[
{
id: 100,
order: 1,
},
{
id: 101,
order: 2,
},
{
id: 102,
order: 3,
},
]
I then transform this array, replacing the values of order. The resulting array becomes the following:
[
{
id: 102,
order: 1,
},
{
id: 101,
order: 2,
},
{
id: 100,
order: 3,
},
]
I use this on the frontend to render a list in the appropriate order, based on the value of order.
But how can I update these 3 entities in my database?
I can obviously make 3 UPDATE statements:
const promises = [];
newArray.forEach(({ id, order }) => {
promises.push(
// executeMutation is just a custom query builder
executeMutation({
query: `UPDATE my_table SET order = ${order} WHERE id = ${id}'`
})
)
})
await Promise.all(promises)
But is it possible to do this in one query?

You can do this using the UNNEST function. First you'll need to handle the query parameters properly. https://www.atdatabases.org/ does this for you, otherwise you need to separately pass a string with placeholders and then the values. If you use #databases, the code could look like:
await database.query(sql`
UPDATE my_table
SET order = updates_table.order
FROM (
SELECT
UNNEST(${newArray.map(v => v.id)}::INT[]) as id,
UNNEST(${newArray.map(v => v.order)}::INT[]) as order
) AS updates_table
WHERE my_table.id = updates_table.id
`);
The trick here is that UNNEST lets you take an array for each column and turn that into a kind of temporary table. You can then use that table to filter & update the records.

Related

Update an array value in nested document in ArangoDB

`Given the following document inside a collection card: I have to update the whole data value for a particular id in staticCard
`
{
"staticCards": [
{
id:123,
search:"",
data:[]
},
{
id:456,
search:"",
data:[]
},
],
"dynamicCards":[
{
id:789,
search:"",
data:[]
},
{
id:127,
search:"",
data:[]
},
{}
]
}
You need to determine the index of the array element, which isn't straightforward if you want to match one of the object attributes instead of the whole object. POSITION() is not an option in this case, but you can solve it with a subquery. Then you can use REPLACE_NTH() to set a new value. Finally, you need to update the respective top-level attribute.
LET pos = FIRST(FOR i IN 0..LENGTH(doc.dynamicCards)-1
FILTER doc.dynamicCards[i].id == 127
LIMIT 1
RETURN i
)
LET new = REPLACE_NTH(doc.dynamicCards, pos, { id: 128, search: "", data: [] })
UPDATE doc WITH { dynamicCards: new } IN coll

Struggle with mongoose query, pushing different Objects into different arrays in a single deeply nested object

I just can't figure out the query and even if it's allowed to write a single query to push 4 different objects into 4 different arrays deeply nested inside the user Object.
I receive PATCH request from front-end which's body looks like this:
{
bodyweight: 80,
waist: 60,
biceps: 20,
benchpress: 50,
timestamp: 1645996168125
}
I want to create 4 Objects and push them into user's data in Mongo Atlas
{date:1645996168125, value:80} into user.stats.bodyweight <-array
{date:1645996168125, value:60} into user.stats.waist <-array
...etc
I am trying to figure out second argument for:
let user = await User.findOneAndUpdate({id:req.params.id}, ???)
But i am happy to update it with any other mongoose method if possible.
PS: I am not using _id given by mongoDB on purpose
You'll want to use the $push operator. It accepts paths as the field names, so you can specify a path to each of the arrays.
I assume the fields included in your request are fixed (the same four property names / arrays for every request)
let user = await User.findOneAndUpdate(
{ id: req.params.id },
{
$push: {
"stats.bodyweight": {
date: 1645996168125,
value: 80,
},
"stats.waist": {
date: 1645996168125,
value: 60,
},
// ...
},
}
);
If the fields are dynamic, use an object and if conditions, like this:
const update = {};
if ("bodyweight" in req.body) {
update["stats.bodyweight"] = {
date: 1645996168125,
value: 80,
};
}
// ...
let user = await User.findOneAndUpdate(
{ id: req.params.id },
{
$push: update,
}
);
The if condition is just to demonstrate the principle, you'll probably want to use stricter type checking / validation.
try this:
await User.findOneAndUpdate(
{id:req.params.id},
{$addToSet:
{"stats.bodyweight":{date:1645996168125, value:80} }
}
)

Node - Knex to return array of objects as a result of joined tables

I am using knexjs and node and running postgres db with this tables: menu and menuItem having one-to-many relationship. I found a solution here knex: what is the appropriate way to create an array from results? however this returns an array of strings. What i need is to return an array of objects and an empty array if null that looks exactly as the example below:
[
{
id: 123,
name: 'Sunday Menu',
items: []
},
{
id: 456,
name: 'Monday Menu',
items: [
{
id: 987,
name: 'Fried Chicken',
pcs: 69
},
{
id: 876,
name: 'Egg Soup',
pcs: 50
},
]
}
]
My menu and menuItem table schema looks similar to this:
menu_table: {
id,
name,
timestamps
}
menuItem_table: {
id,
menu_id,
name,
pcs,
timestamps
}
Currently, my code is like this:
knex('menu').leftJoin('menuitem', 'menu.id', 'menuitem.menu_id')
.select(['menu.id as menuID', knex.raw('ARRAY_AGG(menuitem.name) as items')])
.groupBy('menu.id')
And here's the result:
[
{
"menuID": "20091fff-ca8b-42d6-9a57-9f6e1922d0fa",
"items": [
null
]
},
{
"menuID": "2ddad4fa-7293-46c5-878f-cb2881be3107",
"items": [
"Fried Chicken",
"Egg Soup",
"Vegetable Dish"
]
}
]
UPDATE: I found out how to do it using raw query how ever i can't translate it using knex. Here's my code:
SELECT menu.*, COALESCE(menuitem.items, '[]') AS items FROM menu LEFT JOIN LATERAL (
SELECT json_agg(menuitem.*) AS items FROM menuitem WHERE menu.id = menuitem.menu_id
) menuitem ON true
I finally found a solution to my question. Since I was able to get my desired result thru raw query, i just translate it to knex. My final code is this:
const coalesce = knex.raw(`coalesce(menuitem.items, '[]') as items`)
const sub = knex('menuitem').select(knex.raw('json_agg(menuitem.*) as items')).whereRaw('menu.id = menuitem.menu_id')
return knex('menu').select(['menu.*', coalesce]).joinRaw('left join lateral ? menuitem on true', sub)
I'm gonna go with this code in the mean time until someone would give me the most accurate answer.
This work, im add attachments, ty DevWannabe :
const results = await knex.column({
'id': 'str.id',
'projectName': 'str.project_name',
})
.from('venturedoor.startups as str')
.leftJoin('venturedoor.attachments as att', 'str.id', 'att.startup_id')
// join array attachments
.select(['str.id', knex.raw('ARRAY_AGG(att.*) as attachments')])
.groupBy('str.id')
knex.raw('coalesce(usr.userName,usr.email ,seat.username) as username')
We can use like that measn. If userName is empty so will call email if email also then will call name from seats.

Replacing an object in an object array in Redux Store using Javascript/Lodash

I have an object array in a reducer that looks like this:
[
{id:1, name:Mark, email:mark#email.com},
{id:2, name:Paul, email:paul#gmail.com},
{id:3,name:sally, email:sally#email.com}
]
Below is my reducer. So far, I can add a new object to the currentPeople reducer via the following:
const INITIAL_STATE = { currentPeople:[]};
export default function(state = INITIAL_STATE, action) {
switch (action.type) {
case ADD_PERSON:
return {...state, currentPeople: [ ...state.currentPeople, action.payload]};
}
return state;
}
But here is where I'm stuck. Can I UPDATE a person via the reducer using lodash?
If I sent an action payload that looked like this:
{id:1, name:Eric, email:Eric#email.com}
Would I be able to replace the object with the id of 1 with the new fields?
Yes you can absolutely update an object in an array like you want to. And you don't need to change your data structure if you don't want to. You could add a case like this to your reducer:
case UPDATE_PERSON:
return {
...state,
currentPeople: state.currentPeople.map(person => {
if (person.id === action.payload.id) {
return action.payload;
}
return person;
}),
};
This can be be shortened as well, using implicit returns and a ternary:
case UPDATE_PERSON:
return {
...state,
currentPeople: state.currentPeople.map(person => (person.id === action.payload.id) ? action.payload : person),
};
Mihir's idea about mapping your data to an object with normalizr is certainly a possibility and technically it'd be faster to update the user with the reference instead of doing the loop (after initial mapping was done). But if you want to keep your data structure, this approach will work.
Also, mapping like this is just one of many ways to update the object, and requires browser support for Array.prototype.map(). You could use lodash indexOf() to find the index of the user you want (this is nice because it breaks the loop when it succeeds instead of just continuing as the .map would do), once you have the index you could overwrite the object directly using it's index. Make sure you don't mutate the redux state though, you'll need to be working on a clone if you want to assign like this: clonedArray[foundIndex] = action.payload;.
This is a good candidate for data normalization. You can effectively replace your data with the new one, if you normalize the data before storing it in your state tree.
This example is straight from Normalizr.
[{
id: 1,
title: 'Some Article',
author: {
id: 1,
name: 'Dan'
}
}, {
id: 2,
title: 'Other Article',
author: {
id: 1,
name: 'Dan'
}
}]
Can be normalized this way-
{
result: [1, 2],
entities: {
articles: {
1: {
id: 1,
title: 'Some Article',
author: 1
},
2: {
id: 2,
title: 'Other Article',
author: 1
}
},
users: {
1: {
id: 1,
name: 'Dan'
}
}
}
}
What's the advantage of normalization?
You get to extract the exact part of your state tree that you want.
For instance- You have an array of objects containing information about the articles. If you want to select a particular object from that array, you'll have to iterate through entire array. Worst case is that the desired object is not present in the array. To overcome this, we normalize the data.
To normalize the data, store the unique identifiers of each object in a separate array. Let's call that array as results.
result: [1, 2, 3 ..]
And transform the array of objects into an object with keys as the id(See the second snippet). Call that object as entities.
Ultimately, to access the object with id 1, simply do this- entities.articles["1"].
If you want to replace the old data with new data, you can do this-
entities.articles["1"] = newObj;
Use native splice method of array:
/*Find item index using lodash*/
var index = _.indexOf(currentPeople, _.find(currentPeople, {id: 1}));
/*Replace item at index using splice*/
arr.splice(index, 1, {id:1, name:'Mark', email:'mark#email.com'});

sequelize findAll sort order in nodejs

I'm trying to output all object list from database with sequelize as follow and want to get data are sorted out as I added id in where clause.
exports.getStaticCompanies = function () {
return Company.findAll({
where: {
id: [46128, 2865, 49569, 1488, 45600, 61991, 1418, 61919, 53326, 61680]
},
attributes: ['id', 'logo_version', 'logo_content_type', 'name', 'updated_at']
});
};
But the problem is after rendering, all data are sorted out as follow.
46128, 53326, 2865, 1488, 45600, 61680, 49569, 1418, ....
As I found, it's neither sorted by id nor name. Please help me how to solve it.
In sequelize you can easily add order by clauses.
exports.getStaticCompanies = function () {
return Company.findAll({
where: {
id: [46128, 2865, 49569, 1488, 45600, 61991, 1418, 61919, 53326, 61680]
},
// Add order conditions here....
order: [
['id', 'DESC'],
['name', 'ASC'],
],
attributes: ['id', 'logo_version', 'logo_content_type', 'name', 'updated_at']
});
};
See how I've added the order array of objects?
order: [
['COLUMN_NAME_EXAMPLE', 'ASC'], // Sorts by COLUMN_NAME_EXAMPLE in ascending order
],
Edit:
You might have to order the objects once they've been recieved inside the .then() promise. Checkout this question about ordering an array of objects based on a custom order:
How do I sort an array of objects based on the ordering of another array?
If you want to sort data either in Ascending or Descending order based on particular column, using sequlize js, use the order method of sequlize as follows
// Will order the specified column by descending order
order: sequelize.literal('column_name order')
e.g. order: sequelize.literal('timestamp DESC')
If you are using MySQL, you can use order by FIELD(id, ...) approach:
Company.findAll({
where: {id : {$in : companyIds}},
order: sequelize.literal("FIELD(company.id,"+companyIds.join(',')+")")
})
Keep in mind, it might be slow. But should be faster, than manual sorting with JS.
You can accomplish this in a very back-handed way with the following code:
exports.getStaticCompanies = function () {
var ids = [46128, 2865, 49569, 1488, 45600, 61991, 1418, 61919, 53326, 61680]
return Company.findAll({
where: {
id: ids
},
attributes: ['id', 'logo_version', 'logo_content_type', 'name', 'updated_at'],
order: sequelize.literal('(' + ids.map(function(id) {
return '"Company"."id" = \'' + id + '\'');
}).join(', ') + ') DESC')
});
};
This is somewhat limited because it's got very bad performance characteristics past a few dozen records, but it's acceptable at the scale you're using.
This will produce a SQL query that looks something like this:
[...] ORDER BY ("Company"."id"='46128', "Company"."id"='2865', "Company"."id"='49569', [...])
May be a little late but want to mention an approach.
Sorting based on the [46128, 2865, 49569, 1488, 45600, 61991, 1418, 61919, 53326, 61680] can be done using ARRAY_POSITION function of postgreSQL.
const arr = [46128, 2865, 49569, 1488, 45600, 61991, 1418, 61919, 53326, 61680];
const ord = [sequelize.literal(`ARRAY_POSITION(ARRAY[${arr}]::integer[], "id")`)];
return Company.findAll({
where: {
id: arr
},
attributes: ['id', 'logo_version', 'logo_content_type', 'name', 'updated_at'],
order: ord,
});
I don't think this is possible in Sequelize's order clause, because as far as I can tell, those clauses are meant to be binary operations applicable to every element in your list. (This makes sense, too, as it's generally how sorting a list works.)
So, an order clause can do something like order a list by recursing over it asking "which of these 2 elements is older?" Whereas your ordering is not reducible to a binary operation (compare_bigger(1,2) => 2) but is just an arbitrary sequence (2,4,11,2,9,0).
When I hit this issue with findAll, here was my solution (sub in your returned results for numbers):
var numbers = [2, 20, 23, 9, 53];
var orderIWant = [2, 23, 20, 53, 9];
orderIWant.map(x => { return numbers.find(y => { return y === x })});
Which returns [2, 23, 20, 53, 9]. I don't think there's a better tradeoff we can make. You could iterate in place over your ordered ids with findOne, but then you're doing n queries when 1 will do.
if required, databases order their output by the generic order of values in the order by fields.
if your order is not like this, you may add to the select an order_field, and give it a value based upon the value in id:
case
when id=46128 then 0
when id=2865 then 1
when id=49569 then 2
end as order_field
and order by order_field.
if there are lots of values, you may stuff them in their original order in a temporary table with an identity primary key order_field, and inner join your select to that temporary table by your value field, ordering by order_field.
i don't know how to do this in sequelize, but found here answers on how it does things that i needed.
Worked for me by using "" quotes surrounding the property name.
For Debugging You can see what is the query that is getting generated by sequelize and then try to run it on the particular DB console.
In My Case I was not able to sort the data by last updatedAt column
Code Snippet :
exports.readAll = (req, res) => {
console.log("Inside ReadAll Data method");
let data;
if (!req.body) {
data = CompanyModel.findAll({ order: [[sequelize.literal('"updatedAt"'), 'DESC']]});
} else {
data = CompanyModel.findAll({ order: [[sequelize.literal('"updatedAt"'), 'DESC']]});
}
data
.then((data) => {
res.send(
data
);
})
.catch((err) => {
res.status(500).send({
message: err.message || "Some error occurred while retrieving data.",
});
});
};
SQL Query getting formed in my case :
Inside ReadAll Data method
Executing (default): SELECT "company_name", "company_id", "on_record", "createdAt", "updatedAt" FROM "companies" AS "company" ORDER BY "updatedAt" DESC;
Incase anyone is using uuid type, using example by #Agniveer from above but modified
const ids = [
'f01a057e-5646-4527-a219-336804317246',
'ee900087-4910-42b4-a559-06aea7b4e250',
'b363f116-1fc5-473a-aed7-0ceea9beb14d'
];
const idsFormat = `'${ids.join("','")}'`;
const order = [sequelize.literal(`ARRAY_POSITION(ARRAY[${idsFormat}]::uuid[], "<insert_table_name>"."id")`)];

Resources