How will I fix this table using MySQL? - mysql

I am new to MySQL. I am using MySQL 8.0.
My schema and sample data is as follows:
CREATE TABLE AA (
A int
);
insert into AA (A) values (1);
insert into AA (A) values (1);
insert into AA (A) values (2);
insert into AA (A) values (2);
insert into AA (A) values (1);
insert into AA (A) values (3);
CREATE TABLE BB (
code int,
description varchar(30)
);
insert into BB (code, description) values (1, 'Male');
insert into BB (code, description) values (2, 'Female');
Here's my code
with totalcount as (
select code as 'CODE',
description as 'SEX',
count(A) AS 'TOTAL',
ROUND((COUNT(A) * 100.0) / (SELECT COUNT(A) FROM AA),2) AS 'PERCENT',
sum(count(A)) over (order by CODE asc) AS 'CUMULATIVE',
ROUND((SUM(COUNT(A)) OVER (ORDER BY CODE ) / (SELECT COUNT(A) FROM AA) *100 ),2) AS 'CUMPERCENT'
from AA, BB
where AA.A= BB.code
group by AA.A
)
select *
from totalcount
UNION ALL
SELECT '0' CODE, 'TOTAL' SEX, SUM(TOTAL), SUM(PERCENT), '0' CUMULATIVE, '0.00' CUMPERCENT
from totalcount
ORDER BY CODE;
The output was this
CODE | SEX | TOTAL | PERCENT | CUMULATIVE | CUMPERCENT
0 TOTAL 5 100.00 0 0.00
1 MALE 3 60.00 3 60.00
2 FEMALE 2 40.00 5 100.00
I used UNION since I want the total of these items. In this case, I inserted 0 in TOTAL of SEX column to put it in the first row. Is there another way aside from using UNION and inserting code 0?
As you can see from my table BB, there are only code 1 and 2 for male and female, respectively. I want to show that the value not in this table should be in NOT VALID.
CODE | SEX | TOTAL | PERCENT | CUMULATIVE | CUMPERCENT
0 TOTAL 6 100.00 0 0.00
1 MALE 3 50.00 3 50.00
2 FEMALE 2 33.33 5 83.33
3 NOT VALID 1 16.67 6 100.00
Can you help me with this? Thank you.

This is a very tricky reporting question. One approach uses GROUP BY ROLLUP to generate the total row, along with judicious use of COALESCE to fill in the missing values per your requirements.
SELECT
COALESCE(t1.A, 0) AS CODE,
CASE WHEN t1.A IS NOT NULL THEN COALESCE(t2.DESCRIPTION, 'NOT VALID') ELSE 'TOTAL' END AS SEX,
t1.TOTAL,
ROUND(100.0 * t1.TOTAL / SUM(CASE WHEN t1.A IS NOT NULL THEN t1.TOTAL ELSE 0 END) OVER (), 2) AS PERCENT,
SUM(CASE WHEN t1.A IS NOT NULL THEN t1.TOTAL ELSE 0 END) OVER (ORDER BY t1.A) AS CUMULATIVE,
ROUND(100.0 * SUM(CASE WHEN t1.A IS NOT NULL THEN t1.TOTAL ELSE 0 END) OVER (ORDER BY t1.A) /
SUM(CASE WHEN t1.A IS NOT NULL THEN t1.TOTAL ELSE 0 END) OVER (), 2) AS CUMPERCENT
FROM
(
SELECT
A,
COUNT(*) AS TOTAL
FROM AA
GROUP BY A WITH ROLLUP
) t1
LEFT JOIN BB t2
ON t2.CODE = t1.A
ORDER BY
CODE;
Demo

Related

How to return the number of sets for each date

I am running a query that returns:
Date Set Data
2021-07-02 1 A
2021-07-02 2 B
2021-07-02 3 C
2021-08-15 1 D
2021-10-27 1 E
2021-10-27 2 F
I need to also return the number of Sets for each date:
Date Set Data NoSets
2021-07-02 1 A 3
2021-07-02 2 B 3
2021-07-02 3 C 3
2021-08-15 1 D 1
2021-10-27 1 E 2
2021-10-27 2 F 2
SELECT csm_pat_exe_date.pedate_id, csm_patient_exercise.pat_exe_id,
csm_exercise_details.ede_id, csm_exercise.exe_id,
ses_pat_id, ses_pat_note, ses_pat_body_weight, ses_pat_blood_pressure, ses_pat_blood_glucose_level,
exe_name, ede_order, ede_type,
ede_unit, weekofyear(csm_pat_exe_date.pedate_date) AS weekNumber, csm_pat_exe_date.pedate_date, peds_id, peds_set, peds_result, pedate_note, t2.noSets
FROM csm_session_patient, csm_session, csm_exercise, csm_patient_exercise, csm_exercise_details,
csm_pat_exe_date_set, csm_pat_exe_date
INNER JOIN (
SELECT pedate_date, COUNT(*) as noSets
FROM csm_patient_exercise, csm_pat_exe_date
WHERE csm_patient_exercise.pat_id = '1'
AND csm_patient_exercise.pat_exe_id = csm_pat_exe_date.pat_exe_id
GROUP BY pedate_date
) t2 ON csm_pat_exe_date.pedate_date = t2.pedate_date
WHERE csm_session_patient.pat_id= '1'
AND csm_session_patient.ses_id = csm_session.ses_id
AND csm_session.ses_date = csm_pat_exe_date.pedate_date
AND exe_archived IS NULL
AND csm_exercise.exe_id = csm_patient_exercise.exe_id
AND csm_patient_exercise.pat_exe_id = '1'
AND csm_patient_exercise.exe_id = csm_patient_exercise.pat_exe_id
AND csm_patient_exercise.pat_exe_id = csm_pat_exe_date.pat_exe_id
AND csm_pat_exe_date.pedate_date >= '2021-06-01'
AND csm_pat_exe_date.pedate_date <= '2021-09-24'
AND csm_pat_exe_date.pedate_id = csm_pat_exe_date_set.pedate_id
AND csm_pat_exe_date_set.ede_id = csm_exercise_details.ede_id
ORDER BY csm_pat_exe_date.pedate_date, peds_set, ede_order;
You may add the count window function to your select clause eg
Mysql 8+
SELECT
`Date`,
`Set`,
`Data`,
`NoSets`,
COUNT(*) OVER (PARTITION BY Date) as NoSets
FROM
...include the rest of your query here
Older Mysql Versions
You may use variables or aggregates to achieve your count
Schema (MySQL v5.5)
CREATE TABLE my_table (
`Date` DATETIME,
`Set` INTEGER,
`Data` VARCHAR(1)
);
INSERT INTO my_table
(`Date`, `Set`, `Data`)
VALUES
('2021-07-02', '1', 'A'),
('2021-07-02', '2', 'B'),
('2021-07-02', '3', 'C'),
('2021-08-15', '1', 'D'),
('2021-10-27', '1', 'E'),
('2021-10-27', '2', 'F');
Query #1
SELECT
`Date`,
`Set`,
`Data`,
`NoSets`
FROM (
SELECT
t.*,
#maxcnt:=IF(#prevdate2=`DATE`,IF(#maxcnt>cnt,#maxcnt,cnt),cnt) as NoSets,
#prevdate2:=`Date`
FROM (
SELECT
#cnt:=IF(#prevdate1=`DATE`,#cnt+1,1) as cnt,
#prevdate1:=`Date`,
m.*
FROM
my_table m
CROSS JOIN (SELECT #cnt:=0,#prevdate1:=NULL) vars
ORDER BY `Date`
) t
CROSS JOIN (SELECT #maxcnt:=0,#prevdate2:=NULL) vars
ORDER BY `Date`,cnt DESC
) t2;
Date
Set
Data
NoSets
2021-07-02 00:00:00
3
C
3
2021-07-02 00:00:00
2
B
3
2021-07-02 00:00:00
1
A
3
2021-08-15 00:00:00
1
D
1
2021-10-27 00:00:00
2
F
2
2021-10-27 00:00:00
1
E
2
Query #2
SELECT
t1.`Date`,
t1.`Set`,
t1.`Data`,
t2.`NoSets`
FROM
my_table t1
INNER JOIN (
SELECT `Date`, COUNT(*) as NoSets
FROM my_table
GROUP BY `Date`
) t2 ON t1.`Date`=t2.`Date`;
Date
Set
Data
NoSets
2021-07-02 00:00:00
1
A
3
2021-07-02 00:00:00
2
B
3
2021-07-02 00:00:00
3
C
3
2021-08-15 00:00:00
1
D
1
2021-10-27 00:00:00
1
E
2
2021-10-27 00:00:00
2
F
2
or
SELECT
t1.`Date`,
t1.`Set`,
t1.`Data`,
(
SELECT COUNT(*) FROM my_table t2 WHERE t2.Date=t1.Date
) as `NoSets`
FROM
my_table t1
View on DB Fiddle
Let me know if this works for you.

MySQL Add missing Months in resultset

I'm trying to add missing months in this result set.
Where month is missing, add it with the value 0 for Quantita.
SELECT MONTH(Data) AS Mese,Count(*) AS Quantita
FROM prenotazioni
WHERE Cancellata IS NULL
AND FKCampo = 1
AND YEAR(Data) = YEAR(CURDATE()) -1
GROUP BY Mese
ORDER BY Mese ASC
+------+----------+
| Mese | Quantita |
+------+----------+
| 4 | 123 |
+------+----------+
| 5 | 100 |
+------+----------+
| 7 | 377 |
+------+----------+
| 9 | 54 |
+------+----------+
The following is messy and I am sure there has to be a cleaner way...
So, first I create a table with all the months:
CREATE TABLE all_months (
month_num INT
);
INSERT INTO all_months VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12)
Then I join your query with this table (in my case on the right) and use a case-when to decide if there is a count or not:
SELECT
all_months.month_num,
CASE
WHEN tmp.Quantita is NULL THEN 0
ELSE tmp.Quantita
END as Quantita
FROM (
SELECT MONTH(Data) AS Mese,Count(*) AS Quantita
FROM prenotazioni
WHERE Cancellata IS NULL
AND FKCampo = 1
AND YEAR(Data) = YEAR(CURDATE()) -1
GROUP BY Mese
) as tmp
RIGHT JOIN all_months on all_months.month_num=tmp.Mese
ORDER BY all_months.month_num ASC
Your query is now in tmp and ordering is happening after that. The result is:
month_num Quantita
1 1
2 2
3 0
4 0
5 0
6 0
7 0
8 3
9 1
10 0
11 1
12 1
Demo: here
A solution that does not require creating a new table.
SELECT
`months`.`number`,
CASE
WHEN tmp.Quantita IS NULL THEN 0
ELSE tmp.Quantita
END AS Quantita
FROM (
SELECT MONTH(Data) AS Mese, COUNT(*) AS Quantita
FROM prenotazioni
WHERE Cancellata IS NULL
AND FKCampo = 1
AND YEAR(Data) = YEAR(CURDATE()) -1
GROUP BY Mese
) AS tmp RIGHT JOIN (
SELECT 1 AS `number`
UNION SELECT 2
UNION SELECT 3
UNION SELECT 4
UNION SELECT 5
UNION SELECT 6
UNION SELECT 7
UNION SELECT 8
UNION SELECT 9
UNION SELECT 10
UNION SELECT 11
UNION SELECT 12
) AS `months` ON `months`.`number` = tmp.Mese
ORDER BY `months`.`number` ASC;
One method is to construct or find a table that has all the months you want and use a LEFT JOIN.
In your case, though, conditional aggregation might do this with a simpler query. This assumes that you have at least one row for each month, even if that is filtered out by the existing where clause.
So, this might work:
SELECT MONTH(Data) AS Mese,
SUM(Cancellata IS NULL AND FKCampo = 1 ) AS Quantita
FROM prenotazioni
WHERE YEAR(Data) = YEAR(CURDATE()) - 1
GROUP BY Mese
ORDER BY Mese ASC ;
Of course, if the original table does not have all the months, then they still won't be in the result set and you are back to having to use an outer join.
Same idea of #urban but simplifying the query using left join:
create table prenotazioni
(
id int auto_increment,
Cancellata int default null null,
FKCampo int default 1 null,
Data date default null null,
constraint table_nametes_pk
primary key (id)
);
insert into prenotazioni (Cancellata, FKCampo, Data)
values (null, 1, "2019-04-01"),
(null, 1, "2019-04-01"),
(null, 1, "2019-05-01"),
(null, 1, "2019-05-01"),
(null, 1, "2019-05-01"),
(null, 1, "2019-01-01"),
(null, 1, "2019-01-01"),
(null, 1, "2020-01-01"),
(null, 1, "2018-01-01");
CREATE TABLE all_months (
month_num INT
);
INSERT INTO all_months VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12);
select month_num, count(p.Data)
from
all_months am left join prenotazioni p on (am.month_num = month(p.Data))
where
p.id is null
or
(Cancellata IS NULL AND FKCampo = 1 AND YEAR(Data) = YEAR(CURDATE()) - 1)
GROUP BY am.month_num
ORDER BY am.month_num;
result:

MySQL CASE WHEN numb IS NULL ignore record WHERE numb IS NOT NULL

I have these data in a table:
numb m value
8070 1 7.63
NULL 1 7.64
NULL 1 7.65
8070 2 7.939
8070 2 7.935
8070 2 7.941
NULL 3 7.62
8070 4 7.92
8070 4 7.935
I need MIN(value) and MAX(value) for each m, and if there is a value without numb (NULL), then the ones with a numb should be ignored.
So I should be getting the following results:
numb m value
NULL 1 7.64
NULL 1 7.65
8070 2 7.935
8070 2 7.941
NULL 3 7.62
8070 4 7.92
8070 4 7.935
I've tried quite a lot of different things, but nothing seems to work, and I have no more ideas how to find relevant info. Can you please point me to the right direction?
UPDATE:
to get the number of values it looks like this:
COALESCE(
IF(
COUNT(
CASE
WHEN m IN (2, 4)
THEN value
ELSE
CASE
WHEN m IN (1, 3) AND numb IS NULL
THEN value
END
END
) = 0,
NULL,
COUNT(
CASE
WHEN m IN (2, 4)
THEN value
ELSE
CASE
WHEN m IN (1, 3) AND numb IS NULL
THEN value
END
END
)
),
COUNT(
CASE
WHEN m IN (1, 3)
AND numb IS NOT NULL
THEN value
END
)
) AS cnt
This query should give you the results you want. It has two levels of nested derived tables. The first:
SELECT m,
MIN(CASE WHEN numb IS NULL THEN value END) AS min_null,
MAX(CASE WHEN numb IS NULL THEN value END) AS max_null,
MIN(CASE WHEN numb IS NOT NULL THEN value END) AS min_normal,
MAX(CASE WHEN numb IS NOT NULL THEN value END) AS max_normal
FROM numbers
GROUP BY m;
computes the minimum and maximum values for each value of m, dependent on whether numb was a number or NULL. In the next level,
SELECT m,
COALESCE(min_null, min_normal) AS min_value,
COALESCE(max_null, max_normal) AS max_value
FROM (... query 1...)
we use compute the appropriate minimum and maximum values to use (if there was a NULL value, we use that, otherwise we use the one associated with numeric values of numb). Finally we JOIN the numbers table to the result of query 2 to find the appropriate values of numb for each value of m:
SELECT n.numb, n.m, n.value
FROM numbers n
JOIN (... query 2 ...) num ON num.m = n.m AND (num.min_value = n.value OR num.max_value = n.value)
ORDER BY n.m, n.value
Output:
numb m value
null 1 7.64
null 1 7.65
8070 2 7.935
8070 2 7.941
null 3 7.62
8070 4 7.92
8070 4 7.935
Demo on dbfiddle
The full query:
SELECT n.numb, n.m, n.value
FROM numbers n
JOIN (SELECT m,
COALESCE(min_null, min_normal) AS min_value,
COALESCE(max_null, max_normal) AS max_value
FROM (SELECT m,
MIN(CASE WHEN numb IS NULL THEN value END) AS min_null,
MAX(CASE WHEN numb IS NULL THEN value END) AS max_null,
MIN(CASE WHEN numb IS NOT NULL THEN value END) AS min_normal,
MAX(CASE WHEN numb IS NOT NULL THEN value END) AS max_normal
FROM numbers
GROUP BY m) n) num ON num.m = n.m AND (num.min_value = n.value OR num.max_value = n.value)
ORDER BY n.m, n.value

LEFT JOIN on the same table doubling rows

I have two tables that I am trying to LEFT join but I am not getting the expected results.
Rooms have multiple Children on different days, however Children are only counted in a Room after they have started and if they have hours allocated on that day. The output I am trying to achieve is this.
Room | MaxNum | Mon(Week1) | Tue(Week1) | Mon(Week2) | Tue(Week2)
Blue | 5 | 4 | 4 | 3 | 2
Green | 10 | 10 | 10 | 9 | 9
Red | 15 | 15 | 15 | 15 | 15
Here is the schema and some data...
create table Rooms(
id INT,
RoomName VARCHAR(10),
MaxNum INT
);
create table Children (
id INT,
RoomID INT,
MonHrs INT,
TueHrs INT,
StartDate DATE
);
INSERT INTO Rooms VALUES (1, 'Blue', 5);
INSERT INTO Rooms VALUES (2, 'Green', 10);
INSERT INTO Rooms VALUES (3, 'Red', 15);
INSERT INTO Children VALUES (1, 1, 5, 0, '2018-12-02');
INSERT INTO Children VALUES (2, 1, 0, 5, '2018-12-02');
INSERT INTO Children VALUES (3, 1, 5, 5, '2018-12-09');
INSERT INTO Children VALUES (4, 1, 0, 5, '2018-12-09');
INSERT INTO Children VALUES (5, 2, 5, 0, '2018-12-09');
INSERT INTO Children VALUES (6, 2, 0, 5, '2018-12-09');
The SQL I am having trouble with is this. It may not be the correct approach.
SELECT R.RoomName, R.MaxNum,
R.MaxNum - SUM(CASE WHEN C1.MonHrs > 0 THEN 1 ELSE 0 END) AS Mon1,
R.MaxNum - SUM(CASE WHEN C1.TueHrs > 0 THEN 1 ELSE 0 END) AS Tue1,
R.MaxNum - SUM(CASE WHEN C2.MonHrs > 0 THEN 1 ELSE 0 END) AS Mon2,
R.MaxNum - SUM(CASE WHEN C2.TueHrs > 0 THEN 1 ELSE 0 END) AS Tue2
FROM Rooms R
LEFT JOIN Children C1
ON R.id = C1.RoomID
AND C1.StartDate <= '2018-12-02'
LEFT JOIN Children C2
ON R.id = C2.RoomID
AND C2.StartDate <= '2018-12-09'
GROUP BY R.RoomName;
There is a double up happening on the Rows in the LEFT JOINs that is throwing the counts way off and I don't know how to prevent them. You can see the effect if you replace the SELECT with *
Any suggestions would help a lot.
This sort of problem usually surfaces from doing an aggregation in a too broad point in the query, which then results in duplicate counting of records. Try aggregating the Children table in a separate subquery:
SELECT
R.RoomName,
R.MaxNum,
R.MaxNum - C.Mon1 AS Mon1,
R.MaxNum - C.Tue1 AS Tue1,
R.MaxNum - C.Mon2 AS Mon2,
R.MaxNum - C.Tue2 AS Tue2
FROM Rooms R
LEFT JOIN
(
SELECT
RoomID,
COUNT(CASE WHEN MonHrs > 0 AND StartDate <= '2018-12-02'
THEN 1 END) AS Mon1,
COUNT(CASE WHEN TueHrs > 0 AND StartDate <= '2018-12-02'
THEN 1 END) AS Tue1,
COUNT(CASE WHEN MonHrs > 0 AND StartDate <= '2018-12-09'
THEN 1 END) AS Mon2,
COUNT(CASE WHEN TueHrs > 0 AND StartDate <= '2018-12-09'
THEN 1 END) AS Tue2
FROM Children
GROUP BY RoomID
) C
ON R.id = C.RoomID;
Note that we can avoid the double left join in your original query by instead using conditional aggregation on the start date.
Late edit: You probably don't even need a subquery at all, q.v. the answer by #Salman. But either of our answers should resolve the double counting problem.
You need to use one LEFT JOIN and move the date filter from JOIN condition to the aggregate:
SELECT R.id, R.RoomName, R.MaxNum
, R.MaxNum - COUNT(CASE WHEN C.StartDate <= '2018-12-02' AND C.MonHrs > 0 THEN 1 END) AS Mon1
, R.MaxNum - COUNT(CASE WHEN C.StartDate <= '2018-12-02' AND C.TueHrs > 0 THEN 1 END) AS Tue1
, R.MaxNum - COUNT(CASE WHEN C.StartDate <= '2018-12-09' AND C.MonHrs > 0 THEN 1 END) AS Mon2
, R.MaxNum - COUNT(CASE WHEN C.StartDate <= '2018-12-09' AND C.TueHrs > 0 THEN 1 END) AS Tue2
FROM Rooms R
LEFT JOIN Children C ON R.id = C.RoomID
GROUP BY R.id, R.RoomName, R.MaxNum

How to calculate ratio using sql query?

I have a table like below:
ID Name Department Gender
1 Crib MA MALE
2 Lucy Bsc FEMALE
3 Phil Bcom MALE
4 Ane MA FEMALE
I have 1000 row of records like this. I want to find the ratio from column Gender( MALE & FEMALE) of all students.
I need a query to perform this.
SQL Fiddle
MySQL 5.5.32 Schema Setup:
CREATE TABLE table1
(`ID` int, `Name` varchar(4), `Department` varchar(4), `Gender` varchar(6))
;
INSERT INTO table1
(`ID`, `Name`, `Department`, `Gender`)
VALUES
(1, 'Crib', 'MA', 'MALE'),
(2, 'Lucy', 'Bsc', 'FEMALE'),
(3, 'Phil', 'Bcom', 'MALE'),
(4, 'Ane', 'MA', 'FEMALE')
;
Query 1:
SELECT sum(case when `Gender` = 'MALE' then 1 else 0 end)/count(*) as male_ratio,
sum(case when `Gender` = 'FEMALE' then 1 else 0 end)/count(*) as female_ratio
FROM table1
Results:
| MALE_RATIO | FEMALE_RATIO |
|------------|--------------|
| 0.5 | 0.5 |
Try something like this
select sum(case when gender = 'MALE' then 1 else 0 end) / count(*) * 100 as perc_male,
sum(case when gender = 'FEMALE' then 1 else 0 end) / count(*) * 100 as perc_female
from students
This should give you the actual ratio, and should work with little or no modifcation in MySQL and SQL Server. You may have to modify the cast statement a little - my MySQL is rusty, and I think it may handle that slightly differently.
SELECT
(CAST((SELECT COUNT(*) FROM tblName WHERE Gender='MALE') AS FLOAT) /
CAST((SELECT COUNT(*) FROM tblName WHERE Gender='FEMALE') AS FLOAT))
AS ratioMaleFemale;
You're pretty close:
select (select count(*)
from table where gender='MALE' )/count(*)*100 as percentage_male,
(select count(*)
from table where gender='FEMALE' )/count(*)*100 as percentage_female
from table;
How about
select gender, count(*)
from table
group by gender
then it's very simple to calculate the ratio yourself.