I am trying to create a knex migration. The migration should be a transaction that should add role and some users to database. If the users are already in db the transaction should change their role_id to new role_id
exports.up = function(knex) {
async function transaction(t) {
await t.raw('INSERT INTO "public"."role" VALUES (3, \'external_support\');');
let i;
for(i = 0; i < newUsers.length; i += 1) {
const result = await t.raw('SELECT id FROM "public"."user" WHERE email = ?;', [
newUsers[i].email
]);
if (result.rowCount === 0) {
await t.raw('INSERT INTO "public"."user" (email, first_name, last_name) VALUES (?, ?, ?);', [
newUsers[i].email,
newUsers[i].firstname,
newUsers[i].lastname
]);
await t.raw('INSERT INTO "public"."users_roles" VALUES ((SELECT id FROM "public"."user" WHERE email = ?) , 3);', [
newUsers[i].email
]);
} else {
await t.raw('UPDATE "public"."users_roles" SET role_id = 3 WHERE user_id = (SELECT id FROM "public"."user" WHERE email = ?);', [
newUsers[i].email
]);
}
}
}
So basic logic behind this is (should be)
- add new role
- check whether users exist
- If not add users and set their role_id to new
- If yes then change their role_id to new
So, what I get is
`error: SAVEPOINT can only be used in transaction blocks`
Why?
The issue here was that I did not pay attention to documentation. I know I had seen this but I totally forgot.
By default, each migration is run inside a transaction.
So creating the transaction in transaction will lead to fussy behavior. In this case that was double committing.
Related
I am making search route for property table where user can enter city, state, street_address , minamount , max_amount to search for different properties. My problem is if user only enter one or two fileds search should be filter by those field only. and if user does not enter any parameters, it should show every property.
const sqlQuery = `SELECT * FROM property WHERE state = ? AND city = ? AND street_address = ? AND min_amount >= ? AND max_amount <= ?; `
const values = [req.body.state, req.body.city, req.body.street_address ,req.body.min_amount,req.body.max_amount];
let data = [];
db.query (sqlQuery, values, function (err, results, fields) {
if (err) {
console.log(err)
}
if (results.length >= 1) {
}
You need to construct your sqlQuery manually by checking the existence of each parameter and appending a corresponding WHERE clause individually for each parameter, if this parameter exists.
db structure
I am creating simple conversation app. I have three tables - users(id,name), conversations(id,user1_id, user2_id) and messages (id,conversationId,sender,message,date). Is there a way to actually get all user's conversations and data about another user in one query?
The query below gets id's of the user that logged user has conversations with. I would like now to get these users data base on their id. Thanks for all suggestions.
const getChats = (req, res) => {
const userId = req.params.userId;
db.query(
"SELECT DISTINCT CASE WHEN user1_id = ? THEN user2_id ELSE user1_id END userID FROM conversations WHERE ? IN (user2_id , user1_id)",
[userId, userId],
(e, r) => {
res.send(r);
}
);
};
module.exports = { getChats };
First, I'll answer the second question "how can get rid of doubling conversations where only conversation id differs and users are basically swapped?" to resolve this issue you need to check the conversation table before insertion by the below query
db.query(
"SELECT * FROM conversations WHERE (user1_id = ? AND user2_id = ?) OR (user1_id = ? AND user2_id = ?)",
[senderId, receiverId,receiverId,senderId],
(e, r) => {
res.send(r);
}
);
if the above query returns a record then we'll not insert a new row.
now query for message with user data of a conversation
db.query(
"SELECT m.*,c.id,
s.name as "SenderUserName",
r.name as "ReceiverUserName",
FROM messages m
inner join conversations c on m.conversationId = c.id
inner join user r on r.id = c.user1_id
inner join user s on s.id = c.user2_id
WHERE (c.user1_id = ? AND c.user2_id = ?) OR (c.user1_id = ? AND
c.user2_id = ?)",
[senderId, receiverId,receiverId,senderId],
(e, r) => {
res.send(r);
}
);
the above query will return all messages between 2 users.
though you can remove the conservations table from the structure and it will be easy approach, here is the structure that i suggest
users table
name
type
id
int
name
varchar
messages table
name
type
id
int
message
text
sender_id
int
receiver_id
int
created_at
timestamp
I have a node app where I need to run many MySQL queries asynchronously when a route is called from the browser. I have tried using callbacks but this is highly 'messy' and I'm still not getting the asynchronous behavior I seek - subsequent queries require data from previous queries as criteria.
For example, I run a query that selects email addresses from one table and then need to use that list of email address results to query another table for other course completion data. That's the simplest breakdown of my use case.
Not sure how to implement async/await with this use case or if would even be possible with so many queries. Or maybe this is more of a MySQL best practice issue. Not sure at this point.
But here's a look at the actual queries that should be run when the route is called.
const query1 = `DELETE FROM enrollments;`
const query2 = `INSERT INTO enrollments (
event_id,
email,
enrollment_id,
) SELECT
V.id AS event_id,
C.email,
E.id AS enrollment_id,
FROM
event V
LEFT JOIN
enrollment E ON V.id = E.event_id
LEFT JOIN
contact C ON E.contact_id = C.id
INNER JOIN
location LOC ON V.location_id = LOC.id
WHERE
V.course_id = 256
AND
V.startTime > CURRENT_TIMESTAMP
AND
V.status LIKE 'CONFIRMED'
AND
E.status NOT LIKE 'CANCELLED';`
const query3 = `DELETE FROM elr_results;`
const query4a = `SELECT email FROM enrollments;`
const query4c = `INSERT INTO elr_results (registrationID, coursename, EMAIL) VALUES (?, ?, ?);`
const query4d = `DELETE FROM enrollments_ld;`
const query4f = `INSERT INTO enrollments_ld (registrationID, coursename, email) VALUES (?, ?, ?);`
const query5 = `DELETE FROM tb_credlybadgeresult;`
const query6 = `INSERT INTO tb_credlybadgeresult (recipientemail, badge_id, badge_name, badge_template_state, user_id) VALUES (?, ?, ?, ?, ?)`
const query7 = `DELETE FROM tb_prereqs;`
const query8 = `INSERT INTO tb_prereqs (
event_id,
enrollment_id,
email,
full_name,
status)
SELECT
event_id,
enrollment_id,
email,
CONCAT(TRIM(firstname), " ", TRIM(lastname)),
status
FROM
enrollments;`
const query9 = `UPDATE
prereqs pre
SET
pre.ASnR = 'NO';`
const query10 = `UPDATE
prereqs pre
INNER JOIN
elr_results elr on pre.email = elr.email
SET
pre.F3 = 'YES'
WHERE
elr.coursename LIKE "%Fundamentals%"
AND
elr.coursename LIKE "%Part 3%";`
const query11 = `UPDATE
prereqs pre
INNER JOIN
enrollments_ld ld on pre.email = ld.email
SET
pre.F3 = 'YES'
WHERE
ld.coursename LIKE "%Fundamentals%"
AND
ld.coursename LIKE "%Part 3%";`
const query12 = `UPDATE
prereqs pre
INNER JOIN
enrollments_ld ld on pre.email = ld.email
SET
pre.ASnR = "YES"
WHERE
ld.coursename LIKE "%Advanced Searching%";`
I want to get the number of users that have a lower number of XP points than the member who used the command, this way I can get his rank.
However I don't know much in javascript and sql queries, and I'm hard stuck with this, where it simply returns [object Object] instead of a number.
My sql table
const table = sql.prepare("SELECT count(*) FROM sqlite_master WHERE type='table' AND name = 'scores';").get();
if (!table['count(*)']) {
// If the table isn't there, create it and setup the database correctly.
sql.prepare("CREATE TABLE scores (id TEXT PRIMARY KEY, user TEXT, guild TEXT, points INTEGER, level INTEGER, money INTEGER);").run();
// Ensure that the "id" row is always unique and indexed.
sql.prepare("CREATE UNIQUE INDEX idx_scores_id ON scores (id);").run();
sql.pragma("synchronous = 1");
sql.pragma("journal_mode = wal");
}
// And then we have two prepared statements to get and set the score data.
client.getScore = sql.prepare("SELECT * FROM scores WHERE user = ? AND guild = ?");
client.setScore = sql.prepare("INSERT OR REPLACE INTO scores (id, user, guild, points, level, money) VALUES (#id, #user, #guild, #points, #level, #money);");
});
My attempt
if (message.content.startsWith(prefix + "cl stats")) {
const curxp = score.points;
client.rank = sql.prepare("SELECT count(*) FROM scores WHERE points >= #curxp AND guild = #guild").get();
console.log(client.rank);
await message.reply(`${client.rank}`);
}
Found a solution. Probably not the best but it works.
client.getRank = sql.prepare("SELECT count(*) FROM scores WHERE points >= ? AND guild = ?");
function getRank(){
const curXP = score.points;
let rankOBJ = client.getRank.get(curXP, message.guild.id)
console.log(rankOBJ);
let rankStr = JSON.stringify(rankOBJ);
let rank = rankStr.match(/\d/g);
return rank;
};
Can this be done as one query so there is not multiple request to the DB with the loop? Im trying to get each cameras last photo if any.
async function asyncForEach(array, callback) {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array);
}
}
let cameras = await knex({ cameras: "device_manager_camera" })
.select()
.where("owner_id", 13);
const start = async () => {
let report = [];
asyncForEach(cameras, async camera => {
let photo = await knex({ photos: "device_manager_photo" })
.where("camera_id", camera.id)
.first();
if (photo) {
report[camera.name] = photo.timestamp;
} else {
report[camera.name] = "never";
}
});
console.log(report);
};
start();
First of all, I'd recommend you to write your SQL query in plain SQL and it would be much easier to translate it to knex commands.
As for your request, I've come up with this query which returns the array of [{ camera_id, timestamp }]. It selects cameras of the owner with id 13 and joins this to max timestamps grouped query from table photos.
select
c.name,
coalesce(t.timestamp::text, 'never') as timestamp
from
cameras as c
left join (
select
p.camera_id,
max(p.timestamp) as timestamp
from
photos as p
group by
camera_id
) as t on t.camera_id = c.id
where
c.owner_id = 13;
Correct your table names and columns if necessary.
Bonus style points. I'd not recommend using timestamp as a column name. It is a reserved column in some databases and it might need quotes around it to explicitly specify it as a column in your query, which might be annoying.