I am trying to use parameterized SQL in a graceful way. My front and consists of the user constructing a somewhat complex query on a number of fields. I do not know how many fields the user will specify. His query arrives to me in JSON format. Here's a sketch of my attempt to receive this request and query my database. The last three lines are of greatest interest: I want to generate clean parameterized SQL, but I have to increment the number beside the $ for each additional field being queried. I need the loop index. What is the most graceful, Javascript-elegant way of constructing this loop? (Or if there is an entirely better way to get the job done, I'd be happy to hear that also.)
app.post("/foo", (req,res)=>{
const spec = JSON.parse(req.body);
sql = `SELECT afield FROM atable WHERE `
//** THE LOOP OF INTEREST:
for ([key, value] of spec) {
sql += "{key} = ${index}";
}
Keep it simple:
const spec = JSON.parse(req.body);
sql = `SELECT afield FROM atable WHERE `;
let index = 0;
for ([key, value] of spec) {
sql += "{key} = ${index}";
index++;
}
or
const spec = JSON.parse(req.body);
sql = `SELECT afield FROM atable WHERE `;
for (let index = 0; index < spec.length; index++) {
const [key, value] = spec[index];
sql += "{key} = ${index}";
}
Thanks, #Unmitigated, for almost. Here's the working code:
for (const [index, [key, value]] of Object.entries(spec).entries()) {
if (index == 0) sql += `WHERE ${key} = $${index} `
else sql += `AND ${key} = $${index} `
}
Related
I came across the following topic, it just has 1 line instead of 2 columns.
How do I return the second value here (see topic below)
Compare my variable with a csv file and get the matching value in javascript
This is my CSV file values:
csv screenshot of columns
This is what I have currently
IT just checks the file for the serial number from the user and marks the div with text "Valid".
This Valid should have the second Columns value.
<script>
const checkm = document.getElementById('check');
checkm.addEventListener('click', serialChecker)
async function serialChecker(){
const url = 'http://localhost/validator/serials.csv';
const response = await fetch(url);
// wait for the request to be completed
const serialdata = await response.text();
console.log(serialdata);
const inputserialnumber = document.getElementById('serialnumber').value.toString();
console.log(inputserialnumber);
// serialdata.match(/inputserialnumber/)
// serialdata.includes(inputserialnumber)
if(serialdata.includes(inputserialnumber) == true && inputserialnumber.length == 7 ){
document.getElementById('validity').innerHTML = "Valid";
startConfetti(); // from confetti.js
}else {
document.getElementById('validity').innerHTML = "Invalid";
stopConfetti(); // from confetti.js
}
//document.getElementById('test').innerHTML = "Valid";
}
</script>
This is my console output
It shows the full csv(currently), & the users input
changed the csv data into to different arrays if that helps:
array
& Thanks all in advance for taking the time to reply to my silly question!
EXTRA Clarification:
What I'm trying to do is a validate website checker.
So the user inputs their serial through an simple input field. & I have the serials in a csv file with an extra column that has the name matching to the serial.
So if the user inputs 1234567 it is present in the CSV file, my current code returns value = true for that. as it is present in the CSV file.
But I want it to return the value next to 1234567 (so in the second Column) instead, in this case "test1". So I can use that value instead of just a standard "Valid" text to be pushed back onto the website.
You can match values of two arrays by their index. In your case, I think it's easiest to use Array.map() to return a transformed array based on the one you loop trough. So for example, if you have two arrays called namesArray and valuesArray, do the following:
const validationResults = valuesArray.map((value, index) => {
return {
valid: checkValidity(value), // or whatever your validation function is called
name: namesArray[index] // match the index of the namesArray with the index of this one (valuesArray)
};
// or `return namesArray[index] + ', valid: ' + checkValidity(value)`
});
This loops through the valuesArray, and validationResults will then be an array of what you return per each item in the map function above.
One important note is that this assumes the arrays are both in the same order . If you want to sort them, for instance, do this after this.
Looking up and registering the values in a Map seems like the best answer.
// ...
const serialdata = await response.text();
const seriallookup = new Map();
// Set all Serial values to Names
for (let s in serialdata.split("\n")) {
let data = s.split(',');
seriallookup.set(data[0], data[1]);
}
Using this, checking for a serial's existance could be done with .has()
if (inputserialnumber.length == 7 && seriallookup.has(inputserialnumber)) {
And set to the elements text using
document.getElementById('validity').innerHTML = serialdata.get(inputserialnumber);
If the .csv file most likely wouldn't change between multiple requests (or if you only send just one request), you should probably initialize and request the data outside of the function.
Thank you all for the feedback.
I have not been able to use your suggestions exactly as intended.
But I managed to combine the idea's and create a new piece that does the trick for me!
const checkm = document.getElementById('check');
checkm.addEventListener('click', serialChecker)
async function serialChecker(){
const url = 'http://localhost/validator2/naamloos.csv';
const response = await fetch(url);
// wait for the request to be completed
const serialdata = await response.text();
const table = serialdata.split('\r\n');
const serialsArray = [];
const nameArray = [];
table.forEach(row =>{
const column = row.split(',');
const sArray = column[0];
const nArray = column[1];
serialsArray.push(sArray);
nameArray.push(nArray);
})
var array1 = serialsArray,
array2 = nameArray,
result = [],
i, l = Math.min(array1.length, array2.length);
for (i = 0; i < l; i++) {
result.push(array1[i], array2[i]);
}
result.push(...array1.slice(l), ...array2.slice(l));
function testfunction(array, variable){
var varindex = array.indexOf(variable)
return array[varindex+1]
}
//calling the function + userinput for serial
const inputserialnumber = document.getElementById('serialnumber').value.toString();
console.log(testfunction(result, inputserialnumber))
if(serialsArray.includes(inputserialnumber) == true && inputserialnumber.length == 7 ){
document.getElementById('validity').innerHTML = "Valid " + testfunction(result, inputserialnumber);
startConfetti();
}else {
document.getElementById('validity').innerHTML = "Invalid";
stopConfetti();
}
}
Hope this can help someone out in having an input field on their website with a .csv file in the backend (possible to have multiple for the user to select with a dropdown box with the async function).
This will check the file & will return the value from the csv that matches the serial!(based on serial number & length of the serial number(7characters))
I am currently trying to loop and add each element of the quantity of each bid and ask which appears as bids[0][1], bids[1][1], bids[1][2] and add each element in the Array sequence. Any help will be greatly appreciated.
I tried adding the array but I am unable to turn the Json data to code here. Below is the API reference
I tried the code:
const binanceTrade = JSON.parse(data)
const bidsQuantity = binanceTrade.bids[0][1]
const askQuantity = binanceTrade.asks[0][1]
for(var i = 0; i<bidsQuantity.length; i++){
var j = 1;
bidsQuantity = bidsQuantity.push(binanceTrade.bids[j][1])
console.log(bidsQuantity)
j++
//bids[0][1] + bids[1][2]
}
And the public Binance API route for reference: https://api.binance.com/api/v3/depth?symbol=BTCUSDT&limit=5
You can use reduce() to loop over the bids and asks arrays, totaling the second element of each item.
const binanceTrade = JSON.parse(data);
const bidsQuantity = binanceTrade.bids.reduce((acc, [_, quantity]) => acc + quantity, 0);
const asksQuantity = binanceTrade.asks.reduce((acc, [_, quantity]) => acc + quantity, 0);
One approach would be to use map
const bidsQuantity = [];
binanceTrade.bids.map((bids) => {
bidsQuantity.push(bids[1]);
});
You can do this again in a similar way for the asks
I want to make an update query in javascript to my database. For this query I want to update some 'order_index' values of a couple a rows. I am trying to do this with a CASE function and using variables for the when ? then ? part. however, the amount of row changes can vary so I can't pre-determine the amount of times I need to put when ? then ? in the query. My first solution was to make a function that will push when ${id[x]} then ${index[x]} into the query the right amount of times, but this is sql-injection sensitive. I tried to adjust it to the same, only then with placeholders, but now I don't know how to fill those placeholders, since it can be of variable length. Is there a way to use a single list of values, instead of multiple detached values, to fill multiple placeholders? Another solution for this problem is also welcome, if I'm not thinking with the right approach for the problem.
here is an example of what I am doing atm:
const values = [];
for (let k in ids) {
values.push(` when '${ids[k]}' then ${indices[k]}`);
}
const query = `UPDATE table1 SET order_index = (CASE id
${values.join(" ")}
else order_index
END)
WHERE other_id = ?`;
connection.query(query, other_id, function (error) {
and here something I would like to have/try to make:
const values = [];
const when_then_values = [];
for (let k in ids) {
values.push(` when ? then ?`);
when_then_values.push(ids[k]);
when_then_values.push(indices[k]);
}
const query = `UPDATE table1 SET order_index = (CASE id
${values.join(" ")}
else order_index
END)
WHERE other_id = ?`;
connection.query(query, [when_then_values, other_id], function (error) {
Is there any particular reason you want to use the generic SQL library and not something like sequelize? when you use dynamic queries it's generally a better option to use a library that is not just for plain raw SQL
I found out it is possible to fill multiple placeholders with a single list, by removing the extra brackets at the parameter input. now it looks like this:
const values = [];
const when_then_values = [];
for (let k in ids) {
values.push(` when ? then ?`);
when_then_values.push(ids[k]);
when_then_values.push(indices[k]);
}
when_then_values.push(other_id);
const query = `UPDATE table1 SET order_index = (CASE id
${values.join(" ")}
else order_index
END)
WHERE other_id = ?`;
connection.query(query, when_then_values, function (error) {
Of course there is still a ${values.join(" ")} inside the query, but since it only adds a predefined string with placeholders, I think this will be fine (?).
I am trying to post all entries in a 1d array to a column in a google sheets. The array is the product of filtering two larger arrays and returning the names that do not appear on both lists.
below is an example of the generated array.
unPub = [fake name, test1, test2, test3]
Here is the code I have written so far:
function unPublished(){
const q3 = SpreadsheetApp.openById("1111111111");
const packAllergies = q3.getSheetByName("PACK_ALLERGIES");
const packSrch = packAllergies.getRange("D5:D" + packAllergies.getLastRow()).getValues().flat();
const allergyNames = allergy.getRange("A2:A" + allergy.getLastRow()).getValues().flat();
var unPub = (packSrch.filter(e => !allergyNames.includes(e)));
var sRow = allergy.getLastRow()+1
if (unPub.length > 0){
unPub.forEach(e => allergy.getRange(sRow,1).setValue(e));
}
}
I have tried a for loop to iterate over the list as well as forEach and still only get the last entry of the unPub array to post in the defined range.
How can I get each element in the array to post to the column starting at sRow?
Explanation:
You don't need a loop to set values to the sheet. In fact it is not recommended, see best practices.
You need the following two steps:
transform your row array into a column array:
unPub=unPub.map(v=>[v]);
because you want to set the data into a column.
remove the forEach loop and directly pass the values with a single line:
allergy.getRange(sRow,1,unPub.length,1).setValues(unPub);
Solution:
function unPublished(){
const q3 = SpreadsheetApp.openById("1111111111");
const packAllergies = q3.getSheetByName("PACK_ALLERGIES");
const packSrch = packAllergies.getRange("D5:D" + packAllergies.getLastRow()).getValues().flat();
const allergyNames = allergy.getRange("A2:A" + allergy.getLastRow()).getValues().flat();
var unPub = (packSrch.filter(e => !allergyNames.includes(e)));
var sRow = allergy.getLastRow()+1;
unPub=unPub.map(v=>[v]);
allergy.getRange(sRow,1,unPub.length,1).setValues(unPub);
}
Issue with your approach:
Besides performance issues which I described in the explanation section, your forEach loop does not work because you overwrite every value on the same cell. If you see, this part allergy.getRange(sRow,1) does not change in the for loop, given that sRow is constant.
If you want your approach to work, then you need to introduce an iterator i in the forEach loop and use that to iterate through the cells:
unPub.forEach((e,i) => allergy.getRange(sRow+i,1).setValue(e));
function unPublished(){
const q3 = SpreadsheetApp.openById("1111111111");
const packAllergies = q3.getSheetByName("PACK_ALLERGIES");
const packSrch = packAllergies.getRange("D5:D" + packAllergies.getLastRow()).getValues().flat();
const allergyNames = allergy.getRange("A2:A" + allergy.getLastRow()).getValues().flat();
var unPub = (packSrch.filter(e => !allergyNames.includes(e)));
var sRow = allergy.getLastRow()+1
if (unPub.length > 0){
unPub.forEach((e,i) => allergy.getRange(sRow+i,1).setValue(e));
}
}
but I really recommend you the first approach I mentioned.
In my node server I have a filter function that looks like this
let totalHours = 0;
let laborCost = 0;
const employees = await Employees.findOne({
company
});
const timeCards = await TimeCard.find({
employee: employees.employees
}).populate("employee")
const filterTime = timeCards.filter(element => {
let totalHoursByEmployee = 0;
const times = element.times.filter(time => {
if (time.job === jobId) {
let hours = parseFloat(new Date(time.hours) / 3600000)
totalHours += hours;
totalHoursByEmployee += hours;
laborCost += hours * time.hourlyRate;
}
return time.job === jobId;
})
element.totalHoursByEmployee = totalHoursByEmployee;
element.times = times;
return element.times.length !== 0;
})
res.json({
times: filterTime,
totalHours,
laborCost
})
When I send back my response the front end my times key is an array but it does not contain the key o totalHoursByEmployee in any indexes of the array. I know that filter doesn't mutate the array but am I missing something. If I console log filterTime[0].totalHoursByEmployee right before I send my response then the correct number shows on the back end console but when I get it on the front end that key of totalHoursByEmployee is no where to be found.
I figured out the answer. It is because I am using Mongoose. So when I ran the query of
const timeCards = await TimeCard.find({ employee: employees.employees }).populate("employee")
it was returning a mongoose object and the mongoose object will not let you add keys unless they are defined in the Schema. So to fix it I ran
const timeCards = await TimeCard.find({ employee: employees.employees }).populate("employee").lean()
The .lean() returns a JSON object and not a mongoose object so now I can add key value pairs to it.