Avoid getting an array of id in results (mssql - node.js) - javascript

I recently started to work with mssql (SQLServer) instead of mySQL.
So I updated my routes to work with mssql.
However I encountered a problem :
When I use a join for example, with 2 tables that have a column 'Id', I got an array of Id instead of a single Id. (both ids are the same)
Is that possible to get only a single id, and not an array ?
Thank you in advance !
const query = 'SELECT sessions.*, chiffres.*, participants.*\
FROM dbo.sessions\
INNER JOIN dbo.participants ON participants.id = sessions.id\
INNER JOIN dbo.chiffres ON chiffres.id = participants.id'
return ps.prepare(query, err => {
if (err) {
return res.json({ errors: { sql: err } })
}
return ps.execute(optsValues, (err, results) => {
if (err) {
return res.json({ errors: { sql: err } })
}
return ps.unprepare(err => {
if (err) {
return res.json({ errors: { sql: err } })
}
return res.json({ sessions: results?.recordset })
})
})
})
results.recordset =>
[
{
"id": [2, 2, 2],
"num_session": "blabla"
...
}
]

I'm not familiar so much with javascript, but as the COLUMN is exactly the same name in each table and you have not aliased the columns then how do you expect the client to separate them out?
It is generally poor practice to use table.* in your queries since the table structure may change over time and your code would not immediately error and perhaps cause adverse impacts dopwnstream. Always be specific with the columns you want to choose.
Try something like this for the query:
const query = 'SELECT sessions.id as SessionID, sessions.num_session, chiffres.id as ChiffresID, chiffres.ca, participants.id as ParticipantID, participants.name\
FROM dbo.sessions\
INNER JOIN dbo.participants ON participants.id = sessions.id\
INNER JOIN dbo.chiffres ON chiffres.id = participants.id'

Related

Cannot Execute SQL Query in a .map()

I have an endpoint that receives an array in the req.body. I need to fetch that array and for each element of that array, i need to execute the SQL Update Query. This is the code:
const approveShifts = (req, res) => {
try {
const { guard_id } = req.params;
const { shiftIDs, isBooked } = req.body;
shiftIDs.map((shift_id) => {
connection.query(
`UPDATE shift SET isBooked=${isBooked}, fk_guard=${guard_id} WHERE shiftID=${shift_id}`,
(err, rows) => {
if (!err) {
res.status(200).json({
success: true,
message: `Successfully Approved Shift #${shift_id} for Guard #${guard_id}`,
});
} else {
res.status(404).json({
success: false,
message: "Shift Not Found!",
});
}
}
);
});
} catch (error) {
res.status(500).json({
success: false,
message: error.message,
});
}
};
This is my req.body:
{
"shiftIDs": [64],
"isBooked": 1
}
The issue is, no matter what kind of testing i do, the only output i get is "Shift Not Found!" from the else statement of the query. Nothing else happens. I can't get it to work. Can someone guide me ?
A couple of things here- firstly I recommend you use prepared statements instead of string templates for your query:
// (assuming this is mysql client?)
connection.query('UPDATE shift SET isBooked = ?, fk_guard = ? WHERE shiftID = ?', [isBooked, guard_id, shift_id], (err, rows, fields) => {...})
// if it's the mysql2 client, use connection.execute() instead of connection.query()
This works by replacing each ? with the value in the array, in order. This will help avoid SQL injection problems.
Secondly, you can do this in 1 query instead of mapping by using the IN SQL operator because you are setting the same value for isBooked and fk_guard for every shiftID:
// assuming shiftIDs is an array
connection.query('UPDATE shift SET isBooked = ?, fk_guard = ? WHERE shiftID IN (?)', [isBooked, guard_id, shiftIDs], (err, rows, fields) => {...});
And as someone else said, you should console.log(err) right before res.status(404) to see what the error is. And by the way, if the shift doesn't exist, no rows will be updated but no error will be thrown either, so your response wouldn't be 404.

Make a MySQL query reusable with double ?? for tables and ? for selectors with NodeJS and JavaScript

Hi. I am looking to create a reusable query with MySQL and NodeJS
The first step I took after the DB connection was to create a data access object and interact with the DB while using promises. To stay relative secure, I am using placeholders:
" ? " for rows and values and double " ?? " for tables.
I want to create functions to be reused. So if I am creating one function for insert, I need to use same function to insert records in different tables while using parameters.
The Data access layer looks like this: Here I am using parameters that will go to each function and replace the placeholders from the sql statements.
/*
Insert user accepts three parameters. Statement from mysql, table marked with ?? in the statement and the
payload object that have to match the database columns
*/
insertRecord: (sqlStatement, table, payload) => {
return new Promise((resolve, reject) => {
db.query(sqlStatement, [table, payload], (error, response) => {
if (error) return reject(error);
return resolve(response);
});
});
},
/*
This function accepts four parametters. The first parameter is the statement. Second parameter is the table selector
marked with ?? in the statement.
The selector is the row, and the value specified
*/
selectSimpleStatement: (sqlStatement, table, row, value) => {
return new Promise((resolve, reject) => {
db.query(sqlStatement, [table, row, value], (error, response) => {
if (error) return reject(error);
else return resolve(response);
});
});
}
}
The second implementation was to create all the MySQL queries separately because I wanted to keep it Clean
module.exports = {
insertStatement: 'INSERT INTO ?? SET ?',
sqlSimpleSelect: 'SELECT * FROM ?? WHERE ? = ?'
}
Using the first sql statement was easy. I worked to insert in all the tables with the same function. But the select is a pain. With postman I am getting empty arrays even if the values are correct...
const { sqlSimpleSelect } = require('../../database/statements.js');
const bcrypt = require('bcrypt');
exports.add = async (req, res, next) => {
const {username, password} = req.body;
try {
// Select from users where username = username variable
const user = await selectSimpleStatement(sqlSimpleSelect, 'users', 'username', username);
res.send(user);
}
catch (error) {
res.send(error)
}
}```
I am getting empty array. Can please help me?
Thanks, Daniel

NODE.JS, Express, mySQL2 : Issues appending an object inside of another object

I'm having an issue in trying to add the results of a query into an object. I am new to Node and Express so I apologize if I am making some stupid mistakes. I have been reading docs and watching videos pretty much non stop while I try to get this to work. Async is still wierd for my brain so I am sure I am doing things wrong, the function I placed seems wrong, I would appreciate so much any videos or articles you know of that could aid my comprehension as well haha!
Basically I have a table called stages, where the user can add or remove stages. I am pulling all the stages and then trying to add the projects that belong in those stages as an array of objects.
Ok so here is the code:
var myStages = []; //Global variable to hold stages.
//Get all the Stages
app.get('/', (req, res) => {
pool.getConnection((err, connection) => {
if(err) throw err;
console.log(`Connected as ID ${connection.threadId}`);
//Query
connection.query('SELECT * FROM solarcrm_stages', (err, rows) => {
connection.release();
if(!err) {
getProjects(rows);
} else {
console.log(err);
}
})
function getProjects(stages) {
myStages = stages;
Object.entries(myStages).forEach(entry => {
const[key] = entry;
connection.query('SELECT * FROM project WHERE iProjectStage = ?', [myStages[key]['iStageID']], (err, rows) => {
connection.release();
if(!err) {
myStages[key]['Projects'] = rows;
console.log(myStages); //Projects shows up as 'Projects: [ [TextRow] ]'
} else {
console.log(err);
}
});
});
}
})
})
The current result I get is this, Stage 1 has projects that should appear but shows [TextRow], Stage 2 should be empty as it is:
[
TextRow {
iStageID: 1,
sStageName: 'Stage1',
sStateIcon: 'null',
bActive: 1,
iOrderID: 1,
sStageDesc: null,
Projects: [ [TextRow] ]
},
TextRow {
iStageID: 2,
sStageName: 'Stage 2',
sStateIcon: 'null',
bActive: 1,
iOrderID: 2,
sStageDesc: null,
Projects: []
}
]
Thank you so much for any help or guidance you can offer.

How to use forEach when iterating over collection objects MongoDB?

please suggest me how to make a selection from database comparing the ID in the collection with each element of the array?
Here is the code that unfortunately returns an empty array:
index(req, res) {
Room.find({_id: req.user.rooms.forEach((item)=>{
return item;
})
})
.then((rooms) => {
console.log(rooms)
res.send(rooms)
}
)
.catch(err => res.status(400).json(err));
}
req.user.rooms - each item of this array is ID, that I want to compare with what is in the collection Room.
It's pretty straight-forward in their docs for how to query items in a list.
Your code should look something like this:
index(req, res) {
// Additional validation should be done to make sure that req.user.rooms
// is an array with length > 0. I'll leave that for you to do.
const rooms = req.user.rooms;
Room.find({ _id: rooms})
.then((rooms) => {
console.log(rooms)
res.send(rooms)
})
.catch(err => res.status(400).json(err));
}
Going beyond that, you should really not be doing DB queries from your controller; it's not a good architectural practice This is how it could look in your node service
roomController.js
const RoomService = require("/path/to/services/directory"); // Let services contain all business logic. Don't give them anything related to your web server framework
async index(req, res) {
// Additional validation should be done to make sure that req.user.rooms
// is an array with length > 0. I'll leave that for you to do.
try {
const rooms = await RoomService.retrieveById(req.user.rooms);
res.send( { success: true, data: rooms } ); // We send back the result when we get one
} catch ( err ) {
// We respond to the client with our error, ideally you'll log it too
res.status( err.statusCode ).send( { success: false, error: err } );
}
}
RoomService.js
const Room = require("/path/to/your/model");
module.exports = {
retrieveById: async function(ids) {
try {
return await Room.find({ _id: ids}); // Typically this would be abstracted to a DB layer. but putting it here for brevity
} catch( err ) {
throw new Error( err ); // This is caught in our controller, which we send to client
}
}
};

Create a nested return model with Knex.js

I'm using Knex.js to query a MySQL database in a Hapi.js route. The following code works but requires a nested query:
{
path: '/recipes',
method: 'GET',
handler: (req, res) => {
const getOperation = Knex.from('recipes')
// .innerJoin('ingredients', 'recipes.guid', 'ingredients.recipe')
.select()
.orderBy('rating', 'desc')
.limit(10)
.then((recipes) => {
if (!recipes || recipes.length === 0) {
res({
error: true,
errMessage: 'no recipes found'
});
}
const recipeGuids = recipes.map(recipe => recipe.guid);
recipes.forEach(r => r.ingredients = []);
const getOperation2 = Knex.from('ingredients')
.whereIn('recipe', recipeGuids)
.select()
.then((ingredients) => {
recipes.forEach(r => {
ingredients.forEach(i => {
if (i.recipe === r.guid) {
r.ingredients.push(i);
}
});
});
res({
count: recipes.length,
data: recipes
});
});
});
}
}
Is there a way to create a return model with Knex.js that has nested objects that match the parent's id/guid so that I don't have nested promises?
Short answer: No.
With Knex, you can retrieve data the same as with SQL, which is record based, not object based, so the closest that you could come would be to use a join to allow doing just a single select to retrieve a single array having elements: recipes, guids, ingredients. This would repeat the recipe & guid for each ingredient, which you avoid by using nested objects. (See the answer below by #Fazal for an example of this.)
As another alternative, you could store the ingredients as a 'blob' field in the recipe table, but I don't believe that MySQL would allow you to create an Array field, so when retrieving the data, you would have to do a transform of the field into the array. And transform it from the Array before updating it into the table. Like: storableData = JSON.stringify(arrayData) and arrayData = JSON.parse(storableData)
There are a few other things that I would suggest to help you improve the code though. (Yeah, I know, not really the question here):
Separate the routing functionality from data handling.
Also, separate data manipulation functionality from retrieval.
Use throw & .catch for creating and handling unsuccessful responses.
The separation of routing, data retrieval, data manipulation makes testing, debugging, and future comprehension easier as each function has a more atomic purpose.
Throwing/.catching unsuccessful process conditions makes it much simpler to have more comprehensive error processing by allowing you to put (most of the time) a single .catch in your router response handling (Hapi.js may even do this .catch for you???).
Also, see the other .catch and .on('query-error' that I added for logging errors. You may have a different logging mechanism you want to use rather than the console. I use Winston. And note that .on('query-error' is NOT a .catch. There will still be an Error() that is thrown, and must be handled somewhere, this will just give you good info about the failure close to the source.
(Sorry, the below code is untested)
path: '/recipes',
method: 'GET',
handler: (req, res) => {
return getRecipeNIngredients()
.then((recipes) => {
res({
count: recipes.length,
data: recipes
});
})
.catch((ex) => {
res({
error: true,
errMessage: ex.message
});
});
};
function getRecipeNIngredients() {
let recipes = null;
return getRecipes()
.then((recipeList) => {
recipes = recipeList;
const recipeGuids = recipes.map(recipe => recipe.guid);
recipes.forEach(r => r.ingredients = []);
return getIngredients(recipeGuids);
})
.then((ingredients) => {
recipes.forEach(r => {
ingredients.forEach(i => {
if (i.recipe === r.guid) {
r.ingredients.push(i);
}
});
});
return recipes;
})
.catch((ex) => {
console.log(".getRecipeNIngredients ERROR ex:",ex); // log and rethrow error.
throw ex;
});
};
function getRecipes() {
return Knex.from('recipes')
// .innerJoin('ingredients', 'recipes.guid', 'ingredients.recipe')
.select()
.orderBy('rating', 'desc')
.limit(10)
.on('query-error', function(ex, obj) {
console.log("KNEX getRecipes query-error ex:", ex, "obj:", obj);
})
.then((recipes) => {
if (!recipes || recipes.length === 0) {
throw new Error('no recipes found')
}
})
};
function getIngredients(recipeGuids) {
return Knex.from('ingredients')
.whereIn('recipe', recipeGuids)
.select()
.on('query-error', function(ex, obj) {
console.log("KNEX getIngredients query-error ex:", ex, "obj:", obj);
})
};
I hope this is Useful!
Gary.
I created a library that return nested object even it has types for typescript
Nested Knex
import * as n from 'nested-knex';
n.array(
n.type({
id: n.number("recipe.id", { id: true }),
title: n.string("recipe.title"),
ingredients: n.array(
n.type({
id: n.number("ingredients.id", { id: true }),
title: n.string("ingredients.title")
})
)
})
)
.withQuery(
knex
.from("recipes")
.innerJoin("ingredients", "recipes.guid", "ingredients.recipe")
.select()
.orderBy("rating", "desc")
.limit(10)
)
.then(recipes => {});
so recipes even have types
You can easily avoid nest query. Just use subquery as-
knex.select('*')
.from(function () {
this.select('*').from('recipes').limit(10).as('recipes'); // limit here
})
.leftJoin('ingredients', 'ingredients.recipe_id', 'recipes.guid')
.then((rec) => {
console.log(rec);
})
see.. just few lines of code.

Categories