SQL fiscal quarter totals... I've already got the calendar ones - mysql

Here's the SQL query I have for the calendar year's quarterly totals
SELECT
SUM(CASE WHEN WEEK(LAST_DAY) <= 13 THEN BILLABLE END) AS Q1
,SUM(CASE WHEN WEEK(LAST_DAY) >= 14 AND WEEK(LAST_DAY) <= 26 THEN BILLABLE END) AS Q2
,SUM(CASE WHEN WEEK(LAST_DAY) >= 27 AND WEEK(LAST_DAY) <= 39 THEN BILLABLE END) AS Q3
,SUM(CASE WHEN WEEK(LAST_DAY) >= 40 AND WEEK(LAST_DAY) <= 53 THEN BILLABLE END) AS Q4,
Emp_Name
FROM 'emp_info'
WHERE YEAR(LAST_DAY) = YEAR(CURRENT_TIMESTAMP)
GROUP BY Emp_Name
I need to shift it so it gives me the fiscal year (July 1 to June 30) totals.
I know when doing so the week numbers will need to start on July 1, not January 1 and there'll probably be a case statement in there somewhere. But I can't get it to come out correctly.

If I were you, I'd store fiscal quarters in a table. Then your queries would be dead simple. And not only would they be dead simple, it would be obvious when they were correct.

Try this query -
SELECT
Emp_Name,
IF(DATE(LAST_DAY) <= DATE('2011-6-30'), YEAR(LAST_DAY) - 1, YEAR(LAST_DAY)) f_year,
QUARTER(LAST_DAY) quarter,
SUM(BILLABLE)
FROM
emp_info
GROUP BY
Emp_Name,
f_year,
quarter;

You are aware that your query doesn't divide year into 4 quarters by month? Instead of using the WEEK() function use MONTH().
By operating on months and years (YEAR()) calculating sum for fiscal year will be easy.

Your real problem is going to be that generally speaking, date and time functions will only work on ISO/(Other common) calendar date/time objects, but you need them to work on a company-specific fiscal calendar.
This is why Calendar files are used, as a translation between fiscal and ISO calendars.
Generate your calendar fileout for a few years in either direction, then you can join to it, restricting/grouping by fiscal year and period. No case statements needed.
I also recommend storing most dates in your tables as actual ISO dates, then only translating/using fiscal dates when actually necessary - we have some summation tables here which are keyed off fiscal year/period, but which display shopping-behaviour data to customers (who don't know/care when our fiscal year is)...

Related

Dynamic SQL query to select Quarters and perform aggregation on the data

sl.. Country Channel Type Clicks Spend Impressions Date
1. india Social a 14 $25 1,331 2/11/2021
2. india Search b 1,748 $1,801 1,166,140 2/11/2021
3. india Display c 28,615 $3,901 8,279,595 2/11/2021
4. india Display a 1,500 $1,000 1,233 7/10/2020
5. india Display a 11,500 $500 5,133 10/1/2020
6. india Display a 599 $200 6570 1/1/2020
So, what I needed to do is to create clicks/impressions for every quarter based on the historical data. The historical data is the same just that the dates are different, now to create (clicks/impressions) for this quarter the value will be
select sum(clicks)/sum(impressions)
from table_name
groupby (country, channel, type)
where ________
I need help in the where clause, I need to select the data from only some specific quarters while generating clicks/impressions for any quarter and the logic to select is:
Sum(clicks(q3 2020,q4 2020, q1 2020))/Sum(impressions(q3 2020,q4 2020, q1 2020))
where we need to find the quarters and the year from the date column.
By dynamically I mean if we move to the next quarter then I need to compare the average of the last 2 quarters and the same quarter previous year.
I wrote the code to find the quarters and year from the date, but how to proceed?
CASE
WHEN EXTRACT(MONTH FROM day) BETWEEN 7 AND 9 THEN 'Q1'
WHEN EXTRACT(MONTH FROM day) BETWEEN 10 AND 12 THEN 'Q2'
WHEN EXTRACT(MONTH FROM day) BETWEEN 1 AND 3 THEN 'Q3'
WHEN EXTRACT(MONTH FROM day) BETWEEN 4 AND 6 THEN 'Q4'
END AS quarter,
EXTRACT(Year FROM day) AS Year
Desired output
slno. quarter clicks/impressions
1. Q1 2021 0.53
2. Q4 2020 1.35
.......
When you're trying to filter data, don't do complex maths on the data and filter the results. That requires processing the whole table, then throwing away the rows not required.
Instead, do the maths in the filter parameters, which will allow the database to fulfil the filtering by checking indexes and only loading the rows it needs.
Fot example...
SELECT
SUM(clicks) / SUM(impressions)
FROM
yourTable
WHERE
(Date >= '2020-07-01' AND Date < '2021-01-01')
OR (Date >= '2020-01-01' AND Date < '2020-04-01')
That ensures you only process the data for the last two quarters of 2020, plus the first quarter of 2020.
The question then becomes, how to work out those dates based on today's date.
The start of the current quarter can be as provided by this... How do I get the first date of a quarter in MySQL?
If you store the result of that calculation in a variable named #CurrentQuarterStart, the WHERE clause becomes this...
WHERE
(Date >= #CurrentQuarterStart - INTERVAL 2 QUARTERS AND Date < #CurrentQuarterStart)
OR (Date >= #CurrentQuarterStart - INTERVAL 4 QUARTERS AND Date < #CurrentQuarterStart - INTERVAL 3 QUARTERS)

MySQL - Display all amounts per month on a given fiscal year

I would to seek some help from the SQL Experts here in Stackoverflow.
I currently have this kind of table:
And I have been successfully getting the sum amount per year with this query:
select case when month(savings_date) >=11
then year(savings_date) +1
else year(savings_date)
end as fiscal, sum(amount)
from net_savings
group by fiscal
And having this output:
Now I would like to display all the sum amounts per month with a given input of fiscal year. How would I do this?
My fiscal year starts from november and ends at october. So if I have october 2015 in my records, it should not show up when I enter 2016 as fiscal year.
You can do this:
select month(savings_date), sum(amount)
from net_savings
where dateadd(savings_date, interval -2 month)
group by month(savings_date);

Is it possible to loop in MySQL for sales data?

I am trying to build a useful query for our sales team to see how many sales our business has made in the current month, compared to the same point in the previous months.
So if today is the 14th of September, I want to compare how many sales we'd made between 1st - 14th of August and so on to see if we are up or down.
I have created the query to pull the data, but it's not in any kind of loop. Can anyone suggest a way to do this please? Below shows me the data for July 2015...
SELECT CONCAT(MONTH(OrderDate),'-',YEAR(OrderDate)) AS MontyYear,
COUNT(sw_orders.OrderNumber) OrderCount,
SUM(Gross) GrossIncome
FROM
orders
WHERE
orders.MasterOrderNumber = ''
AND Date(OrderDate) >= '2015-07-01'
AND Date(OrderDate) <= Concat('2015-07-', DAY(CURDATE()))
order by orders.ordernumber;
It feels like I need a variable that is the month number, so starts at "1" for January - then counts up per loop and use is used in the OrderDate part of the query?
I would not use string literals in the query, but make use of MySql's DAY and MONTH function:
WHERE orders.MasterOrderNumber = ''
AND DAY(OrderDate) <= DAY(CURDATE())
GROUP BY MONTH(OrderDate)

Trying to find this day last year. Ex: Get the 1st Friday of October, not the October the 4th

I'm not sure this is even possible without using PHP, but I'd love to try.
I have a database that looks like this (a bunch of other stuff, but this is all that is relevant:
Date_Day (is a range from 1 to 31 with no trailing 0)
Date_Month (is a range from January to December, not numerical)
Date_Year (is the year in 4 digit format, ex: 2005)
Total (number with 2 decimal places)
I know the way the dates are stored is awful, but this is the database I was given. If there is a query that I could use these columns to create an actual DATETIME column, I would happily do it, I just don't know what that query looks like.
I have this query that returns the Total sales amount for this day for all previous years:
SELECT
Date_Year, Date_Month, SUM(Total)
FROM
tablename
WHERE
Date_Year < YEAR(CURDATE())
AND
Date_Month = MONTHNAME(CURDATE())
AND
Date_Day = DAY(CURDATE())
GROUP BY
Date_Year, Date_Month
So if I run this today, I get the daily totals for October 4th for all previous years. The issue is that in sales, this isn't very helpful for comparing growth. What I really need is the daily totals for the 1st Friday in October for all previous years.
Is this possible without having to rely on PHP? If so, I would be very grateful for your help.
Thank you.
You might be looking for DAYOFWEEK()
Returns the weekday index for date (1 = Sunday, 2 = Monday, …, 7 = Saturday). These index values correspond to the ODBC standard.
mysql> SELECT DAYOFWEEK('2007-02-03');
> 7
SELECT
Date_Year, Date_Month, SUM(Total)
FROM
tablename
WHERE
Date_Year < YEAR(CURDATE())
AND
Date_Month = MONTHNAME(CURDATE())
AND
Date_Day = DAY(LAST_DAY(CURDATE()) - ((28 + WEEKDAY(LAST_DAY(CURDATE())) - 4)))
GROUP BY
Date_Year, Date_Month
maybe this will help

How to group by week in MySQL?

Oracle's table server offers a built-in function, TRUNC(timestamp,'DY'). This function converts any timestamp to midnight on the previous Sunday. What's the best way to do this in MySQL?
Oracle also offers TRUNC(timestamp,'MM') to convert a timestamp to midnight on the first day of the month in which it occurs. In MySQL, this one is straightforward:
TIMESTAMP(DATE_FORMAT(timestamp, '%Y-%m-01'))
But this DATE_FORMAT trick won't work for weeks. I'm aware of the WEEK(timestamp) function, but I really don't want week number within the year; this stuff is for multiyear work.
You can use both YEAR(timestamp) and WEEK(timestamp), and use both of the these expressions in the SELECT and the GROUP BY clause.
Not overly elegant, but functional...
And of course you can combine these two date parts in a single expression as well, i.e. something like
SELECT CONCAT(YEAR(timestamp), '/', WEEK(timestamp)), etc...
FROM ...
WHERE ..
GROUP BY CONCAT(YEAR(timestamp), '/', WEEK(timestamp))
Edit: As Martin points out you can also use the YEARWEEK(mysqldatefield) function, although its output is not as eye friendly as the longer formula above.
Edit 2 [3 1/2 years later!]:
YEARWEEK(mysqldatefield) with the optional second argument (mode) set to either 0 or 2 is probably the best way to aggregate by complete weeks (i.e. including for weeks which straddle over January 1st), if that is what is desired. The YEAR() / WEEK() approach initially proposed in this answer has the effect of splitting the aggregated data for such "straddling" weeks in two: one with the former year, one with the new year.
A clean-cut every year, at the cost of having up to two partial weeks, one at either end, is often desired in accounting etc. and for that the YEAR() / WEEK() approach is better.
Figured it out... it's a little cumbersome, but here it is.
FROM_DAYS(TO_DAYS(TIMESTAMP) -MOD(TO_DAYS(TIMESTAMP) -1, 7))
And, if your business rules say your weeks start on Mondays, change the -1 to -2.
Edit
Years have gone by and I've finally gotten around to writing this up.
https://www.plumislandmedia.net/mysql/sql-reporting-time-intervals/
The accepted answer above did not work for me, because it ordered the weeks by alphabetical order, not chronological order:
2012/1
2012/10
2012/11
...
2012/19
2012/2
Here's my solution to count and group by week:
SELECT CONCAT(YEAR(date), '/', WEEK(date)) AS week_name,
YEAR(date), WEEK(date), COUNT(*)
FROM column_name
GROUP BY week_name
ORDER BY YEAR(DATE) ASC, WEEK(date) ASC
Generates:
YEAR/WEEK YEAR WEEK COUNT
2011/51 2011 51 15
2011/52 2011 52 14
2012/1 2012 1 20
2012/2 2012 2 14
2012/3 2012 3 19
2012/4 2012 4 19
You can get the concatenated year and week number (200945) using the YEARWEEK() function. If I understand your goal correctly, that should enable you to group your multi-year data.
If you need the actual timestamp for the start of the week, it's less nice:
DATE_SUB( field, INTERVAL DAYOFWEEK( field ) - 1 DAY )
For monthly ordering, you might consider the LAST_DAY() function - sort would be by last day of the month, but that should be equivalent to sorting by first day of the month ... shouldn't it?
Just ad this in the select :
DATE_FORMAT($yourDate, \'%X %V\') as week
And
group_by(week);
If you need the "week ending" date this will work as well. This will count the number of records for each week. Example: If three work orders were created between (inclusive) 1/2/2010 and 1/8/2010 and 5 were created between (inclusive) 1/9/2010 and 1/16/2010 this would return:
3 1/8/2010
5 1/16/2010
I had to use the extra DATE() function to truncate my datetime field.
SELECT COUNT(*), DATE_ADD( DATE(wo.date_created), INTERVAL (7 - DAYOFWEEK( wo.date_created )) DAY) week_ending
FROM work_order wo
GROUP BY week_ending;
Previous Sunday:
STR_TO_DATE(CONCAT(YEARWEEK(timestamp,2),'0'),'%X%V%w')
Previous Monday:
STR_TO_DATE(CONCAT(YEARWEEK(timestamp,3),'1'),'%x%v%w')
DATE_FORMAT(date,format) reference:
%V - Week (01..53), where Sunday is the first day of the week; WEEK() mode 2; used with %X
%v - Week (01..53), where Monday is the first day of the week; WEEK() mode 3; used with %x
%w - Day of the week (0=Sunday..6=Saturday)
%X - Year for the week where Sunday is the first day of the week, numeric, four digits; used with %V
%x - Year for the week, where Monday is the first day of the week, numeric, four digits; used with %v
I like the week function in MySQL, but in my situation, I wanted to know which week of the month a row was in. I utlized this solution:
where run_date is a timestamp like 2021-02-25 00:00:00
concat (
date_format(run_date, '%Y-%m'),
' wk ',
(week(run_date,1) - ( week(date_format(run_date, '%Y-%m-01')) - 1))
) as formatted_date
This outputs:
2021-02-23 ---> 2021-02 wk 4
2021-02-25 ---> 2021-02 wk 4
2021-02-11 ---> 2021-02 wk 2
2021-03-02 ---> 2021-03 wk 1
The idea behind this is that I want to know (with relative certainty) which week of the month in question did the date occur?
So we concatenate:
date_format(run_date, '%Y-%m') to get 2021-02
then we add the literal text string wk
then we use:
week(run_date, 1) to get the week (1 to start Monday) of this record, (which would be 7 because 02/21/2021 is in the 7th week of the year, and we subtract whatever the week is on the 1st day of this same month - the week() for 2021-02-01 is 5, because it is in the 5th week of the year:
(week(date_format(run_date, '%Y-%m-01'))
Unfortunately, this will start out the counting at 0, which people don't like, so we subtract 1 from the last part of the concatenation result so that the "week" start at 1.
This may be a good option:
SELECT
year(datetime_field) as year_date, week(datetime_field) as week_date
FROM
bd.table
GROUP BY
year_date, week_date;
It would look like this:
'2020', '14'
'2020', '15'
'2020', '16'
'2020', '17'
'2020', '18'