Update table based on result of select on another table - mysql

I have two tables like this:
person:
id | name | sale | commission
1 | abc | 0 | 0
2 | xyz | 0 | 0
sale:
id | date | person_id | sale | commission
1 | 2016-05-01 | 1 | 10 | 1
2 | 2016-05-02 | 1 | 10 | 1
3 | 2016-05-03 | 1 | 10 | 1
4 | 2016-05-01 | 2 | 20 | 2
5 | 2016-05-02 | 2 | 20 | 2
6 | 2016-05-01 | 2 | 20 | 2
I want to update person table with single update query and change the table something like this:
person:
id | name | sale | commission
1 | abc | 30 | 3
2 | xyz | 60 | 6
I know I can sum sale like following but how to update following query result into person table directly.
SELECT person_id, SUM(sale), SUM(commission)
FROM sale
GROUP BY person_id;

As Strawberry said in the comments under your question, think long and hard before you save this information. It is denormalized, and it becomes stale. Rather, consider using it during report generation. Otherwise, well, as said, you may run into problems.
drop table if exists person;
create table person
( personId int auto_increment primary key,
name varchar(100) not null,
totSales decimal(9,2) not null,
totComm decimal(9,2)
);
insert person(name,totSales,totComm) values
('Joe',0,0),
('Sally',0,0);
-- just added persons 1 and 2 (auto_inc)
drop table if exists sale;
create table sale
( saleId int auto_increment primary key,
saleDate date not null,
personId int not null,
sale decimal(9,2) not null,
commission decimal(9,2) not null,
index(personId), -- facilitate a snappier "group by" later
foreign key (personId) references person(personId) -- Ref Integrity
);
insert sale(saleDate,personId,sale,commission) values
('2016-05-01',2,10,1),
('2016-05-01',1,40,4),
('2016-05-02',1,30,3),
('2016-05-07',2,10,1),
('2016-05-07',2,90,9);
-- the following dies on referential integrity, FK, error 1452 as expected
insert sale(saleDate,personId,sale,commission) values ('2016-05-01',4,10,1);
The update statement
update person p
join
( select personId,sum(sale) totSales, sum(commission) totComm
from sale
group by personId
) xDerived
on xDerived.personId=p.personId
set p.totSales=xDerived.totSales,p.totComm=xDerived.totComm;
The results
select * from person;
+----------+-------+----------+---------+
| personId | name | totSales | totComm |
+----------+-------+----------+---------+
| 1 | Joe | 70.00 | 7.00 |
| 2 | Sally | 110.00 | 11.00 |
+----------+-------+----------+---------+
2 rows in set (0.00 sec)
xDerived is merely an alias name. All derived tables need an alias name, whether or not you use the alias name explicitly.

UPDATE person
SET sale = (
SELECT SUM(s.sale) FROM sale s
WHERE s.person_id = person.id
);
works for me. See it in action at: http://ideone.com/F32oUU
EDIT for new version with additional aggregated column:
UPDATE person SET
sale = (
SELECT SUM(s.sale) FROM sale s
WHERE s.person_id = person.id
),
commission = (
SELECT SUM(s.commission) FROM sale s
WHERE s.person_id = person.id
);
http://ideone.com/yo1A9Y
This being said, I feel sure that a JOIN solution is better, and am hopeful another answerer will be able to post such a solution.

Related

MySQL Group_Concat Row to colum and Count Value

I am working with 2 tables and need help to produce an output by using group concat, and i need to sum the value first be grouping
Here is the fiddle: https://www.db-fiddle.com/f/kSwpa6y4UByAMeQWix3m3x/1
Here is the table:
CREATE TABLE teacher (
TeacherId INT, BranchId VARCHAR(5));
INSERT INTO teacher VALUES
("1121","A"),
("1132","A"),
("1141","A"),
("2120","B"),
("2122","B"),
("2123","B");
CREATE TABLE activities (
ID INT, TeacherID INT, Hours INT);
INSERT INTO activities VALUES
(1,1121,2),
(2,1121,1),
(3,1132,1),
(4,1141,NULL),
(5,2120,NULL),
(6,2122,NULL),
(7,2123,2),
(7,2123,2);
My SQL:
select IFNULL(sumhours.hr,0) as totalhours, t.branchid, t.teacherid
from teacher t
left join
(select teacherid, sum(hours) as hr from activities
group by teacherid
order by hr asc) as sumhours
on
t.teacherid = sumhours.teacherid
order by branchid, hr
Output:
+---------------+-------------------+--------------------+
| totalhours | branchid | teacherid |
+---------------+-------------------+--------------------+
| 0 | A | 1141 |
| 1 | A | 1132 |
| 3 | A | 1121 |
| 0 | B | 2120 |
| 0 | B | 2122 |
| 4 | B | 2123 |
+---------------+-------------------+--------------------+
Explanation:
Table teacher consist teacher id and branch id, while table activities consist of id, foreign key teacher id, and hours. Hours indicate duration of each activities made by teacher. Teacher can do more than one activities or may not do any activities. Teachers who not doing any activity will be set to null.
The objective of queries is to produce a table that consist of summary of teachers activity by branch and group by hours.
In the expected output table, 'Hours' is a fixed value to indicate hours from 0 - 4. A and B columns are branch. The value indicates total number of teachers who are doing activities. So, for row 0, there are 1 teacher for branch A and 2 teachers for branch B who are not doing activities.
Expected output:
+-----------+------------+------------+
| Hours | A | B |
+-----------+------------+------------+
| 0 | 1 | 2 |
| 1 | 1 | 0 |
| 2 | 0 | 0 |
| 3 | 1 | 0 |
| 4 | 0 | 1 |
+-----------+------------+------------+
It seems like you've pretty much figured it out...
SELECT totalhours hours
, branchid
, COUNT(*) total
FROM
( SELECT COALESCE(y.hr,0) totalhours
, x.branchid
, x.teacherid
FROM teacher x
JOIN
( SELECT teacherid
, SUM(hours) hr
FROM activities
GROUP
BY teacherid
ORDER
BY hr ASC
) y
ON x.teacherid = y.teacherid
) a
GROUP
BY hours
, branchid
ORDER
BY hours
, branchid;
For the rest, I would handle the pivot (and any missing data) in application code

Update first occurrence of value in a time interval

I'm trying to set the value of another column on the first occurrence of any value in a username column in monthly intervals, if there's another column with an specific value.
create table table1
(
username varchar(30) not null,
`date` date not null,
eventid int not null,
firstFlag int null
);
insert table1 (username,`date`, eventid) values
('john','2015-01-01', 1)
, ('kim','2015-01-01', 1)
, ('john','2015-01-01', 1)
, ('john','2015-01-01', 1)
, ('john','2015-03-01', 2)
, ('john','2015-03-01', 1)
, ('kim','2015-01-01', 1)
, ('kim','2015-02-01', 1);
This should result in:
| username | date | eventid | firstFlag |
|----------|------------|---------|-----------|
| john | 2015-01-01 | 1 | 1 |
| kim | 2015-01-01 | 1 | 1 |
| john | 2015-01-01 | 1 | (null) |
| john | 2015-01-01 | 1 | (null) |
| john | 2015-03-01 | 2 | 1 |
| john | 2015-03-01 | 1 | (null) |
| kim | 2015-01-01 | 1 | (null) |
| kim | 2015-02-01 | 1 | 1 |
I've tried using joins as described here, but it updates all rows:
update table1 t1
inner join
( select username,min(`date`) as minForGroup
from table1
group by username,`date`
) inr
on inr.username=t1.username and inr.minForGroup=t1.`date`
set firstFlag=1;
As a1ex07 points out, it would need another per row unique constrain to update the rows I need to:
update table1 t1
inner join
( select id, username,min(`date`) as minForGroup
from table1
where eventid = 1
group by username,month(`date`)
) inr
on inr.id=t1.id and inr.username=t1.username and inr.minForGroup=t1.`date`
set firstFlag=1;
Add an Id column, and use it on the join on constrains.
To allow only those that satisfies a specific condition on another column you need the where clause inside the subquery, otherwise it would try to match different rows as the subquery would return rows with eventid=2 while the update query would return only those with eventid=1.
To use yearly intervals instead of monthly, change the group by statement to use years.

how to give a space by joining 2 columns having repeated data

I have a table structure like this
CREATE TABLE `test` (
`id` int(11) NOT NULL,
`userid` int(11) DEFAULT NULL,
`loan` int(11) DEFAULT NULL,
`name` varchar(90) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
+----+--------+------+--------+
| id | userid | loan | name |
+----+--------+------+--------+
| 1 | 1 | 100 | x |
| 2 | 1 | 200 | X|
| 3 | 2 | 2000 | y|
| 4 | 3 | 1000 | z|
| 5 | 1 | 500 | a|
| 6 | 2 | 700 | b|
As you can see userid is repeating.For example userid 1 is having loan 100,200 and 500
My requirement is to get userid,loan and name and if the userid is repating then loan will be the sum of the repeating userid and name should be space
for example,In the above table userid 1 is repeating so sum is (100+200+500=800)
similarly for userid 2 the sum is 2700
The output i want should be like the below
+--------+-----------+----+
| userid | SUM(loan) |name|
+--------+-----------+----+
| 1 | 800 | |
| 2 | 2700 | |
| 3 | 1000 |z |
+--------+-----------+
I can do with userid and loan but I dont know how to put a space in name field if the userid is repeating
I tried like this
SELECT userid,SUM(loan) FROM
testforsum
GROUP BY userid
and the ouput I am getting is like this
+--------+-----------+
| userid | SUM(loan) |
+--------+-----------+
| 1 | 800 |
| 2 | 2700 |
| 3 | 1000 |
+--------+-----------+
I tried to create a sqlfiddle for it but I dont know why insertion is not happening.You can see the table here
SELECT userid, IF(COUNT(*) > 1, ' ', name) AS name, SUM(loan)
FROM testforsum GROUP BY userid;
select q.userid, q.s,
case when q.n > 1 then ' ' else q.name end
from
(SELECT userid, SUM(loan) s, count(loan) n, max(name) name
FROM testforsum GROUP BY userid) q;
Some explanation:
(...) q is a subquery which calculates for each userId sum of loans (NULLS are ignored), number of loans (NULLS are ignored).
max(name) it's a "fake" aggregate function, I used it to get any name for each userId (they are all the same for each userId so I can do that) because in ANSI SQL this query is wrong:
SELECT userid, IF(COUNT(*) > 1, ' ', name) AS name, SUM(loan)
FROM testforsum GROUP BY userid;
as all the expressions in the SELECT list should be either aggregate functions (for several rows return a single value) or expressions from the GROUP BY clause. As I know MySQL lets you break these rules, but I prefer to follow ANSI whenever it's possible.
So the subquery q results in a table with unique userIds with their names + sum and number of their loans.
Finally, the parent query filters the result of the q subquery using CASE.

How to get GROUP_CONCAT only for rows with maximum value

Suppose, we have a table:
SELECT * FROM users_to_courses;
+---------+-----------+------------+---------+
| user_id | course_id | pass_date | file_id |
+---------+-----------+------------+---------+
| 1 | 1 | 2014-01-01 | 1 |
| 1 | 1 | 2014-01-01 | 2 |
| 1 | 1 | 2014-02-01 | 3 |
| 1 | 1 | 2014-02-01 | 4 |
+---------+-----------+------------+---------+
Schema:
CREATE TABLE `users_to_courses` (
`user_id` int(10) unsigned NOT NULL,
`course_id` int(10) unsigned NOT NULL,
`pass_date` date NOT NULL,
`file_id` int(10) unsigned NOT NULL,
PRIMARY KEY (`user_id`, `course_id`, `pass_date`, `file_id`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
One user can pass a certain course multiple times, and every time he passes multiple certificates can be generated. user_id and course_id store the links to users and courses tables. file_id - to files table, where info about certificate files is stored.
In our example user #1 has passed course #1 twice and every time 2 certificates were issued: 4 records totally.
How can I get this data: for user_id=1 for every course get MAX(pass_date) and all the files, attached to this date. So far I could only get this:
SELECT
users_to_courses.course_id,
MAX(users_to_courses.pass_date) AS max_passed_date,
GROUP_CONCAT(users_to_courses.file_id SEPARATOR ',') AS files
FROM
users_to_courses
WHERE
users_to_courses.user_id=1
GROUP BY
users_to_courses.course_id;
+-----------+-----------------+---------+
| course_id | max_passed_date | files |
+-----------+-----------------+---------+
| 1 | 2014-02-01 | 1,2,3,4 |
+-----------+-----------------+---------+
I need this:
+-----------+-----------------+---------+
| course_id | max_passed_date | files |
+-----------+-----------------+---------+
| 1 | 2014-02-01 | 3,4 |
+-----------+-----------------+---------+
I think, this requires a compound GROUP BY.
fiddle
Try the below query it first gets max date for all the records and then we can join only those record in the outer query. You can use the same query for more than one user by adding group by utc.user_id
SELECT
utc.course_id,
mdt.maxDate AS max_passed_date,
GROUP_CONCAT(utc.file_id SEPARATOR ',') AS files
FROM
users_to_courses utc
join
(SELECT MAX(pass_date) AS maxDate, course_id cId, user_id uId
FROM users_to_courses GROUP BY user_id, course_id) AS mdt
ON
mdt.uId = utc.user_id
AND
mdt.cId = utc.course_id
AND
mdt.maxDate = utc.pass_date
WHERE
utc.user_id=1
GROUP BY
utc.course_id;

how to merge 2 tables with one request

so i got 2 tables with following structure:
CREATE TABLE courses(
id bigint not null auto_increment,
title varchar(255) default '',
primary key(id)
);
CREATE TABLE course_dates(
id bigint not null auto_increment,
course_id bigint,
`date` date,
key idx(course_id,date),
primary key(id)
);
so courses are stored in first table and course dates in second (each course can have unlimited number of dates)
i need to get all course rows (with all its dates) at one time using one query
for example, if i have tables with such data:
courses:
id | title
1 | course#1
2 | course#2
course_dates:
id | course_id | date
1 | 1 | 2012-12-25
2 | 1 | 2012-12-27
3 | 1 | 2012-12-31
4 | 2 | 2012-12-23
5 | 2 | 2012-12-30
then i need result rows like this:
id | course_id | date | title
1 | 1 | 2012-12-25 | course#1
2 | 1 | 2012-12-27 | course#1
3 | 1 | 2012-12-31 | course#1
4 | 2 | 2012-12-23 | course#2
5 | 2 | 2012-12-30 | course#2
A simple INNER JOIN will do.
SELECT b.*, a.title
FROM Courses a
INNER JOIN Courses_Dates b
ON a.id = b.Course_ID
SQLFiddle Demo
SQLFiddle Demo (with ORDER BY clause)
To learn more about joins, see the link below
Visual Representation of SQL Joins