Get week over week data - mysql

I have a table as follows
CREATE TABLE messages (
id int(10) UNSIGNED AUTO_INCREMENT NOT NULL,
messagetext text NOT NULL,
created_at timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
PRIMARY KEY(id)
)
What I would like to do is to get two weeks of data, the current week and the previous week, ordered by days. For example how many messages were created on Sunday, Monday, Tuesday,Wednesday.... for this week and the last week.
I do have a query as follows
SELECT
COUNT(id) AS 'Count'
FROM
'messages'
WHERE
(WEEK(created_at) = WEEK(NOW()))
GROUP BY
WEEKDAY(created_at)
ORDER BY
week(created_at) ASC,
weekday(created_at) ASC
but I don't know how to sort it day wise. For example the query above will list me how many did i get today or yesterday but that is not what what I want. What I would like to have is to get the count day wise as well as week wise.
Here is what I would like to have
Please let me know if i clear enough with the situation that I am facing.
Update:
For more information , please see Juan's question below
If the current days are less that 14 days , in that case, the script should return the count for the days that are in the previous week , not the week before the previous week. Here is how Juan explained (imagine today is friday. if you count 14 day back you get this week monday to friday. previous week monday to sunday... and prev prev week friday to sunday. And not sure if that is the result you want)
With Regards
Gagan

Use conditional SUM
SELECT week(created_at) week_id,
SUM( IF(weekday(created_at) = 0, 1, 0) ) as monday,
SUM( IF(weekday(created_at) = 1, 1, 0) ) as tuesday,
SUM( IF(weekday(created_at) = 2, 1, 0) ) as wednesday,
SUM( IF(weekday(created_at) = 3, 1, 0) ) as thursday,
SUM( IF(weekday(created_at) = 4, 1, 0) ) as friday,
SUM( IF(weekday(created_at) = 5, 1, 0) ) as saturday,
SUM( IF(weekday(created_at) = 6, 1, 0) ) as sunday
FROM 'messages'
WHERE created_at >= DATE_SUB(NOW(), INTERVAL 14 DAY)
AND ( week_id = WEEK( NOW() )
OR week_id = WEEK( DATE_SUB(NOW(), INTERVAL 7 DAY)
)
GROUP BY
week_id
ORDER BY
week_id

Im going to send a big guess. But did you try something like this?
SELECT
week(created_at) week_id,
weekday(created_at) weekday_id,
COUNT(id) AS 'Count'
FROM
'messages'
WHERE
week_id = WEEK( NOW() )
OR week_id = WEEK( DATE_SUB(NOW(), INTERVAL 7 DAY) )
GROUP BY
week_id, weekday_id
ORDER BY
week_id, weekday_id

Related

How can I efficiently calculate the sales since the nth day of the month?

I have an up to date mysql database installation and I need a function to calculate the sales between the nth of the last month and today. The function will be called several times per day because I'm looking for the point at which the cumulative sales since the previous nth of the month cross a threshold.
The background is that I am developing a subscription site. Customers will sign up on ad-hoc days of the year and make purchases throughout the year. I want to be able to calculate the sales on the month-to-date basis.
If a customer signs up on the nth of the month then I need to calculate the sales between the previous nth of the month and today.
If the customer signs up on the 28th and today is the 30th then you'd think that there had only been 2 days of sales but if today is 30th March then it's been 30 days. In another example: if the customer signs up on Oct 31st how would this handle Feb 28th or April 30th?
I've looked at functions such as timestampdiff() but can not figure out a practical solution. By that I mean one that will do the job and can do the task efficiently without costing toooo many cpu cycles.
Thanks in anticipation
You might be looking for something like :
DATE_ADD(
LAST_DAY(DATE_SUB(CURDATE(), interval 2 MONTH)),
INTERVAL 15 DAY
)
Given a number of days N, it returns the Nth day of last month.
As of today, when given 15 days, this yields '2018-12-15' (the 15th day of last month, eg December 15th, 2018).
If you want to ensure that the returned date will never exceed the last day of last month (like : current month is March and N = 31), you can use :
LEAST(
DATE_ADD(
LAST_DAY(DATE_SUB(CURDATE(), interval 2 MONTH)),
INTERVAL 15 DAY),
LAST_DAY(DATE_SUB('CURDATE(), interval 1 MONTH))
)
Typically, this :
SELECT LEAST(
DATE_ADD(
LAST_DAY(DATE_SUB('2018-03-28', interval 2 MONTH)),
INTERVAL 31 DAY),
LAST_DAY(DATE_SUB('2018-03-28', interval 1 MONTH))
)
Yieds : '2018-02-28' -(the last day of February).
Thanks to both of you for your contributions. I'd already spent several hours looking into the issue and some of the most promising answers came from stackoverflow which is why I thought to post my question here.
I agree that the question is ill formed having spent quite a bit more time trying to define the problem. As a consequence I'm going to change the problem into something which an be easily resolved.
Rather than rely on the subscription date I'm going to run on a calendar month basis. This forces changes elsewhere but I think I can live with them.
Thanks again
I could recommend TIMESTAMP and INTERVAL. It is simple and efficient without the need to try to do something trivial enough
So consider the following schema and query
DROP TABLE IF EXISTS `example_sales`;
CREATE TABLE `example_sales`(
`id` INT(11) UNSIGNED AUTO_INCREMENT,
`id_customer` MEDIUMINT(8) UNSIGNED NOT NULL,
`profits` DECIMAL(16,2) NOT NULL DEFAULT 0,
`ts` TIMESTAMP NOT NULL,
PRIMARY KEY(`id`)
) ENGINE=InnoDB DEFAULT CHARACTER SET = utf8 COLLATE = utf8_unicode_ci;
-- add some values, it doesn't matter the order in the example
INSERT INTO `example_sales`( `id_customer`, `profits`, `ts` ) VALUES
( 1, 10.00, NOW( ) - INTERVAL 12 DAY ),
( 1, 14.00, NOW( ) - INTERVAL 1 WEEK ),
( 1, 110.00, NOW( ) - INTERVAL 30 DAY ),
( 1, 153.00, NOW( ) - INTERVAL 8 DAY ),
( 1, 5.00, NOW( ) - INTERVAL 2 DAY ),
( 1, 97.00, NOW( ) - INTERVAL 13 DAY ),
( 1, 1.00, '2018-02-28 13:00:00' ),
( 1, 2.00, '2018-03-28 13:00:00' ),
( 1, 3.00, '2018-01-30 13:00:00' ),
( 1, 4.00, '2018-03-30 13:00:00' ),
( 1, 42.00, NOW( ) - INTERVAL 42 DAY );
Updated, since I've originally didn't fully understand the question. I've kept the timestamp though and made some corrections I didn't noticed earlier.
-- '2018-03-28' or '2018-03-29' instead of NOW( ) or anything you like
SET #last := DATE( NOW( ) );
SET #first := LEAST( DATE_SUB( #last, INTERVAL 1 MONTH ), LAST_DAY( DATE_SUB( #last, INTERVAL 1 MONTH ) ) );
-- change last and first test different sets
SELECT `profits`, DATE( `ts` ) AS `date`
FROM `example_sales`
WHERE `id_customer` = 1
HAVING `date` BETWEEN #first AND #last
ORDER BY `date`;
And when you are confident enough that this will do the job
SELECT SUM( `profits` ), DATE( `ts` ) AS `date`
FROM `example_sales`
WHERE `id_customer` = 1
HAVING `date` BETWEEN #first AND #last;
Hope that this will do the trick this time.

Grouping COUNT by Time in MySql

I have a simple query that give me the count of application types; it looks something like this:
SELECT Application_Type, COUNT(*) FROM Loan_Applications GROUP BY Application_Type;
It returns something like this:
Home 3
Car 21
Commercial 16
There is a field in the database called Submission_Date (Of type Date)
How can I query and break up this data by week?
Type This week Last week 2 weeks ago
Home 1 1 1
Car 9 6 6
Commercial 10 0 3
You can try something like:
SELECT
Application_Type,
SUM(IF(Submission_Date BETWEEN CURRENT_DATE AND CURRENT_DATE - INTERVAL 1 WEEK, 1, 0)) AS 'This week',
SUM(IF(Submission_Date BETWEEN CURRENT_DATE- INTERVAL 1 WEEK AND CURRENT_DATE - INTERVAL 2 WEEK, 1, 0)) AS 'Last week',
SUM(IF(Submission_Date BETWEEN CURRENT_DATE- INTERVAL 2 WEEK AND CURRENT_DATE - INTERVAL 3 WEEK, 1, 0)) AS '2 weeks ago',
FROM Loan_Applications
GROUP BY Application_Type
;
Or:
SET #date1w = CURRENT_DATE - INTERVAL 1 WEEK;
SET #date2w = CURRENT_DATE - INTERVAL 2 WEEK;
SET #date3w = CURRENT_DATE - INTERVAL 3 WEEK;
SELECT
Application_Type,
SUM(IF(Submission_Date BETWEEN CURRENT_DATE AND #date1w, 1, 0)) AS 'This week',
SUM(IF(Submission_Date BETWEEN #date1w AND #date2w, 1, 0)) AS 'Last week',
SUM(IF(Submission_Date BETWEEN #date2w AND #date3w, 1, 0)) AS '2 weeks ago',
FROM Loan_Applications
GROUP BY Application_Type
;
You can make a SUMIF type of calculation. The following sums the number of rows where the submission date is within the last week.
SUM(CASE WHEN submission_date >= CURDATE() - 7 THEN 1 ELSE 0 END)
You could then repeat this for different ranges, to get any "bands" that you desire.
Try
SELECT
Application_Type,
SUM(WEEKOFYEAR(Submission_Date) = WEEKOFYEAR(NOW())) AS `This week`,
SUM(WEEKOFYEAR(Submission_Date) = WEEKOFYEAR(DATE_ADD(NOW(),INTERVAL -1 WEEK))) AS `Last week`,
SUM(WEEKOFYEAR(Submission_Date) = WEEKOFYEAR(DATE_ADD(NOW(),INTERVAL -2 WEEK))) AS `2 weeks ago`
FROM Loan_Applications GROUP BY Application_Type;
;
it is based on the fact that SUM of a boolean expression in the group by will count the cases when the expression is true

Last date in quarter MySQL

I have a table with some dates. I need a query which will return the max (last) date from this table and last date of quarter this max date belongs to.
So for data i table
ID| EDATE
--+----------
1|2014-03-06
2|2014-10-12
this query should return 2014-10-12 and 2014-12-31.
As I understand you want the last day of the quarter, so 31 March, 30 June, 30 Sept, 31 Dec? So you can use the answer from Gordon Linoff and adjust it to do that.
You only need a case statement on month(date) and concat that with the year.
http://dev.mysql.com/doc/refman/5.1/de/control-flow-functions.html
str_to_date(
concat(
year(edate),
(case
when month(edate) in (1, 2, 3) then '-03-31'
when month(edate) in (4, 5, 6) then '-06-30'
when month(edate) in (7, 8, 9) then '-09-30'
else '-12-31'
end)
),
'%Y-%m-%d'
)
Getting the day of the last quarter for the date is a bit yucky, but possible. Here is a sort of brute force solution:
select edate,
str_to_date(concat(year(edate), '-', 1 + floor((month(edate) - 1)/ 3)) * 3, '-',
(case when month(edate) in (1, 2, 3, 10, 11, 12) then 31 else 30 end)),
'%Y-%m-%d'
)
from table t
order by edate desc
limit 1;
Here is a SQL Fiddle that demonstrates it.
You can use LAST_DAY to select the last day of a specific month depending on where your quarters end you may have to change the 3,6,9,12 to different months.
select t1.max_date,
(
case
when month(max_date) <= 3
then last_day(concat(year(max_date),'-3-1'))
when month(max_date) <= 6
then last_day(concat(year(max_date),'-6-1'))
when month(max_date) <= 9
then last_day(concat(year(max_date),'-9-1'))
else last_day(concat(year(max_date),'-12-1'))
end
) last_quarter_day
from (
select max(EDATE) max_date from myTable
) t1
I found the simplest answer:
SELECT MAKEDATE(YEAR(edate),1)
+ INTERVAL QUARTER(edate) QUARTER
- INTERVAL 1 DAY
This query takes the first day of year, adds quarters to it and subtracts 1 day to get the last day in wanted quarter. So the required query should look like:
SELECT MAX(edate),
MAKEDATE(YEAR(MAX(edate)),1)
+ INTERVAL QUARTER(MAX(edate)) QUARTER
- INTERVAL 1 DAY
FROM table

Retrive last 1 week records on per day basis

I have created a table emp_info with email,mobile, timestamp as fields.
I want to retrieve last 1 week record on per day basis. And for this I have tried
SELECT count(*)
FROM `emp_info`
WHERE DATE(timestamp ) > DATE_SUB( CURDATE( ) , INTERVAL 1 WEEK )
ORDER BY saved_timestamp
and it shows total no. of records entered in last 7 days which is not my desired out put.
So I want the out put of records for 7 days like:
Day count
Monday 2
Tuesday 0
.... ..
.... ..
So somebody please help me out?
Just add the DAYNAME to your column list and aggregate.
SELECT DAYNAME(timestamp), COUNT(*)
FROM `emp_info`
WHERE DATE(timestamp ) > DATE_SUB( CURDATE( ) , INTERVAL 1 WEEK )
GROUP BY DAYNAME(timestamp)
ORDER BY saved_timestamp
Try to extract the day of week from the date then use the count and group it by the day of the week and you can get the count to each of the day.
SELECT DAYOFWEEK(DATE(timestamp)), count(*) FROM `emp_info` WHERE DATE(timestamp ) > DATE_SUB( CURDATE( ) , INTERVAL 1 WEEK ) group by DAYOFWEEK(DATE(timestamp))

Changing start-date in MySQL for week

I found the following code to help in creating a weekly report based on a start date of Friday. The instructions say to replace ".$startWeekDay." with a 4. When I put '".$startDay."' as '2013-01-30', I get errors.
Also I get a report by day rather than week as I desire.
SELECT SUM(cost) AS total,
CONCAT(IF(date - INTERVAL 6 day < '".$startDay."',
'".$startDay."',
IF(WEEKDAY(date - INTERVAL 6 DAY) = ".$startWeekDay.",
date - INTERVAL 6 DAY,
date - INTERVAL ((WEEKDAY(date) - ".$startWeekDay.")) DAY)),
' - ', date) AS week,
IF((WEEKDAY(date) - ".$startWeekDay.") >= 0,
TO_DAYS(date) - (WEEKDAY(date) - ".$startWeekDay."),
TO_DAYS(date) - (7 - (".$startWeekDay." - WEEKDAY(date)))) AS sortDay
FROM daily_expense
WHERE date BETWEEN '".$startDay."' AND '".$endDay."'
GROUP BY sortDay;
The following code is what I am using
SELECT count(DISTINCT (
UserID)
) AS total, CONCAT(IF(date(LastModified) - INTERVAL 6 day < date(LastModified),
date(LastModified),
IF(WEEKDAY(date(LastModified) - INTERVAL 6 DAY) = 4,
date(LastModified) - INTERVAL 6 DAY,
date(LastModified) - INTERVAL ((WEEKDAY(date(LastModified)) - 4)) DAY)),
' - ', date(LastModified)) AS week
FROM `Purchase`
WHERE `OfferingID` =87
AND `Status`
IN ( 1, 4 )
GROUP BY week
The output I get is
total week
3 2013-01-30 - 2013-01-30
1 2013-01-31 - 2013-01-31
I'm not sure exactly how you want to display your week, the sql above is attempting to display date ranges. If this isn't a requirement, your query could be very simple, you can just offset your time by two days (since friday is two days away from the natural star of the week) and use the week function to get the week number.
The query would look like this:
select count(distinct (UserID)) as total
, year( LastModified + interval 2 day ) as year
, week( LastModified + interval 2 day ) as week_number
FROM `Purchase`
WHERE `OfferingID` =87
AND `Status`
IN ( 1, 4 )
group by year, week_number;