I am running an Express server and connecting to MySQL Database. I am have two tables which are referenced via the user_id. When the user provides the email address I need to query users to find the user_id then run another query to join the tracking tables. What is the best way to achieve this? Also any best practice recommendations for any part of the code is appreciated!
Thanks
MySQL
CREATE TABLE users (
user_id INT NOT NULL AUTO_INCREMENT UNIQUE,
first_name VARCHAR(100) NOT NULL,
last_name VARCHAR(100) DEFAULT NULL,
email VARCHAR(320) NOT NULL UNIQUE,
date_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (user_id)
);
CREATE TABLE tracking (
tracking_id INT NOT NULL AUTO_INCREMENT,
user_id INT NOT NULL,
metric VARCHAR(100) NOT NULL,
unit VARCHAR(100) NOT NULL,
amount DOUBLE DEFAULT NULL,
date_tracked DATE,
date_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (tracking_id),
INDEX user_ind (user_id),
FOREIGN KEY (user_id)
REFERENCES users(user_id)
ON DELETE CASCADE
);
Express (Nodejs)
const express = require("express");
const app = express();
const port = 3000;
var mysql = require("mysql");
var connection = mysql.createConnection({
host: "",
user: "",
password: "",
database: "tracker"
});
app.get("/data/:email/metric/:metric", (req, res) => {
console.log(req.params);
let user_id = "";
connection.connect();
connection.query(
`SELECT user_id FROM users WHERE email = '${req.params.email}';`,
function(error, results, fields) {
if (error) throw error;
user_id = results[0].user_id;
}
);
connection.query(
`SELECT users.first_name, users.last_name, users.email, tracking.metric, tracking.unit, tracking.amount, tracking.date_tracked FROM users JOIN tracking ON users.user_id = tracking.user_id WHERE users.user_id = '${user_id}' AND metric = '${
req.params.metric
}';`,
function(error, results, fields) {
if (error) throw error;
res.send(results);
}
);
connection.end();
});
app.listen(port, () => console.log(`Example app listening on port ${port}!`));
You can accomplish this with a single query, just JOIN the tables on user_id
SELECT
u.first_name,
u.last_name,
u.email,
t.metric,
t.unit,
t.amount,
t.date_tracked
FROM users u
INNER JOIN tracking AS t ON t.user_id = u.user_id
WHERE u.email = '<email>' AND t.metric = '<metric>'
Also, FWIW you should use parameterised queries to protect yourself from SQL Injection
const values = [
req.params.email,
req.params.metric
];
connection.query(
'SELECT ... WHERE u.email = ? AND t.metric = ?',
values,
(err, results, fields) => {
if (err) return next(e); // propagate the error to Express, rather than throwing in the callback
res.send(results);
}
);
I need to query users to find the user_id then run another query to join the tracking tables. What is the best way to achieve this?
You can achieve it with only 1 query:
SELECT users.first_name, users.last_name, users.email,
tracking.metric, tracking.unit, tracking.amount,
tracking.date_tracked
FROM users
JOIN tracking
ON users.user_id = tracking.user_id
WHERE users.email = '${req.params.email}' AND
metric = '${req.params.metric}
Also any best practice recommendations for any part of the code is appreciated!
First of all, you should connect to mySql after the server as started .
Connecting&Disconnecting on each request will impact the performance(It's expensive task opening a socket and connecting outside)
so try something like :
CONNECTION.connect().then(() => {
// mysql is ready , let's start express server
// accepting connections
app.listen(port, () => console.log(`ready on ${port}!`));
}).catch(console.error)
Another good practice you should know is running queries in parallel
with Promise.all([]). when you need to run multiple , unrelated queries ,
you can Execute them all one after the other , making the total wait time for all results much smaller .
Related
Heres my tables that a wanna fetch the data, i am using express to make a get request from the next app:
model Provider {
id String #id #default(cuid())
name String
email String
password String
phone String
photo String?
service Service? #relation(fields: [serviceId], references: [id])
serviceId String?
location Location? #relation(fields: [locationId], references: [id])
locationId String?
createdAt DateTime #default(now())
text String
starAverage Float
medals Medals[]
comment Comment[]
}
model Service {
id String #id #default(cuid())
type String
provider Provider[]
}
I wanna fetch the type of the service of the services table of a provider, not the serviceId, and this is my route.
router.get('/list', async (req: Request, res: Response) => {
const allClients = await prisma.client.findMany()
res.json({allClients})
})
this is how i am fetching the data of the rest API using axios
const [providers, setProviders] = useState([] as any[])
useEffect(() => {
axios.get('http://localhost:5140/providers/list')//my list of all providers
.then(res => {
console.log(res)
setProviders(res.data)
}).catch(err => {
console.log(err)
})
}, )
const renderedProviders = Object.values(providers).map(provider => {
return (
<div
className="card"
style={{ width: "18rem"}}
key={provider.id}
>
<img className="card-img-top" src="..."/>
<div className="card-body">
<h3>{provider.name}</h3>
<p>{provider.starAverage} estrekas</p>
<p>{provider.serviceId}</p>
</div>
</div>
);
});
return (
<div className="d-flex flex-row flex-wrap justify-content-between">
{renderedProviders}
</div>
)
for now a only get the serviceId of a provider, not the type of the service
To fetch data from another table referenced by a foreign key in a database, you can use a JOIN clause in your SQL query. A JOIN clause allows you to combine rows from two or more tables based on a related column between the tables.
this how you can use a JOIN clause to fetch data from two tables, users and orders, that are related by a foreign key.
SELECT users.*, orders.*
FROM users
JOIN orders ON orders.user_id = users.id
the JOIN clause combines rows from the users and orders tables based on the user_id column in the orders table and the id column in the users table. The SELECT clause specifies the columns to be retrieved from the users and orders tables.
Edited
how you can reference this in the express route and in the http request from axios ?
you can use the sequelize.query(Sequelize is a promise-based Node.js ORM) method to execute a raw SQL query.
app.get('/users/:id', (req, res) => {
const { id } = req.params;
const query = `
SELECT users.*, orders.*
FROM users
JOIN orders ON orders.user_id = users.id
WHERE users.id = :id
`;
const replacements = { id };
sequelize.query(query).then(([results, metadata]) => {
res.send(results);
});
});
the sequelize.query method is used to execute a raw SQL query that includes a JOIN clause to fetch data from the users and orders tables.
I have three tables with data schema, like:
TABLE user (
user_id BINARY(16) PRIMARY KEY NOT NULL,
created DATETIME NOT NULL,
last_updated DATETIME,
coordinator BINARY(16),
num_updates INT NOT NULL
);
TABLE summary (
user_id BINARY(16) PRIMARY KEY NOT NULL,
calculation_time DATETIME NOT NULL,
calculation_method VARCHAR(25) NOT NULL,
label VARCHAR(50) NOT NULL,
critical_count INT NOT NULL,
median_risk FLOAT(10)
);
TABLE actions(
user_id BINARY(16) PRIMARY KEY NOT NULL,
label VARCHAR(50) NOT NULL,
access_count INT NOT NULL,
median FLOAT(10)
);
The data for all the users (user table) is simply fetched using the lambda handler function in the following manner:
const AWS = require('aws-sdk');
const rdsDataService = new AWS.RDSDataService();
module.exports.hello = async (event, context, callback) => {
const req_id = "5a9dbfca-74d6-471a-af27-31beb4b53bb2";
const sql = 'SELECT * FROM user WHERE user_id=:id';
try {
const params = {
resourceArn: 'arn:aws:rds:us-west-********************',
secretArn: 'arn:aws:secretsmanager:us-west**************',
sql,
database: 'dev_db1',
continueAfterTimeout: true,
includeResultMetadata: true,
parameters: [{ 'name': 'id', 'value': { 'stringValue': `${req_id}` } }]
}
const db_res = await rdsDataService.executeStatement(params).promise();
const convertToJson = (dbresponse) => {
const columns = dbresponse.columnMetadata.map(col => col.name);
const row_data = dbresponse.records.map(row => {
const json_obj = {};
row.map((val, i) => {
json_obj[columns[i]] = Object.values(val)[0];
});
return json_obj;
});
return row_data;
};
const modified_data = convertToJson(db_res);
const response = {
body: {
statusCode: 200,
message: 'Data fetched successfully',
data: modified_data,
}
};
callback(null, response);
} catch (error) {
console.log('Error Received', error);
const error_res = {
body: {
statusCode: error.statusCode,
message: error.message,
data: null
}
}
callback(null, error_res);
}
};
If the same is followed for another table summary or actions, it also works. Now, I need to combine all the columns of these three tables and then return the data (returned rows should match on the basis of req_id).
My working snippet: https://dbfiddle.uk/?rdbms=mysql_5.7&fiddle=016ecc94c792611fbaca810605e81a6a
But the final result obtained contains the column user_id in duplicated form i.e. three times inclusion. I don't need the same column to be repeated thrice.
I am a bit new to handling MySQL queries, so unable to figure out the exact reason for the error even when the table exists. The MYSQL version used in Aurora is 5.7.
Any help to resolve the same is appreciated!
Plan A: Explicitly specify the columns you want. Extra benefit: You can get rid of the ids, which tend to be useless to others reading the output.
Plan B: (This option is not always possible.) Instead of JOIN .. ON t1.a = t2.a, say JOIN .. USING(a)
I like to use short aliases. Here's doing all things together:
SELECT u.last_name, u.first_name,
s.risk_score,
t.likes
FROM user AS u
JOIN summary AS s USING(user_id)
LEFT JOIN test AS t USING(user_id)
In general, it is not wise to have a 1:1 relationship (as you have via user_id); you may as well have all the columns in a single table.
try this
SELECT users.*, summary.* from users, summary WHERE users.user_id = summary.user_id
OR
SELECT * from users, summary WHERE users.user_id = summary.user_id
I am a beginner in mySQL and I am trying to create a user's table with information about the user (see code) and populate a column with details that I get from a new table that gets created.
Now I want to be able to put some information from the 'creditcards' table like number for example, to the 'users' table which includes a column 'creditcard', so that I can see each user's credit card number.
I am also comparing the name of the user with the name of the credit card owner so it populates the table according to the user.
I couldn't find any information about the specific problem I am having here.
Here's how I am trying to write:
con.query(createNewCreditCard, [name, type, number, expiration, svss], (err, results) => {
if (err) {
console.error(err);
} else {
const JoinCreditCard = 'INSERT INTO users (creditcard) SELECT number,name FROM creditcards WHERE users.name = creditcards.name';
const userCreateModel = `
CREATE TABLE IF NOT EXISTS users (
id INT(11) NOT NULL AUTO_INCREMENT,
name VARCHAR(45) NOT NULL,
username VARCHAR(45) NOT NULL,
email VARCHAR(45) NOT NULL,
phonenumber VARCHAR(45) NOT NULL,
password VARCHAR(255) NOT NULL,
creditcard INT(11),
salt VARCHAR(255),
created_at DATE,
update_at DATE,
deleted_at DATE,
lastSignIn DATE,
PRIMARY key (id)
)
`;
const CreditCardModel = `
CREATE TABLE IF NOT EXISTS creditcards (
id INT(11) NOT NULL AUTO_INCREMENT,
name VARCHAR (25) NOT NULL,
type VARCHAR(10) NOT NULL,
number INT(12) NOT NULL,
expiration DATE,
svss INT(3) NOT NULL,
created_at DATE,
PRIMARY key (id)
)
`;
I am trying to create a user named ula and then a credit card with the name ula (and all other columns) which I am sending from postman.
The credit card part in nodejs looks like this:
const createCreditCard = async (req, res, next) => {
const {
name,
type,
number,
expiration,
svss
}: {
name: String,
type: String,
number: String,
expiration: String,
svss: String
} = req.body;
const createAt = new Date(Date.now());
try {
const createNewCreditCard = 'INSERT INTO creditcards (name, type, number, expiration, svss) VALUES (?,?,?,?,?)';
con.query(createNewCreditCard, [name, type, number, expiration, svss], (err, results) => {
if (err) {
console.error(err);
} else {
const JoinCreditCard = 'UPDATE users SET creditcard=' + number + ' WHERE name="' + name + '"';
console.log(results);
}
});
res.status(201).send({ success: true, message: 'New credit card was created', data: {name, type, number, expiration, svss} });
} catch (error) {
res.status(500).send({ success: false, message: 'Server error' });
}
await next;
}
The server returns 201, I go to mysql, open users, see column creditcard and its NULL.
Because the entry already exists in the database for users table, you should use UPDATE instead of INSERT.
An example that should work with your code (you already know name and number as vars because you just created the credit card info with them, no need to select them again):
con.query(createNewCreditCard, [name, type, number, expiration, svss], (err, results) => {
if (err) {
console.error(err);
} else {
const JoinCreditCard = 'UPDATE users SET creditcard=' + number + ' WHERE name="' + name + '"';
EDIT: this is the new code from your edit. You already use prepared statements, so forget my notice about that. I've updated the query to follow this. What was missing in your code is that you need to actually do the query! Only declaring the constant won't do anything to your database..
try {
const createNewCreditCard = 'INSERT INTO creditcards (name, type, number, expiration, svss) VALUES (?,?,?,?,?)';
con.query(createNewCreditCard, [name, type, number, expiration, svss], (err, results) => {
if (err) {
console.error(err);
} else {
console.log(results);
const JoinCreditCard = 'UPDATE users SET creditcard=? WHERE name=?';
con.query(JoinCreditCard, [number, name], (err, results) => {
if (err) {
console.error(err);
} else {
console.log(results);
}
});
}
});
res.status(201).send({ success: true, message: 'New credit card was created', data: {name, type, number, expiration, svss} });
} catch (error) {
res.status(500).send({ success: false, message: 'Server error' });
}
NOTE: you should know that using the name to reference the credit card will not allow you to have multiple credit cards for one user, and should be careful about users with the same name, or else this query will update both users. It would be safer to always use the user id field in the WHERE clause. (you should know it at this point)
THIS IS WHAT I RECOMMEND:
it's usually better that the creditcard in users only stores the id from the creditcards table. Like this, relations are on the primary key and it's more optimized (you need to get the id after the credit card creation request, in an inner SELECT in following code).
use the ids to identify rows updates, to prevent 2 users to be updated
delete name from creditcards table, it's already in users
having a third table to reference the relations like states user1974729 is not mandatory, however, it will be the case if you conveniently want to be able to have more than one credit card per user or more than one user that share a card (1 to n relation)
code:
//relation based on id instead of number stored in users + name removed. I assume at this point, you know the id of your user (in var "id" used below in `WHERE` clause)
con.query(createNewCreditCard, [type, number, expiration, svss], (err, results) => {
if (err) {
console.error(err);
} else {
const JoinCreditCard = 'UPDATE users SET creditcard=(SELECT id FROM creditcards WHERE type="' + type + '" AND number="' + number + '" AND expiration="' + expiration + '" AND svss="' + svss + '") WHERE id="' + id + '"';
//no change in userCreateModel
//deleted "name" in CreditCardModel
const CreditCardModel = `
CREATE TABLE IF NOT EXISTS creditcards (
id INT(11) NOT NULL AUTO_INCREMENT,
type VARCHAR(10) NOT NULL,
number INT(12) NOT NULL,
expiration DATE,
svss INT(3) NOT NULL,
created_at DATE,
PRIMARY key (id)
)
`;
it is important that the tables are in normalized forms.
There should be 3 tables.
Users -- all the user data
Credit cards -- all the credit card related information.
Users credit card map -- map users to credit card information.
Firstly, if anyone can edit my question title or question to make more sense, please do.
I have a node/express app making mysql queries with mysql.js. I have a query that looks up a table of questions and then runs a map function on the results. Within that map function, I need to query another table, of answers, corresponding to each record in the questions table. The value I need is the number of answers to that question, ie the number of records in each answers table. I've tried all kinds of different examples, but nothing quite fits my case in a way that makes sense to me. New at Node and Express, and even MySQL so having a hard time picking out quite what to.
I understand that the problem is the async nature of node. getAnswersCount() returns "count" before the query finishes. Below is my code. Need some advice on how to achieve this.
The value 123 is assigned to count just to clarify the trace results.
app.get('/', (req, res) => {
db.query('SELECT * FROM questions LIMIT 0, 100',
(error, results) => {
if (error) throw error;
questions = results.map(q => ({
id: q.id,
title: q.title,
description: q.description,
answers: getAnswersCount( q.id )
}));
res.send( questions );
});
});
const getAnswersCount = ( id ) =>
{
const tableName = 'answers_' + id;
var count = 123;
var sql = `CREATE TABLE IF NOT EXISTS ${tableName}(
id int primary key not null,
answer varchar(250) not null
)`;
db.query( sql,
(error, results) => {
if (error) throw error;
//console.log( 'answers table created!' );
});
sql = `SELECT COUNT(*) AS answersCount FROM ${tableName}`;
db.query( sql,
(error, results) => {
if (error) throw error;
//console.log( count ); // will=123
count = results[0].answersCount;
//console.log( count ); // will = results[0].answerCount
});
// I know this code runs before the query finishes, so what to do?
//console.log( count ); //still 123 instead of results[0].answersCount
return count;
}
EDIT: After attempting various versions of Michael Platt's suggestion in his answer without success, I finally worked out a solution using Express callbacks and a promise, adding the answers values to the questions array afterwards:
app.get( '/', (req, res, next ) => {
db.query('SELECT * FROM questions LIMIT 0, 100',
(error, results) => {
if (error) throw error;
questions = results.map(q => ({
id: q.id,
title: q.title,
description: q.description,
}));
next();
});
}, (req, res ) => {
questions.map( currentElem => {
getAnswersCount( currentElem.id ).then( rowData => {
currentElem.answers = rowData[0].answersCount;
if( currentElem.id == questions.length ) res.send( questions );
});
});
});
const getAnswersCount = ( id ) => {
const tableName = 'answers_' + id;
var sql = `CREATE TABLE IF NOT EXISTS ${tableName}(
id int primary key not null,
answer varchar(250) not null
)`;
db.query( sql,
(error, results) => {
if (error) throw error;
//console.log( 'answers table created!' );
});
sql = `SELECT COUNT(*) AS answersCount FROM ${tableName}`;
return new Promise( ( resolve, reject ) => {
db.query( sql, ( error, results ) => {
if ( error ) return reject( err );
resolve( results );
});
});
}
I'm not sure which database module you are using to connect to and query the database but you could make the method async and then await the response from the query like so:
const getAnswersCount = async ( id ) =>
{
const tableName = 'answers_' + id;
var count = 123;
var sql = `CREATE TABLE IF NOT EXISTS ${tableName}(
id int primary key not null,
answer varchar(250) not null
)`;
var results = await db.query(sql);
sql = `SELECT COUNT(*) AS answersCount FROM ${tableName}`;
var count = db.query(sql)[0].answerCount;
// I know this code runs before the query finishes, so what to do?
//console.log( count ); //still 123 instead of results[0].answersCount
return count;
}
app.get('/', async (req, res) => {
db.query('SELECT * FROM questions LIMIT 0, 100',
(error, results) => {
if (error) throw error;
questions = results.map(q => {
const answerCount = await getAnswersCount( q.id )
return {
id: q.id,
title: q.title,
description: q.description,
answers: answerCount
}
}));
res.send( questions );
});
});
I think that will give you what you want and run correctly but it might require a bit of tweaking. You may need to async the function on the actual route itself as well and await the call for getAnswersCount but that should just about do it.
I have created a simple CRUD application with ExpressJS and MySQL. What I am trying to do is to show the "Users Name" of a specified post, based on which user created that post.
I have 2 tables... posts & users. The posts table has an author column that fills with the users ID when a post is created.
I am able to make the specific "Post" show its data based on the post ID (i.e Post Title & Post Body). However when I try to show the "Users Name" based on which user created that specific post it shows every "Users Name" inside of the users table.
Can anyone help me so that when I view a specific post it will only show the correct "Users Name" based on the posts that that user has created?
My code is below:
posts.js
// Read A Post
router.get('/:id', (req, res, next) => {
let sql = `SELECT * FROM posts WHERE id = ${req.params.id}`;
let query = db.query(sql, (error, result) => {
if(error) throw error;
db.query(`SELECT name FROM users`, (error, userName) => {
if(error) throw error;
res.render('post', {
posts: result,
author: userName
});
});
});
});
posts.pug
extends layout
block content
ul.list-group
each post in posts
li.list-group-item
h1= JSON.stringify(post.title)
h3= JSON.stringify(author)
p= JSON.stringify(post.body)
a.btn.btn-primary.float-right(href='/posts/update/' + post.id) Update
a.btn.btn-danger.float-right(href='/posts/delete/' + post.id) Delete
posts Schema
Field Type Null Key Default Extra
id int(11) NO PRI NULL auto_increment
title varchar(255) YES NULL
body varchar(255) YES NULL
author varchar(255) YES NULL
users Schema
Field Type Null Key Default Extra
id int(11) NO PRI NULL auto_increment
name varchar(255) YES NULL
email varchar(255) YES NULL
username varchar(255) YES NULL
password varchar(255) YES NULL
Any help is appreciated. Thank you.
in the code you make 2 independent queries:
1) select relevant posts
2) select all users
use the Inner Join to achieve what you want.
https://www.w3schools.com/sql/sql_join_inner.asp
your code will look smth like this:
// Read A Post
router.get('/:id', (req, res, next) => {
let sql = `SELECT * FROM posts INNER JOIN users ON posts.userid= users.id WHERE posts.id = ${req.params.id} `;
let query = db.query(sql, (error, result) => {
if(error) throw error;
res.render('post', {
posts: result,
author: result
});
});
});