MySql query group by day and by time - mysql

I'm trying create an SQL query to resolve my problem.
My Table:
+----+---------------------+-------+
| id | date | value |
+----+---------------------+-------+
| 1 | 2014-10-10 05:10:10 | 10 |
+----+---------------------+-------+
| 2 | 2014-10-10 09:10:10 | 20 |
+----+---------------------+-------+
| 3 | 2014-10-10 15:10:10 | 30 |
+----+---------------------+-------+
| 4 | 2014-10-10 23:10:10 | 40 |
+----+---------------------+-------+
| 5 | 2014-10-11 08:10:10 | 15 |
+----+---------------------+-------+
| 6 | 2014-10-11 09:10:10 | 25 |
+----+---------------------+-------+
| 7 | 2014-10-11 10:10:10 | 30 |
+----+---------------------+-------+
| 8 | 2014-10-11 23:10:10 | 40 |
+----+---------------------+-------+
I want to sum value in groups by days and this days in three sub groups like a 'morning'(06:00 - 12:00), 'afternoon'(12:00 - 18:00) and 'night'(00:00 - 06:00 and 18:00 - 24:00).
something like this:
+------------+-------+---------+-----------+-------+
| date | value | morning | afternoon | night |
+------------+-------+---------+-----------+-------+
| 2014-10-10 | 100 | 20 | 30 | 50 |
+------------+-------+---------+-----------+-------+
| 2014-10-11 | 110 | 70 | 0 | 40 |
+------------+-------+---------+-----------+-------+

You could use a couple of sums over case expressions:
SELECT DAY(`date`) AS `date`
SUM(CASE WHEN HOUR(`date`) BETWEEN 6 AND 12 THEN value ELSE 0 END) AS `morning`,
SUM(CASE WHEN HOUR(`date`) BETWEEN 12 AND 18 THEN value ELSE 0 END) AS `afternoon`,
SUM(CASE WHEN HOUR(`date`) < 6 OR HOUR(`date`) > 18 THEN value ELSE 0 END) AS `evening`
FROM my_table
GROUP BY DAY(`date`)

There are multiple ways to go about this, but for myself I'd do it by first extracting the pseudo information in a CROSS APPLY, and then grouping on this information.
I believe this offers significant readibility benefits, and allows you to re-use any calculations in other clauses. For example, you have centralised the grouping mechanism, meaning that you only need to change it in the one place rather than in the select and the group by. Similarly, you could add "extraData.Morning = 1" to a WHERE clause rather than re-writing the calculation for mornings.
For example:
CREATE TABLE #TestData (ID INT, Data DATETIME, Value INT)
INSERT INTO #TestData (ID, Data, Value) VALUES
(1 ,'2014-10-10 05:10:10' ,10)
,(2 ,'2014-10-10 09:10:10' ,20)
,(3 ,'2014-10-10 15:10:10' ,30)
,(4 ,'2014-10-10 23:10:10' ,40)
,(5 ,'2014-10-11 08:10:10' ,15)
,(6 ,'2014-10-11 09:10:10' ,25)
,(7 ,'2014-10-11 10:10:10' ,30)
,(8 ,'2014-10-11 23:10:10' ,40)
SELECT
extraData.DayComponent
,SUM(td.Value)
,SUM(CASE WHEN extraData.Morning = 1 THEN td.Value ELSE 0 END) AS Morning
,SUM(CASE WHEN extraData.Afternoon = 1 THEN td.Value ELSE 0 END) AS Afternoon
,SUM(CASE WHEN extraData.Night = 1 THEN td.Value ELSE 0 END) AS Night
FROM #TestData td
CROSS APPLY (
SELECT
DATEADD(dd, 0, DATEDIFF(dd, 0, td.Data)) AS DayComponent
,CASE WHEN DATEPART(HOUR, td.Data) BETWEEN 6 AND 12 THEN 1 ELSE 0 END AS Morning
,CASE WHEN DATEPART(HOUR, td.Data) BETWEEN 12 AND 18 THEN 1 ELSE 0 END AS Afternoon
,CASE WHEN DATEPART(HOUR, td.Data) BETWEEN 0 AND 6
OR DATEPART(HOUR, td.Data) BETWEEN 18 AND 24 THEN 1 ELSE 0 END AS Night
) extraData
GROUP BY
extraData.DayComponent
DROP TABLE #TestData

Related

Mysql Query with multiple subqueries with group by distinct condition

Hi I am a PHP Developer weak in MySQL Medium complex queries make my head fired.
The below is the table vulnerability.
+-----------------------------------------------------------------------------------------------+
| id | webisite_id | low_count| high_count | medium_count | date_time | vul_date |
+-----------------------------------------------------------------------------------------------+
| 20 | 6 | 1 | 1 | 1 | 2018-07-04 09:14:04 | 2018-02-01 |
| 19 | 6 | 30 | 30 | 30 | 2018-07-04 09:13:38 | 2018-01-30 |
| 18 | 6 | 1 | 1 | 1 | 2018-07-04 09:13:16 | 2018-01-01 |
+-----------------------------------------------------------------------------------------------+
This table represent count of low, medium, high - vulnerability count for each website in database. We can enter as many entries for each websites. But the only relevant entry for a website is the latest entry in each month (based on vul_date).
Here I need help I want query which fetch sum of counts low, high, medium of each websites of each month of last 1 year, for example if -> website with id 1 has 1, 2, 3 low, high, medium number of vulnerabilities, on month June and
-> that of with id 2 has 7, 8, 9 respectively the result should be for June 8, 10, 12. And like last 1 year's each month result should be get. If no entry it should be 0.
Note that the entries should be the maximum value of vul_date and if a site has multiple entries on the same vul_date get the latest date_time entry.
I tried to write question as much as simple. hope the question is understood.
Please help me on this
Thanks in advance.
I think below query will work for you.
SELECT
SUM(low_count),
SUM(medium_count),
SUM(high_count),
MONTH(vul_date)
FROM
(SELECT
low_count, medium_count, high_count, vul_date, date_time
FROM
test
WHERE
(website_id , vul_date) IN (SELECT website_id, MAX(vul_date)
FROM test GROUP BY website_id , MONTH(vul_date))) t
WHERE
date_time IN (SELECT MAX(date_time) FROM test GROUP BY website_id , vul_date)
GROUP BY MONTH(vul_date);
What it does is, first finds the latest entry month wise for each website id which is your max vul_date.
SELECT website_id, MAX(vul_date)
FROM test GROUP BY website_id , MONTH(vul_date)
If there are more than one entry for a vul_date, it uses date_time to select maximum value from them. Finally it sums all website date after grouping it month wise.
You can change the above query to get 0 value for those months where there is no entry for any websites.
DROP TABLE IF EXISTS T;
CREATE TABLE T(id INT, website_id INT, low_count INT, high_count INT, medium_count INT, date_time DATETIME, vul_date DATE);
INSERT INTO T VALUES
( 20 , 6 , 1 , 1 , 1, '2018-07-04 09:14:04' , '2018-02-01'),
( 19 , 6 , 30, 30, 30, '2018-07-04 09:13:38' , '2018-01-30'),
( 18 , 6 , 2 , 2 ,2 , '2018-07-04 09:13:16' , '2018-01-01'),
( 17 , 6 , 2 , 2 ,2 , '2018-07-04 09:12:01' , '2018-01-01'),
( 90 , 1,1,2,3,'2017-07-05 01:00:00',' 2017-07-06'),
( 90 , 2,8,9,10,'2017-07-05 01:00:00',' 2017-07-06');
select coalesce(c.yyyymm,d.yyyymm) yyyymm,
coalesce(c.lo,0) lo,
coalesce(c.hi,0) hi,
coalesce(c.med,0) med
from
(
SELECT concat(year(a.vul_date),'-',month(a.vul_date)) yyyymm,
SUM(LOW_COUNT) lo,SUM(HIGH_COUNT) hi,sum(medium_count) med
from
(
select website_id,date_time,vul_date
from t
where date_time = (select max(date_time) from t t1 where t1.website_id = t.website_id and t1.vul_date = t.vul_date)
) a
join
(select website_id, date_time,vul_date,
LOW_COUNT,HIGH_COUNT,medium_count
from t) b
on b.website_id = a.website_id and b.date_time = a.date_time
group by concat(year(a.vul_date),'-',month(a.vul_date))
) c
right join
(select distinct concat(year(dte),'-',month(dte)) yyyymm from dates d
where dte between date_sub(now(), interval 1 year) and now()
) d on d.yyyymm = c.yyyymm
;
Sub query a get the vul_date with the most recent data_time which is then self joined, aggregated and then infilled with missing dates using a right join to a dates/calender table. If you don't have a dates/calender it would be useful for this kind of exercise nut there are alternatives which you can find in SO.
Result
+---------+------+------+------+
| yyyymm | lo | hi | med |
+---------+------+------+------+
| 2017-7 | 9 | 11 | 13 |
| 2017-8 | 0 | 0 | 0 |
| 2017-9 | 0 | 0 | 0 |
| 2017-10 | 0 | 0 | 0 |
| 2017-11 | 0 | 0 | 0 |
| 2017-12 | 0 | 0 | 0 |
| 2018-1 | 32 | 32 | 32 |
| 2018-2 | 1 | 1 | 1 |
| 2018-3 | 0 | 0 | 0 |
| 2018-4 | 0 | 0 | 0 |
| 2018-5 | 0 | 0 | 0 |
| 2018-6 | 0 | 0 | 0 |
| 2018-7 | 0 | 0 | 0 |
+---------+------+------+------+
13 rows in set (0.04 sec)

count and sum different values per each day

how count different values per each day ? and the total value for each day? in one request.
key1 | key2 | tdate | tview
1 | 20161123454647 | 2016-11-23 11:11:11 | view1
2 | 20161123454648 | 2016-11-23 11:11:11 | view2
3 | 20161122454649 |2016-11-22 11:11:11 | view2
4 | 20161122454650 |2016-11-22 11:11:11 | view1
5 | 20161122454653 |2016-11-22 11:11:11 | view2
6 | 20161122454661 |2016-11-22 11:11:11 | view2
7 | 20161121454622 |2016-11-21 11:11:11 | view3
8 | 20161121454679 |2016-11-21 11:11:11 | view1
9 | 20161121454684 |2016-11-21 11:11:11 | view3
I found to count the total of all values of tview per day :
SELECT DATE(tdate) Date, COUNT(DISTINCT tview) totalOfViews FROM mytable GROUP BY DATE(tdate)
I have a key (key2) which the concatenation of date and the number of a render because I don't want to hive two same render in the same day.
It's most easy for me when I insert a new render with 'INSERT ON DUPLICATE key2 UPDATE'. I update just the number of view with one request. I don't know 'INSET ON DUPLICATE' <2 differents keys> UPDATE :newview. Interesting question too ;-)
The date is a timestamp in my table.
I use php 7, MySQL and PDO to do statement.
One of interesting output:
day | totaView1 | totalView2 | totalView3 | totalView1+view2 |totalOfViews
2016-11-23 | 1 | 1 | 0 | 2 | 2
2016-11-22 | 1 | 3 | 0 | 4 | 4
2016-11-21 | 1 | 0 | 2 | 1 | 3
After i found to range date of request and compare évolution of number view per day. Example:
Day (currentmonth) | totaView1 | totalView1 (lastmonth) |totalOfViews
Is the "alter table" can do this result?
One possibility is to use conditional aggregation:
SELECT DATE(tdate) AS day,
SUM(CASE WHEN tview = 'view1' THEN 1 ELSE 0 END) AS totaView1,
SUM(CASE WHEN tview = 'view2' THEN 1 ELSE 0 END) AS totaView2,
SUM(CASE WHEN tview = 'view3' THEN 1 ELSE 0 END) AS totaView3,
SUM(CASE WHEN tview = 'view1' OR tview = 'view2'
THEN 1 ELSE 0 END) AS totaView1Or2,
COUNT(*) AS totalOfViews
FROM mytable
GROUP BY DATE(tdate)

Rails+ActiveRecords: How to group records by date and show their count based on a condition?

I have data like this in a DB table
ID | numbers | created_at
1 | 10 | 2016-04-01
2 | 20 | 2016-04-01
3 | -8 | 2016-04-01
4 | 1 | 2016-04-02
5 | 81 | 2016-04-03
6 | -12 | 2016-04-03
7 | 0 | 2016-04-03
And the desired output I'd like to get:
Date | Greater (or even) than 0 | Smaller than 0
2016-04-01 | 2 | 1
2016-04-02 | 1 | 0
2016-04-03 | 2 | 1
Is there a way to load this data from one query?
All I can think of our subqueries and doing queries in the Rails view where I want to render the fetched data.
I would try something like this:
lines = Model.
select('date, SUM(IF(number > 0, 1, 0)) as greater_count', SUM(IF(number < 0, 1, 0)) as smaller_count').
group(:date).
order(:date)
lines.each do |line|
puts [line.date, line.greater_count, line.smaller_count]
end
Assuming your Created_at is a date with no time components... similar to spickermann's but less platform specific.
Select Created_at as Date
, Sum(case WHEN numbers >= 0 THEN 1 Else 0 END) as GTE_0
, Sum(case WHEN numbers < 0 THEN 1 Else 0 END) as LT_0
FROM Table
GROUP BY Created_at
What this does is evaluate the number if >=0 then it sets a value of 1 which it then sums by date for Greater than Equal to 0 (GTE_0) Similar done for LT_0 (Less than 0).

Mapping a column value to which dates it has rows of

I have a table of sign-ins for people who are logging time on different projects and I need to compile a report of which days each project was worked on.
My table looks something like this:
id | project | time_in
----------------------------------
1 | 1 | 2014-12-07 05:00:00
2 | 2 | 2014-12-08 06:00:00
3 | 1 | 2014-12-05 14:00:00
4 | 3 | 2014-12-07 08:30:00
5 | 2 | 2014-12-07 12:00:00
6 | 1 | 2014-12-08 05:00:00
7 | 2 | 2014-12-05 06:00:00
8 | 1 | 2014-12-06 14:00:00
9 | 3 | 2014-12-08 08:30:00
10 | 2 | 2014-12-06 12:00:00
time_in is of type TIMESTAMP.
What I need to figure out is, given a date range (e.g. December 5 - 8), which days of the week each project was worked on. I'm totally flexible on the query, i.e. I can generate the query using a loop, and I'm also flexible on how the result set looks, so long as I can parse it to get the information I need. For example, maybe:
project | days
-----------------------------------------------------
1 | 2014-12-07,2014-12-08,2014-12-05,2014-12-06
2 | 2014-12-08,2014-12-07,2014-12-05,2014-12-06
3 | 2014-12-07,2014-12-08
Or better yet:
project | d0 | d1 | d2 | d3
---------------------------
1 | 1 | 1 | 1 | 1
2 | 1 | 1 | 1 | 1
3 | 0 | 0 | 1 | 1
I honestly have no idea where to even begin on a query like this, if it's even possible.
You could use group_concat to achieve your initial results:
select project, group_concat(date(time_in) order by time_in)
from yourtable
group by project
SQL Fiddle Demo
If you want additional columns, you could use max with case:
select project,
max(case when date(time_in) = '2014-12-05' then 1 else 0 end) d0,
max(case when date(time_in) = '2014-12-06' then 1 else 0 end) d1,
max(case when date(time_in) = '2014-12-07' then 1 else 0 end) d2,
max(case when date(time_in) = '2014-12-08' then 1 else 0 end) d3
from yourtable
group by project
More Fiddle

Mysql inner join query

I'm using two tables in the database.
The first contains data related to the successful and unsuccessful payments while the second table contains data regarding the status of services.
The result of the query should combine both tables and as a result list the successful and unsuccessful payments grouped by the days as well as the status of services grouped by days.
First table looks like:
id | charged | date
-----------------------------
8 | OK | 2011-12-03
7 | OK | 2011-12-03
9 | NO | 2011-12-03
11 | OK | 2011-12-04
14 | NO | 2011-12-04
The second table looks like:
id | status | date
--------------------------
8 | 1 | 2011-12-03
9 | 1 | 2011-12-03
11 | 0 | 2011-12-04
12 | 0 | 2011-12-04
14 | 1 | 2011-12-04
The correct query result should be:
date | not_charged | charged | status_1 | status_0
-----------------------------------------------------------
2011-12-04 | 1 | 1 | 1 | 2
2011-12-03 | 1 | 2 | 2 | 0
The query that I've tried looks like this:
SELECT i.date, SUM(
CASE WHEN i.charged = 'NO'
THEN 1 ELSE 0 END ) AS not_charged, SUM(
CASE WHEN i.charged = 'OK'
THEN 1 ELSE 0 END ) AS charged, SUM(
CASE WHEN s.status = '1'
THEN 1 ELSE 0 END ) AS status_1, SUM(
CASE WHEN s.status = '0' THEN 1 ELSE 0 END ) AS status_0
FROM charge i INNER JOIN status s ON s.date = i.date
GROUP BY i.date
But I get the wrong result that looks like this
date | not_charged | charged | status_1 | status_0
---------------------------------------------------------
2011-12-04 | 3 | 3 | 2 | 4
2011-12-03 | 2 | 4 | 6 | 0
What I'm doing wrong and how can I get the correct result?
Thanks for all suggestions.
Try this one -
SELECT date,
SUM(IF(charged = 'NO', 1, 0)) not_charged,
SUM(IF(charged = 'OK', 1, 0)) charged,
SUM(IF(status = 1, 1, 0)) status_1,
SUM(IF(status = 0, 1, 0)) status_0
FROM (
SELECT date, charged, NULL status FROM charge
UNION ALL
SELECT date, NULL charged, status FROM status
) t
GROUP BY date DESC;
+------------+-------------+---------+----------+----------+
| date | not_charged | charged | status_1 | status_0 |
+------------+-------------+---------+----------+----------+
| 2011-12-04 | 1 | 1 | 1 | 2 |
| 2011-12-03 | 1 | 2 | 2 | 0 |
+------------+-------------+---------+----------+----------+
This assumes the ID columns related that service status and payment status together...
SELECT
COALESCE(charge.date, status.date) AS date,
SUM(CASE WHEN charge.charged = 'NO' THEN 1 ELSE 0 END) AS not_charged,
SUM(CASE WHEN charge.charged = 'OK' THEN 1 ELSE 0 END) AS charged,
SUM(CASE WHEN status.status = '0' THEN 1 ELSE 0 END) AS status_0,
SUM(CASE WHEN status.status = '1' THEN 1 ELSE 0 END) AS status_1
FROM
charge
FULL OUTER JOIN
status
ON charge.id = status.id
GROUP BY
COALESCE(charge.date, status.date)
Note, I'm note sure how you want to deal with 7 (No status record) and 12 (no charge record). This currently just counts what is there.
Alternatively, if you don't want to related the records by ID, you can still relate by date but you need to change your logic.
At present you're getting this, because you only relate by date...
id | charged | date id | status | date
----------------------------- --------------------------
8 | OK | 2011-12-03 8 | 1 | 2011-12-03
8 | OK | 2011-12-03 9 | 1 | 2011-12-03
7 | OK | 2011-12-03 8 | 1 | 2011-12-03
7 | OK | 2011-12-03 9 | 1 | 2011-12-03
9 | NO | 2011-12-03 8 | 1 | 2011-12-03
9 | NO | 2011-12-03 9 | 1 | 2011-12-03
11 | OK | 2011-12-04 11 | 0 | 2011-12-04
11 | OK | 2011-12-04 12 | 0 | 2011-12-04
11 | OK | 2011-12-04 14 | 1 | 2011-12-04
14 | NO | 2011-12-04 11 | 0 | 2011-12-04
14 | NO | 2011-12-04 12 | 0 | 2011-12-04
14 | NO | 2011-12-04 14 | 1 | 2011-12-04
Instead you need to consolidate the data down to 1 per date per table, then join...
SELECT
COALESCE(charge.date, status.date) AS date,
charge.not_charged,
charge.charged,
status.status_0,
status.status_1
FROM
(
SELECT
date,
SUM(CASE WHEN charged = 'NO' THEN 1 ELSE 0 END) AS not_charged,
SUM(CASE WHEN charged = 'OK' THEN 1 ELSE 0 END) AS charged
FROM
charge
GROUP BY
date
)
AS charge
FULL OUTER JOIN
(
SELECT
date,
SUM(CASE WHEN charged = '0' THEN 1 ELSE 0 END) AS status_0,
SUM(CASE WHEN charged = '1' THEN 0 ELSE 1 END) AS status_1
FROM
status
GROUP BY
date
)
AS status
ON charge.date = status.date
There are other methods, but hopefully this explains a bit for you.
I suggest using a UNION ALL:
select date,
coalesce(sum(not_charged),0) not_charged,
coalesce(sum(charged),0) charged,
coalesce(sum(status_1),0) status_1,
coalesce(sum(status_0),0) status_0
from (select date,
case charged when 'NO' then 1 end not_charged,
case charged when 'OK' then 1 end charged,
0 status_1,
0 status_0
from charge
union all
select date,
0 not_charged,
0 charged,
case status when '1' then 1 end status_1,
case status when '0' then 1 end status_0
from status) sq
group by date