Related
I have created add product API like this. This is working fine. I'm posting successfully varient data by sharing product id as a foreign key, but I'm confused about how can I update product data. Can I update data by using this code?
try {
const { name, description, photo, tag, productId ,lableType,reccomendedProduct, varientDetails, isTex,GSTrate,GSTtyp, HSNcode, categoryId, subCategoryId, videoUpload,} = req.body;
const data= db.product.findOne({ where: { id: productId },
include: [{ model: db.tagModel, attributes: ["id","name","productId"]}, { model: db.reccomendProduct, attributes: ["id", "productName","productId"]},
{ model: db.varientModel, attributes: ["id", "sort","sku","productId","waightunitno","unit","mrp","discount","price","stock","minstock","outofstock"]}]
}).then(product => {
if (product) {
db.product.update({
categoryId: categoryId ? categoryId : product.categoryId,
subCategoryId: subCategoryId ? subCategoryId : product.subCategoryId,
name:name,
description:description,
lableType:lableType,
isTex:isTex,
// photo:req.file ? req.file.location:'',
photo:photo,
GSTrate:GSTrate,
GSTtyp:GSTtyp,
HSNcode:HSNcode,
categoryId:categoryId,
subCategoryId:subCategoryId,
videoUpload:videoUpload }, { where: { id: product.id }
})
}
if(varientDetails ) {
db.varientModel.findAll ({ where: { productId:productId }})
.then(varient => {
console.log(varient+data)
for (let i=0; i < varientDetails.length; i++) {
db.varientModel.update({
productId:productId,
sort:varientDetails[i].sort,
sku: varientDetails[i].sku,
waightunitno:varientDetails[i].waightunitno,
unit:varientDetails[i].unit,
mrp:varientDetails[i].mrp,
discount: varientDetails[i].discount,
price: varientDetails[i].price,
stock: varientDetails[i].stack,
minstock: varientDetails[i].minstock,
outofstock: varientDetails[i].outofstock
}, { where: { productId:productId[i] }
})
}
})
}
Yes, there are ways to do it.
I don't find them as expressive and as clear as multiple one.
1. Creating Query on own
You can create function like this
function updateUsers(updateFirstValue, updateSecondValue, productIds) {
let query = "";
for (let index = 0; index < productIds.length; index++) {
query += `update tableName set firstUpdateValue="${updateFirstValue[index]}",secondUpdateValue="${updateSecondValue[index]}" where productId="${productIds[index]}";`;
}
return query;
}
//This is vulnerable to SQL Injection so change according to your needs
//It's just idea of doing it
let firstUpdates = [800, 900, 10];
let secondUpdates = [1.23, 2.23, 8.97];
let productIds = [1, 9, 3];
let generatedQuery = updateUsers(firstUpdates, secondUpdates, productIds);
console.log(generatedQuery);
// to run this with sequelize we can execute plain query with this
//sequelize.query(generatedQuery);
2. Using bulkCreate and updateOnDuplicate
let updatingValue = [
{productId:1, sort:100,sku:800},
{productId:2, sort:800,sku:8.27},
{productId:3, sort:400,sku:7.77}
];
model.bulkCreate(updatingValue,{
fields:["productid","sort","sku"],
updateOnDuplicate: ["sort","sku"]
}
// these are the column name that gets updated if primaryKey(productId) gets matched you have to update these accordingly
)
It had problem before but is updated now this PR
Other methods but quite complicated are also here.
I have to print some data in excel format using excel4node. Within the data I have objects that contains information which I want to transfer to excel format. But for some reason it shows error: TypeError: Cannot read property 'username' of undefined. Inside the object I have property username and its value.
Here code below:
const wb = new xl.Workbook();
const data = {};
data = {
username: 'name',
department: 'department',
title: 'title',
percentage: 23,
correct: 27,
date: 2021-09-03T16:38:05.107Z
}
const fileName = "Excel.xlsx";
const filePath = path.resolve(__dirname, "../.././public/files/", fileName);
const ws = wb.addWorksheet("Sheet 1");
const style = wb.createStyle({
font: {
color: "FFFFFFFF",
size: 12,
},
});
const form = [
"name",
"name",
"name",
"name",
"name",
"name",
];
for (let i = 0; i < form.length; i++) {
ws.cell(1, i + 1)
.string(form[i])
.style(style);
switch(i) {
case 1:
ws.column(2).setWidth(30);
break;
case 3:
ws.column(4).setWidth(30);
break;
case 4:
ws.column(5).setWidth(30);
break;
case 5:
ws.column(6).setWidth(30);
break;
}
}
for(let i = 1; i <= data.length; i++) {
ws.cell(i + 1, 1).number(i);
ws.cell(i + 1, 2).string(data[i].username);
ws.cell(i + 1, 3).date(data[i].date.toString());
ws.cell(i + 1, 4).string(data[i].department);
ws.cell(i + 1, 5).number(data[i].percentage);
ws.cell(i + 1, 6).number(data[i].correct);
}
wb.write(filePath);
At the bottom of your script, you can see that you are looping over the data.length and attempting to insert it into your cells.
At the top of the page, your data object is not an Array but an object, and indexing an object must be done by the keys and not the index value. This is why it cannot find the username since it's looking for "object.1.username".
Switch your data object to a array if you would like to use index values.
const data = {};
data = {
username: 'name',
department: 'department',
title: 'title',
percentage: 23,
correct: 27,
date: 2021-09-03T16:38:05.107Z
}
now becomes
const data = [{
username: 'name',
department: 'department',
title: 'title',
percentage: 23,
correct: 27,
date: 2021-09-03T16:38:05.107Z
}];
I'm trying to update or create a document in a MongoDB collection, using "mongoose" this way :
this.statsModel.findOne(
{product_id: requestData.ean},
).then((stats: mongoose.Schema) => {
const productId: string = requestData.ean;
// Update stats with the new scan...
const beforeStats: mongoose.Schema = stats;
const scan: any = {
coords: {
lat: requestData.lat,
lon: requestData.lon,
},
at: new Date(),
};
if (stats) {
stats.scans.push(scan);
stats.update();
} else {
const newStat = new this.statsModel();
newStat._id = requestData.ean;
newStat.product_id = requestData.ean;
newStat.scans = [scan];
newStat.purchases = [];
newStat.save();
}
When this code runs, no new element appears in the "scans" property if had a stats document.
The document is properly created if the stats document was not found.
I tried to change "update()" method to "save()" method, but, this way, i got a "Version error No matching document for the id..."
What i'm doing wrong ?
Regards...
Finally, update the type of the stats promised to Model instead of mongoose.Schema :
this.statsModel.findOne(
{product_id: requestData.ean},
).then((stats: Model<Stats>) => {
const productId: string = requestData.ean;
// Update stats with the new scan...
const beforeStats: mongoose.Schema = stats;
const scan: any = {
coords: {
lat: requestData.lat,
lon: requestData.lon,
},
at: new Date(),
};
if (stats) {
stats.scans.push(scan);
stats.save();
} else {
const newStat = new this.statsModel();
newStat._id = requestData.ean;
newStat.product_id = requestData.ean;
newStat.scans = [scan];
newStat.purchases = [];
newStat.save();
}
So the save() method properly works...
Thx
I am trying to create a leaderboard command for my Discord bot and having some trouble getting the data to display, my current code only displays the top user, you can see in the image there are 4 lines, this is because there are 4 entries in the database, so it's getting the information but not displaying all the data. Could someone point out what I am doing wrong/what I would need to change to fix this. (The blocked out bits in the photos is my username)
Code:
const top10 = db
.prepare(
'SELECT * FROM scores WHERE guild = ? ORDER BY points DESC LIMIT 10;',
)
.all(message.guild.id);
if (!top10) {
return;
}
for (const data of top10) {
let userNames = '';
for (let i = 0; i < top10.length; i++) {
const user = bot.users.cache.get(data.user).tag;
userNames += `\`${i + 1}\` ${user}\n`;
}
const level = `\`${data.level}\`\n`;
const xp = `\`${data.points.toLocaleString('en')}\`\n`;
const embed = new MessageEmbed()
.setAuthor(`Leaderboard for ${message.guild.name}`, message.guild.iconURL({ dynamic: true }))
.setColor(0x51267)
.addFields({ name: 'Top 10', value: userNames, inline: true },
{ name: 'Level', value: level, inline: true },
{ name: 'XP', value: xp, inline: true });
message.channel.send(embed);
return;
}
Current Output:
Desired Output:
From reading your code, I believe you made a mistake in the structuring of your code. From what it seems, your code gets the first item in top10, then adds that to the string as many times as the length of the top10 array. It then gets the level and xp for the first user, adds it to a string, then constructs this into an embed.
Here is the code revised so it should work as you intended:
let userNames = '';
let levels = '';
let xp = '';
for (let i = 0; i < top10.length; i++) {
const data = top10[i];
const user = (await bot.users.fetch(data.user)).tag;
userNames += `\`${i + 1}\` ${user}\n`;
levels += `\`${data.level}\`\n`;
xp += `\`${data.points.toLocaleString('en')}\`\n`;
}
const embed = new MessageEmbed()
.setAuthor(`Leaderboard for ${message.guild.name}`, message.guild.iconURL({ dynamic: true }))
.setColor(0x51267)
.addFields({ name: 'Top 10', value: userNames, inline: true },
{ name: 'Level', value: levels, inline: true },
{ name: 'XP', value: xp, inline: true });
message.channel.send(embed);
return;
You problem is, you just add \n to js object, so its no work.
As varian you can create 3 arrs with data, then, map db result push information to data.
And better use message.guild.members.chache.get for check if user on server, because, bot.users.cache.get(data.user).tag will return undined, after bot restart, if user don`t send any messages in handled bot channels.
const top10 = db.prepare('SELECT * FROM scores WHERE guild = ? ORDER BY points DESC LIMIT 10;').all(message.guild.id);
if (!top10) {
return;
}
let usersArr = [];
let levelArr = [];
let xpArr = [];
top10.forEach(dataUser, index => {
let findUser = message.guild.members.cache.get(dataUser.user);
if (findUser) {
usersArr.push(`\`${index + 1}\` ${user.tag}`);
levelArr.push(dataUser.level);
xpArr.push(dataUser.points.toLocaleString('en'));
}
});
const embed = new MessageEmbed()
.setAuthor(`Leaderboard for ${message.guild.name}`, message.guild.iconURL({ dynamic: true }))
.setColor(0x51267)
.addFields({ name: 'Top 10', value: usersArr.join('\n'), inline: true }, { name: 'Level', value: levelArr.join('\n'), inline: true }, { name: 'XP', value: xpArr.join('\n'), inline: true });
message.channel.send(embed);
I have this query
select * from foo where id in (:ListOfIds)
i call this method many times, but each call has different values for example
select * from foo where id in (1,2,5)
select * from foo where id in (3,4,6)
So how can i pass the list to this query ?
In the case that the maximum size of the IN list is known, and it isn't too big, it is best to use a bind variable per potential list item. For any values that the app doesn't know, bind a null.
For example, the SQL statement could be:
sql = `select * from foo where id in (:v1, :v2, :v3, :v4)`;
Then, if you only have three data items, you would bind:
binds = [30, 60, 90, null];
const results = await connection.execute(sql, binds);
In an app where this query is frequently invoked, you get the advantage this efficiently uses the internal statement cache, which improves performance of repeated queries.
Another solution is to use binds (for security) but build up the exact SQL string like:
binds = ['Christopher', 'Hazel', 'Samuel'];
sql = `select first_name, last_name from employees where first_name in (`;
for (var i=0; i < binds.length; i++)
sql += (i > 0) ? ", :" + i : ":" + i; sql += ")";
// "sql" becomes "select first_name, last_name from employees where first_name in (:0, :1, :2)"
But, depending how often this query is executed, and how changeable the number of bind values is, you can end up with lots of 'unique' query strings. So you might not get statement caching benefits that executing a fixed SQL statement would give - and you could push out other statements from the cache, causing repeated executions of them to be inefficient.
Regarding the JavaScript itself, last time I checked, this simple for loop was faster than map/join solutions.
For very large numbers of bind values, you can try this:
const sql = `SELECT first_name, last_name
FROM employees
WHERE first_name IN (SELECT * FROM TABLE(:bv))`;
const inlist = ['Christopher', 'Hazel', 'Samuel']; // a very large list
const binds = { bv: { type: "SYS.ODCIVARCHAR2LIST", val: inlist } };
const result = await connection.execute(sql, binds);
console.dir(result, { depth: null });
Note it uses an object type which takes some extra round-trips, so this solution is more suited for the cases where the earlier solutions are not viable,.
Reference:
node-oracledb documentation: Binding Multiple Values to a SQL WHERE IN Clause.
Do you have a comma-delimited list of numbers in a single string or do you have an array of numbers?
Here's an example of how you can handle a comma delimited list of numbers with a custom type and table function. First, run this in the database using a tool like SQL Developer:
create or replace type number_ntt as table of number;
/
create or replace function string_to_number_ntt(
p_string in varchar2,
p_separator in varchar2 := ','
)
return number_ntt
is
l_elements apex_application_global.vc_arr2;
l_retval number_ntt := number_ntt();
begin
l_elements := apex_util.string_to_table(p_string, p_separator);
for x in 1 .. l_elements.count
loop
l_retval.extend();
l_retval(l_retval.count) := l_elements(x);
end loop;
return l_retval;
end string_to_number_ntt;
/
Note that the function makes use of the apex_util package which is likely already installed in the DB.
Once that's in place, you can use it as follows:
const oracledb = require('oracledb');
const config = require('./dbConfig.js');
let conn;
oracledb.getConnection(config)
.then((c) => {
conn = c;
return conn.execute(
`select employee_id,
last_name,
department_id
from employees
where department_id in (
select column_value
from table(string_to_number_ntt(:depts))
)`,
{
depts: '30, 60, 90'
},
{
outFormat: oracledb.OBJECT
}
);
})
.then(result => {
console.log(result.rows);
})
.catch(err => {
console.log(err);
})
.then(() => {
if (conn) { // conn assignment worked, must close
return conn.close();
}
})
.catch(err => {
console.log();
});
Example results:
$ node my-script.js
[ { EMPLOYEE_ID: 114, LAST_NAME: 'Raphaely', DEPARTMENT_ID: 30 },
{ EMPLOYEE_ID: 115, LAST_NAME: 'Khoo', DEPARTMENT_ID: 30 },
{ EMPLOYEE_ID: 116, LAST_NAME: 'Baida', DEPARTMENT_ID: 30 },
{ EMPLOYEE_ID: 117, LAST_NAME: 'Tobias', DEPARTMENT_ID: 30 },
{ EMPLOYEE_ID: 118, LAST_NAME: 'Himuro', DEPARTMENT_ID: 30 },
{ EMPLOYEE_ID: 119, LAST_NAME: 'Colmenares', DEPARTMENT_ID: 30 },
{ EMPLOYEE_ID: 103, LAST_NAME: 'Hunold', DEPARTMENT_ID: 60 },
{ EMPLOYEE_ID: 104, LAST_NAME: 'Ernst', DEPARTMENT_ID: 60 },
{ EMPLOYEE_ID: 105, LAST_NAME: 'Austin', DEPARTMENT_ID: 60 },
{ EMPLOYEE_ID: 106, LAST_NAME: 'Pataballa', DEPARTMENT_ID: 60 },
{ EMPLOYEE_ID: 107, LAST_NAME: 'Lorentz', DEPARTMENT_ID: 60 },
{ EMPLOYEE_ID: 100, LAST_NAME: 'King', DEPARTMENT_ID: 90 },
{ EMPLOYEE_ID: 101, LAST_NAME: 'Kochhar', DEPARTMENT_ID: 90 },
{ EMPLOYEE_ID: 102, LAST_NAME: 'De Haan', DEPARTMENT_ID: 90 } ]
If you have an array of numbers you can convert them to a comma delimited list of numbers as follows:
[30, 60, 90].join(',')
Here's an example of what your execute statement would look like:
return conn.execute(
`select *
from foo
where id in (
select column_value
from table(string_to_number_ntt(:ListOfIds))
)`,
{
ListOfIds: ListOfIds.join(',')
},
{
outFormat: oracledb.OBJECT
}
);
Do not manually concat strings of a SQL query with parameters, this may lead to SQL Injection vulnerabilities.
I've created a tagged template literal sql to write my SQL queries.
Example:
const query1 = sql`select * from table where a in (${[1, 2, 3]})`
query1 === {
query: "select * from table where a in (:0, :1, :2)",
parameters:[1,2,3]
}
const query2 = sql`
select *
from table
where name like ${'some str'}
and a in (${[1, 2, 3]})
and b = ${100}
and c in (${['val1', 'val2']})
`
query2 === {
query: "select * from table where name like :0 and a in (:1, :2, :3) and b = :4 and c in (:3, :4)",
parameters: ["some str", 1, 2, 3, 100, "val1", "val2"]
}
const [result1, result2] = await Promise.all([
connection.execute(query1.query, query1.parameters),
connection.execute(query2.query, query2.parameters)
])
Source code (TypeScript):
interface ParameterizedSQL {
query: string;
parameters: any[];
}
export function sql(queryParts: TemplateStringsArray, ...parameters: any[]): ParameterizedSQL {
if ((queryParts.length - 1) === parameters.length) {
return {
query: queryParts.map((part, index) => index < parameters.length ? `${part}${parameterIndexes(parameters, index)}` : part).join(''),
parameters: parameters.flat(),
};
}
throw new Error("Invalid number of parameters.");
}
function parameterIndexes(parameters: any[], index: number): string {
const newIndex = parameters.slice(0, index).reduce((p, c) => p + (Array.isArray(c) ? c.length : 1), 0);
const parameter = parameters[index];
if (Array.isArray(parameter)) {
const indexes = new Array<number>(parameter.length).fill(index).map((e, i) => e + i);
return ':' + indexes.join(', :');
}
else {
return ':' + newIndex;
}
}
Source code (Javascript):
function sql(queryParts, ...parameters) {
if ((queryParts.length - 1) === parameters.length) {
return {
query: queryParts.map((part, index) => index < parameters.length ? `${part}${parameterIndexes(parameters, index)}` : part).join(''),
parameters: parameters.flat(),
};
}
throw new Error("Invalid number of parameters.");
}
function parameterIndexes(parameters, index) {
const newIndex = parameters.slice(0, index).reduce((p, c) => p + (Array.isArray(c) ? c.length : 1), 0);
const parameter = parameters[index];
if (Array.isArray(parameter)) {
const indexes = new Array(parameter.length).fill(index).map((e, i) => e + i);
return ':' + indexes.join(', :');
}
else {
return ':' + newIndex;
}
}
I assiume you are working with node-oracledb as I'm doing right now. I wasn't able to solve it with the executeMany functionality even though I tried but in a github issue it says that they don't support it as of 2 years ago.
So here is my solution without having to know the amount of elements beforehand. You should wrap try catch accordingly as well as closing connections again. I didn't want to bloat the example. Read their documentation!
let queries = []
values.forEach(value => {
const query = {
sqlQuery: `SELECT * FROM MY_TABLE where MY_COLUMN = (:value)`,
binds: {
value
}
}
queries.push(query)
})
const connection = await getConnection()
const results = []
for (let i = 0; i < queries.length; i+=1) {
const result = await connection.execute(queries[i].sqlQuery, queries[i].binds)
results.push(result.rows[0])
}
return results