update the link table - mysql

I'm validating following SQL approach from the community where I'm completely updating the user's roles. (Full update of the join table)
User
+----+-------+------+
| id | first | last |
+----+-------+------+
| 1 | John | Doe |
| 2 | Jane | Doe |
+----+-------+------+
Role
+----+----------+
| id | name |
+----+----------+
| 1 | admin |
| 2 | accounts |
| 3 | sales |
+----+----------+
UserRole
+--------+--------+
| userid | roleid |
+--------+--------+
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
| 2 | 2 |
| 2 | 3 |
+--------+--------+
My SQL approach -> first delete all, second insert all records
DELETE FROM UserRole WHERE userid = 1;
INSERT INTO UserRole(userid, roleid) VALUES(1, 2), (1, 3);
Is there a better way? I mean to do this in a single query possibly for these sorts of linking/join tables?
Edit
I think what I should have said to find an efficient SQL operation instead of a single query.
Here's another SQL
DELETE FROM UserRole WHERE user_id = 1 AND role_id NOT IN (2, 3);
INSERT INTO UserRole(user_id, role_id) VALUES(1, 2), (1, 3)
ON DUPLICATE KEY UPDATE user_id = VALUES(user_id), role_id = VALUES(role_id);

If you want to add all roles for all users into the userRoles table, you can delete from userRoles, then recreate table like below:
DECLARE #users TABLE ( userID INT, UserName NVARCHAR(MAX) )
DECLARE #roles TABLE ( roleID INT, RoleName NVARCHAR(MAX) )
DECLARE #userRoles TABLE ( userID INT, roleID INT )
INSERT INTO #users (userID,UserName) VALUES (1,'name1'),(2,'name2'),(3,'name3')
INSERT INTO #roles (roleID,RoleName) VALUES (1,'admin'),(2,'accounts'),(3,'sales')
INSERT INTO #userRoles (userID,roleID)
SELECT U.userID,R.roleID FROM #users U
FULL OUTER JOIN #roles R ON 1=1
ORDER BY userID
SELECT * FROM #userRoles
OUTPUT:
userID roleID
1 1
2 1
3 1
1 2
2 2
3 2
1 3
2 3
3 3

Related

Mysql With Rollup did not run what I expected

Here is my example (mysql Ver 14.14 Distrib 5.7.21, for Win64 (x86_64))
DROP TABLE IF EXISTS t_tt;
CREATE TEMPORARY TABLE t_tt SELECT 1 AS tid,'team 1' AS teamName,111 AS teamData;
INSERT INTO t_tt VALUES(2,'team 2',222);
INSERT INTO t_tt VALUES(3,'team 3',333);
SELECT
tid,
isnull(tid),
IF(isnull(tid),'total',teamName) AS displayName,
SUM(teamData)
FROM t_tt GROUP BY tid WITH ROLLUP;
And I think the result must be :
+-----+-------------+-------------+---------------+
| tid | isnull(tid) | displayName | SUM(teamData) |
+-----+-------------+-------------+---------------+
| 1 | 0 | team 1 | 111 |
| 2 | 0 | team 2 | 222 |
| 3 | 0 | team 3 | 333 |
| 3 | 1 | total | 666 |
+-----+-------------+-------------+---------------+
Actually,the real answer is :
+-----+-------------+-------------+---------------+
| tid | isnull(tid) | displayName | SUM(teamData) |
+-----+-------------+-------------+---------------+
| 1 | 0 | team 2 | 111 |
| 2 | 0 | team 3 | 222 |
| 3 | 0 | team 3 | 333 |
| NULL| 0 | team 3 | 666 |
+-----+-------------+-------------+---------------+
I dont know why the column of displayName is begins with "team 2" ,but not "team 1".
And the last row isnull(tid) should equals 1 , but not 0.
The problem is that you are using a MySQL extension. You have teamName in the SELECT, but it is not in the GROUP BY. So, MySQL is free to take this value from any row. To be honest, I thought it would need to be from a matching row. I don't fully understand the behavior.
But, this is easy to fix. One simple method is simply to add an aggregation function:
SELECT tid, tid is null,
COALESCE(max(teamName), 'total') AS displayName,
SUM(teamData)
FROM t_tt
GROUP BY tid WITH ROLLUP;
Note that for the additional row from the ROLLUP, the value is arbitrary.
I also found that the results were fixed if the table were pre-declared with a primary key:
CREATE TABLE t_tt (
tid int primary key,
teamName varchar(255),
teamData int
);
INSERT INTO t_tt VALUES(1, 'team 1', 111);
INSERT INTO t_tt VALUES(2, 'team 2', 222);
INSERT INTO t_tt VALUES(3, 'team 3', 333);
It's better to process all the extra information out of the group query, since some columns not grouped could have an arbitrary value:
SELECT tid,
isnull(tid),
IF(isnull(tid),'total',teamName) AS displayName,
team_sum
FROM
(
SELECT
tid,
teamName,
SUM(teamData) as team_sum
FROM t_tt
GROUP BY tid WITH ROLLUP
) t
See this Sql Fiddle: http://sqlfiddle.com/#!9/8871cc/9

SubQuery returns one row when getting data from main query comma separated ids?

SELECT
e.*,
(
SELECT GROUP_CONCAT(topic_name)
FROM topic
WHERE id IN (e.topic_ids)) AS topics
FROM exam e
result :
topics = xyz topic
this query returns a single name of topic as result but when i use this :
SELECT
e.*,
(
SELECT GROUP_CONCAT(topic_name)
FROM topic
WHERE id IN (1,4)) AS topics
FROM exam e
result :
topics = xyz topic,abc topic
That works fine,and exam table had the same value in DB (comma separated topic ids = 1,4) as varchar type field.
is there any issue with datatype of field?
First, let me lecture you about how bad CSV in field is.
| id | topic_ids |
|----|-----------|
| 1 | a,b,c |
| 2 | a,b |
This, is how Satan look like in relational DB. Probably the worst, just after the
"lets put columns as line and use a recursive join to get everything back."
How it should be ?
exam
| id |
|----|
| 1 |
| 2 |
exam_topic
| exam_id | topic_id |
|---------|----------|
| 1 | a |
| 1 | b |
| 1 | c |
| 2 | a |
| 2 | b |
topic
| id |
|----|
| a |
| b |
| c |
Now, as awful as it may be, this is the "dynamic" alternative, using FIND_IN_SET() :
SELECT
e.*,
(
SELECT GROUP_CONCAT(topic_name)
FROM topic
WHERE FIND_IN_SET(id, e.topic_ids) > 0
) AS topics
FROM exam e
SQL Fiddle
MySQL 5.6 Schema Setup:
CREATE TABLE exam
(`id` int, `topic_ids` varchar(5))
;
INSERT INTO exam
(`id`, `topic_ids`)
VALUES
(1, 'a,b,c'),
(2, 'a,b'),
(3, 'b,c,d'),
(4, 'd')
;
CREATE TABLE topic
(`id` varchar(1), `topic_name` varchar(4))
;
INSERT INTO topic
(`id`, `topic_name`)
VALUES
('a', 'topA'),
('b', 'topB'),
('c', 'topC'),
('d', 'topD')
;
Query 1:
SELECT
e.*,
(
SELECT GROUP_CONCAT(topic_name)
FROM topic
WHERE FIND_IN_SET(id, e.topic_ids) > 0
) AS topics
FROM exam e
Results:
| id | topic_ids | topics |
|----|-----------|----------------|
| 1 | a,b,c | topA,topB,topC |
| 2 | a,b | topA,topB |
| 3 | b,c,d | topB,topC,topD |
| 4 | d | topD |

Select data with count of data from another table [duplicate]

This question already has answers here:
How can I get multiple counts with one SQL query?
(12 answers)
Closed 7 years ago.
I have two tables: `jobs' & 'clients'.
The clients table looks like this:
/----+------\
| id | name |
+----+------+
| 1 | Ben |
| 2 | Max |
\----+------/
And the jobs table:
/----+-----------+--------\
| id | client_id | status |
+----+-----------+--------+
| 1 | 1 | alpha |
| 2 | 1 | beta |
| 3 | 1 | beta |
| 4 | 2 | beta |
\----+-----------+--------/
I'm looking to create a statement that will return the name of the client along with the number of times each status appears, like this:
/------+-------+------\
| name | alpha | beta |
+------+-------+------+
| Ben | 1 | 2 |
| Max | 0 | 1 |
\------+-------+------/
I do not require there to be a 0 where no values exist though.
I have tried SELECT name, (SELECT status FROM jobs WHERE client_id = clients.id) FROM clients but it returns more than one row in the sub-query.
Here's an example with a solution: http://sqlfiddle.com/#!9/80593/3
create table clients (id int, name varchar(10));
insert into clients values (1, 'Ben'), (2, 'Max');
create table jobs (id int, client_id int, status varchar(10));
insert into jobs values (1, 1, 'alpha'), (2, 1, 'beta'), (3, 1, 'beta'), (4, 2, 'beta');
select
clients.name,
sum(case when jobs.status = 'alpha' then 1 else 0 end) alpha,
sum(case when jobs.status = 'beta' then 1 else 0 end) beta
from jobs
inner join clients
on jobs.client_id = clients.id
group by clients.name;

Update a table in MySQL with all data from another table

I have 3 tables: ak_class, ak_objects, ak_class_object
ak_class:
class_id | class_description | class_name |
1 | some description | some name |
2 | some description | some name |
3 | some description | some name |
ak_objects:
object_id | object_description | object_name |
1 | some description | some name |
2 | some description | some name |
3 | some description | some name |
ak_class_object:
class_object_id | class_id | object_id |
1 | 1 | 1 |
2 | 2 | 2 |
3 | 3 | 3 |
I need to fill in the ak_class_object with a class_id from ak_class table and object_id from ak_objects table.
The question is how can I update (I need to update as there is some wrong data currently) the class_id from the ak_class table with all the ids? I was thinking of using it with JOIN ut I don't know which id to use to Join them as class_id is only to be updated
UPD: I was trying to do it like this, but it didn't work:
DELIMITER $$
DROP PROCEDURE class_object_1$$
CREATE PROCEDURE class_object_1()
BEGIN
DECLARE i INT DEFAULT 0;
WHILE (i < 250000) DO
UPDATE ak_class_object
SET class_id = SELECT DISTINCT class_id from ak_class, object_id = SELECT DISTINCT class_id from ak_objects;
SET i = i + 1;
END WHILE;
END$$
I am writing the generic syntax, change the table names and column name as per your requirements.
update table1 inner join table2
on table1.id = table2.fk_id
set table1.column = table1.columnUpdated

mysql concat from select result

I would like to insert data in MySQL with automatic naming on field username but how i can do it?.
Currently data at table is:
+----+----------+
| id | username |
+----+----------+
| 1 | admin1 |
| 2 | admin2 |
+----+----------+
I try using this sql but it's can't:
INSERT INTO `tbl_user` (
`username`
)
VALUES (
CONCAT('admin',(SELECT MAX(SUBSTRING_INDEX(`username`,'admin',-1))+1 FROM `tbl_user`))
);
and get error message #1093 - You can't specify target table 'tbl_user' for update in FROM clause
Final result i want is:
+----+----------+
| id | username |
+----+----------+
| 1 | admin1 |
| 2 | admin2 |
| 6 | admin3 |
| 9 | admin4 |
+----+----------+
is that possible? thanks.
You can use a trigger that would update the column username after an insert. Here's some more information on how to actually do this: http://www.roseindia.net/sql/trigger/mysql-trigger-after-insert.shtml
Edit
I forgot that MySQL won't allow you to update a table from a trigger declared on the same table.
However, this should do what you're trying to do:
SET #id := (SELECT id FROM YOUR_TABLE ORDER BY id DESC LIMIT 1);
INSERT INTO YOUR_TABLE (username) VALUES(
CONCAT("ADMIN", #id + 1)
);
Query:
SQLFIDDLEExample
INSERT INTO `tbl_user` (
`username`
)
VALUES (
CONCAT('admin',(SELECT MAX(CAST(REPLACE(`username`,'admin','') AS SIGNED INTEGER))+1
FROM (SELECT * FROM tbl_user) t))
);
Result:
| ID | USERNAME |
---------------------
| 1 | admin1 |
| 2 | admin2 |
| (null) | admin3 |