MySQL alternative to on duplicate key update - mysql

I'm trying to pull from multiple tables to insert into a table to create role assignments in moodle's database based on the categories that are created but I need it to update on duplicate key but I cant use ON DUPLICATE KEY UPDATE because the fields im trying to match on role id, context id, and user id are not primary keys in the mdl_role_assignments table.
insert into vclassmoodle.mdl_role_assignments (roleid,contextid,userid,timemodified,modifierid,itemid,sortorder) select
mdl_role.id as roleid, mdl_context.id as contextid, mdl_user.id as userid, unix_timestamp() as timemodified, 3 as modifierid, 0 as itemid, 0 as sortorder
from
mdl_context
left join
mdl_course_categories ON mdl_context.instanceid = mdl_course_categories.id
left join
mdl_user ON mdl_course_categories.idnumber = mdl_user.idnumber
join
mdl_role ON mdl_role.shortname = 'manager'
where mdl_context.contextlevel = 40 and mdl_course_categories.depth > 1
Let me know if I need to clarify on anything
Thanks

Just been having a look at the function role_assign() in /lib/accesslib.php
If there is a duplicate then it doesn't update, so you could just ignore duplicates.
Although you should really use the role_assign() function rather than insert data directly. In case the role assignment changes in the future, but also because it triggers a role_assigned event which might be used elsewhere.
Still use your query but ignore existing records and create a loop to call role_assign(), something like this
SELECT mdl_role.id as roleid,
mdl_context.id as contextid,
mdl_user.id as userid
FROM mdl_context
JOIN mdl_course_categories ON mdl_context.instanceid = mdl_course_categories.id
JOIN mdl_user ON mdl_course_categories.idnumber = mdl_user.idnumber
JOIN mdl_role ON mdl_role.shortname = 'manager'
WHERE mdl_context.contextlevel = 40
AND mdl_course_categories.depth > 1
AND NOT EXISTS (
SELECT mdl_role_assignments.id
FROM mdl_role_assignments
WHERE mdl_role_assignments.roleid = mdl_role.id
AND mdl_role_assignments.contextid = mdl_context.id
AND mdl_role_assignments.userid = mdl_user.id
AND mdl_role_assignments.itemid = 0
AND mdl_role_assignments.component = '')
Note that a duplicate is a combination of roleid, userid and contextid but also component and itemid. So component = '' needs to be checked too.

Related

How to get a VALUE of an INSERT INTO -> ON DUPLICATE KEY UPDATE function into a SELECT?

i have setup a DB and what i want is the following:
In Table a i got the users with infos and his points
In Table b i got more infos about each user and his "level"
in Table c i got basic settings
in Table d i got detailed infos about the levels each user can have
What i would like to do is:
either INSERT ON DUPLICATE KEY UPDATE or IF EXISTS UPDATE (insert would be my fav)
I would like to do the query with multiple users at once (by their id)
The problem right now is how do i get the VALUE into the SELECT Statement ?
My Code so far:
INSERT INTO users (id) VALUES ('12345')
ON DUPLICATE KEY UPDATE
points = (points + (
SELECT (d.multiplier * c.base_points)
FROM b
INNER JOIN d ON b.level = d.id , c
WHERE b.level = d.id AND b.id = '12345'));
i now want to change:
WHERE
b.level = d.id AND
b.id = '12345'));
so that i can use VALUES(id) - but when i do that i get NULL as result
WHERE
b.level = d.id AND
b.id = VALUES(id)));
:(
How can i use the id i have ?
regards

Column 'field' in field list is ambiguous in mysql

INSERT INTO users (phone, no_of_coupon)
SELECT couponentries.phone, 1
FROM couponentries LEFT JOIN users ON couponentries.phone =
users.phone
WHERE users.phone IS NULL
ON DUPLICATE KEY UPDATE users.no_of_coupon = users.no_of_coupon + 1;
I am trying to input date in the couponentries table and then it will automatically insert only the phone numbers into users table. If there is a duplicate phone number, no_of_coupon will increment by 1. But when I executed this code in the phpmyadmin trigger trigger, it has an error below. Could someone help me please? Thanks
Could not able to execute Column 'users.no_of_coupon' in field list is ambiguous
You need to provide an alias to users table used in join clause
INSERT INTO users (phone, no_of_coupon)
SELECT c.phone, 1
FROM couponentries c
LEFT JOIN users u ON c.phone = u.phone
WHERE u.phone IS NULL
ON DUPLICATE KEY UPDATE users.no_of_coupon = users.no_of_coupon + 1;
I guess you don't need the join part you can just use
INSERT INTO users (phone)
SELECT c.phone
FROM couponentries c
ON DUPLICATE KEY UPDATE no_of_coupon = no_of_coupon + 1;
Demo

how to delete rows in a single table involving multiple table

hi i'm just new to sql and i have a hard time deleting records. i need to Delete all the records from subjcode table where the teacher is “MARSHALL”. i used this query but i doesn't work:
delete
from subjcode
where (
select sa.sno
from subjcode sa,
teacher,
course
where teacher.tname = 'MARSHALL'
and teacher.tno = course.tno
and course.cno = sa.cno
) = subjcode.sno;
and there is the table and its columns:
subjcode: sno,cno,score
course: cno,tno,cname
teacher:tno,tname
i know that all i need is just the tname,tno, and the cno but i don't know the proper query. please help me thanks
You can delete using join:
delete s
from subjcode s
join course c on s.cno = c.cno
join teacher t on t.tno = c.tno
where t.tname = 'MARSHALL';
See this for reference:
https://dev.mysql.com/doc/refman/5.7/en/delete.html
Try this:
delete s
from subjcode s
join course c on s.cno = c.cno
join teacher t on c.tno = t.tno
where t.tname = 'MARSHALL';

Is there a way to optimize this update query?

I have a master table called "parent" and a related table called "childs"
Now I run a query against the master table to update some values with the sum from the child table like this.
UPDATE master m SET
quantity1 = (SELECT SUM(quantity1) FROM childs c WHERE c.master_id = m.id),
quantity2 = (SELECT SUM(quantity2) FROM childs c WHERE c.master_id = m.id),
count = (SELECT COUNT(*) FROM childs c WHERE c.master_id = m.id)
WHERE master_id = 666;
Which works as expected but is not a good style because I basically make multiple SELECT querys on the same result. Is there a way to optimize that? (Making a query first and storing the values is not an option.
I tried this:
UPDATE master m SET (quantity1, quantity2, count) = (
SELECT SUM(quantity1), SUM(quantity2), COUNT(*)
FROM childs c WHERE c.master_id = m.id
) WHERE master_id = 666;
but that doesn't work.
Update: Here is the solution, thanks to everbody:
You can do something like this:
UPDATE master m
INNER JOIN childs c ON m.master_id = c.master_id
SET master.quantity1 = c.quantity1,
master.count = 1
If you have only one child record at a time. However if you want to use a group function like SUM() in the joined table that doesn't work. Either you get a "Invalid use of group function" if you leave the "group by" part or a "You have an error in your sql syntax if you use "GROUP BY c.master_id"
-- This doesnt work :(
UPDATE master m
INNER JOIN childs c ON m.master_id = c.master_id
SET master.quantity1 = SUM(c.quantity1),
master.count = COUNT(c.*)
GROUP by c.master_id
The solution is to use JOIN with a subquery:
UPDATE master m
INNER JOIN
(
SELECT master_id,
SUM(quantity1) as quantity1,
COUNT(*) as count
FROM childs c
GROUP BY master_id
) c
ON c.master_id = m.master_id
SET m.quantity1 = c.quantity1,
m.count = c.count
WHERE m.master_id = 666;
But since this pulls every row from the childtable the overhead would likely be bigger than using more subqueries like in the original sql. So you should add a WHERE clause to the joined table to get only the rows you need.
Another interesting approach is this syntax, which does the same as the JOIN with the WHERE clause but you should only use if if you want to update all rows with the same values and your subquery only returns one row, since the result from the subquery gets appended to the result and can be used like any column.
UPDATE master m,
(
SELECT SUM(c.quantity1) as sum_of_quantity,
COUNT(*) as rowcount FROM child c WHERE c.master_id = 666
) as c
SET m.quantity1 = c.sum_of_quantity,
m.count = c.rowcount
WHERE m.master_id = 666;
Rewriting Lieven's solution to MySQL:
UPDATE master m
JOIN (
SELECT master_id
, SUM(quantity1) as quantity1
, SUM(quantity2) as quantity2
, COUNT(*) as count
FROM childs c
GROUP BY
master_id
) c
ON c.master_id = m.master_id
SET
m.quantity1 = c.quantity1
,m.quantity2 = c.quantity2
,m.count = c.count
WHERE m.master_id = 666;
I don't know if it is allowed in MySQL, but SQL Server allows you to use the result of a select in an update.
UPDATE master m SET
quantity1 = c.quantity1
, quantity2 = c.quantity2
, count = c.count
FROM master m
INNER JOIN (
SELECT master_id
, quantity1 = SUM(quantity1)
, quantity2 = SUM(quantity2)
, count = COUNT(*)
FROM childs c
WHERE master_id = 666
GROUP BY
master_id
) c ON c.master_id = m.master_id
You could select your data into a temporary table, and then update using that data.
If you also want to insert "new" data in the same roundtrip, look into INSERT INTO ... SELECT FROM ... ON DUPLICATE KEY UPDATE ...
If you already are doing inserts if row doesn't exist, then that would be redundant with this example.
example:
INSERT INTO master m (id, quantity1, quantity2, count)
SELECT master_id, SUM(quantity1) q1, SUM(quantity2) q1, COUNT(*) c
FROM childs
GROUP BY master_id
ON DUPLICATE KEY UPDATE
m.quantity1 = q1,
m.quantity2 = q2,
m.count = c
NOTE! This is untested code, but I think it should be possible to backreference the select result in the UPDATE.
Syntax reference: http://dev.mysql.com/doc/refman/5.0/en/insert.html

How to Fix This MySQL Query So It Works?

I have the following query:
UPDATE lessonstatus
INNER JOIN user ON lessonstatus.user_id = user.user_id
SET user_id = (SELECT user_id FROM user WHERE username = 'too_many_accounts')
WHERE last_name = 'stupid'
AND first_name = 'user'
AND username != 'too_many_accounts'
AND lessonstatus.lesson_id NOT IN (SELECT lesson_id FROM lessonstatus WHERE user_id = 1);
However, I get the following error when trying to execute it:
Error Code : 1093
You can't specify target table 'lessonstatus_rtab' for update in FROM clause
How would I fix this query so that it works?
You can't SELECT from a table (even in a subquery) that you're updating in the same query. That's what the error "can't specify target table" means.
But you can join user and lessonstatus multiple times in the UPDATE statement, and use the join criteria creatively to pick out the individual row you want.
The way to simulate NOT IN with a join is to do a LEFT OUTER JOIN. Where the right side of that join is not matched, that's where NOT IN would be true.
UPDATE lessonstatus l1
INNER JOIN user u1 ON (l1.user_id = u1.user_id)
INNER JOIN user u2 ON (u2.username = 'too_many_accounts')
LEFT OUTER JOIN lessonstatus l2
ON (l1.lesson_id = l2.lesson_id AND l2.user_id = 1)
SET l1.user_id = u2.user_id
WHERE u1.last_name = 'stupid' AND u1.first_name = 'user'
AND u1.username != 'too_many_accounts'
AND l2.lesson_id IS NULL; -- equivalent to "l NOT IN l2"
nb: I have tested this query for syntax, but not with real data. Anyway, it should get you started.
There are more errors ("user" table and "user_rtab" alias do not match, use of non-qualified field names is not recommended), but UPDATE syntax itself should be similar:
UPDATE lessonstatus
SET user_id = (SELECT TOP 1 user_id FROM user WHERE username = 'too_many_accounts')
FROM lessonstatus
INNER JOIN user ON lessonstatus.user_id = user_rtab.user_id
WHERE last_name = 'stupid'
AND first_name = 'user'
AND username != 'too_many_accounts'
AND lessonstatus.lesson_id NOT IN (
SELECT lesson_id FROM lessonstatus WHERE user_id = 1
);