How to compute statistics with date intervals via MySQL - php

I want to compute some stats (with a MySQL backend) sorted by date and with dynamic intervals (week, month, year).
Here is a little example :
Mysql table :
tracker_click
| ID | SITE_ID | CREATED_AT |
| ---- |---------| --------------------|
| 153 | 2 | 2013-07-22 15:43:25 |
| 154 | 2 | 2013-07-25 16:45:46 |
| 2501 | 2 | 2013-09-15 17:45:48 |
I want to get the total click number by SITE_ID by week for the last month with one query
And the same thing by month for the last year.
An example of what I want by week for the last month is :
| click number | SITE_ID | BEGIN_DATE | END_DATE |
|----------------|---------|----------------------|---------------------|
| 25 | 2 | 2013-07-01 00:00:00 | 2013-07-08 00:00:00 |
| 19 | 2 | 2013-08-09 00:00:00 | 2013-08-16 00:00:00 |
| 53 | 2 | 2013-0717- 00:00:00 | 2013-08-24 00:00:00 |
I don’t know if there is a solution to get exaclty this array with only one query without any other processes.
Thank you

This should get you the counts for the last month (ie, last 4 weeks), including weeks where the count is 0 for each site id. If you have a table of sites to get the site id from it means the cross join to the sub query can be replaced with a simple cross join to a table.
This generates a range of numbers from 0 to 5 and subtracts that number of weeks from the current date, formats that to give the Sunday and Saturday of the resulting week and checks that the resuling week is a week between the current date and the current date minus 1 month (done this way rather that just subtracting 4 weeks to cope with variable length months).
SELECT Weeks.aWeek_start, Weeks.aWeek_end, all_site_id.site_id, COUNT(tracker_click.id)
FROM
(
SELECT STR_TO_DATE(DATE_FORMAT(DATE_SUB(NOW(), INTERVAL units.i WEEK), '%Y%U Sunday 00:00:00'), '%X%V %W %H:%i:%s') AS aWeek_start,
STR_TO_DATE(DATE_FORMAT(DATE_SUB(NOW(), INTERVAL units.i WEEK), '%Y%U Saturday 23:59:59'), '%X%V %W %H:%i:%s') AS aWeek_end
FROM (SELECT 0 i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5)units
WHERE DATE_FORMAT(DATE_SUB(NOW(), INTERVAL units.i WEEK), '%Y%U') BETWEEN DATE_FORMAT(DATE_SUB(NOW(), INTERVAL 1 MONTH), '%Y%U') AND DATE_FORMAT(NOW(), '%Y%U')
) Weeks
CROSS JOIN
(
SELECT DISTINCT site_id
FROM tracker_click
) AS all_site_id
LEFT OUTER JOIN tracker_click
ON tracker_click.CREATED_AT BETWEEN Weeks.aWeek_start AND Weeks.aWeek_end
AND tracker_click.site_id = all_site_id.site_id
GROUP BY Weeks.aWeek_start, Weeks.aWeek_end, all_site_id.site_id
A similar query could be done for months of the year
SELECT Months.aMonth_start, Months.aMonth_end, all_site_id.site_id, COUNT(tracker_click.id)
FROM
(
SELECT DATE_FORMAT(DATE_SUB(NOW(), INTERVAL units.i MONTH), '%Y/%m/01 00:00:00') AS aMonth_start,
DATE_FORMAT(LAST_DAY(DATE_SUB(NOW(), INTERVAL units.i MONTH)), '%Y/%m/%d 23:59:59') AS aMonth_end
FROM (SELECT 0 i UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9 UNION SELECT 10 UNION SELECT 11)units
) Months
CROSS JOIN
(
SELECT DISTINCT site_id
FROM tracker_click
) AS all_site_id
LEFT OUTER JOIN tracker_click
ON tracker_click.CREATED_AT BETWEEN Months.aMonth_start AND Months.aMonth_end
AND tracker_click.site_id = all_site_id.site_id
GROUP BY Months.aMonth_start, Months.aMonth_end, all_site_id.site_id

SELECT count(*), site_id, extract(WEEK from created_at) as start_date,
date_add(extract(WEEK from created_at), 1, week) as end_date
FROM click_tracker
GROUP BY site_id, extract(WEEK from created_at)
and you can add where clause to filter results additionally.

Related

Select SUM "separately" for the past 30 days

I have a MySQL table which is as follows
------------------------------------------------
| id | user_id | day | value |
| INT(11) | INT(11) | INT(8) | DECIMAL(5,2) |
------------------------------------------------
Day is (Ymd) number like this: 20191012
I want to grab the SUM(value) for Today and the past 29 days separately (30 days, based on day column)
I thought of a loop like this
for($i=0;$i<=30;$i++)
{
$query = "SELECT SUM(value) FROM table WHERE day=".date('Ymd', strtotime("-{$i} days"));
}
But how can I achieve this more efficiently?
Any help is appreciated.
You could use Group By to fetch it with a single MySQL-Query
SELECT day, SUM(value) FROM table GROUP BY day WHERE day >= ".date('Ymd', strtotime("-29 days"));
It should work because your day field is a number and will increase everyday so you can just compare it with gte
Expected output will be
---------------------------
| day | SUM(value) |
| INT(8) | |
---------------------------
| 20191012 | 15.25 |
---------------------------
| 20191011 | 29.13 |
---------------------------
| ... | ... |
---------------------------
J4I: If there are days without values in your dataset, the missing "day-row" is also missing in the output so it could be that there are less than 30 result-rows.
This query will work for any version of MySQL. It creates a temporary numbers table with the numbers from 0-29, then uses that to compute a list of days from today to 29 days earlier. This is then LEFT JOINed to the data table to compute the sum per day, with 0 sums where there is no data for a given day:
SELECT d.day, COALESCE(SUM(value), 0)
FROM (SELECT DATE_FORMAT(CURDATE() -INTERVAL n10.n * 10 + n.n DAY, '%Y%m%d') AS day
FROM (SELECT 0 AS 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) n
CROSS JOIN (SELECT 0 AS n UNION ALL SELECT 1 UNION ALL SELECT 2) n10) d
LEFT JOIN `table` t ON t.day = d.day
GROUP BY d.day
Demo on dbfiddle

MySql - Get next date which satisfy a condition

I have a mysql table called 'helper_leaves' in which user have entered their leave dates for the next 2 months.
I would like to get 2 upcoming working dates for a particular user_id which are NOT in the 'helper_leaves' table and also should be next to the current date.
**id | user_id | leave_date**
1 | 1 | 2016-07-07
2 | 1 | 2016-07-09
3 | 1 | 2016-07-15
4 | 1 | 2016-08-03
I want write a query to get next 2 working dates of user_id = 1, from now.
Please note, this table is having only the leave dates. But I want to get the next 2 availability dates .
For the sample data given, I expected to get 2016-07-14 and 2016-07-16 as the next 2 working dates because today is 2016-07-13
Note: All the dates which are not in this table is considered as a working date.
Please help!!!!
Can you please try with below query?
In that, i have just generate next 60 dates and exclude leave_date from that with limit 2.
SELECT CURDATE() + INTERVAL a + b DAY dte
FROM
(SELECT 0 a UNION SELECT 1 a UNION SELECT 2 UNION SELECT 3
UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7
UNION SELECT 8 UNION SELECT 9 ) d,
(SELECT 0 b UNION SELECT 10 UNION SELECT 20
UNION SELECT 30 UNION SELECT 40) m
WHERE CURDATE() + INTERVAL a + b DAY < DATE_ADD(CURDATE(),INTERVAL 60 DAY) and dte not in ( select leave_date from helper_leaves where user_id = 1)
ORDER BY a + b limit 2
For this you can use interval
For Ex:
SELECT DATE_ADD(leave_date, INTERVAL 1 DAY) AS workingDay;

Splitting month into weeks and using GROUP BY

This is a question, which I could not find answer to anywhere. Okay. here it is.
I have two date ranges (This month and the last month)
Last month - 01/01/2015 (January 1 2015) to 31/01/2015
This month - 01/02/2015 (1st Feb 2015) to 28/02/2015
Now, each month has weeks. I have a table with column created_at. I want to fetch all the records week-wise into an array (to plot a graph) with their corresponding sum(value) or count(value) .
So it will be something like this:
Last Month:
Week 1 - 25
Week 2 - 34
etc.
This Month:
Week 1: 55
Week 2: 56
etc.
The date is in this format in created_at: 2015-07-21 01:27:14 (Y-m-d H:i:s)
In MySql You can use WEEK() to get the number of the week (from 1 to 53)
O you can use WEEKDAY() or DAYOFWEEK() the first bigins on Monday the second on Sunday.
You can use them into a GROUP BY with HAVING
Something like:
SELECT count(*)
FROM `YourTable`
WHERE `created_at` >= '2015-10-01' AND `created_at`< '2015-11-01'
GROUP BY WEEK(`created_at`)
To use the workaround you found You need to do something similar:
create a table named "numbers" with a field "id" (autoincrement) and 31 rows (one for each day of a month)
Then use a query like this:
SELECT count(i.created_at)
FROM
(SELECT DATE_FORMAT(DATE_ADD('2015-12-01', INTERVAL -n.id DAY), '%Y-%m-%d') AS AllDays
FROM numbers n) AS DaysOfMonth
Left Join
YourTableName i
ON STR_TO_DATE(i.created_at, '%Y-%m-%d') = DaysOfMonth.AllDays
GROUP BY WEEK(AllDays)
(try to adapt it to your needs)
What you need to do is group by the week and then sum the values. Here's a simple example of how it might work:
SELECT DATE_FORMAT(created_at,'%Y-%V') as interval, SUM(units_sold) as total_sold
FROM sales
GROUP BY DATE_FORMAT(created_at,'%Y-%V')
What you'll be getting is the year ant week number (ex. 2015-50) and the sum from that interval.
A table like this:
+----+------------+---------------------+
| id | units_sold | created_at |
+----+------------+---------------------+
| 1 | 2 | 2015-01-01 09:00:00 |
| 2 | 4 | 2015-01-04 10:00:00 |
| 3 | 1 | 2015-01-12 12:00:00 |
| 4 | 4 | 2015-01-16 13:00:00 |
+----+------------+---------------------+
Would result to:
+----------+------------+
| interval | total_sold |
+----------+------------+
| 2015-01 | 6 |
| 2015-03 | 5 |
+----------+------------+
I think it is useful for you...
SELECT GROUP_CONCAT(id), COUNT(id) AS idcount,SUM(id) AS idsum,
MONTHNAME(order_created_date) AS month_name, WEEK(order_created_date)
AS weeks FROM orders GROUP BY WEEK(order_created_date)

calculate sales per day in MySQL

I have a simple table which holds the date and the total sales made for a certain group:
date | totalsales
=======================
2014-05-01 | 3000
2014-05-02 | 3100
2014-05-03 | 3500
2014-05-04 | 3650
I like to calculate some things like:
sales per day
average sales
growth in %
Result should look like (calculate by hand so maybe wrong :) )
date | sales | average | growth
=======================================
2014-05-01 | 0 | 0 | 0
2014-05-02 | 100 | 50 | 100
2014-05-03 | 400 | 166.66 | 400
2014-05-04 | 150 | 162.5 | 37.5
Is this even possible in a sql statement or should I calculate with PHP or another server software?
Assuming each date gets its own unique row, you could do it by joining back to your original table like so:
SELECT t1.Date, CASE WHEN t2.Date IS NULL THEN 0 ELSE (t1.totalsales - t2.totalsales)
END AS sales
FROM table t1
LEFT JOIN table t2 ON t2.Date = DATE_ADD(t1.Date, INTERVAL -1 DAY)
ORDER BY 1
This will give you at least your first column, and you should be able to figure out the math for the rest from there. It's important to use a left join with the CASE statement here because otherwise you won't get the lowest date in your table (your first row)
If each date does not get its own unique row, this is method is still viable, you just need to create your datasets in a subquery using GROUP BY and SUM on the date column.
Here is the full query with no subselect at each row : (Thanks to #nmarsh for writting the hardest part)
See SQL fiddle : http://sqlfiddle.com/#!2/be4654/34/0
SELECT
t1.Date,
CASE
WHEN t2.date IS NULL THEN 0 ELSE (t1.totalSales - t2.totalSales)
END AS sales,
CASE
WHEN t2.date IS NULL THEN 0 / (#curRow := #curRow + 1) ELSE ((#curSum := #curSum + (t1.totalSales - t2.totalSales)) / (#curRow := #curRow + 1))
END AS average,
CASE
WHEN t3.date IS NULL AND t2.date IS NULL THEN 0
WHEN t3.date IS NULL THEN (t1.totalSales - t2.totalSales)
WHEN t2.date IS NULL THEN 0 ELSE ((t1.totalSales - t2.totalSales) * 100) / (t2.totalSales - t3.totalSales)
END AS growth
FROM test t1
LEFT JOIN test t2 ON t2.date = DATE_ADD(t1.Date, INTERVAL -1 DAY)
LEFT JOIN test t3 ON t3.date = DATE_ADD(t2.Date, INTERVAL -1 DAY)
JOIN (SELECT #curRow := 0) r
JOIN (SELECT #curSum := 0) ct
ORDER BY 1;
Original table :
date | totalsales
=======================
2014-05-01 | 3000
2014-05-02 | 3100
2014-05-03 | 3500
2014-05-04 | 3650
OUTPUT
date | sales | average | growth
=======================================
2014-05-01 | 0 | 0 | 0
2014-05-02 | 100 | 50 | 100
2014-05-03 | 400 | 166.66 | 400
2014-05-04 | 150 | 162.5 | 37.5
You can use recursive statement. In each iteration, calculate the requested data for one day, and drop the first (oldest) day.
You can also do it with PHP, which seems better because you don't want to put too much load on the MySQL tables in case it does not saves you time/calculations.
Not too sure about the numbers, but if you are more specific about the results I can double check.
You can use ROW_NUMBER() to create 2 data sets and join them on ROW_NUMBER() and ROW_NUMBER()-1 to get an offset to current and previous values to calculate the growth. Sample :
DECLARE #Data TABLE (SalesDate DATETIME, totalSales INT)
INSERT INTO #Data (SalesDate , totalSales) VALUES ('2014-05-01' , 3000)
INSERT INTO #Data (SalesDate , totalSales) VALUES ('2014-05-02' , 3100)
INSERT INTO #Data (SalesDate , totalSales) VALUES ('2014-05-03' , 3500)
INSERT INTO #Data (SalesDate , totalSales) VALUES ('2014-05-04' , 3650)
SELECT
CurrentDt.SalesDate
,ISNULL(CurrentDt.totalSales - PreviousDt.totalSales ,0) AS Sales
,FirstDate.FirstDate
, NULLIF(CAST((CurrentDt.SalesDate - FirstDate.FirstDate) AS INT)+1,0) AS SellingDays
,(ISNULL(CurrentDt.totalSales - PreviousDt.totalSales ,0))
/ NULLIF(CAST((CurrentDt.SalesDate - FirstDate.FirstDate) AS INT)+1,0) AS AverageSales
FROM
(SELECT Min(SalesDate) AS FirstDate FROM #Data) AS FirstDate,
/*Base Sales Data*/
(
SELECT
ROW_NUMBER() OVER(ORDER BY SalesDate) AS RowNum
,SalesDate
,totalSales
FROM
#Data
) AS CurrentDt
/*Previous Value for Growth*/
LEFT JOIN
(
SELECT
ROW_NUMBER() OVER(ORDER BY SalesDate) AS RowNum
,SalesDate
,totalSales
FROM
#Data
) AS PreviousDt
ON CurrentDt.RowNum -1 = PreviousDt.RowNum
I have used MSSQL, but MySQL supports ROW_NUMBER OVER.
I hope this query help you
SELECT
sample.id,
sample.date AS oggi,
sample.value AS sales,
((SELECT SUM(sample.value) FROM sample WHERE sample.date <= oggi ) / (SELECT COUNT(sample.value) FROM sample WHERE sample.date <= oggi ) ) AS avarege,
sample.value / IF((SELECT sample.value FROM sample WHERE sample.date = (oggi - INTERVAL 1 DAY )) = 0,sample.value,(SELECT sample.value FROM sample WHERE sample.date = (oggi - INTERVAL 1 DAY ))) *100 AS 'growt-percent'
-- (SELECT SUM(sample.value) FROM sample WHERE sample.date <= oggi ) AS somma,
-- (SELECT count(sample.value) FROM sample WHERE sample.date <= oggi ) AS conta,
-- (SELECT sample.value FROM sample WHERE sample.date = (oggi - INTERVAL 1 DAY )) as valoreieri,
FROM sample
WHERE sample.date BETWEEN '2014-05-01 00:00:00' AND '2014-05-31 00:00:00'
table data is
id date value
1 2014-05-01 00:00:00 0
2 2014-05-02 00:00:00 100
3 2014-05-03 00:00:00 400
4 2014-05-04 00:00:00 150
5 2014-05-05 00:00:00 200
result is
id oggi sales avarege growt-percent
1 2014-05-01 00:00:00 0 0.0000 (NULL)
2 2014-05-02 00:00:00 100 50.0000 100.0000
3 2014-05-03 00:00:00 400 166.6667 400.0000
4 2014-05-04 00:00:00 150 162.5000 37.5000
5 2014-05-05 00:00:00 200 170.0000 133.3333
note that i use datetime field not only date
if you have question about query ask
sorry for my bad english
edit
the last 3 rows are commented because i used it only for test

Count all unique records

I have a database with the following format:
myTable
productgroupID | productID | views | date
1 | 10 | 25 | 2013-05-23
4 | 105 | 15 | 2013-05-23
7 | 60 | 65 | 2013-05-23
7 | 60 | 55 | 2013-05-22
7 | 60 | 45 | 2013-05-21
Now I want to sum all views of a product in the moth May.
Result should be:
productgroupID | productID | viewed | month
7 | 60 | 165 | 2013-05-01
1 | 10 | 25 | 2013-05-01
4 | 105 | 15 | 2013-05-01
I tried the query below, but this gives me all views of a specific productgroupID. But I need the sum of the unique productgroupID & productID.
SELECT COUNT( views ) AS viewed, productgroupID FROM product_stats_daily GROUP BY productgroupID
If you want the views totaled, then you can use the sum() aggregate function and then you can group by the month and year for the date:
select productGroupId,
productId,
sum(views) viewed,
month(date) Month,
year(date) Year
from myTable
group by productGroupId, productId, month(date), year(date);
See SQL Fiddle with Demo
You could also use Date_Format to get the date in the format that you want:
select productGroupId,
productId,
sum(views) viewed,
DATE_FORMAT(date, '%Y-%m-01') date
from myTable
group by productGroupId, productId, DATE_FORMAT(date, '%Y-%m-01')
See SQL Fiddle with Demo
COUNT will count number of rows while SUM will sum up value of retrieved rows.
So your query becomes:
SELECT SUM( views ) AS viewed, productgroupID
FROM product_stats_daily
GROUP BY productgroupID
There's probably a more elegant way to force the date to the beginning of the month, but this should work:
SELECT
ProductGroupID,
ProductID,
SUM(views) AS viewed,
DATE_FORMAT(date, '%Y-%m-01') AS Month
FROM myTable
GROUP BY
ProductGroupID,
ProductID,
DATE_FORMAT(date, '%Y-%m-01')

Categories