SQL Syntax to Group by Date and Case statement - mysql

My mysql database resembles the following and am struggling with the proper use of case, group by and order by statements. The sample dataset:
ID Date Direction
1 2017-04-01 1
2 2017-04-01 1
3 2017-04-01 -1
4 2017-04-01 1
5 2017-04-01 -1
6 2017-04-01 1
7 2017-04-02 -1
8 2017-04-02 -1
9 2017-04-02 -1
10 2017-04-02 1
11 2017-04-02 -1
12 2017-04-03 -1
I am trying to understand the best way to aggregate this table and group by date so that the query returns the following recordset. The "positive" column below is simply a count of the direction column (above) where direction > 0. Similarly, the "negative" column is a count of the direction column where direction < 0 like so:
DATE positive negative
2017-04-01 4 2
2017-04-02 1 4
2017-04-03 0 1
I have tried:
SELECT DATE,
CASE
WHEN direction < 0 THEN 'negative'
WHEN direction > 0 THEN 'positive'
END AS updownType, count(*) AS updownCount
FROM table WHERE DATE BETWEEN '2017-04-01' AND '2017-04-03'
GROUP BY DATE, updownType
ORDER BY DATE ASC
I've reviewed dozens of SO threads on this topic, but just haven't run across what I am looking for yet. Any suggestions are appreciated

oops you just miss it . see below
SELECT DATE,
count(CASE WHEN direction < 0 THEN '1' END) AS negative,
Count(CASE WHEN direction > 0 THEN '1' END) AS positive
FROM table WHERE DATE BETWEEN '2017-04-01' AND '2017-04-03'
GROUP BY DATE
ORDER BY DATE ASC

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?

The sum of Opening and Closing Balances for each Financial Year Grouped by Category in MYSQL

I have used three days attempting how to figure out this with no avail.I also did some search even from this forum but failed too.
It might look like Duplicate Question but to be honest this is different from others has been asked.
My question is how to get the sum of Balance Carry forward C/F and Closing balance for each financial year being GROUPED BY loan_id For each Date Range ie.Financial year?
transaction_t
Na loan_id date credit_amount debit_amount
1 1 2017-01-01 5,000 4,000
2 1 2017-05-01 6,000 2,000
3 2 2017-10-01 1,000 1,500
4 1 2018-10-30 2,000 400
5 2 2018-11-01 12,00 1,000
6 2 2019-01-15 1,800 500
7 1 2019-05-21 100 200
The above table schema and its data have mysql fiddle here I have also read this thread MySQL Open Balance Credit Debit Balance which is only working for single user.
So far I have tried:
SELECT loan_id,
SUM(credit)-(SELECT SUM(a.debit) FROM transaction_t a
WHERE a.transaction_date
BETWEEN '2019-01-01' AND '2020-12-31' AND a.loan_id = loan_id) AS OpeningBalance,
sum(credit),sum(debit),
(#OpeningBalance+SUM(credit))-SUM(debit) AS closing_balance
FROM transaction_t tr
WHERE transaction_date BETWEEN DATE('2019-01-01') AND DATE('2020-12-31')
GROUP BY loan_id
But the above is not giving correct results ,How do i get the results like these ones?
A: Query made for date between 2017-01-01 and 2018-12-31
loan_id opening_balance sum(credit_amount) sum(debit_amount) closing_balance
1 0 13,000.00 6,400.00 6,600.00
2 0 2,200.00 2,500.00 -300
B: Query made for date between 2019-01-01 and 2020-12-31
loan_id opening_balance sum(credit_amount) sum(debit_amount) closing_balance
1 6,600 100.00 200.00 6,500.00
2 -300 1,800.00 500.00 1,000
You are looking for conditional aggregation.
The key thing is that you need to start scanning the table from the beginning of the history in order to generate the initial balance. Then you just need to adjust the conditional sums:
Consider:
SET #start_date = '2017-01-01';
SET #end_date = '2018-12-31';
SELECT
loan_id,
SUM(CASE WHEN transaction_date < #start_date THEN credit - debit ELSE 0 END) opening_balance,
SUM(CASE WHEN transaction_date BETWEEN #start_date AND #end_date THEN credit ELSE 0 END) sum_credit,
SUM(CASE WHEN transaction_date BETWEEN #start_date AND #end_date THEN debit ELSE 0 END) sum_debit,
SUM(CASE WHEN transaction_date <= #end_date THEN credit - debit ELSE 0 END) closing_balance
FROM transaction_t
WHERE transaction_date <= #end_date
GROUP BY loan_id
In your DB Fiddle, this returns:
loan_id opening_balance sum_credit sum_debit closing_balance
1 0 13000 6400 6600
2 0 2200 2500 -300
And when changing the dates to 2020-2021:
loan_id opening_balance sum_credit sum_debit closing_balance
1 6600 100 200 6500
2 -300 1800 500 1000
NB: that was a well-asked question, that SO could use more of!

MySQL - Calculate accumulation since reset event in a Table

This issue is a reference for my other question
Python solution has been done based on extract from MySQL DB (5.6.34) where original data are stored.
My question is: Is it possible to make such calculation straight in MySQL?
Just to remind:
There is 'runners' table with accumulated distance per runner and reset tags
runner startdate cum_distance reset_event
0 1 2017-04-01 100 1
1 1 2018-04-20 125 0
2 1 2018-05-25 130 1
3 2 2015-04-05 10 1
4 2 2015-10-20 20 1
5 2 2016-11-29 50 0
I would like to calculate an accumulated distance per runner since the reset point (my comments in brackets ()):
runner startdate cum_distance reset_event runner_dist_since_reset
0 1 2017-04-01 100 1 100 <-(no reset since begin)
1 1 2018-04-20 125 0 25 <-(125-100)
2 1 2018-05-25 130 1 30 <-(130-100)
3 2 2015-04-05 10 1 10 <-(no reset since begin)
4 2 2015-10-20 20 1 10 <-(20-10)
5 2 2016-11-29 50 0 30 <-(50-20)
So far I was able to calculate only differences between reset events:
SET #DistSinceReset=0;
SELECT
runner,
startdate,
reset_event,
IF(cum_distance - #DistSinceReset <0, cum_distance, cum_distance - #DistSinceReset) AS 'runner_dist_since_reset',
#DistSinceReset := cum_distance AS 'cum_distance'
FROM
runners
WHERE
reset_event = 1
GROUP BY runner, startdate
This answer is for MySQL 8.
The information you want is the most recent cum_distance for each user with reset_event = 1. You are using MySQL 8, so you can use window functions.
Here is one method:
select r.*,
(cum_distance - coalesce(preceding_reset_cum_distance, 0)) as runner_dist_since_reset
from (select r.*,
min(cum_distance) over (partition by runner order by preceding_reset) as preceding_reset_cum_distance
from (select r.*,
max(case when reset_event = 1 then start_date end) over
(partition by runner
order by start_date
rows between unbounded preceding and 1 preceding
) as preceding_reset
from runners r
) r
) r;

How to filter MYSQL database table by current date

My table:
id message sent_user_id sent_user_name received_user_id received_user_name table_no message_time status
1 Need a help 6 Saman kumara 11 Kamal Perera 0 2016-12-07 17:28:41 0
2 Need a help 6 Saman kumara 10 Nimal Rajapaksha 0 2017-01-05 18:17:35 0
3 Need a help 6 Saman kumara 10 Nimal Rajapaksha 0 2017-01-05 18:21:20 0
4 Need a help 6 Saman kumara 10 Nimal Rajapaksha 0 2017-01-05 18:22:15 1
I need to select all rows where message_time equals to current date.
SELECT * FROM message_info WHERE message_time = CURDATE();
This is query I tried. But it does not work.
SELECT * FROM message_info WHERE DATE_FORMAT(message_time, "%y-%m-%d") = CURDATE();
You want to compare the dates, so you have to remove the time data from the datetime in your table. You can do this by using DATE_FORMAT().
If there is an index on the column message_time you should use this query:
SELECT *
FROM message_info
WHERE message_time
BETWEEN DATE_FORMAT(CURDATE(), "%y-%m-%d 00:00:00") AND DATE_FORMAT(CURDATE(), "23-59-59")
When you use a function to change the value of your column its index can not be used. By changing the search algorithm to look for dates between beginning of day x and end of day x you are getting the same result without having to change the value for the search.

mysql - How to get if day past of any data in SELECT

I'm trying to make a summary making a indicator if any data in the SELECT past day from now... or just show a day (if > 0 : + , if < 0 : - ).
Like this: these are my tables.
We supose, today is 2013-12-25
tb_employee:
ID_EMP EMPLOYEE
1 Employee 1
2 Employee 2
3 Employee 3
tb_requirement:
ID_REQ REQUIREMENT
1 Requirement 1
2 Requirement 2
3 Requirement 3
4 Requirement 4
tb_detail:
ID_DET ID_EMP ID_REQ EXPIRATION
1 1 1 2013-12-29
2 1 2 2013-12-28
3 1 3 2013-12-31
4 2 2 2014-01-05
5 2 3 2013-12-20
6 2 4 2013-12-15
Now, the SELECT QUERY should show like this:
ID_EMP EMPLOYEE REQUIREMENTS_GOT ANY_REQ_EXPIRE
1 Employee 1 3 YES
2 Employee 2 3 NO
I hope i explained well. Maybe it could be with DATEDIFF ?
Thank you for answers... and of course, Merry Christmas !
Since you're trying to determine if any of the requirements expired, you should compare the minimal expiry date to today's date. There's no need to use datediff - a simple > operator packed in a case statement would do:
SELECT id_emp,
employee,
COUNT(*) AS requirements_got,
CASE WHEN CURDATE() > MIN(expiration) THEN 'yes' ELSE 'no' END AS any_req_expire
FROM tb_detail
JOIN tb_employee ON tb_detail.id_emp = tb_employee.id_emp
GROUP BY id_emp, employee