MySQL user retention and day to day - mysql

I'm trying to figure out how to write my SQL query to get users day to day and retention.
consider having the following row table round_statistics
on each play round i have date of the round,
now i would like to:
1. know how many users play two days in a row meaning played on Sunday and Monday, Monday and Tuesday, but Sunday and Tuesday doesn't count as two days in a row.
2. users retention 1-7
retention 7 is : % of users that have the chance to play the last 7 days (meaning they are registered at least 7 days) and had some activity (record) after 7 days.
retention 6-1 are the same only for 6-1 days.
Please help me to find out my game retention :) you will get a free coins to play it....
Thanks.
The table structure is:
user_id,round_time
for example if i played 3 times today:
user id | round_time
1000, | '2013-08-10 14:02:53'
1000, | '2013-08-10 14:03:25'
1000, | '2013-08-10 14:04:47'
the result structure is:
date | 2013-08-10 | 2013-07-10
day to day | 10 | 100
retention 7 | 15 | 125
retention 6 | 20 | 210
retention 5 | 30 | 320
retention 4 | 40 | 430
retention 3 | 50 | 540
retention 2 | 60 | 650
retention 1 | 120 | 1620

My sql don't has analytic functions, neither CTE and pivot table features, for this reasons it is not direct to do your required query (and nobody answer your question).
For this data:
create table t ( uid int, rt date);
insert into t values
(99, '2013-08-7 14:02:53' ), <- gap
(99, '2013-08-9 14:02:53' ), <-
(99, '2013-08-10 14:03:25' ),
(1000, '2013-08-7 14:02:53' ),
(1000, '2013-08-8 14:03:25' ),
(1000, '2013-08-9 14:03:25' ),
(1000, '2013-08-10 14:04:47');
This is an approach before pivot retentions, for a given date ( '2013-08-10 00:00:00' , '%Y-%m-%d') :
select count( distinct uid ) as n, d, dt from
(
select uid,
'2013-08-10 00:00:00' as d,
G.dt
from
t
inner join
( select 7 as dt union all
select 6 union all select 5 union all
select 4 union all select 3 union all
select 2 union all select 1 union all select 0) G
on DATE_FORMAT( t.rt, '%Y-%m-%d') between
DATE_FORMAT( date_add( '2013-08-10 00:00:00', Interval -1 * G.dt DAY) ,
'%Y-%m-%d')
and
DATE_FORMAT( '2013-08-10 00:00:00' , '%Y-%m-%d')
where DATE_FORMAT(rt , '%Y-%m-%d') <= DATE_FORMAT( '2013-08-10 00:00:00' ,
'%Y-%m-%d')
group by uid, G.dt
having count( distinct DATE_FORMAT( T.rt, '%Y-%m-%d') ) = G.dt + 1
) TT
group by dt
Your pre-cooked data ( DT = 0 means today visits, DT = 1 means 2 consecutive days, ...):
| N | D | DT |
--------------------------------
| 2 | 2013-08-10 00:00:00 | 0 |
| 2 | 2013-08-10 00:00:00 | 1 |
| 1 | 2013-08-10 00:00:00 | 2 |
| 1 | 2013-08-10 00:00:00 | 3 |
Here it is ( for same data ):
select count( distinct uid ) as n, d, dt from
(
select uid,
z.zt as d,
G.dt
from
t
cross join
( select distinct DATE_FORMAT( t.rt, '%Y-%m-%d') as zt from t) z
inner join
( select 7 as dt union all
select 6 union all select 5 union all
select 4 union all select 3 union all
select 2 union all select 1 union all select 0) G
on DATE_FORMAT( t.rt, '%Y-%m-%d') between
DATE_FORMAT( date_add( z.zt, Interval -1 * G.dt DAY) ,
'%Y-%m-%d')
and
z.zt
where z.zt <= z.zt
group by uid, G.dt, z.zt
having count( distinct DATE_FORMAT( T.rt, '%Y-%m-%d') ) = G.dt + 1
) TT
group by d,dt
order by d,dt
Results at sqlfiddle: http://sqlfiddle.com/#!2/c26ec/10/0
| N | D | DT | GROUP_CONCAT( UID) |
--------------------------------------------
| 2 | 2013-08-07 | 0 | 1000,99 |
| 1 | 2013-08-08 | 0 | 1000 |
| 1 | 2013-08-08 | 1 | 1000 |
| 2 | 2013-08-09 | 0 | 1000,99 |
| 1 | 2013-08-09 | 1 | 1000 |
| 1 | 2013-08-09 | 2 | 1000 |
| 2 | 2013-08-10 | 0 | 1000,99 |
| 2 | 2013-08-10 | 1 | 99,1000 |
| 1 | 2013-08-10 | 2 | 1000 |
| 1 | 2013-08-10 | 3 | 1000 |

Related

Count records from mysql table between two dates and return zero if rows are empty

I have a table in mysql with two fields:
DATA | PAGE
2020-07-30 21:44:08 | abc
2020-07-31 20:14:18 | abc
2020-08-02 12:23:08 | xyz
2020-08-04 10:21:27 | abc
2020-08-04 12:54:32 | def
2020-08-04 15:41:44 | abc
I would like to query the table for page "abc" and get the sum of the daily views from the last 7 days and return zero if the page has no views:
DATA | VIEWS
2020-07-29 | 0
2020-07-30 | 1
2020-07-31 | 1
2020-08-01 | 0
2020-08-02 | 0
2020-08-03 | 0
2020-08-04 | 2
After some trials and errors adapting some queries I found here, I reached this version that return the results I want, but I don't think it's perfect and the limit using the calendar should not be needed.
SELECT DATE_FORMAT(data, "%Y-%m-%d") as data, count(*)-1 as views
FROM (
SELECT date(data) as data FROM visitas as t1 where page like "abc"
UNION ALL
SELECT curdate() - interval a day AS data
FROM (
SELECT 0 as a UNION
SELECT 1 UNION
SELECT 2 UNION
SELECT 3 UNION
SELECT 4 UNION
SELECT 5 UNION
SELECT 6
) as t2
) as t3 GROUP BY data
ORDER BY data DESC
LIMIT 7
Can this query be improved?
Thanks
You can do that without any JOIN and use UNION instead:
SELECT dd,SUM(cnt) as VIEWS FROM (
SELECT DATE(NOW())- INTERVAL 6 DAY as dd , 0 as cnt
UNION
SELECT DATE(NOW())- INTERVAL 5 DAY as dd , 0 as cnt
UNION
SELECT DATE(NOW())- INTERVAL 4 DAY as dd , 0 as cnt
UNION
SELECT DATE(NOW())- INTERVAL 3 DAY as dd , 0 as cnt
UNION
SELECT DATE(NOW())- INTERVAL 2 DAY as dd , 0 as cnt
UNION
SELECT DATE(NOW())- INTERVAL 1 DAY as dd , 0 as cnt
UNION
SELECT DATE(NOW())- INTERVAL 0 DAY as dd , 0 as cnt
UNION ALL
SELECT DATE(`date`) as dd, 1 as cnt FROM `test3` WHERE page='abc' AND DATE(`date`) >= DATE(NOW()) - INTERVAL 7 DAY
) as tb1 GROUP BY dd
The result will be
dd | VIEWS
--------------------
2020-07-29 | 0
2020-07-30 | 1
2020-07-31 | 1
2020-08-01 | 0
2020-08-02 | 0
2020-08-03 | 0
2020-08-04 | 2
With a LEFT join of the calendar to the table:
SELECT DATE_FORMAT(c.data, "%Y-%m-%d") data,
COUNT(page) views
FROM (
SELECT CURDATE() - INTERVAL a day data
FROM (
SELECT 0 as a UNION
SELECT 1 UNION
SELECT 2 UNION
SELECT 3 UNION
SELECT 4 UNION
SELECT 5 UNION
SELECT 6
) t
) c
LEFT JOIN visitas v
ON DATE(v.data) = c.data AND page = 'abc'
GROUP BY c.data
ORDER BY c.data DESC
See the demo.
Results:
| data | views |
| ---------- | ----- |
| 2020-08-04 | 2 |
| 2020-08-03 | 0 |
| 2020-08-02 | 0 |
| 2020-08-01 | 0 |
| 2020-07-31 | 1 |
| 2020-07-30 | 1 |
| 2020-07-29 | 0 |

Create a calendar database table like this

I would like to create a calendar SQL table like this one but for some years.
(I'm using mysql 5.7.28)
Date is DD-MM-YYYY
Is it possible?
If you are running MySQL 8.0, you can use a recursive query:
with recursive cte as (
select '2019-01-01 00:00:00' dt
union all
select dt + interval 1 hour from cte where dt < '2020-01-01' - interval 1 hour
)
select
row_number() over(order by dt) id,
date(dt) day,
time(dt) start_hour,
time(dt + interval 1 hour) end_hour
from cte
The recursive cte generates a list of datetimes between the given boundaries (here, that's year 2019), with a 1 hour increment. Then, the outer query produces the expected result, by extracting the day and hour parts.
You can adjust the boudaries and the increment as per your exact requirements.
Side note: I would suggest to retain the full datetime in the calendar table as well; there are many situation where having a proper datetime value is more convenient than separated dates and times.
Demo on DB Fiddle for the first day only:
id | day | start_hour | end_hour
-: | :--------- | :-------------- | :--------------
1 | 2019-01-01 | 00:00:00.000000 | 01:00:00.000000
2 | 2019-01-01 | 01:00:00.000000 | 02:00:00.000000
3 | 2019-01-01 | 02:00:00.000000 | 03:00:00.000000
4 | 2019-01-01 | 03:00:00.000000 | 04:00:00.000000
5 | 2019-01-01 | 04:00:00.000000 | 05:00:00.000000
6 | 2019-01-01 | 05:00:00.000000 | 06:00:00.000000
7 | 2019-01-01 | 06:00:00.000000 | 07:00:00.000000
8 | 2019-01-01 | 07:00:00.000000 | 08:00:00.000000
9 | 2019-01-01 | 08:00:00.000000 | 09:00:00.000000
10 | 2019-01-01 | 09:00:00.000000 | 10:00:00.000000
11 | 2019-01-01 | 10:00:00.000000 | 11:00:00.000000
12 | 2019-01-01 | 11:00:00.000000 | 12:00:00.000000
13 | 2019-01-01 | 12:00:00.000000 | 13:00:00.000000
14 | 2019-01-01 | 13:00:00.000000 | 14:00:00.000000
15 | 2019-01-01 | 14:00:00.000000 | 15:00:00.000000
16 | 2019-01-01 | 15:00:00.000000 | 16:00:00.000000
17 | 2019-01-01 | 16:00:00.000000 | 17:00:00.000000
18 | 2019-01-01 | 17:00:00.000000 | 18:00:00.000000
19 | 2019-01-01 | 18:00:00.000000 | 19:00:00.000000
20 | 2019-01-01 | 19:00:00.000000 | 20:00:00.000000
21 | 2019-01-01 | 20:00:00.000000 | 21:00:00.000000
22 | 2019-01-01 | 21:00:00.000000 | 22:00:00.000000
23 | 2019-01-01 | 22:00:00.000000 | 23:00:00.000000
24 | 2019-01-01 | 23:00:00.000000 | 00:00:00.000000
In earlier versions, you would typically create a large table of numbers by cross-joining subqueries, and use the number range to increment the initial date. row_number() can be emulated with a MySQL variable:
select
#id:=#id + 1 id,
date(dt) day,
time(dt) start_hour,
time(dt + interval 1 hour) end_hour,
0 prenoted
from (
select '2019-01-01' + interval d0.n + 10 * d1.n + 100 * d2.n + 1000 * d3.n hour dt
from
(
select 0 n union all select 1 union all select 2 union all select 3 union all select 4
union all select 5 union all select 6 union all select 7 union all select 8 union all select 9
) d0
cross join (
select 0 n union all select 1 union all select 2 union all select 3 union all select 4
union all select 5 union all select 6 union all select 7 union all select 8 union all select 9
) d1
cross join (
select 0 n union all select 1 union all select 2 union all select 3 union all select 4
union all select 5 union all select 6 union all select 7 union all select 8 union all select 9
) d2
cross join (
select 0 n union all select 1 union all select 2 union all select 3 union all select 4
union all select 5 union all select 6 union all select 7 union all select 8 union all select 9
) d3
where '2019-01-01' + interval d0.n + 10 * d1.n + 100 * d2.n + 1000 * d3.n hour < '2020-01-01'
) t
cross join (select #id := 0 id) i
order by dt
The above query gives you a maximum span of 10 000 hours (wich represent a little more than 416 days); you can add another cross join and update the arithmetic to handle up to 100 000 hours (and so on).
Demo on DB Fiddle for the first 24 hours.

Create a time series from from-to-entries

I have some period data that look like this:
PName DtFrom DtTo Amount
Period_1 2018-01-01 2018-01-10 100
Period_2 2018-01-03 2018-01-08 10
Period_3 2018-01-05 2018-01-12 1
I would like to get the following time series adding the amounts that are inside the date frame of each period:
2018-01-01 100
2018-01-02 100
2018-01-03 110
2018-01-04 110
2018-01-05 111
2018-01-06 111
2018-01-07 111
2018-01-08 111
2018-01-09 101
2018-01-10 101
2018-01-11 1
2018-01-12 1
I have done a lot of research using DATE_ADD, DATEDIFF and so on, also using other StackOverflow questions. But without success. Any idea?
Although I'd really advocate handling this in application code, here's a solution which uses a little integer utility table. You could use a calendar table instead, or construct a sequence of integers on-the-fly...
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(Perid SERIAL PRIMARY KEY
,DtFrom DATE NOT NULL
,DtTo DATE NOT NULL
,Amount INT NOT NULL
);
INSERT INTO my_table VALUES
(1,'2018-01-01','2018-01-10',100),
(2,'2018-01-03','2018-01-08',10),
(3,'2018-01-05','2018-01-12',1);
SELECT * FROM ints;
+---+
| i |
+---+
| 0 |
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
| 6 |
| 7 |
| 8 |
| 9 |
+---+
SELECT dtfrom + INTERVAL y.i DAY dt
, SUM(x.amount) total
FROM my_table x
JOIN ints y
WHERE dtfrom + INTERVAL y.i DAY <= dtto
GROUP
BY dt;
+------------+-------+
| dt | total |
+------------+-------+
| 2018-01-01 | 100 |
| 2018-01-02 | 100 |
| 2018-01-03 | 110 |
| 2018-01-04 | 110 |
| 2018-01-05 | 111 |
| 2018-01-06 | 111 |
| 2018-01-07 | 111 |
| 2018-01-08 | 111 |
| 2018-01-09 | 101 |
| 2018-01-10 | 101 |
| 2018-01-11 | 1 |
| 2018-01-12 | 1 |
+------------+-------+
If the gap between dtfrom and dtto can be more than 10 days, you can expand the solution along these lines...
SELECT dtfrom + INTERVAL i2.i*10 + i1.i DAY dt
, SUM(x.amount) total
FROM my_table x
JOIN ints i1
JOIN ints i2
WHERE dtfrom + INTERVAL i2.i*10 + i1.i DAY <= dtto
GROUP
BY dt;
Try this:
SELECT A.`DATE`, B.Amount
FROM
(SELECT TO_DATE('2018-01-01','YYYY-MM-DD') `DATE` UNION ALL
SELECT TO_DATE('2018-01-02','YYYY-MM-DD') `DATE` UNION ALL
SELECT TO_DATE('2018-01-03','YYYY-MM-DD') `DATE` UNION ALL
SELECT TO_DATE('2018-01-04','YYYY-MM-DD') `DATE` UNION ALL
SELECT TO_DATE('2018-01-05','YYYY-MM-DD') `DATE` UNION ALL
SELECT TO_DATE('2018-01-06','YYYY-MM-DD') `DATE` UNION ALL
SELECT TO_DATE('2018-01-07','YYYY-MM-DD') `DATE` UNION ALL
SELECT TO_DATE('2018-01-08','YYYY-MM-DD') `DATE` UNION ALL
SELECT TO_DATE('2018-01-09','YYYY-MM-DD') `DATE` UNION ALL
SELECT TO_DATE('2018-01-10','YYYY-MM-DD') `DATE` UNION ALL
SELECT TO_DATE('2018-01-11','YYYY-MM-DD') `DATE` UNION ALL
SELECT TO_DATE('2018-01-12','YYYY-MM-DD') `DATE`
) A LEFT JOIN yourTable B ON A.`DATE` BETWEEN B.DtFrom AND b.DtTo;
That's the query if you must do it in sql, but it's easily getting the expected result in the application logic as #Strawberry indicated in his comment to your question.
Try this
select
sum(if(STR_TO_DATE(?, '%d-%m-%Y') >= so_period_date.dt_from and
STR_TO_DATE(?, '%d-%m-%Y') <= so_period_date.dt_to,
so_period_date.amount, 0)) as amount
from so_period_date

Mysql query to get count per months

I have a table for my users that have a field named "created" that have the registration date.
How can i get a list that contains a count for the registrations number per month in last 12 months?
Like this:
Month Count
1 1232
2 2222
3 122
4 4653
... ...
12 7654
I'm not used to working with mysql, so until now i just know how to count the number of registrations in last year, not how to group that count by last 12 months. Thanks in advance!
UPDATE
Now I'm getting this, using #fthiella solution:
+------------------------------+-------------------------------+----------+
| Year(FROM_UNIXTIME(created)) | Month(FROM_UNIXTIME(created)) | Count(*) |
+------------------------------+-------------------------------+----------+
| 2012 | 4 | 9927 |
| 2012 | 5 | 5595 |
| 2012 | 6 | 4431 |
| 2012 | 7 | 3299 |
| 2012 | 8 | 429 |
| 2012 | 10 | 3698 |
| 2012 | 11 | 6208 |
| 2012 | 12 | 5142 |
| 2013 | 1 | 1196 |
| 2013 | 2 | 10 |
+------------------------------+-------------------------------+----------+
How can i force query to give me the months with count = 0?
Solution by #fthiella (thanks a lot!):
SELECT y, m, Count(users.created)
FROM (
SELECT y, m
FROM
(SELECT YEAR(CURDATE()) y UNION ALL SELECT YEAR(CURDATE())-1) years,
(SELECT 1 m UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4
UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8
UNION ALL SELECT 9 UNION ALL SELECT 10 UNION ALL SELECT 11 UNION ALL SELECT 12) months) ym
LEFT JOIN users
ON ym.y = YEAR(FROM_UNIXTIME(users.created))
AND ym.m = MONTH(FROM_UNIXTIME(users.created))
WHERE
(y=YEAR(CURDATE()) AND m<=MONTH(CURDATE()))
OR
(y<YEAR(CURDATE()) AND m>MONTH(CURDATE()))
GROUP BY y, m;
And the results:
+------+----+----------------------+
| y | m | Count(users.created) |
+------+----+----------------------+
| 2012 | 5 | 5595 |
| 2012 | 6 | 4431 |
| 2012 | 7 | 3299 |
| 2012 | 8 | 429 |
| 2012 | 9 | 0 |
| 2012 | 10 | 3698 |
| 2012 | 11 | 6208 |
| 2012 | 12 | 5142 |
| 2013 | 1 | 1196 |
| 2013 | 2 | 10 |
| 2013 | 3 | 0 |
| 2013 | 4 | 0 |
+------+----+----------------------+
If created is an INT field, you should use FROM_UNIXTIME function to convert it to a date field, and then MONTH function to extract the month:
SELECT Month(FROM_UNIXTIME(created)), Count(*)
FROM yourtable
WHERE FROM_UNIXTIME(created) >= CURDATE() - INTERVAL 1 YEAR
GROUP BY Month(FROM_UNIXTIME(created))
this will count all the rows that have been created in the last 12 months. Please notice that it's probably better to also group by the YEAR:
SELECT Year(FROM_UNIXTIME(created)), Month(FROM_UNIXTIME(created)), Count(*)
FROM yourtable
WHERE FROM_UNIXTIME(created) >= CURDATE() - INTERVAL 1 YEAR
GROUP BY Year(FROM_UNIXTIME(created)), Month(FROM_UNIXTIME(created))
If you need to count the registration numbers instead of the rows, you could use something like
COUNT(registration_number)
to skip null values, or
COUNT(DISTINCT registration_number)
to count only distinct ones.
Edit
If you also need to show months that have count=0, I would use a query like this that returns all of the months for the current and for the previous year:
SELECT y, m
FROM
(SELECT YEAR(CURDATE()) y UNION ALL SELECT YEAR(CURDATE())-1) years,
(SELECT 1 m UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4
UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8
UNION ALL SELECT 9 UNION ALL SELECT 10 UNION ALL SELECT 11 UNION ALL SELECT 12) months;
And then I'd use a LEFT JOIN, that returns all of the rows of the first query, and only the rows of the second query that matches:
SELECT y, m, Count(yourtable.created)
FROM (
SELECT y, m
FROM
(SELECT YEAR(CURDATE()) y UNION ALL SELECT YEAR(CURDATE())-1) years,
(SELECT 1 m UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4
UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8
UNION ALL SELECT 9 UNION ALL SELECT 10 UNION ALL SELECT 11 UNION ALL SELECT 12) months) ym
LEFT JOIN yourtable
ON ym.y = YEAR(FROM_UNIXTIME(yourtable.created))
AND ym.m = MONTH(FROM_UNIXTIME(yourtable.created))
WHERE
(y=YEAR(CURDATE()) AND m<=MONTH(CURDATE()))
OR
(y<YEAR(CURDATE()) AND m>MONTH(CURDATE()))
GROUP BY y, m
(please notice that here I am considering just the last 12 months, so if we are in the middle April 2013 it will count rows in the interval May 2012 - April 13, if this is not the correct behaviour please let me know)
SELECT MONTH(reg_date) , COUNT(reg_date)
FROM your_table
WHERE reg_date >= NOW() - INTERVAL 1 YEAR
GROUP BY MONTH(reg_date)
In case this helps anyone else, I additionally wanted this query but for a given time interval that was not necessarily the last 12 months (also with a timestamp for the date):
set #start='2013-05-01 00:00:00';
set #end='2015-03-31 00:00:00';
SELECT YEAR(FROM_UNIXTIME(yourtable.created_date)), MONTH(FROM_UNIXTIME(yourtable.created_date)), COUNT(yourtable.created_date)
FROM yourtable
WHERE created_date BETWEEN UNIX_TIMESTAMP( DATE(#start) ) AND UNIX_TIMESTAMP( DATE(#end) )
GROUP BY YEAR(FROM_UNIXTIME(yourtable.created_date)), MONTH(FROM_UNIXTIME(yourtable.created_date))
ORDER BY YEAR(FROM_UNIXTIME(yourtable.created_date)), MONTH(FROM_UNIXTIME(yourtable.created_date));

MySQL: filling empty fields with zeroes when using GROUP BY

I've got MySQL table
CREATE TABLE cms_webstat (
ID int NOT NULL auto_increment PRIMARY KEY,
TIMESTAMP_X timestamp DEFAULT CURRENT_TIMESTAMP,
# ... some other fields ...
)
which contains statistics about site visitors.
For getting visits per hour I use
SELECT
hour(TIMESTAMP_X) as HOUR
, count(*) AS HOUR_STAT
FROM cms_webstat
GROUP BY HOUR
ORDER BY HOUR DESC
which gives me
| HOUR | HOUR_STAT |
| 24 | 15 |
| 23 | 12 |
| 22 | 9 |
| 20 | 3 |
| 18 | 2 |
| 15 | 1 |
| 12 | 3 |
| 9 | 1 |
| 3 | 5 |
| 2 | 7 |
| 1 | 9 |
| 0 | 12 |
And I'd like to get following:
| HOUR | HOUR_STAT |
| 24 | 15 |
| 23 | 12 |
| 22 | 9 |
| 21 | 0 |
| 20 | 3 |
| 19 | 0 |
| 18 | 2 |
| 17 | 0 |
| 16 | 0 |
| 15 | 1 |
| 14 | 0 |
| 13 | 0 |
| 12 | 3 |
| 11 | 0 |
| 10 | 0 |
| 9 | 1 |
| 8 | 0 |
| 7 | 0 |
| 6 | 0 |
| 5 | 0 |
| 4 | 0 |
| 3 | 5 |
| 2 | 7 |
| 1 | 9 |
| 0 | 12 |
How should I modify the query to get such result (with one mysql query, without creating temporary tables)?
Is it possible to get such result with one MySQL query?
Create another table with a single column,
CREATE TABLE hours_list (
hour int NOT NULL PRIMARY KEY
)
Fill it with all 24 hours.
Then do a join on that table to fill in the zeroes.
SELECT
hs.hour as HOUR, COUNT(ws.ID) AS HOUR_STAT
FROM hours_list hs
LEFT JOIN cms_webstat ws ON hs.hour = hour(ws.TIMESTAMP_X)
GROUP BY hs.hour
ORDER BY hs.hour DESC
This is just the 'why it is not returning` part. Marcus' answer covers the 'how to' part.
The SQL
SELECT
hour(TIMESTAMP_X) as HOUR
, count(*) AS HOUR_STAT
FROM cms_webstat
GROUP BY HOUR
ORDER BY HOUR DESC
gets the count of the records per hour, for the timestamps present in the table
It does not give the details of what is not present in the table. Since there is no recors for the timestamp corresponding to the hour 8 (from your example) the SQL does not return any records.
I've finaly found the answer.
Maybe I'm insane, but this works.
SELECT HOUR, max(HOUR_STAT) as HOUR_STAT FROM (
(
SELECT HOUR(TIMESTAMP_X) as HOUR, count(*) as HOUR_STAT
FROM cms_webstat
WHERE date(TIMESTAMP_X) = date(now())
)
UNION (SELECT 0 as HOUR, 0)
UNION (SELECT 1 as HOUR, 0)
UNION (SELECT 2 as HOUR, 0)
UNION (SELECT 3 as HOUR, 0)
UNION (SELECT 4 as HOUR, 0)
UNION (SELECT 5 as HOUR, 0)
UNION (SELECT 6 as HOUR, 0)
UNION (SELECT 7 as HOUR, 0)
UNION (SELECT 8 as HOUR, 0)
UNION (SELECT 9 as HOUR, 0)
UNION (SELECT 10 as HOUR, 0)
UNION (SELECT 11 as HOUR, 0)
UNION (SELECT 12 as HOUR, 0)
UNION (SELECT 13 as HOUR, 0)
UNION (SELECT 14 as HOUR, 0)
UNION (SELECT 15 as HOUR, 0)
UNION (SELECT 16 as HOUR, 0)
UNION (SELECT 17 as HOUR, 0)
UNION (SELECT 18 as HOUR, 0)
UNION (SELECT 19 as HOUR, 0)
UNION (SELECT 20 as HOUR, 0)
UNION (SELECT 21 as HOUR, 0)
UNION (SELECT 22 as HOUR, 0)
UNION (SELECT 23 as HOUR, 0)
)
AS `combined_table`
GROUP BY HOUR
ORDER BY HOUR DESC
One MySQL query as desired.
$sql = 'SELECT g, MAX(v) AS v, MAX(c) AS c FROM (';
$sql .= '(SELECT DATE_FORMAT(viewed, \'%d.%m.%Y\') AS g, COUNT(1) AS v, 0 AS c FROM '.$this->prefix.'view WHERE campaignid IN ('.join(', ',$ids).') GROUP BY g)';
$sql .= ' UNION (SELECT DATE_FORMAT(clicked, \'%d.%m.%Y\') AS g, 0 AS v, COUNT(1) AS c FROM '.$this->prefix.'clicks WHERE campaignid IN ('.join(', ',$ids).') GROUP BY g)';
$today = strtotime("00:00:00");
for ($i=$today; $i>=time()-30*86400; $i-=86400) {
$sql .= ' UNION (SELECT \''.date('d.m.Y',$i).'\' AS g, 0 AS v, 0 AS c)';
}
$sql .= ') AS tmp GROUP BY g ORDER BY g DESC';
$chart = DB::getAll($sql);
p($chart);
Thanks! Made it! From 2 tables, clicks and views, joined.. works. ajaxel.com