Counting daily and hourly results in mySQL - php

I have two tables in mySQL, one for views and one for downloads with the following key structure:
ip | hit
---------------------------------------
127.0.0.1 | 2015-02-08 15:16:59
127.0.0.1 | 2015-02-08 15:20:22
127.0.0.1 | 2015-02-08 15:20:35
127.0.0.1 | 2015-02-08 15:21:13
10.0.1.1 | 2015-02-09 16:29:13
10.0.1.1 | 2015-02-09 16:42:12
10.0.1.1 | 2015-02-09 16:52:30
10.0.1.1 | 2015-02-10 10:52:30
10.0.1.1 | 2015-02-10 10:52:30
10.0.1.1 | 2015-02-10 12:52:30
I need to return a query that will count how many rows there are every day, ideally it would also return empty days/hours but I can fill those gaps in with PHP easily if the SQL query would be overly complex.
time | count
---------------------------------------
2015-02-08 | 4
2015-02-09 | 3
2015-02-10 | 3
I also need to get the same information on a hourly basis
time | count
---------------------------------------
2015-02-08 15:00:00 | 4
2015-02-09 16:00:00 | 3
2015-02-10 10:00:00 | 2
2015-02-10 10:00:00 | 1
So far i have the following query after cobbling together a few I found online into something that actually works, but it only returns the day of the month as being unique and I need to have the query be stretchable over multiple months so only knowing the day is not massively helpful.
select dayofmonth(hit) as Day_of_month, count(*) as records from table_name hit >= '2015-02-01 00:00:00' and hit < '2015-03-01 00:00:00' group by dayofmonth(hit)
I've seen a fair few of these similar posts around but the only ones I have found use functions not present in mySQL it seems, any help would be appreciated.
Edit
After much more fiddling I now have it returning counts for each date with the following which i can easily enough limit by wheres, however I'm still none the wiser to getting this to work on an hourly basis.
SELECT date(hit) AS The_Date, count( * ) AS count FROM table_name GROUP BY The_Date ORDER BY The_Date ASC

The easiest way to do that could be using date_format function
To get all records grouped by day
select date_format(date, '%Y-%m-%d') time, count(*) from test group by time order by time;
To get all records grouped by hour
select date_format(date, '%Y-%m-%d %H:00:00') time, count(*) from test group by date_format(date, '%Y-%m-%d %H') order by time;
Here is sqlfiddle for your example

Try something like this:
SELECT DATE(hit) AS Day, HOUR(TIME(hit)) AS Hour, COUNT(*) as Count
FROM table_name
GROUP BY Day, Hour
(edited)

Related

Group By alternative in Laravel 5.4

I have a table Schedule with 3 columns (id, ref_number, pay_date). Each ref_number has a pay_date in every month. So the table looks something like this:
id | ref_number | pay_date
-----------------------------
1 | A001 | 2018-06-29
1 | A001 | 2018-07-29
1 | A002 | 2018-06-30
1 | A002 | 2018-07-30
1 | A002 | 2018-08-30
1 | A003 | 2018-06-29
I want to fetch only the earliest record for every ref_number that have pay_date between today and a date (30 or 31 days from today). The below query works fine in Mysql (I would pass the dates dynamically later).
SELECT id,ref_number,MIN(pay_date) FROM schedule
WHERE (pay_date BETWEEN '2018-06-30' AND '2018-07-30')
GROUP BY ref_number
I know we can turn the mysql "strict" to false in Database config file and the Group By would behave as expected, but without having to change that is there any other way around this problem?
What would be the eloquent equivalent for this query? With or without groupby.
After searching for a while, I came across an answer using nested select statement and gave it a try in Laravel. It worked exactly as I wanted. Here's the sweet piece of code:
Schedule::select(DB::raw(id, ref_number, MIN(pay_date) as pay_date))
->from(DB::raw("(SELECT *
FROM schedule
WHERE (pay_date > CURDATE()))
temp")
)
->groupBy('temp.ref_number')
->get();

MySQL: Get record where TIME('xx:yy:zz') between start and end time

This is the table:
+----+-----------+------------+----------+
| id | id_sensor | start_time | end_time |
+----+-----------+------------+----------+
| 1 | 12 | 21:15:00 | 02:45:00 |
| 2 | 7 | 00:00:00 | 23:15:00 |
| 3 | 5 | 04:30:00 | 16:30:00 |
+----+-----------+------------+----------+
I need to get record(s) where a specific time (e.g. 01:00:00) passed by PHP is between. start_time and end_time are TIME fields in UTC, I'm passing to the query hour via php, note, converted in php from user_timezone to UTC.
SELECT * FROM test WHERE TIME('01:00:00') BETWEEN start_time AND end_time;
Query returns only record id 2, not the 1. I need both, in this case (for id 1, end time ofcourse is next day).
Of course, if we looking for TIME('01:00:00'), we don't need the id 3.
Thank you.
I think this is the logic you want:
SELECT *
FROM test
WHERE (start_time < end_time AND TIME('01:00:00') BETWEEN start_time AND end_time) OR
(start_time > end_time AND TIME('01:00:00') NOT BETWEEN end_time AND start_time);
now() returns both, current time and date. You can also work with curtime(), which returns only the current time.
BTW, i think that working with SELECT * should be avoided (maybe you used it just for this example), it is IMHO always better to list the fields needed explicitly.

MySQL select time groupings where timestamps overlap from different rows with timezone difference

This question seems different to the others asked so I'll ask it here.
I have a MySQL table that stores from and to timestamps, I would like to select groupings from this table to work out groups of when people are "online" at the same time. The idea behind this madness is to automatically group people together in time slots that intersect. Ideally it would be great to get the best time for this group (but this may not be possible).
I have two tables, a table called "times" that store the times and a table called "users" that store users details, the users table also include a time difference field (in hours) that should be applied to times (all times are stored in UTC).
Here are my tables:
Users
userid | timediff
------------------
1 | 0
2 | 0
3 | 1
4 | 4
5 | -8
6 | 2
7 | 2
Times
userid | from | to
1 | 2015-01-13 16:00:00 | 2015-01-13 23:00:00
2 | 2015-01-13 13:00:00 | 2015-01-13 21:00:00
3 | 2015-01-13 14:00:00 | 2015-01-13 22:00:00
4 | 2015-01-13 11:00:00 | 2015-01-13 12:00:00
5 | 2015-01-13 10:00:00 | 2015-01-13 12:00:00
6 | 2015-01-13 11:00:00 | 2015-01-13 12:00:00
7 | 2015-01-13 09:00:00 | 2015-01-13 10:00:00
In a perfect world this would group people like so:
1 | 2015-01-13 16:00:00 | 2015-01-13 23:00:00
2 | 2015-01-13 13:00:00 | 2015-01-13 21:00:00
3 | 2015-01-13 14:00:00 | 2015-01-13 22:00:00
these people are online together between 16:00 - 21:00
4 | 2015-01-13 11:00:00 | 2015-01-13 12:00:00
5 | 2015-01-13 10:00:00 | 2015-01-13 12:00:00
6 | 2015-01-13 11:00:00 | 2015-01-13 12:00:00
these people are online together between 11:00 - 12:00
(also please take into account this isn't taking into account the time difference for ease of understanding but I'm happy to figure that out if needed separately).
This may not be possible with just sql and I may need to use PHP, I haven't posted any sample code as I'm not sure the best direction to take, any pointers would be great!
This is not a super simple project. It has lots of pieces to it, specifically timezone offsets, time-range comparisons, and coincidence searching.
But let's give it a try. For starters, let's create a view to handle the timezone offset stuff. We really don't want to be mucking about with that computation over and over. This view will do it.
CREATE VIEW `utctimes`
AS select `t`.`userid` AS `userid`,
`t`.`from` AS `from`,
`t`.`to` AS `to`,
`t`.`from` + interval `u`.`timediff` hour AS `utcfrom`,
`t`.`to` + interval `u`.`timediff` hour AS `utcto`
from `times` `t`
join `users` `u` on `u`.`userid` = `t`.`userid`;
Next, let's self-join this view and do some time-range comparisons to find out when more than one person is online. To see if a pair of from/to ranges overlap, this logic does it.
a.from <= b.to
and b.from <= a.to
You can convince yourself that the two ranges overlap if both those conditions are true.
We'll assume both are online even if one comes on exactly at noon and the other goes off exactly at noon, even though that might be a poor assumption.
This query will give us a list of time-ranges and the number of users online sometime during each time-range. It does this with a promiscuous (and therefore somewhat expensive) self-join.
select count(*) as users_on,
greatest(a.utcfrom, b.utcfrom) utcfrom,
least(a.utcto, b.utcto) utcto
from utctimes a
join utctimes b on a.userid <> b.userid
where a.utcfrom <= b.utcto
and b.utcfrom <= a.utcto
group by greatest(a.utcfrom, b.utcfrom), least(a.utcto, b.utcto)
order by count(*) desc,
greatest(a.utcfrom, b.utcfrom),
timestampdiff(minute, greatest(a.utcfrom, b.utcfrom),
least(a.utcto, b.utcto)) desc
This will give the most popular range first, then some other ranges in order of popularity. It does yield some overlapping ranges.
Once you have the most popular time ranges, you can find out which users are online during those ranges. This JOIN, for example, will do that.
select r.users_on, r.utcfrom online_session_start,
timediff(r.utcto, r.utcfrom) online_session_duration,
q.userid, q.`from`, q.`to`
from utctimes q
join (
select count(*) as users_on,
greatest(a.utcfrom, b.utcfrom) utcfrom,
least(a.utcto, b.utcto) utcto
from utctimes a
join utctimes b on a.userid <> b.userid
where a.utcfrom <= b.utcto
and b.utcfrom <= a.utcto
group by greatest(a.utcfrom, b.utcfrom), least(a.utcto, b.utcto)
) r on q.utcfrom <= r.utcto
and r.utcfrom <= q.utcto
order by 2,3,4

Effectively Firing a multiple query

I was practicing drawing graphs on various statistics for the purpose of data analysis. But I am not able to figure out an efficient way to fire multiple mysql query at the back-end.
I am trying to draw Period Vs No of Visitors graph.
Please Note: Period here refers to week,month,3 months,6 months,1 year,2 years.
Period will be selected by the user from the select box.
For example: When User selects 3 week, I need to construct No of Visitors per 3 week graph.
My DataBase Contains Two Column: For each of the site hit, it records:
(1) timestamp and
(2)user ID.
If I fire query multiple times for each select option, then performance would be quite poor.So, How to do it efficiently?
UPD:
When User Select stats per 3 month:
Then I am firing mysql query as:
Select count(*) From stats_tab WHERE timestamp BETWEEN JAN AND MAR;
Select count(*) From stats_tab WHERE timestamp BETWEEN APR AND JUN;
Select count(*) From stats_tab WHERE timestamp BETWEEN JUL AND SEP;
............
Each count returned from each of the query will be the y-axis value for my graph
When User Select stats per year:
Then I am firing mysql query as:
Select count(*) From stats_tab WHERE timestamp BETWEEN 2009 AND 2010;
Select count(*) From stats_tab WHERE timestamp BETWEEN 2010 AND 2011;
Select count(*) From stats_tab WHERE timestamp BETWEEN 2011 AND 2012;
............
Don't hit database with multiple queries. Get all your values with one query appropriately applying GROUP BY and WHERE
SELECT YEAR(timestamp) year, COUNT(*) total
FROM stats_tab
WHERE timestamp BETWEEN NOW() - INTERVAL 3 YEAR AND NOW()
GROUP BY YEAR(timestamp);
SELECT MONTH(timestamp) month, COUNT(*) total
FROM stats_tab
WHERE timestamp BETWEEN NOW() - INTERVAL 6 MONTH AND NOW()
GROUP BY MONTH(timestamp);
SELECT DAY(timestamp) day, COUNT(*) total
FROM stats_tab
WHERE timestamp BETWEEN NOW() - INTERVAL 7 DAY AND NOW()
GROUP BY DAY(timestamp);
Sample output:
| YEAR | TOTAL |
----------------
| 2011 | 2 |
| 2012 | 1 |
| 2013 | 9 |
| MONTH | TOTAL |
-----------------
| 2 | 1 |
| 3 | 2 |
| 5 | 1 |
| DAY | TOTAL |
---------------
| 20 | 1 |
| 21 | 1 |
| 22 | 1 |
Here is SQLFiddle demo

Getting records between two dates for a certain amount of time

Say I have a table which looks like the following:
id | name | date
1 | test1 | 2013-05-12 00:00:01
2 | test2 | 2013-05-13 00:00:01
3 | test3 | 2013-05-14 00:00:01
4 | test4 | 2013-05-15 00:00:01
5 | test5 | 2013-05-15 00:00:02
An example of what I am looking to do would be to go back through the records for the past 3 days and then I want to count how many records there are on each individual say.
So, for the 15th it would return 2, 14th 1, etc.
I know I can do the following to get a count between 2 dates (this would be for the 15th):
SELECT COUNT(id) as recordCount FROM exampletable WHERE date >= STR_TO_DATE('130515', '%y%m%d') - INTERVAL 1 DAY AND date < STR_TO_DATE('130515', '%y%m%d')
However I am unsure how I would do it so I could get an array for the past 3 days.
I have an idea of how I could do it in PHP, having a for loop and then changing the first argument in STR_TO_DATE each time, but I am curious, is there a way I could do this using a SQL query only?
UPDATED
SELECT COUNT(*) recordCount
FROM exampletable
WHERE DATE(`date`) BETWEEN CURDATE() - INTERVAL 3 DAY AND CURDATE()
GROUP BY DATE(`date`)
NOTE This query wont use any index on date
SELECT COUNT(*) AS 'recordCount' FROM dbo.Table_1
WHERE dt > DATEADD(dd,-3,GETDATE())
GROUP BY DATEPART(dd,dt)

Categories