Merging MySQL database rows into columns - mysql

I have a database with the structure, where id and name are the key
id
name
cp
time
1
abc
1
10
1
abc
2
3
1
abc
3
12
2
xyx
1
12
2
xyx
2
11
2
xyx
2
13
and I need a query to merge it into a new table structure where its ID and name are only 1 row with the following structure, with time in each column value.
id
name
cp1
cp2
cp3
1
abc
10
3
12
2
xyz
12
11
13
Any help is appreciated thank you.

Assuming that you have a typo in your data and the 6th record should have a cp of 3, then you can use conditional aggregation:
SELECT t.id,
t.name,
MAX(CASE WHEN cp = 1 THEN t.time END) AS cp1,
MAX(CASE WHEN cp = 2 THEN t.time END) AS cp2,
MAX(CASE WHEN cp = 3 THEN t.time END) AS cp3
FROM T AS t
GROUP BY t.id, t.name;
If you are guaranteed to have one record per combination of id, name and cp then the MAX is largely irrelevant because you are taking the MAX of just one row, so it is deterministic. If you could have duplicates then you may need additional logic to determine which of the multiple records should be returned, or if you want to apply different aggregation (e.g. SUM).
Example on DB Fiddle

You can join your table 3 times:
SELECT m.id as id,
m.name as name,
m1.time as cp1,
m2.time as cp2,
m3.time as cp3
FROM mytable m -- This is the base table
LEFT JOIN mytable m1 ON -- This is a join to take cp1 if present
m.id = m1.id
AND m.name = m1.name
AND m1.cp = 1
LEFT JOIN mytable m2 ON -- This is a join to take cp2 if present
m.id = m2.id
AND m.name = m2.name
AND m2.cp = 2
LEFT JOIN mytable m3 ON -- This is a join to take cp3 if present
m.id = m3.id
AND m.name = m3.name
AND m3.cp = 3
GROUP BY m.id,
m.name,
m1.time,
m2.time,
m3.time
It has been joined 3 times to be sure that if you have any of cp1, cp2 or cp3 null it works. If you are sure that there are no rows absence for cp1 cp2 cp3 you can leave just 2 inner joins (instead of 3 left joins).
Note that this solution works with any relational database, not only mysql because there is no reference to special function of the database, but only standard SQL joins.

GROUP BY will summarize each id and name. And GROUP_CONCAT will give you the list of cp's. Here's a sample query.
SELECT id, name, GROUP_CONCAT(cp) as cps
FROM your_table
GROUP BY id, name

Related

join two tables on match and no match

I Have two table
One is Catgory
Category Value
ABC 1
DEF 2
ALL 3
Second table Job
JOB Category
123 ABC
234 DEF
456 GGG
778 TTT
I need to add join these two table on Category. But I need Answer as below
JOB VALUE
123 1
234 2
456 3
778 3
Basically if category match then get value assigned to this. But if there is ALL in category column and have value then if category doesn't match then shows value for ALL for unmatched value as in example of job 456 and 778.
You can use two left joins:
select j.*, coalesce(c.value, c_default.value) as value
from job j left join
category c
on j.category = c.category left join
category c_default
on c_default.category = 'ALL';
You could also use a correlated subquery:
select j.*,
(select c.value
from category c
where c.category in ('ALL', j.category)
order by (c.category = j.category) desc
limit 1
) as value
from job j;
Use an outer join and coalesce
select job, Coalesce(c.value, (select value from category where category='ALL')) as value
from job j
left join category c on c.category=j.category
You could also use a window function to rank matching and non matching categories
with j as (
select j.job, c.value, Row_Number() over(partition by job order by case when c.category='all' then 1 else 0 end) rn
from job j
join category c on c.category=j.category or c.category='ALL'
)
select job,value
from j
where rn=1

SQL subquery to return MIN of a column and corresponding values from another column

I'm trying to query
number of courses passed,
the earliest course passed
time taken to pass first course, for each student who is not currently expelled.
The tricky part here is 2). I constructed a sub-query by mapping the course table onto itself but restricting matches only to datepassed=min(datepassed). The query appears to work for a very sample, but when I try to apply it to my full data set (which would return ~1 million records) the query takes impossibly long to execute (left it for >2 hours and still wouldn't complete).
Is there a more efficient way to do this? Appreciate all your help!
Query:
SELECT
S.id,
COUNT(C.course) as course_count,
C2.course as first_course,
DATEDIFF(MIN(C.datepassed),S.dateenrolled) as days_to_first
FROM student S
LEFT JOIN course C
ON C.studentid = S.id
LEFT JOIN (SELECT * FROM course GROUP BY studentid HAVING datepassed IN (MIN(datepassed))) C2
ON C2.studentid = C.studentid
WHERE YEAR(S.dateenrolled)=2013
AND U.id NOT IN (SELECT id FROM expelled)
GROUP BY S.id
ORDER BY S.id
Student table
id status dateenrolled
1 graduated 1/1/2013
3 graduated 1/1/2013
Expelled table
id dateexpelled
2 5/1/2013
Course table
studentid course datepassed
1 courseA 5/1/2014
1 courseB 1/1/2014
1 courseC 2/1/2014
1 courseD 3/1/2014
3 courseA 1/1/2014
3 couseB 2/1/2014
3 courseC 3/1/2014
3 courseD 4/1/2014
3 courseE 5/1/2014
SELECT id, course_count, days_to_first, C2.course first_course
FROM (
SELECT S.id, COUNT(C.course) course_count,
DATEDIFF(MIN(datepassed),S.dateenrolled) as days_to_first,
MIN(datepassed) min_datepassed
FROM student S
LEFT JOIN course C ON C.studentid = S.id
WHERE S.dateenrolled BETWEEN '2013-01-01' AND '2013-12-31'
AND S.id NOT IN (SELECT id FROM expelled)
GROUP BY S.id
) t1 LEFT JOIN course C2
ON C2.studentid = t1.id
AND C2.datepassed = t1.min_datepassed
ORDER BY id
I would try something like:
SELECT s.id, f.course,
COALESCE( DATEDIFF( c.first_pass,s.dateenrolled), 0 ) AS days_to_pass,
COALESCE( c.num_courses, 0 ) AS courses
FROM student s
LEFT JOIN
( SELECT studentid, MIN(datepassed) AS first_pass, COUNT(*) AS num_courses
FROM course
GROUP BY studentid ) c
ON s.id = c.studentid
JOIN course f
ON c.studentid = f.studentid AND c.first_pass = f.datepassed
LEFT JOIN expelled e
ON s.id = e.id
WHERE s.dateenrolled BETWEEN '2013-01-01' AND '2013-12-31'
AND e.id IS NULL
This query assumes a student can pass only one course on a given day, otherwise you can get more than one row for a student as its possible to have many first courses.
For performance it would help to have an index on dateenrolled in student table and a composite index on (studentid,datepassed) in courses table.

Join two MySQL Tables and get result from categories

SELECT art.*,arg. FROM rd_articles AS art
LEFT JOIN rd_argument AS arg ON art.cat=arg.id WHERE art.enabled=1 ORDER BY art.id DESC
LIMIT 10
This is simple join query
Article table structure is
ID cat Description Date
1 1 Abc 08-01-2014
2 1 Aaa 10-01-2014
3 2 Abcv 11-01-2014
4 3 Aaa 12-01-2014
5 3 Aaa 14-01-2014
Arguments table is
ID Name
1 A
2 B
3 C
I want pick last updated(Date) one item from each cat.
How ?
This assumes that the enabled column is in rd_articles:
SELECT art.*, arg.*
FROM (
SELECT * FROM rd_articles
INNER JOIN (
SELECT cat, MAX(date) AS maxdate
FROM rd_articles
WHERE enabled = 1
GROUP BY cat
) md ON rd_articles.cat = md.cat AND rd_articles.date = md.maxdate
) art
LEFT JOIN rd_argument AS arg ON art.cat = arg.id
The innermost query gets the maximum date for each category, then joins it to the rd_articles table to get only those rd_articles rows that have the latest date for each article. That becomes the cat alias, which is then left-joined to the arguments table just like in your original query. You can add the LIMIT 10 at the end if needed; I wasn't sure what to do with that.
Note that if there's a tie for a category's latest date, you'll get more than one row for each category. If a tie could happen you'll need to break the tie somehow, for example by using the description or the ID. Let me know if that's the case and I'll update my answer.
SELECT ART.*, ARG.*
FROM ARTICLE AS ART
INNER JOIN RD_AGRUEMENT AS ARG
ON ARG.ID = ART.ID
WHERE (ID, DATE) IN
(SELECT ID, MAX(DATE) FROM ARTICLE GROUP BY ID)

Mysql (conditional?) query from two tables

Not sure if I have phrased the title properly, but here it goes. I have these two tables:
table:staff
id Name groupId Status
1 John Smith 1 1
2 John Doe 1 1
3 Jane Smith 2 1
4 Jerry Smith 1 1
table:jobqueue
id job_id staff_id jobStatus
1 1 1 1
2 2 1 1
3 5 2 1
4 7 3 0
Now, what I need to do is to find the staff with the least amount of job assigned to him which I am able to do by querying the jobqueue table.
SELECT min(cstaff),tmp.staff_id FROM (SELECT t.staff_id, count(staff_id) cstaff from jobqueue t join staff s on t.staff_id=s.id join group g on s.groupId=g.id where g.id=26 GROUP BY t.id ) tmp
This works fine, but the problem is if a staff is not assigned to any job at all, this query wont get them, because it only queries the jobqueue table, where that particular staff won't have any entry. I need to modify the query to include the staff table and if a staff is not assigned any job in the jobqueue then I need to get the staff details from the staff table. Basically, I need to find staff for a group who are not assigned any job and if all staffs are assigned job then find staff with the least amount of jobs assigned. Could use some help with this. Also, tagging as Yii as I would like to know if this is doable with Yii active-records. But I am okay with a plain sql query that will work with Yii sql commands.
not sure that it is optimal query, but it works:
select d.groupId, d.name, (select count(*) from jobqueue as e where e.staff_id=d.id) as jobassigned
from staff as d
where d.id in (
select
(
select a.id
from staff as a
left outer join
jobqueue as b
on (a.id = b.staff_id)
where a.groupId = c.groupId
group by a.id
order by count(distinct job_id) asc
limit 1
) as notassigneduserid
from (
select distinct groupId from staff
) as c)
maybe need some comments:
c query is needed to get all distinct groupId - if you have separate table for this, you can replace it
notassigneduserid statement for each groupId select user with minimal job count
d query is needed to fetch actual user names, groupId for all found "unassigned users" and present it
here is the results for data from question:
Group Staff Jobs assigned
1 Jerry Smith 0
2 Jane Smith 1
with
counts as (
select s.groupId
, s.id
, (select count(*) from jobqueue where staff_id = s.id) count
from staff s
group by s.id, s.groupId),
groups as (
select groupId, min(count) mincount
from counts
group by groupId)
select c.groupId, c.id, c.count
from counts c
join groups g on c.groupId = g.groupId
where c.count = g.mincount
This SQL will give you all the staff with the minimum number of jobs in each group. It might be that more than one staff has the same minimum number of jobs. The approach is to use common table expressions to build first a list of counts, and then to retrieve the minimum count for each group. Finally I join the counts and groups tables and retrieve the staff that have the minimum count for each group.
I tested this on SQL Server, but the syntax should work for MySQL as well. To your data I added:
id Name groupId Status
5 Bubba Jones 2 1
6 Bubba Smith 1 1
and
id job_id staff_id jobStatus
5 4 5 1
Results are
group name count
1 Bubba Smith 0
1 Jerry Smith 0
2 Bubba Jones 1
2 Jane Smith 1
BTW, I would not try to do this with active record, it is far too complex.
As Ilya Bursov said this answer wasn't respond exactly what was asked. So here is a more optimized solution:
SELECT *
FROM (
SELECT s.id as id_staff, s.Name, s.groupId, count(distinct t.id) as jobsXstaff
FROM staff s
LEFT JOIN jobqueue t ON s.id=t.staff_id
GROUP BY s.id, s.groupId
ORDER BY s.groupId, jobsXstaff
) tmp
GROUP BY groupId
Old answer below.
This works but without table group which I don't create. You can simply join table groups as you did:
SELECT min(cstaff),tmp.id
FROM (
SELECT s.id, count( staff_id ) cstaff
FROM jobqueue t
RIGHT JOIN staff s ON t.staff_id = s.id
GROUP BY t.id
) tmp
As you see you need to get all values from table staff (right join) and select the id staff from it's own table (s.id instead of t.staff_id). Also you have to get tmp.id instead of staff_id now.

Select statement that that counts total number of distinct entries in one table, depending on data from another table

I have two tables: DATA and USERS
USERS
id sqft postal province city
==========================================================
1 1 Y7R BC Vancouver
2 2 Y7R BC Vancouver
3 1 L5B ON Toronto
and
DATA
id uid power
=======================
1 1 1000
2 2 1300
3 1 1500
uid in table DATA matches to id in table USERS
I want to be able to count the the number of distinct uid in DATA where the postal code is Y7R and sqft is 1
SELECT COUNT(id)
FROM `DATA` AS `d`
INNER JOIN `USERS` AS `u`
ON u.id=d.uid
WHERE u.postal='Y7R' AND u.sqft=1
GROUP BY u.id;
They should be distinct anyway if you have a proper schema, if so just remove the group by clause.
SELECT COUNT(DISTINCT D.UID) FROM DATA D
LEFT JOIN USERS U ON D.UID=U.ID
WHERE U.POSTAL='Y7R' AND U.SQFT=1)
In case you need distinct
You can use this solution:
SELECT COUNT(DISTINCT a.id)
FROM USERS a
JOIN DATA b ON a.id = b.uid
WHERE a.sqft = 1 AND
a.postal = 'Y7R'
Try this one:
SELECT COUNT(DISTINCT a.id)
FROM USERS a
INNER JOIN DATA b
ON a.id = b.uid
WHERE a.sqft = 1 AND
a.postal = 'Y7R'