Creating summary report from multiple fields from a set of data - mysql

I have a task to write an sql to summarize the following data:
select
payment,
status,
interest,
principal
from payment
payment status interest principal
1 A 100 0
2 r 0 500
3 o 0 400
4 d 0 100
5 A 0 200
6 A 200 0
7 A 300 0
8 A 0 300
a payment relating to interest would be where interest is not 0
a payment relating to principal would be where a principal is not 0
the data would need to be split into type
The result I am looking for is similar to this:
Interest total count principal total count
Status - A 3 1
Other 0 4
total of all payments 3 5
I have been using group function but have not been able to group beyond the interest amount. Any suggestions would be greatly appreciated.

I would suggest to use GROUP BY + WITH ROLLUP for this. Something like:
SELECT
IF(`status` = 'A', 'Status A', 'Others') as `-`,
SUM(`interest` > 0) as `Interest total count`,
SUM(`principal` > 0) as `principal total count`
FROM
(
SELECT 1 as `payment`, 'A' as `status`, 100 as `interest`, 0 as `principal`
UNION ALL
SELECT 2, 'r', 0, 500
UNION ALL
SELECT 3, 'o', 0, 400
UNION ALL
SELECT 4, 'd', 0, 100
UNION ALL
SELECT 5, 'A', 0, 200
UNION ALL
SELECT 6, 'A', 200, 0
UNION ALL
SELECT 7, 'A', 300, 0
UNION ALL
SELECT 8, 'A', 0, 300
) as `sub`
GROUP BY 1 WITH ROLLUP;
Result:
- Interest total count principal total count
Others 0 3
Status A 3 2
3 5
Query:
SELECT
IF(`status` = 'A', 'Status A', 'Others') as `-`,
SUM(`interest` > 0) as `Interest total count`,
SUM(`principal` > 0) as `principal total count`
FROM
`payment`
GROUP BY 1 WITH ROLLUP;

Related

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 get only latest record from different ranges?

I am looking at a case in which we have a number of tanks filled with liquid. The amount of liquid is measured and information is stored in a database. This update is done every 5 minutes. Here the following information is stored:
tankId
FillLevel
TimeStamp
Each tank is categorized in one of the following 'fill-level' ranges:
Range A: 0 - 40%
Range B: 40 - 75%
Range C: 75 - 100%
Per range I count the amount of events per tankId.
SELECT sum(
CASE
WHEN filllevel>=0 and filllevel<40
THEN 1
ELSE 0
END) AS 'Range A',
sum(
CASE
WHEN filllevel>=40 and filllevel<=79
THEN 1
ELSE 0
END) AS 'Range B',
sum(
CASE
WHEN filllevel>79 and filllevel<=100
THEN 1
ELSE 0
END) AS 'Range C'
FROM TEST ;
The challenge is to ONLY count the latest record for each tank. So for each tankId there is only one count (and that must be the record with the latest time stamp).
For the following data:
insert into tank_db1.`TEST` (ts, tankId, fill_level) values
('2017-08-11 03:31:18', 'tank1', 10),
('2017-08-11 03:41:18', 'tank1', 45),
('2017-08-11 03:51:18', 'tank1', 95),
('2017-08-11 03:31:18', 'tank2', 20),
('2017-08-11 03:41:18', 'tank2', 30),
('2017-08-11 03:51:18', 'tank2', 80),
('2017-08-11 03:31:18', 'tank3', 30),
('2017-08-11 03:41:18', 'tank3', 45),
('2017-08-11 03:51:18', 'tank4', 55);
I would expect the outcome to be (only the records with the latest timestamp per tankId are counted):
- RANGE A: 0
- RANGE B: 1 (tankdId 3)
- RANGE C: 2 (tankId 1 and tankId2)
Probably easy if you are an expert, but for me it is real hard to see what the options are.
Thanks
You can use the following query to get the latest per group timestamp value:
select tankId, max(ts) as max_ts
from test
group by tankId;
Output:
tankId max_ts
--------------------------------
1 tank1 11.08.2017 03:51:18
2 tank2 11.08.2017 03:51:18
3 tank3 11.08.2017 03:41:18
4 tank4 11.08.2017 03:51:18
Using the above query as a derived table you can extract the latest per group fill_level value. This way you can apply the logic that computes each range level:
select sum(
CASE
WHEN t1.fill_level>=0 and t1.fill_level<40
THEN 1
ELSE 0
END) AS 'Range A',
sum(
CASE
WHEN t1.fill_level>=40 and t1.fill_level<=79
THEN 1
ELSE 0
END) AS 'Range B',
sum(
CASE
WHEN t1.fill_level>79 and t1.fill_level<=100
THEN 1
ELSE 0
END) AS 'Range C'
from test as t1
join (
select tankId, max(ts) as max_ts
from test
group by tankId
) as t2 on t1.tankId = t2.tankId and t1.ts = t2.max_ts
Output:
Range A Range B Range C
---------------------------
1 0 2 2
Demo here
I get a different result (oh, well, same result as GB):
SELECT GROUP_CONCAT(CASE WHEN fill_level < 40 THEN x.tankid END) range_a
, GROUP_CONCAT(CASE WHEN fill_level BETWEEN 40 AND 75 THEN x.tankid END) range_b
, GROUP_CONCAT(CASE WHEN fill_level > 75 THEN x.tankid END) range_c
FROM test x
JOIN (SELECT tankid,MAX(ts) ts FROM test GROUP BY tankid) y
ON y.tankid = x.tankid AND y.ts = x.ts;
+---------+-------------+-------------+
| range_a | range_b | range_c |
+---------+-------------+-------------+
| NULL | tank3,tank4 | tank1,tank2 |
+---------+-------------+-------------+
EDIT:
If I was solving this problem, and wanted to include the tank names in the result, then I'd probably execute the following...
SELECT x.*
FROM test x
JOIN
( SELECT tankid,MAX(ts) ts FROM test GROUP BY tankid) y
ON y.tankid = x.tankid
AND y.ts = x.ts
...and handle all the other problems, concerning counts, ranges, and missing/'0' values in application code.

Why MySQL full outer join returns nulls?

Why MySQL full outer join returns nulls?
Hi
I have the following data:
s_id,date,p_id,amount_sold
1, '2015-10-01', 1, 10
2, '2015-10-01', 2, 12
7, '2015-10-01', 1, 11
3, '2015-10-02', 1, 11
4, '2015-10-02', 2, 10
5, '2015-10-15', 1, 22
6, '2015-10-16', 2, 20
8, '2015-10-22', 3, 444
and i want my query to output something like this: (A = sum of amount_sold for p_id=1 for that date,B = sum of amount_sold for p_id=2 for that date)
date,A,B,Difference
'2015-10-01',21,12,9
'2015-10-02',11,10,1
'2015-10-15',22,0,22
'2015-10-01',0,20,-20
I tried with this query, but the order its returning is having NULLS and the output is wrong:
SELECT A.p_id,A.date,sum(A.amount_sold) A,B.Bs, (sum(A.amount_sold) - B.Bs) as difference FROM sales as A
LEFT JOIN (
SELECT SUM( amount_sold ) Bs,p_id,s_id, DATE
FROM sales
WHERE p_id =2
group by date
) as B ON A.s_id = B.s_id
where A.p_id=1 or B.p_id=2
group by A.date, A.p_id
UNION
SELECT A.p_id,A.date,sum(A.amount_sold) A,B.Bs, (sum(A.amount_sold) - B.Bs) as difference FROM sales as A
RIGHT JOIN (
SELECT SUM( amount_sold ) Bs,p_id,s_id, DATE
FROM sales
WHERE p_id =2
group by date
) as B ON A.s_id = B.s_id
where B.p_id=2
group by A.date, A.p_id
It returned:
p_id date A Bs difference
1 2015-10-01 21 NULL NULL
2 2015-10-01 12 12 0
1 2015-10-02 11 NULL NULL
2 2015-10-02 10 10 0
1 2015-10-15 22 NULL NULL
2 2015-10-16 20 20 0
What am i doing wrong here? and what is the correct way of doing it? any help would be appreciated.
A full join isn't needed. You can use conditional aggregation instead:
select
date,
sum(case when p_id = 1 then amount_sold else 0 end) a,
sum(case when p_id = 2 then amount_sold else 0 end) b,
sum(case when p_id = 1 then amount_sold else 0 end)
- sum(case when p_id = 2 then amount_sold else 0 end) difference
from sales
where p_id in (1,2)
group by date

mysql count zeros in sequence

I got mysql database and I need to get number of zeros in sequence and print them all with date from first zero, so for example I got a table like this
id, date, impuls_count
1, '12-05-15 12:00:00', 60
2, '12-05-15 12:01:00', 0
3, '12-05-15 12:02:00', 0
4, '12-05-15 12:03:00', 49
5, '12-05-15 12:04:00', 0
6, '12-05-15 12:05:00', 0
7, '12-05-15 12:06:00', 0
8, '12-05-15 12:07:00', 0
9, '12-05-15 12:08:00', 30
10, '12-05-15 12:09:00', 0
this should give the result like this:
'12-05-15 12:01:00', 2
'12-05-15 12:04:00', 4
'12-05-15 12:09:00', 1
I tried to solve it on my own but my query works very slow(I got 5000 rows in a table) and it sometimes prints same row twice
SELECT qwe.date, ile
FROM (SELECT p.date,
(SELECT COUNT(*)
FROM performance_v2
WHERE date > p.date
AND date <
(SELECT MIN(date)
FROM performance_v2
WHERE date > p.date AND impuls_count > 0)) ile
FROM performance_v2 p
WHERE p.impuls_count > 0
AND (date(p.date)
BETWEEN '2015-05-08%'
AND '2015-05-08%')
AND (time(p.date)
between '14:00:00' and '22:00:00')
ORDER BY 1) qwe
WHERE ile > 0
In MySQL, this is easiest to solve using variables. The idea is to have a counter increment each time the value of impuls_count changes. This defines groups of common values. You can then filter the values and aggregate to get what you want:
select min(date), count(*)
from (select t.*,
(#g := if(#ic = impuls_count, #g,
if(#ic := impuls_count, #g + 1, #g + 1)
)
) as grp
from table t cross join
(select #ic := 0, #g := 0)
order by id
) t
where impuls_count = 0
group by grp

MySQL Count frequency of records

Table:
laterecords
-----------
studentid - varchar
latetime - datetime
reason - varchar
students
--------
studentid - varchar -- Primary
class - varchar
I would like to do a query to show the following:
Sample Report
Class No of Students late 1 times 2 times 3 times 4 times 5 & more
Class A 3 1 0 2 0 0
Class B 1 0 1 0 0 0
My query below can show the first column results:
SELECT count(Distinct studentid), class FROM laterecords, students
WHERE students.studenid=laterecords.studentid AND
GROUP BY class
I can only think of getting the results for each column and store them into php arrays. Then echo them to table in HTML.
Is there any better SQL way to do the above? How to do up the mysql query ?
Try this:
SELECT
a.class,
COUNT(b.studentid) AS 'No of Students late',
SUM(b.onetime) AS '1 times',
SUM(b.twotime) AS '2 times',
SUM(b.threetime) AS '3 times',
SUM(b.fourtime) AS '4 times',
SUM(b.fiveormore) AS '5 & more'
FROM
students a
LEFT JOIN
(
SELECT
aa.studentid,
IF(COUNT(*) = 1, 1, 0) AS onetime,
IF(COUNT(*) = 2, 1, 0) AS twotime,
IF(COUNT(*) = 3, 1, 0) AS threetime,
IF(COUNT(*) = 4, 1, 0) AS fourtime,
IF(COUNT(*) >= 5, 1, 0) AS fiveormore
FROM
students aa
INNER JOIN
laterecords bb ON aa.studentid = bb.studentid
GROUP BY
aa.studentid
) b ON a.studentid = b.studentid
GROUP BY
a.class
How about :
SELECT numlates, `class`, count(numlates)
FROM
(SELECT count(laterecords.studentid) AS numlates, `class`, laterecords.studentid
FROM laterecords,
students
WHERE students.studentid=laterecords.studentid
GROUP BY laterecords.studentid, `class`) aliastbl
GROUP BY `class`, numlates