How to get GROUP_CONCAT only for rows with maximum value - mysql

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;

Related

Update table based on result of select on another table

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.

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 data based on a correlated table?

Listings table
+------------+---------+
| name | id |
+------------+---------+
| Example 1 | 1 |
| Example 2 | 2 |
| Example 3 | 3 |
| Example 4 | 4 |
| Example 5 | 5 |
| Example 6 | 6 |
+------------+---------+
Categories table
+------------+---------+
| name | id |
+------------+---------+
| Catname 1 | 1 |
| Catname 2 | 2 |
| Catname 3 | 3 |
+------------+---------+
ListingCats table
+--------+---------+
| cat_id | list_id |
+--------+---------+
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
| 2 | 1 |
| 3 | 1 |
| 3 | 3 |
| 2 | 2 |
| 1 | 5 |
| 2 | 6 |
+--------+---------+
I am trying to build 2 queries which should be simple.
The first thing needed is to get a count of how many listings in the listings table corelate to a given category ID in the listingcats table.
The second part is getting all of the data (*) in the rows from the listings table that corelate to the given category id in the listingcats table.
I have tried a number of joins and for some reason none want to work properly. Can anyone help based on the example tables given above please. The 'given' category ID in this case would be '1'.
For the first query, you can use a simple join, and return a count
SELECT COUNT(Name)
FROM Listings l
JOIN ListingCats lc ON l.id = lc.cat_id
WHERE lc.cat_id = 1
This will return all rows from the listings table such that the listings id has a corresponding cat_id in the listingcats table, but exclusive to those that have a cat_id of 1. Then, the count aggregate function returns the number of rows.
For the second one, you can just use the same subquery above, but without the aggregate function, and select all values.
SELECT * FROM Listings l
JOIN ListingCats lc ON l.id = lc.cat_id
WHERE lc.cat_id = 1
Try those, please let me know if they work or not and I will try to work through them more with you.
EDIT
After looking back at the question, if you are given a specific cat_id you don't even need to use a join, you can simply query the listings table for one that has that id. If the given id is one:
SELECT COUNT(Name)
FROM Listings l
WHERE l.id = 1
And then again, even more broad for the second one:
SELECT * FROM Listings l WHERE l.id = 1
CREATE TABLE `listings` (
`id` int(10) unsigned NOT NULL auto_increment,
`name` varchar(10) NOT NULL default '0',
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
CREATE TABLE `categories` (
`id` int(10) unsigned NOT NULL auto_increment,
`name` varchar(10) NOT NULL default '0',
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
CREATE TABLE `listings_cats` (
`cat_id` int(10) unsigned NOT NULL,
`list_id` int(10) unsigned NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
SELECT c.id, c.name, COUNT(lc.list_id) as the_count
FROM categories c
JOIN listings_cats lc ON (lc.cat_id = c.id)
GROUP BY c.id;
SELECT l.id, l.name, c.name AS category_name
FROM listings l JOIN listings_cats lc ON (lc.list_id = l.id)
JOIN categories c ON (lc.cat_id = c.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

Manipulating Results From Two Subqueries in MySQL

For a homework assignment, I have to write a MySQL query to calculate the GPA of every student in the database table. I broke the problem down into 3 parts: (1) calculating the number of grade points earned by each student, (2) calculating the number of credits taken, and then (3) dividing grade points by credits. Here are the queries I've written for steps 1 and 2:
Calculate grade points earned:
SELECT ID, SUM( credits ) AS credits_taken
FROM takes
NATURAL JOIN course
GROUP BY ID
2 Find grade points earned:
SELECT ID, SUM( credits * ( SELECT points FROM gradepoint WHERE letter = grade ) ) AS tot_grade_points
FROM takes NATURAL JOIN course
GROUP BY ID
I manually evaluated each query and they return the correct results. But I can't figure out how to return (credits_taken / tot_grade_points) for each student. Here is what I have tried:
SELECT ID, GPA
FROM student AS S NATURAL JOIN
(SELECT ID,( 'credits_taken' / SUM( credits * ( SELECT points FROM gradepoint WHERE letter = grade ) )) AS GPA
FROM takes AS T1 NATURAL JOIN course
WHERE S.ID = T1.ID
AND EXISTS (
SELECT ID, SUM( credits ) AS 'credits_taken'
FROM takes AS T2 NATURAL JOIN course
WHERE S.ID = T2.ID
GROUP BY ID
)
GROUP BY ID) Z
GROUP BY ID
But this gives me the error " Unknown column 'S.ID' in 'where clause'". From what I've read, you can't reference the alias of a table from a subquery in a join operation. Does anyone have another way of doing the calculation of these two subqueries and returning them bound to the student ID?
The 'takes' table maps student IDs to information about the courses they've taken, most importantly the course_id and grade. The 'course' table contains the 'credits' field, the number of credits the course is worth.
EDIT
Here are the relevant table structures:
takes:
Field | Type | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+-------+
| ID | varchar(5) | NO | PRI | | |
| course_id | varchar(8) | NO | PRI | | |
| sec_id | varchar(8) | NO | PRI | | |
| semester | varchar(6) | NO | PRI | | |
| year | decimal(4,0) | NO | PRI | 0 | |
| grade | varchar(2) | YES | | NULL | |
+-----------+--------------+------+-----+---------+-------+
course:
+-----------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+-------+
| course_id | varchar(8) | NO | PRI | | |
| title | varchar(50) | YES | | NULL | |
| dept_name | varchar(20) | YES | MUL | NULL | |
| credits | decimal(2,0) | YES | | NULL | |
+-----------+--------------+------+-----+---------+-------+
I would try:
SELECT takes.sec_id,
SUM( course.credits * gradepoint.points ) / SUM( course.credits ) AS GPA
FROM takes
JOIN gradepoint ON takes.grade = gradepoint.letter
JOIN course ON takes.course_id = course.course_id
GROUP BY takes.sec_id
Since your table structure description is incomplete I had to guess gradepoint schema and I assumed sec_id identifies a student in takes table, if there is another column for that just replace it in the query in both SELECT and GROUP BY parts. Maybe it is ID, but a column name like that is usually used for primary keys. Or maybe there are no primary keys defined at all, which is a bad practise anyway. Also you would need to join student table if you wanted any student info other than id, like name and so on.
I would also recommend using JOIN ... ON ... syntax instead of NATURAL JOIN, not only it is more readable, it also gives you more flexibility, for example see how gradepoint is joined instead of using costly dependent subquery.