This question already has answers here:
How can I return pivot table output in MySQL?
(10 answers)
Closed 4 years ago.
I have the following tables:
Sample
id PK
Test
id PK
name varchar
Section
id PK
name varchar
TestSection
id PK
test_id FK
section_id FK
SampleTestResult
id PK
sample_id FK
test_section_id FK
result
When I do the following query:
SELECT DISTINCT
s.id as sample_id, ts.name as test, str.result
FROM sample s
JOIN sample_test_result str ON s.id=str.sample_id
JOIN test_section ts ON str.test_section_id=ts.id
JOIN section sec ON ts.section_id=sec.id
I get a table that looks like this:
| sample_id | test | result |
--------------------------------------
| 1 | t1 | fail |
| 1 | t2 | pos |
| 2 | t1 | neg |
| 2 | t3 | rpt |
| 3 | t2 | pos |
| 3 | t4 | pos |
However I would like to end up with a table like this:
| sample_id | t1 | t2 | t3 | t4 |
--------------------------------------------
| 1 | fail | pos | NULL | NULL |
| 2 | neg | NULL | rpt | NULL |
| 3 | NULL | pos | NULL | pos |
How can I transpose the table in SQL - is this possible in a query? Or does it have to be an sql view?
With MySQL, the typical way to solve this is to use conditional aggregation :
SELECT
s.id as sample_id,
MAX(CASE WHEN ts.name = 't1' THEN str.result END) as t1,
MAX(CASE WHEN ts.name = 't2' THEN str.result END) as t2,
MAX(CASE WHEN ts.name = 't3' THEN str.result END) as t3,
MAX(CASE WHEN ts.name = 't4' THEN str.result END) as t4
FROM
sample s
JOIN sample_test_result str ON s.id=str.sample_id
JOIN test_section ts ON str.test_section_id=ts.id
JOIN section sec ON ts.section_id=sec.id
GROUP BY s.id
You want conditional aggregation :
SELECT s.id as sample_id,
MAX(CASE WHEN ts.name = 't1' THEN str.result END) as t1,
. . .
FROM sample s JOIN
sample_test_result str
ON s.id=str.sample_id JOIN
test_section ts
ON str.test_section_id=ts.id
JOIN section sec ON ts.section_id=sec.id
GROUP BY s.id;
Related
I am working in MySQL 5.7.35 and I have the following tables:
create table Table1 (
Id int not null auto_increment,
Name varchar(255) not null,
primary key(Id)
);
create table Table2 (
Id int not null auto_increment,
Name varchar(255) not null,
Table1_Id int not null,
primary key(Id),
foreign key(Table1_Id) references Table1(Id)
);
create table Table3 (
Id int not null auto_increment,
Type varchar(255) not null,
Name varchar(255) not null,
Result varchar(255) not null,
Table2_Id int not null,
primary key(Id),
foreign key(Table2_Id) references Table2(Id)
);
Inside, I have the following data:
| Id | Name |
| --- | ---------- |
| 1 | Computer A |
---
| Id | Name | Table1_Id |
| --- | ---------- | --------- |
| 1 | Test Run 1 | 1 |
---
| Id | Type | Name | Result | Table2_Id |
| --- | --------- | --------- | ------- | --------- |
| 1 | Processor | MMX | Pass | 1 |
| 2 | Processor | SSE | Pass | 1 |
| 3 | Processor | SSE 2 | Pass | 1 |
| 4 | Display | Red | Pass | 1 |
| 5 | Display | Green | Pass | 1 |
| 6 | Keyboard | General | Pass | 1 |
| 7 | Keyboard | Lights | Skipped | 1 |
| 8 | Network | Ethernet | Pass | 1 |
| 9 | Network | Wireless | Skipped | 1 |
| 10 | Network | Bluetooth | Fail | 1 |
Desired Query
I would like two columns table1_name and test_result where test_result is a concatenated string with the following logic:
For any given value in Type:
If all are passes, then the result is a Pass
If any are fails, then the result is a Fail
If any are Skipped (poviding the first two points are checked), then the result is Skipped.
So for the current data, the output will be:
| table1_name | test_result |
| ----------- | ---------------------------------------------------------------- |
| Computer A | Processor: Pass, Display: Pass, Keyboard: Skipped, Network: Fail |
Current Query
I am struggling to do the coalecing bit when the items I wish to coalesce are in a child table two levels down. My current query is:
select t1.Name as 'table1_name'
-- coalesce to happen here
from Table1 t1
inner join Table2 t2 on t1.Id = t2.Table1_Id
inner join Table3 t3 on t2.Id = t3.Table2_Id;
I have created a db-fiddle to make things easier.
Use GROUP_CONCAT() to collect all Results for each Name and Type combination in your preferred order and then in another level of aggregation pick the the first 1:
SELECT table1_name,
GROUP_CONCAT(Type, ': ', SUBSTRING_INDEX(Results, ',', 1) SEPARATOR ', ') test_result
FROM (
SELECT t1.Name table1_name, t3.Type,
GROUP_CONCAT(Result ORDER BY Result = 'Fail' DESC, Result = 'Skipped' DESC) Results
FROM Table1 t1
INNER JOIN Table2 t2 on t1.Id = t2.Table1_Id
INNER JOIN Table3 t3 on t2.Id = t3.Table2_Id
GROUP BY t1.Name, t3.Type
) t
GROUP BY table1_name;
If you want to preserve the order of Types in the results:
SELECT table1_name,
GROUP_CONCAT(Type, ': ', SUBSTRING_INDEX(Results, ',', 1) ORDER BY Id SEPARATOR ', ') test_result
FROM (
SELECT t1.Name table1_name, MIN(t3.Id) Id, t3.Type,
GROUP_CONCAT(Result ORDER BY Result = 'Fail' DESC, Result = 'Skipped' DESC) Results
FROM Table1 t1
INNER JOIN Table2 t2 on t1.Id = t2.Table1_Id
INNER JOIN Table3 t3 on t2.Id = t3.Table2_Id
GROUP BY t1.Name, t3.Type
) t
GROUP BY table1_name;
See the demo.
This looks like two levels of aggregation:
select Name, group_concat(name, ': ', result separator ', ')
from (select t1.Name, t3.type,
(case when min(result) = max(result) then min(result)
else 'Skipped'
end) as result
from Table1 t1 inner join
Table2 t2
on t1.Id = t2.Table1_Id inner join
Table3 t3
on t2.Id = t3.Table2_Id
group by t1.Name, t3.type
) nt
group by Name;
I want to calculate count of order status changes within different states.
My Orderstatus table:
| id |ordr_id| status |
|----|-------|------------|
| 1 | 1 | pending |
| 2 | 1 | processing |
| 3 | 1 | complete |
| 4 | 2 | pending |
| 5 | 2 | cancelled |
| 6 | 3 | processing |
| 7 | 3 | complete |
| 8 | 4 | pending |
| 9 | 4 | processing |
Output I want:
| state | count |
|----------------------|-------|
| pending->processing | 2 |
| processing->complete | 2 |
| pending->cancelled | 1 |
Currently I'm fetching the results by SELECT order_id,GROUP_CONCAT(status) as track FROM table group by order_id and then process the data in php to get the output. But is that possible in query itself ?
Use lag():
select prev_status, status, count(*)
from (select t.*,
lag(status) over (partition by order_id order by status) as prev_status
from t
) t
group by prev_status, status;
LAG() is available in MySQL starting with version 8.
Note that you can filter out the first status for each order by putting where prev_status is not null in the outer query.
Your version is not quite correct, because it does not enforce the ordering. It should be:
SELECT order_id,
GROUP_CONCAT(status ORDER BY id) as track
EDIT:
In earlier versions of MySQL, you can use a correlated subquery:
select prev_status, status, count(*)
from (select t.*,
(select t2.status
from t t2
where t2.order_id = t.order_id and t2.id < t.id
order by t2.id desc
limit 1
) as prev_status
from t
) t
group by prev_status, status;
If id column ensure the sequence of records, you can use self join to achieve your requirement as below-
SELECT A.Status +'>'+ B.Status, COUNT(*)
FROM OrderStatus A
INNER JOIN OrderStatus B
ON A.id = B.id -1
WHERE B.Status IS NOT NULL
GROUP BY A.Status +'>'+ B.Status
With a join of the 3 status change types to the grouping of the table that you already did:
select c.changetype, count(*) counter
from (
select 'pending->processing' changetype union all
select 'processing->complete' union all
select 'pending->cancelled'
) c inner join (
select
group_concat(status order by id separator '->') changestatus
from tablename
group by ordr_id
) t on concat('->', t.changestatus, '->') like concat('%->', changetype, '->%')
group by c.changetype
See the demo.
Results:
> changetype | counter
> :------------------- | ------:
> pending->cancelled | 1
> pending->processing | 2
> processing->complete | 2
...or just a simple join...
SELECT CONCAT(a.status,'->',b.status) action
, COUNT(*) total
FROM my_table a
JOIN my_table b
ON b.ordr_id = a.ordr_id
AND b.id = a.id + 1
GROUP
BY action;
+----------------------+-------+
| action | total |
+----------------------+-------+
| pending->cancelled | 1 |
| pending->processing | 2 |
| processing->complete | 2 |
+----------------------+-------+
Note that this relies on the fact that ids are contiguous.
I've been working with a dynamic pivot table in MySQL as referenced here, which works fine. In my situation, my table also has a timestamp column. I want to return the most recent values in the pivot table for the attributes and I can't figure out how to accomplish this. So consider the example table data:
+----+---------+---------------+--------+------------+
| id | item_id | property_name | value | timestamp |
+----+---------+---------------+--------+------------+
| 1 | 1 | color | blue | 2018-01-01 |
| 2 | 1 | size | large | 2018-01-01 |
| 3 | 1 | weight | 65 | 2018-01-01 |
<SNIP>
| 15 | 1 | color | purple | 2018-02-01 |
| 16 | 1 | weight | 69 | 2018-02-01 |
+----+---------+---------------+--------+------------|
For item_id '1' the result row should be:
+---------+--------+--------+--------+
| item_id | color | size | weight |
+---------+--------+--------+--------+
| 1 | purple | large | 69 |
+---------+--------+--------+--------+
Thanks in advance!
Your question is slightly tricky. We can use a subquery to find the most recent record for each item and property. Then, join your table to this subquery and do a regular pivot query to generate the output you want.
SELECT
t1.item_id,
MAX(CASE WHEN t1.property_name = 'color' THEN t1.value END) AS color,
MAX(CASE WHEN t1.property_name = 'size' THEN t1.value END) AS size,
MAX(CASE WHEN t1.property_name = 'weight' THEN t1.value END) AS weight
FROM yourTable t1
INNER JOIN
(
SELECT item_id, property_name, MAX(timestamp) AS max_timestamp
FROM yourTable
GROUP BY item_id, property_name
) t2
ON t1.item_id = t2.item_id AND
t1.property_name = t2.property_name AND
t1.timestamp = t2.max_timestamp
WHERE
t1.item_id = 1
GROUP BY t1.item_id;
item_id | color | size | weight
1 | purple | large | 69
Demo
I have similar to the following database structure:
ApplicationService table :
+-----+--------------+----------+---------------+
| id | name | status | application_id
| 1 | Service 1 | 1 | 24
| 2 | Service 2 | 2 | 24
| 3 | Service 3 | 3 | 25
+-----+--------------+----------+----------------
And there is other table with status definitions:
CustomerStatus
+------------+--------------+----------+-----------+
| status_id | name | level | is_closed
| 1 | Status 1 | 2 | 1
| 2 | Status 2 | 1 | 0
| 3 | Status 3 | 3 | 1
+------------+--------------+----------+----------
The status for each row of the ApplicationServices is calculated as the max level status within the records grouped by application_id.
So to get all records from ApplicationServices with statuses would result in something like this:
+-----+--------------+----------+----------------+-------------+-----------
| id | name | status | application_id | status_name | is_closed
| 1 | Service 1 | 1 | 24 | Status 1 | 1
| 2 | Service 2 | 2 | 24 | Status 1 | 1
| 3 | Service 3 | 3 | 25 | Status 3 | 1
+-----+--------------+----------+----------------+-------------+-----------
Is there an efficient way to attach the results with max(level) grouped by application_id to every row of the result set ?
Try this:
SELECT A.id, A.name, A.status, A.application_id, CS.name AS status_name, CS.is_closed
FROM ApplicationService A
INNER JOIN (SELECT AA.application_id, MAX(CS.level) AS maxLevel
FROM ApplicationService AA
INNER JOIN CustomerStatus CS ON AA.status = CS.status_id
GROUP BY AA.application_id
) AS AA ON A.application_id = AA.application_id
INNER JOIN CustomerStatus CS ON AA.maxLevel = CS.level;
See working SQLfiddle here.
You will probably have to perform a sub-query. The subquery would simply be joining the two tables together, grouping/collapsing by application_id and then fetching the maximum is_closed value:
SELECT
t1.status AS `status`,
t1.application_id AS `app_id`,
MAX(t2.is_closed) AS `max_is_closed`
FROM applicationstatus AS t1
LEFT JOIN customerstatus AS t2 ON
t1.status = t2.status_id
GROUP BY t1.application_id
The max is_closed value should then be accessible by the max_is_closed alias when you incorporate the subquery:
SELECT
t1.id AS id,
t1.name AS name,
t1.status AS `status`,
t1.application_id AS application_id,
t2.name AS status_name,
t3.max_is_closed
FROM applicationstatus AS t1
LEFT JOIN customerstatus AS t2 ON
t1.status = t2.status_id
LEFT JOIN
(SELECT
t1.status AS `status`,
t1.application_id AS `app_id`,
MAX(t2.is_closed) AS `max_is_closed`
FROM applicationstatus AS t1
LEFT JOIN customerstatus AS t2 ON
t1.status = t2.status_id
GROUP BY t1.application_id) AS t3 ON
t1.application_id = t3.app_id
The output from the query:
I am creating an application for my school and I am in trouble constructing the right query.
I have 2 tables,table1 and table2.
table1
---------------------------------------------------------
| StudentID | SubjectID | Present | Type |
---------------------------------------------------------
| 2 | 3 | yes | 1 |
| 2 | 2 | yes | 2 |
| 3 | 1 | no | 3 |
---------------------------------------------------------
table2
---------------------------------------------------------
| SubjectID | SubjectName | Number1 | Number2 |
---------------------------------------------------------
| 1 | Name1 | 6 | 4 |
| 2 | Name2 | 4 | 8 |
| 3 | Name3 | 5 | 2 |
---------------------------------------------------------
SubjectID in table1 is foreign key references table2.
I want to build a query sql that gives me the StudentID`s from table1
that didnt miss any Type 3 subject (i.e no row like this
---------------------------------------------------------
| StudentID | SubjectID | Present | Type |
---------------------------------------------------------
| 3 | 1 | no | 3 |
---------------------------------------------------------
And have completed 75 percent of type 1 (i.e
I find it like this
SELECT t1.StudentID,t1.SubjectID ,t1.Type,t2.Number1 as num
FROM table1 as t1,table2 as t2
WHERE t1.Present=yes and t2.SubjectID=t1.SubjectID
GROUP BY StudentID,SubjectID
HAVING COUNT(*)/num >= 75/100
But I cant combine the two things together.
You can combine queries by giving them aliases and joining as subqueries...
SELECT finisher.StudentID FROM
(
SELECT DISTINCT StudentID
FROM table1 t1
JOIN table2 t2 ON t2.SubjectID = t1.SubjectID
WHERE t1.Present = 'yes' AND t1.Type1 = 1
GROUP BY t1.StudentID, t2.SubjectID
HAVING COUNT(*) / t2.Number2 >= 0.75
) finisher
JOIN
(
SELECT DISTINCT t1.StudentID
FROM table1 t1
LEFT JOIN
(
SELECT DISTINCT StudentID
FROM table1
WHERE Type = 3 AND Present = 'no'
) missed ON missed.StudentID = t1.StudentID
WHERE t1.Type = 3
AND missed.StudentID IS NULL
) notmissed ON finisher.StudentID = notmissed.StudentID
"StudentID`s from table1 that didnt miss any Type 3"... I assume here you don't want to include students without any type 3 rows.
Seems like this is done and duste, but how about...
SELECT x.*
FROM
( SELECT t1.StudentID
, t1.SubjectID
, t1.Type
, t2.Number1 num
FROM table1 t1
JOIN table2 t2
ON t2.SubjectID=t1.SubjectID
WHERE t1.Present='yes'
GROUP
BY t1.StudentID
, t1.SubjectID
HAVING COUNT(*)/num >= 0.75
) x
LEFT
JOIN table1 y
ON y.student_id = x.student_id
AND y.subject_id = x.subject_id
AND y.type = 3
AND y.present = 'no'
WHERE y.student_id IS NULL;