MSSQL - How to divide each month into 4 weeks? - sql-server-2008

I have a query that is close to doing what I need:
select *
from
(
select DATEPART (YEAR, order_date) as theYear,
DATEPART (WEEK, order_date) as theWeek,
SUM (order_totalPrice) as totalSales
from orders_tbl
where order_date >= '01/01/2015'
group by DATEPART (YEAR, order_date), DATEPART (WEEK, order_date)
) as mySourceTable
pivot
(
sum (totalSales)
for theYear in ([2015], [2016], [2017])
) as myPivotTable
order by theWeek asc
This gives a result like:
Week 2015 2016 2017
----------------------------
1 $999 $999 $999
2 $999 $999 $999
3 $999 $999 $999
But this defines "weeks" as being 7 days starting on a certain day of the week. (I think Monday is the default).
What I really want to do is divide each month into 4 weeks, such that I'll have 48 "weeks" per year, like this:
Day of Month Week #
-----------------------
1-7 1
8-14 2
15-21 3
22+ 4
And my final output would look like:
Month Week 2015 2016 2017
----------------------------------------
1 1 $999 $999 $999
1 2 $999 $999 $999
1 3 $999 $999 $999
1 4 $999 $999 $999
2 1 $999 $999 $999
2 2 $999 $999 $999
I want to do this because it makes the most business sense for us.
How can I modify the above query to achieve this?
Stipulation 1: This is a query that I am calling from within a web application code (so, I think this rules out some T-SQL stuff... right?) Yes, I could use the web app code to do various loops or other manipulations, but is there a way to do this purely in a single SQL query?
Stipulation 2: I'm using MS SQL 2008 R2.

You can make use if the DAY functions which returns the day of the month.
DAY(order_date) AS DayOfMonth
Next you can build up your own logic like:
CASE WHEN DAY(order_date) >= 1 AND DAY(order_date) < 8 THEN 1
WHEN DAY(order_date) >= 8 AND DAY(order_date) < 15 THEN 2
WHEN DAY(order_date) >= 15 AND DAY(order_date) < 22 THEN 3
ELSE 4 END AS WeekNumber

If you want to devide month into weeks in Laravel following code may help you
public function weeksOfMonth($year = null, $month)
{
if($year == null ):
$year = Carbon::now()->year;
endif;
$date = Carbon::createFromDate($year,$month);
$j=1;
$week_array = [];
for ($i=1; $i <= $date->daysInMonth ; $i++) {
Carbon::createFromDate($year,$month,$i);
$start_date = Carbon::createFromDate($year,$month,$i)->startOfWeek()->toDateString();
$end_date = Carbon::createFromDate($year,$month,$i)->endOfweek()->toDateString();
$week_array[$j]['start_date'] = $start_date;
$week_array[$j]['end_date'] = $end_date;
$week_array[$j]['week'] = 'week'.$j;
$i+=7;
$j++;
}
return $week_array;
}

Related

MySQL Select data from table with dates between in reverse of interval 7 days

I have a MySQL requirement to select data from a table based on a start date and end date and group it by weekly also selecting the data in reverse order by date. Assume that, I have chosen the start date as 1st November and the end date as 04 December. Now, I would like to fetch the data as 04 December to 28 November, 27 November to 20 November, 19 November to 12 November and so on and sum the value count for that week.
Given an example table,
id
value
created_at
1
10
2021-10-11
2
13
2021-10-17
3
11
2021-10-25
4
8
2021-11-01
5
1
2021-11-10
6
4
2021-11-18
7
34
2021-11-25
8
17
2021-12-04
Now the result should be like 2021-12-04 to 2021-11-28 as one week, following the same in reverse order and summing the column value data for that week. I have tried in the query to add the interval of 7 days after the end date but it didn't work.
SELECT count(value) AS total, MIN(R.created_at)
FROM data_table AS D
WHERE D.created_at BETWEEN '2021-11-01' AND '2021-12-04' - INTERVAL 7 DAY ORDER BY D.created_at;
And it's also possible to have the last week may have lesser than 7 days.
Expected output:
end_interval
start_interval
total
2021-12-04
2021-11-27
17
2021-11-27
2021-11-20
34
2021-11-20
2021-11-13
4
2021-11-13
2021-11-06
1
2021-11-06
2021-10-30
8
2021-10-30
2021-10-25
11
Note that the last week is only 5 days depending upon the selected from and end dates.
One option to address this problem is to
generate a calendar of all your intervals, beginning from last date till first date, with a split of your choice, using a recursive query
joining back the calendar with the original table
capping start_interval at your start_date value
aggregating values for each interval
You can have three variables to be set, to customize your date intervals and position:
SET #start_date = DATE('2021-10-25');
SET #end_date = DATE('2021-12-04');
SET #interval_days = 7;
Then use the following query, as already described:
WITH RECURSIVE cte AS (
SELECT #end_date AS end_interval,
DATE_SUB(#end_date, INTERVAL #interval_days DAY) AS start_interval
UNION ALL
SELECT start_interval AS end_interval,
GREATEST(DATE(#start_date), DATE_SUB(start_interval, INTERVAL #interval_days DAY)) AS start_interval
FROM cte
WHERE start_interval > #start_date
)
SELECT end_interval, start_interval, SUM(_value) AS total
FROM cte
LEFT JOIN tab
ON tab.created_at BETWEEN start_interval AND end_interval
GROUP BY end_interval, start_interval
Check the demo here.

quarterly difference in sum(Revenue) saved in a new column MySQL

I need to add a new column to my table that will have the difference between sum of revenue of a new quarter over the last one.
My data looks like this:
Website Year Quarter Revenue
cosmo.com 2019 4 10
cosmo.com 2020 1 15
cosmo.com 2020 2 5
fashion.com 2019 4 10
fashion.com 2020 1 5
fashion.com 2020 2 20
The desired output is:
Website Year Quarter Revenue Difference
cosmo.com 2019 4 10 +5
cosmo.com 2020 1 15 +5
cosmo.com 2020 2 5 -10
fashion.com 2019 4 10 +10
fashion.com 2020 1 5 -5
fashion.com 2020 2 20 +15
I have tried to see the yearly difference, to begin with, but got a syntax error
select *,
`Revenue` - lag(`Revenue`) over(order by `Year`) as difference
from table`
From what I can tell, you want the difference from the previous quarter, not year. That would be:
select t.*,
(t.Revenue - lag(t.Revenue) over (partition by website order by Year, quarter)) as difference
from table t;
Note the use of partition by for the website.

Duplicate the result set of a MySQL Query

I want to average out values of a specific query based on the weekday of the date in the table, and duplicate it for the other days. I know that this explanation was confusing, see the example below.
Eg:- Table Structure -
ID, Value, Date
Expected Result-
Avg(value), Weekname(day)
12.23 , Mon
12.23 , Tue
12.23 , Wed
12.23 , Thu
12.23 , Fri
34.56 , Sat
56.34 , Sun
Ie, values for Monday - Friday is the same.
Current Result
Avg(value), Weekname(day)
12.23 , Mon
34.56 , Sat
56.34 , Sun
The query looks like this -
select avg(value)
,daynameofweek(date)
from table
group by (CASE WHEN DAYOFWEEK(DATE) NOT BETWEEN 2 and 6 THEN DAYOFWEEK(DATE) END)
Thanks for the help, in Advance.
Edit - Adding example data set
ID Value Date
1 2.500 2017-01-01
2 0.674 2017-01-02
3 2.743 2017-01-03
4 1.460 2017-01-04
5 1.457 2017-01-05
6 1.791 2017-01-06
7 1.896 2017-01-07
8 2.015 2017-01-08
9 2.224 2017-01-09
10 1.635 2017-01-10
11 1.100 2017-01-11
12 0.441 2017-01-12
13 0.809 2017-01-13
14 1.508 2017-01-14
Expected Result
Avg(Value) daynameofweek(date)
1.625 Monday
1.625 Tuesday
1.625 Wednesday
1.625 Thursday
1.625 Friday
1.300 Saturday
1.702 Sunday
You are showing that you end up with 'MON' for Monday to Friday, but this is arbitrarily chosen by the DBMS and you could just as well end up with, say, 'WED', because this day too is in the grouped-by range. So start with getting a determinate value here. Then join with a made-up day table:
select
days.dayname,
data.avg_value
from
(
select
case when dayofweek(date) between 2 and 6 then 2 else dayofweek(date) end as day
avg(value) as avg_value
from table
group by case when dayofweek(date) between 2 and 6 then 2 else dayofweek(date) end
) data
join
(
select 1 as day, 'SUN' as dayname, 7 as sortkey union all
select 2 as day, 'MON' as dayname, 1 as sortkey union all
select 2 as day, 'TUE' as dayname, 2 as sortkey union all
select 2 as day, 'WED' as dayname, 3 as sortkey union all
select 2 as day, 'THU' as dayname, 4 as sortkey union all
select 2 as day, 'FRI' as dayname, 5 as sortkey union all
select 7 as day, 'SAT' as dayname, 6 as sortkey
) days on days.day = data.day
order by data.sortkey;
(If I remember correctly, MySQL allows to group by the alias name (group by day), which is not standard compliant. Use this, if you like it better.)

mysql get average data for full months

Given the following sample data:
tblData
Date Sales
----------------------
2011-12-01 122
2011-12-02 433
2011-12-03 213
...
2011-12-31 235
2011-11-01 122
2011-11-02 433
2011-11-03 213
...
2011-11-30 235
2011-10-10 122
2011-10-11 433
2011-10-12 213
...
2011-10-31 235
Notice that October data begins at 10 October, whereas subsequent months have complete data.
I need to get the average monthly sales over all complete months, which in this case would be November and December 2011.
How would I do this?
SELECT `date`, AVG(`sales`)
FROM sales
GROUP BY YEAR(`date`), MONTH(`date`)
HAVING COUNT(`date`) = DAY(LAST_DAY(`date`));
Example
If you want to limit the result, either
HAVING ...
ORDER BY `date` DESC LIMIT 3
which should always return data for the 3 most recent months, or something like
FROM ...
WHERE DATE_FORMAT(CURDATE() - INTERVAL 3 MONTH, '%Y-%m')
<= DATE_FORMAT(`date`, '%Y-%m')
GROUP BY ...
which should return data for the 3 previous months, if there is any. I'm not sure which is better but I don't believe WHERE gets to use any index on date, and if you're using DATETIME and don't format it you'll also be comparing the days and you don't want that,
Can't test it right now, but please have a try with this one:
SELECT
DATE_FORMAT(`Date`, '%Y-%m') AS yearMonth,
SUM(Sales)
FROM
yourTable
GROUP BY
yearMonth
HAVING
COUNT(*) = DAY(LAST_DAY(`Date`)

MySQL find the mode of multiple subsets

I have a database like this:
custNum date purchase dayOfWeek
333 2001-01-01 23.23 1
333 2001-03-04 34.56 5
345 2008-02-02 22.55 3
345 2008-04-05 12.35 6
... ... ... ...
I'm trying to get the mode (most frequently occuring value) for the dayOfWeek column for each customer. Basically it would be the day of the week each customer shops the most on. Like:
custNum max(count(dayofweek(date)))
333 5
345 3
356 2
388 7
... ...
Any help would be great thanks.
select custNum, dayOfWeek
from tableName t
group by custNum, dayOfWeek
having dayOfWeek = (
select dayOfWeek
from tableName
where custNum = t.custNum
group by dayOfWeek
order by count(*) desc, dayOfWeek
limit 1
)