MySQL grouping totals by multiple date and results using case - mysql

I have a results table which lists a set of values, each linking to another table containing the date that result was made.
I have working SQL to get all the dates (using CASE's) however I can only retrieve a single range of results.
Select
count(CASE
WHEN results.test_id IN ( SELECT id
FROM `test`
WHERE `posted`
BETWEEN '2011-07-01 00:00:00'
AND '2011-07-01 23:59:59')
THEN results.test_id
ELSE NULL
END) AS "1st July"
from `results`
WHERE results.window_id = 2 and results.mark > 90;
I also have another SQL query which gets all the ranges but can only work for one date at a time.
SELECT
CASE
when mark > 90 then '>90%'
when mark > 80 then '>80%'
when mark > 70 then '>70%'
END as mark_results,
COUNT(*) AS count
FROM (SELECT mark from results where window_id =2) as derived
GROUP BY mark_results
ORDER BY mark_results;
What I'd like is to have everything in one unified query, displaying the relevant totals for each range of results. such as below:
Result Range | 1st July | 2nd July | 3rd July | 4th July
>90% | 0 | 0 | 0 | 1
>80% | 1 | 2 | 1 | 1
>70% | 4 | 5 | 5 | 4
So that the totals for each range are displayed under their date.
I assume it's possible.

The following statement joins results and tests in the FROM clause. It then aggregates the query by the mark range, with the counts per day:
Select (CASE when mark > 90 then '>90%'
when mark > 80 then '>80%'
when mark > 70 then '>70%'
END) as mark_results,
sum(case when posted BETWEEN '2011-07-01 00:00:00' AND '2011-07-01 23:59:59' then 1 else 0 end) as July01,
sum(case when posted BETWEEN '2011-07-02 00:00:00' AND '2011-07-02 23:59:59' then 1 else 0 end) as July02,
. . .
from `results` r join
test t
on r.test_id = t.test_id
WHERE r.window_id = 2 and results.mark > 90
group by (CASE when mark > 90 then '>90%'
when mark > 80 then '>80%'
when mark > 70 then '>70%'
END)
order by 1
Just add whatever days you want to the SELECT clause.
I should add . . . if you want all the dates, you need to put them on separate rows:
Select date(posted) as PostedDate,
(CASE when mark > 90 then '>90%'
when mark > 80 then '>80%'
when mark > 70 then '>70%'
END) as mark_results,
count(*) as cnt
. . .
from `results` r join
test t
on r.test_id = t.test_id
WHERE r.window_id = 2 and results.mark > 90
group by date(posted),
(CASE when mark > 90 then '>90%'
when mark > 80 then '>80%'
when mark > 70 then '>70%'
END)
order by 1, 2
In fact, you might consider having a separate row for each date, with the ranges pivoted as columns.

Related

How to use GROUP BY with subquery range?

I'm quite new here and I am tinkering with MYSQL to get a sort of pivot table.
For now I'm blocked here :
SELECT `range`,
Sum(IF(`Vrange` = '< 5',1,0)) as `<5`,
Sum(IF(`Vrange` = ' 5-10',1,0)) as `5-10`,
Sum(IF(`Vrange` = ' 10-15',1,0)) as `10-15`,
Sum(IF(`Vrange` = ' 15-20',1,0)) as `15-20`,
Sum(IF(`Vrange` = ' 20-25',1,0)) as `20-25`,
Sum(IF(`Vrange` = ' 20-25',1,0)) as `20-25`,
Sum(IF(`Vrange` = '> 30',1,0)) as `>30`
FROM(
select `Time`,`HDG`, `Vitesse`,
case
when `HDG` between 1 and 90 then ' 0-90'
when `HDG` between 91 and 180 then ' 91-180'
when `HDG` between 181 and 270 then ' 181-270'
else '271-360'
end as `range`,
case
when `Vitesse` between 0 and 5 then '< 5'
when `Vitesse` between 6 and 10 then ' 5-10'
when `Vitesse` between 11 and 15 then ' 10-15'
when `Vitesse` between 16 and 20 then ' 15-20'
when `Vitesse` between 21 and 25 then ' 20-25'
when `Vitesse` between 25 and 30 then ' 25-30'
else '> 30'
end as `Vrange`
from DataPort
WHERE `Time` > now() - interval 1 day
ORDER BY `Time` DESC
)as SQ
GROUP BY `range`;
I get the following anwser :
| range | <5 | 5-10 | ...
|---------|---------|------------|--------
| 0-90 | 5 | 3 |
| 180-270 | 12 | 20 |
And I would like to display all items of range i.e. 0-90 / 91-180 / 181-270 / 271-360 in each row. How is it possible ? As follow :
| range | <5 | 5-10 | ...
|---------|---------|------------|--------
| 0-90 | 1 | 1 |
| 91-180 |0 or null| 0 or null |
| 180-270 | 12 | 20 |
| 271-360 |0 or null| 0 or null |
Many thanks in advance
Welcome to S/O. This should help get what you had going. You did not need to do an explicit pre-query to get ranges, then sum again in the outer query for the counts.
select
AllRanges.Required Range,
sum( case when DP.Vitesse >= 0 and DP.Vitesse < 5 then 1 else 0 end ) ' < 5',
sum( case when DP.Vitesse >= 5 and DP.Vitesse < 10 then 1 else 0 end ) '5-10',
sum( case when DP.Vitesse >= 10 and DP.Vitesse < 15 then 1 else 0 end ) '10-15',
sum( case when DP.Vitesse >= 15 and DP.Vitesse < 20 then 1 else 0 end ) '15-20',
sum( case when DP.Vitesse >= 20 and DP.Vitesse < 25 then 1 else 0 end ) '20-25',
sum( case when DP.Vitesse >= 25 and DP.Vitesse < 30 then 1 else 0 end ) '25-30',
sum( case when DP.Vitesse >= 30 then 1 else 0 end ) '>30'
from
( select '0-90' Required
UNION select '91-180'
UNION select '181-270'
UNION select '271-360' ) AllRanges
LEFT JOIN DataPort DP
ON AllRanges.Required =
case when DP.HDG >= 0 and DP.HDG <= 90 then '0-90'
when DP.HDG > 90 and DP.HDG <= 180 then '91-180'
when DP.HDG >= 180 and DP.HDG <= 270 then '181-270'
else '271-360' end
AND DP.`Time` > now() - interval 1 day
group by
case when DP.HDG >= 0 and DP.HDG <= 90 then '0-90'
when DP.HDG > 90 and DP.HDG <= 180 then '91-180'
when DP.HDG >= 180 and DP.HDG <= 270 then '181-270'
else '271-360' end
Now, having said that, and the above will work, I would like to point out some less-than-optimal parts of it.
Your "HDG", I believe is a directional Heading and will always be technically 0-359 degrees as 360 is actually back to 0.
In your Vitesse range brackets, and not knowing if any fractional / decimal values or not, but you are using the labels twice, such as 5-10 and 10-15. Shouldn't 10 only be within one of the brackets? Your between was testing between 11 and 15, so shouldn't the header group also match?
Your result columns should be named columns. Not spaces, and especially not special characters, dashes, etc. The results should be a table with direct column names. It is the part of your OUTPUT such as report or web that has heading columns with proper context rather than naming the columns as you were attempting.
Finally, careful on your column names, such as 'Time' Try NOT to use reserved keywords within your SQL table column definitions. Take a look at the commands available, function names, etc. Instead of just time, maybe a EntryTime, LogTime, CreateTime, or similar. A bit more explicit context and you'll avoid having to add tick marks to everything. Also, by qualifying with table.column or alias.column helps prevent ambiguity when joining to multiple tables having similar column names.
Just trying to suggest improvements for this and future as you grow with SQL.
FEEDBACK
As per issue of not getting all ranges, I have revised the query. Notice the inner (select ---- ranges) AllRanges via LEFT JOIN to the DataPort table. In this case, the primary table is now the AllRanges alias with 4 rows for each one you want. THEN, I did the LEFT JOIN to the DataPort table. The join is based on the condition of the AllRanges.Required column matching the conditional CASE --- PLUS the Time condition of the date.
If you have the WHERE clause for the time, it internally will convert the LEFT JOIN to an INNER JOIN thus preventing all 4 ranges.
Should be good to go now.
I am not sure if anything is wrong with your code... Are you saying you are missing "91-180" and "271"360"?
Are you sure you have rows that match that range in your subquery?

MySQL find rows where yesterday's value is > X AND where last 5 days value < X exists

Let's say I have the following table:
date | name | value
----------------------------
2020-09-01 | name1 | 10
2020-09-02 | name1 | 9
2020-09-03 | name1 | 12
2020-09-04 | name1 | 11
2020-09-05 | name1 | 11
I would like to identify names where the latest value >= 10 AND where over the last 5 days it has ever dropped below 10. In the example table above, name1 would be returned because the latest date has a value of 11 (which is > 10), and over the last 5 days it has dropped below 10 at least once.
Here is my SELECT statement, but it always returns zero rows:
SELECT
name,
count(value) as count
FROM table_name
WHERE
(date = #date AND value >= 10) AND
date BETWEEN date_sub(#date, interval 5 day) AND #date AND value < 10
GROUP BY name
HAVING count < 5
ORDER BY name
I understand why it's failing, but I don't know what to change.
In MySQL 8.0, you could use window functions and aggregation:
select name
from (
select t.*, row_number() over(partition by name order by date desc) rn
from mytable t
where date >= #date - interval 5 day and date <= #date
) t
having max(case when rn = 1 then value end) >= 10 and min(value) <= 10
How about something like this:
SELECT Name, COUNT(*) AS Ct FROM
(SELECT A.*,B.mdate,
CASE WHEN A.date=B.mdate AND A.value >= 10 THEN 1
WHEN A.date >= B.mdate - INTERVAL 5 DAY AND A.date <> B.mdate AND A.value < 10 THEN 1
ELSE 0 END AS Chk
FROM table_name A
JOIN (SELECT Name,MAX(DATE) AS mdate FROM table_name GROUP BY Name) B ON A.Name=B.Name
HAVING Chk <> 0) V
GROUP BY Name
HAVING Ct >= 2
Here's a fiddle for reference: https://www.db-fiddle.com/f/jX4GktCdTrUbqHBf7ZQwdr/0
And here's a breakdown of what the query above is doing.
Joining table_name with a sub-query of the same table but with MAX(DATE) value for comparison.
Using CASE function to check for your conditions; if matches with the conditions, it will return 1, if not, return 0. Added HAVING to exclude any 0 value from the CASE function.
Turn the query to become a sub-query (assigned as V) and do a COUNT(*) over how many occurrence happen on the name then using HAVING again to get any name that have 2 or more occurrence.

MySQL duplicate entries search with selective date criteria

Having trouble wrapping my head around having an efficient "duplicate entries" select in a single query.
In the below example, duplicate StockNo can exist spanning multiple Date. I want to search StockNo for duplicate entries, and if at least 1 StockNo record is found within the Date current YEAR-MONTH, then I also need to select its partner that could exist in any other YEAR-MONTH. Is this possible?
Example Query:
SELECT * FROM `sales`
WHERE `StockNo` IN
(SELECT `StockNo` FROM `sales` GROUP BY `StockNo` HAVING COUNT(*) > 1)
AND `Date` LIKE '2016-11-%'
ORDER BY `StockNo`, `TransactionID`;
Example Data:
ID | StockNo | Date
1 | 1 | 2016-11-01
2 | 1 | 2016-11-10
3 | 2 | 2016-11-05
4 | 2 | 2016-10-29
5 | 3 | 2016-10-25
6 | 3 | 2016-10-15
With my example query and data, I have 3 pairs of duplicate entries. It's pretty obvious that I will only return 3 records (ID's 1, 2 & 3) due to AND Date LIKE '2016-11-%', however I need to return ID's 1, 2, 3, 4. I want to ignore ID's 5 & 6 because neither of them fall within the current month.
Hope that makes sense. Thanks for any help you can provide.
SELECT StockNo
FROM sales
GROUP BY StockNo
HAVING SUM(CASE WHEN DATE_FORMAT(Date, '%Y-%m') = '2016-11' THEN 1 ELSE 0 END) > 0
If you also want to retrieve the full records for those matching stock numbers in the above query, you can just add a join:
SELECT s1.*
FROM sales s1
INNER JOIN
(
SELECT StockNo
FROM sales
GROUP BY StockNo
HAVING SUM(CASE WHEN DATE_FORMAT(Date, '%Y-%m') = '2016-11' THEN 1 ELSE 0 END) > 0
) s2
ON s1.StockNo = s2.StockNo
Demo here:
SQLFiddle
Thank you very much Tim for pointing me in the right direction. Your answer was close but it still only returned records from the current month and in the end I used the following query:
SELECT s1.* FROM `sales` s1
INNER JOIN
(
SELECT * FROM `sales` GROUP BY `StockNo` HAVING COUNT(`StockNo`) > 1
AND SUM(CASE WHEN DATE_FORMAT(`Date`, '%Y-%m')='2016-11' THEN 1 ELSE 0 END) > 0
) s2
ON s1.StockNo=s2.StockNo
This one had been eluding me for some time.

Getting first week of date in mysql

I have a lot of table and i need to get the gross income of a movie, now my problem is i don't know how to get the sum of first week only of a movie.
This is what i need.
+-------------------------------------------+
| title | Week one | Week one |
| | (Wed-Sun) | (Mon-Tue) |
+-------------------------------------------+
| title 1 | 50000 | 10000 |
+-------------------------------------------+
If the starting show of a movie is wed then i should make 3 column, first column is title, second column is the wed-sun and third is mon-tue.
Is this possible to query like select movie, sum(wed-sun), sum(mon-tue)
Thanks in advance
This is my answer based on how I understand your question.
SELECT movie, sum(wed-sun), sum(mon-tue) CONVERT(date, getdate()) as day
FROM thetable
WHERE thedate(BETWEEN first AND last)
GROUP BY day
You can user DAYOFWEEK() if you are using date type for that. See http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_dayofweek
For week days from mon-tue
SUM(CASE WHEN DAYOFWEEK(DATE)=2 THEN 1 WHEN DAYOFWEEK(DATE)=3 THEN 1 ELSE 0 END)
For week days from wed-sun
SUM(CASE WHEN DAYOFWEEK(DATE)=4 THEN 1 WHEN DAYOFWEEK(DATE)=5 THEN 1 WHEN DAYOFWEEK(DATE)=6 THEN 1 WHEN DAYOFWEEK(DATE)=7 THEN 1 WHEN DAYOFWEEK(DATE)=1 THEN 1 ELSE 0 END)
If you use WEEKDAY instead of DAYOFWEEK you can shorten the case statements:
SELECT
movie_id,
title,
SUM(CASE WHEN WEEKDAY(date_field) < 2 THEN field_to_sum ELSE 0 END) `mon-tue`,
SUM(CASE WHEN WEEKDAY(date_field) > 1 THEN field_to_sum ELSE 0 END) `wed-sun`
FROM
movies
/* optional WHERE */
GROUP_BY movie_id
Obviously you want WEEKDAY FUNCTION, it returns the weekday index starting from 0-Monday.
Assume you have table Movies with title and starting_show_date columns, and value_table with action_date and amount columns.
You can sum amount by splitting amounts to two parts like this:
select
movies.title,
sum(case when value_table.action_date
< dateadd(movies.starting_show_date , interval 7 -WEEKDAY(movies.starting_show_date) day)
then value_table.amount else 0 end) as FirstWeek,
sum(case when value_table.action_date
>= dateadd(movies.starting_show_date , interval 7 -WEEKDAY(movies.starting_show_date) day)
then value_table.amount else 0 end) as OtherWeeks
from
movies
inner join
value_table
on
movies.id = value_table.movie_id
group by
movies.title

MySQL - get COUNT depends on the value

How can I get the COUNT() of the specific field depends on the value of the field? For example I have the field typeOfAssistance , in the query below I got the total numbers of the typeOfAssistance but I have different values in it which is financial medical and burial, How can I add custom column that will divide the total value depends on the value?
SELECT date,COUNT(*) AS num
FROM requests
WHERE date BETWEEN DATE_ADD(CURDATE(),INTERVAL -20 DAY) AND CURDATE()
GROUP BY date
desired output:
date | financial | burial | medical | total
2014-04-25 | 1 | 2 | 3 | 6
Thanks. Sorry for the explanation. :)
Typically for something like that I would use SUM rather than COUNT for the item breakdowns.
Something like
SELECT date,
SUM(CASE WHEN typeOfAssistance = 'financial' THEN 1 ELSE 0 END) AS financial,
SUM(CASE WHEN typeOfAssistance = 'burial' THEN 1 ELSE 0 END) AS burial,
SUM(CASE WHEN typeOfAssistance = 'medical' THEN 1 ELSE 0 END) AS medical,
COUNT(1) AS Total
FROM requests
GROUP BY date