Coalessing with condition in related table - mysql

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;

Related

Get result from joined tables

I have 2 tables:
Table 1:
| jobid | jobname |
| 1 | job a |
| 2 | job b |
Table 2:
| id | jobid | statusid | statusdate | desc |
| 1 | 1 | 100 | 2019.04.25 10:00:00 | first |
| 2 | 2 | 100 | 2019.04.25 11:00:00 | first |
| 3 | 2 | 100 | 2019.04.25 12:00:00 | second |
Jobs in table2 can have more then one same "statusid", but different "statusdate" and "desc"
I need to get jobs list with the last "statusid" = 100 like this :
| 1 | job a | 1 | 1 | 100 | 2019.04.25 10:00:00 | first |
| 2 | job b | 3 | 2 | 100 | 2019.04.25 12:00:00 | second |
SELECT * FROM table1
INNER JOIN table2 ON table1.id = table2.jobid
GROUP BY table1.id
This query return wrong result like:
| 1 | job a | 1 | 1 | | 100 | 2019.04.25 10:00:00 | first |
| 2 | job b | 3 | 2 | 2 | 100 | 2019.04.25 11:00:00 | first |
You should be able to accomplish that by doing something like this:
Table
drop table if exists table1;
create table table1 (jobid int, jobname char(10));
insert into table1 values (1, 'job a'), (2, 'job b');
drop table if exists table2;
create table table2 (
id int,
jobid int,
statusid int,
statusdate timestamp,
`desc` char(10)
);
insert into table2 values
(1,1,100,'2019.04.25 10:00:00','first')
,(2,2,100,'2019.04.25 11:00:00','first')
,(3,2,100,'2019.04.25 12:00:00','second');
Query
select
t1.*,
t2.*
from table1 t1
inner join (
select jobid, max(statusdate) as maxstatusdate
from table2
group by jobid
) tn on t1.jobid = tn.jobid
inner join table2 t2 on tn.jobid = t2.jobid and tn.maxstatusdate = t2.statusdate;
Results
jobid jobname id jobid statusid statusdate desc
1 job a 1 1 100 25.04.2019 10:00:00 first
2 job b 3 2 100 25.04.2019 12:00:00 second
Explanation
For each job ID, find the maximum status date
Join that to table1 to get information from table1. Common field is jobid
Join their result to table2 that has all the remaining information you want. Common fields are jobid and statusdate. Since we aliased max status date to a different name, make sure we are using the correct name in the join
Example: https://rextester.com/HRSWZ89705
DROP TABLE IF EXISTS table1;
CREATE TABLE table1
(jobid INT NOT NULL PRIMARY KEY
,jobname VARCHAR(12) UNIQUE
);
INSERT INTO table1 VALUES
(1,'job a'),
(2,'job b'),
(3,'job c');
DROP TABLE IF EXISTS table2;
CREATE TABLE table2
(id SERIAL PRIMARY KEY
,jobid INT NOT NULL
,statusid INT NOT NULL
,statusdate DATETIME NOT NULL
,description VARCHAR(12) NOT NULL
);
INSERT INTO table2 VALUES
(1,1,100,'2019-04-25 10:00:00','first'),
(2,2,100,'2019-04-25 11:00:00','first'),
(3,2,100,'2019-04-25 12:00:00','second');
SELECT a.*
, b.id x_id
, b.statusid
, b.statusdate
, b.description
FROM table1 a
LEFT
JOIN
( SELECT x.*
FROM table2 x
JOIN
( SELECT MAX(id) id
FROM table2
WHERE statusid = 100
GROUP
BY jobid
) y
ON y.id = x.id
) b
ON b.jobid = a.jobid
;
+-------+---------+------+----------+---------------------+-------------+
| jobid | jobname | x_id | statusid | statusdate | description |
+-------+---------+------+----------+---------------------+-------------+
| 1 | job a | 1 | 100 | 2019-04-25 10:00:00 | first |
| 2 | job b | 3 | 100 | 2019-04-25 12:00:00 | second |
| 3 | job c | NULL | NULL | NULL | NULL |
+-------+---------+------+----------+---------------------+-------------+
SELECT
t1.*,t2.* FROM
(SELECT
MAX(id) as id
FROM
table2
WHERE statusid = 100
GROUP BY jobid) AS f
JOIN table2 t2
ON t2.id = f.id
JOIN table1 t1
ON t2.jobid = t1.jobid
The sub query select finds the last id for a row with statusid 100, then it joins the actual table based on this id.
You can reorder this as you wish using the correct joins.

How do i turn column results into column headers in SQL? [duplicate]

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;

mysql return default row if no row is found

I have a table structure like this
+----+-----------+--------+
| id | attr | value |
+----+-----------+--------+
| 1 | attr1 | val1 |
| 2 | attr1 | val2 |
| 2 | default | val3 |
| 3 | default | val4 |
+----+-----------+--------+
Here, (id, attr) is the primary key. Also, id (int), attr (varchar), value (varchar).
I want to design a query such that for all distinct values of id I can fetch value of a specific attribute, and if it is not present for that attribute but is present as default value then return that default value.
i_e result above table, for attr1 will be
+----+--------+
| id | value |
+----+--------+
| 1 | val1 |
| 2 | val2 |
| 3 | val4 |
+----+--------+
One way to accomplish this is by combining two queries with a UNION.
The first query fetches the results for the given attribute, and the second query fetches the default results for IDs that do not have the given attribute.
For example:
select id, value
from your_table
where attr = 'attr1'
union
select t1.id, t1.value
from your_table t1
where t1.attr = 'default'
and not exists (select NULL from your_table t2 where t2.id = t1.id and t2.attr = 'attr1')
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(id INT NOT NULL, attr VARCHAR(12) NOT NULL,value VARCHAR(12) NOT NULL,PRIMARY KEY(id,attr));
INSERT INTO my_table VALUES
(1 ,'attr1','val1'),
(2 ,'attr1','val2'),
(2 ,'default','val3'),
(3 ,'default','val4');
SELECT DISTINCT a.id
, COALESCE(b.value,a.value) value
FROM my_table a
LEFT
JOIN my_table b
ON b.id = a.id
AND b.attr = 'attr1';
+----+-------+
| id | value |
+----+-------+
| 1 | val1 |
| 2 | val2 |
| 3 | val4 |
+----+-------+
Try this if you want to find row with max id in default attr and rest all as such
select t1.*
from t t1 inner join (
select attr, max(id) id
from t
group by attr
) t2 on t1.attr = t2.attr
and case when t1.attr = 'default' then t2.id else t1.id end = t1.id;

Query MySQL Group by and Having

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;

MySQL Duplicate rows - specify columns

How can I run a query that finds duplicates between rows? It needs to not match one field but multiple.
Here is the EXPLAIN of the table.
+-------------+--------------+------+-----+-------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+-------------------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| token | varchar(64) | NO | MUL | NULL | |
| maxvar | float | NO | | NULL | |
| maxvbr | float | NO | | NULL | |
| minvcr | float | NO | | NULL | |
| minvdr | float | NO | | NULL | |
| atype | int(11) | NO | | NULL | |
| avalue | varchar(255) | NO | | NULL | |
| createddate | timestamp | NO | | CURRENT_TIMESTAMP | |
| timesrun | int(11) | NO | | NULL | |
+-------------+--------------+------+-----+-------------------+----------------+
I need to match all rows that match: token,maxvar,maxvbr,minvcr,minvdr,type and avalue. If all of those fields match those in another row then treat it as a "duplicate".
Ultimately I want to run this as a delete command but I can easily alter the select.
UPDATE Still looking for solution that deletes with single query in MySQL
Just join the table to itself and compare the rows. You can make sure you keep the duplicate with the lowest ID by requiring the id to be deleted to be greater than the id of a duplicate:
DELETE FROM my_table WHERE id IN (
SELECT DISTINCT t1.id
FROM my_table t1
JOIN my_table t2
WHERE t1.id > t2.id
AND t1.token = t2.token AND t1.maxvar = t2.maxvar
AND t1.maxvbr = t2.maxvbr AND t1.minvcr = t2.minvcr
AND t1.minvdr = t2.minvdr AND t1.type = t2.type)
This query will find all duplicate records which should be deleted -
SELECT t1.id FROM table_duplicates t1
INNER JOIN (
SELECT MIN(id) id, token, maxvar, maxvbr, minvcr, minvdr, atype, avalue FROM table_duplicates
GROUP BY token, maxvar, maxvbr, minvcr, minvdr, atype, avalue
HAVING COUNT(*) > 1
) t2
ON t1.id <> t2.id AND t1.token = t2.token AND t1.maxvar=t2.maxvar AND t1.maxvbr = t2.maxvbr AND t1.minvcr = t2.minvcr AND t1.minvdr = t2.minvdr AND t1.atype = t2.atype AND t1.avalue = t2.avalue;
This query will remove all duplicates -
DELETE t1 FROM table_duplicates t1
INNER JOIN (
SELECT MIN(id) id, token, maxvar, maxvbr, minvcr, minvdr, atype, avalue FROM table_duplicates
GROUP BY token, maxvar, maxvbr, minvcr, minvdr, atype, avalue
HAVING COUNT(*) > 1
) t2
ON t1.id <> t2.id AND t1.token = t2.token AND t1.maxvar=t2.maxvar AND t1.maxvbr = t2.maxvbr AND t1.minvcr = t2.minvcr AND t1.minvdr = t2.minvdr AND t1.atype = t2.atype AND t1.avalue = t2.avalue;
SELECT token,maxvar,maxvbr,minvcr,minvdr,type, avalue,
Count(*)
FROM yourtable
GROUP BY token,maxvar,maxvbr,minvcr,minvdr,type, avalue
HAVING Count(*) > 1
This query returns all the rows that are in the table two times or more often (and how often they are).
Try:
SELECT token,maxvar,maxvbr,minvcr,minvdr,type,avalue, COUNT(*)
FROM table
GROUP BY token,maxvar,maxvbr,minvcr,minvdr,type,avalue
HAVING COUNT(*)>1