Select and group past data relative to a certain date - sql
I have data in two tables ORDERS and training.
Different kinds of training are provided to various customers. I would like to see what was the affect of these training on revenue for the customers involved. For achieving this, I would like to look at the revenue for the past 90 days and the future 90 days for each customer from the date of receiving the training. In other words, if a customer received a training on March 30 2014, I would like to look at the revenue from Jan 1 2014 till June 30 2014. I have come up with the following query which pretty much does the job:
select
o.custno,
sum(ISNULL(a.revenue,0)) as Revenue,
o.Date as TrainingDate,
DATEADD(mm, DATEDIFF(mm, 0, a.created), 0) as RevenueMonth
from ORDERS a,
(select distinct custno, max(Date) as Date from Training group by custno) o
where a.custnum = o.custno
and a.created between DATEADD(day, -90, o.Date) and DATEADD(day, 90, o.Date)
group by o.custno, o.Date, DATEADD(mm, DATEDIFF(mm, 0, a.created), 0)
order by o.custno
The sample output of this query looks something like this:
custno Revenue TrainingDate RevenueMonth
0000100 159.20 2014-06-02 00:00:00.000 2014-03-01 00:00:00.000
0000100 199.00 2014-06-02 00:00:00.000 2014-04-01 00:00:00.000
0000100 79.60 2014-06-02 00:00:00.000 2014-05-01 00:00:00.000
0000100 29.85 2014-06-02 00:00:00.000 2014-06-01 00:00:00.000
0000100 79.60 2014-06-02 00:00:00.000 2014-07-01 00:00:00.000
0000100 99.50 2014-06-02 00:00:00.000 2014-08-01 00:00:00.000
0000250 437.65 2013-02-26 00:00:00.000 2012-11-01 00:00:00.000
0000250 4181.65 2013-02-26 00:00:00.000 2012-12-01 00:00:00.000
0000250 4146.80 2013-02-26 00:00:00.000 2013-01-01 00:00:00.000
0000250 6211.93 2013-02-26 00:00:00.000 2013-02-01 00:00:00.000
0000250 2199.72 2013-02-26 00:00:00.000 2013-03-01 00:00:00.000
0000250 4452.65 2013-02-26 00:00:00.000 2013-04-01 00:00:00.000
Desired output example:
If the training was provided on March 15 2014, for customer number 100, I’d want revenue data in the following format:
CustNo Revenue TrainingDate RevenueMonth
100 <Some revenue figure> March 15 2014 Dec 15 2013 – Jan 14 2014 (Past month 1)
100 <Some revenue figure> March 15 2014 Jan 15 2014 – Feb 14 2014 (Past month 2)
100 <Some revenue figure> March 15 2014 Feb 15 2014 – Mar 14 2014 (Past month 3)
100 <Some revenue figure> March 15 2014 Mar 15 2014 – Apr 14 2014 (Future month 1)
100 <Some revenue figure> March 15 2014 Apr 15 2014 – May 14 2014 (Future month 2)
100 <Some revenue figure> March 15 2014 May 15 2014 – Jun 14 2014 (Future month 3)
Here, the RevenueMonth column doesn’t need to be in this format as long as it has the data relative to the training date. The ‘past’ and ‘future’ month references in the braces are only to explain the output, they need not be present in the output.
My query gets the data and groups the data by month. I would like the months to be grouped relative to the training date. For example - If the training was given on March 15, I would like the past month to be from Feb 15 till March 14, my query doesn't do that. I believe a little tweak in this query might just achieve what I'm after.
Any help with the query would be highly appreciated.
Something along these lines may do what you want:
select
t.custno,
sum(ISNULL(o.revenue,0)) as Revenue,
t.maxDate as TrainingDate,
((DATEDIFF(day, TrainingDate, o.created) + 89) / 30) - 3 as DeltaMonth
from ORDERS o
join (select custno, max([Date]) as maxDate from Training group by custno) t
on o.custnum = t.custno
where o.created
between DATEADD(day, -89, t.maxDate) and DATEADD(day, 90, t.maxDate)
group by t.custno, t.maxDate, DeltaMonth
order by t.custno
The general strategy is to compute a difference in months (or 30-day periods, really) from the training date, and group by that. This version uses from 89 days before to 90 days after the training, because if you run from -90 to +90 then you have one more day (the training day itself) than divides evenly into 30-day periods.
The query follows the general structure of the original, but there are several changes:
it computes DeltaMonth (from -3 to 2) as an index of 30-day periods relative to the training date, with the training date being the last day of DeltaMonth number -1.
it uses the DeltaMonth alias in the GROUP BY clause instead of repeating its formula. That provides better clarity, simplicity, and maintainability.
I changed the table aliases. I simply could not handle there being a table named "ORDERS" and an alias "o", with the latter not being associated with the former.
the query uses modern join syntax, for better clarity
the GROUP BY clause updated appropriately
Related
SQL Conditional update column value based on previous row value
I have a table with attendance dates in SQL Workbench/J. I need to define the attendance periods of the people, where any attendance period with gaps less or equal than 90 days are merged into a single attendance period and any gaps larger than that are considered a different attendance period. For example for a single person this is the table I have id year start_date end_date prev_att_month diff 1 2012 2012-08-01 2012-08-31 2012-07-01 31 1 2012 2012-07-01 2012-07-31 2012-04-01 91 1 2012 2012-04-01 2012-04-30 2012-03-01 31 1 2012 2012-03-01 2012-03-31 2012-02-01 29 1 2012 2012-02-01 2012-02-29 2012-01-01 31 1 2012 2012-01-01 2012-01-31 2011-12-01 31 1 2011 2011-12-01 2011-12-31 2011-11-01 30 1 2011 2011-11-01 2011-11-30 2011-10-01 31 1 2011 2011-10-01 2011-10-31 2011-09-01 30 1 2011 2011-09-01 2011-09-30 2011-08-01 31 1 2011 2011-08-01 2011-08-31 2011-07-01 31 1 2011 2011-07-01 2011-07-31 2011-05-01 61 1 2011 2011-05-01 2011-05-31 2011-04-01 30 1 2011 2011-04-01 2011-04-30 2011-03-01 31 1 2011 2011-03-01 2011-03-31 2011-02-01 28 1 2011 2011-02-01 2011-02-28 2010-08-01 184 1 2010 2010-08-01 2010-08-31 2010-07-01 31 1 2010 2010-07-01 2010-07-31 2010-06-01 30 1 2010 2010-06-01 2010-06-30 2010-05-01 31 1 2010 2010-05-01 2010-05-31 2010-04-01 30 1 2010 2010-04-01 2010-04-30 where I defined the previous attendance month column with a lag function and then found the difference between that column and the the start_date in the diff column. This way I can check the gaps between the attendance months I want this output of the attendance periods with the 90 day rule explained above: id start_date end_date 1 1/04/2010 31/08/2010 1 1/02/2011 30/04/2012 1 1/07/2012 31/08/2012 Does any one have an idea of how to do this? So far I was just able to define the difference between the attendance months but since this is a large data set I have not been able to find a solution to define the attendance periods without making a row to row analysis. with [table] as ( select id, year, start_date, end_date, lag(start_date) over (partition by id order by id, year, start_date) as prev_att_month, start_date-prev_att_month as diff from source ) select * from [table] where id = 1
One method would be to use a windowed COUNT to count how many times a value greater than 90 has appeared in the diff column, which provides a unique group number. Then you can just group your data into those groups and get the MIN and MAX values: WITH Grps AS( SELECT V.id, V.year, V.start_date, V.end_date, V.prev_att_month, V.diff, COUNT(CASE WHEN diff > 90 THEN 1 END) OVER (PARTITION BY ID ORDER BY V.start_date ASC) AS Grp FROM (VALUES(1,2012,CONVERT(date,'20120801'),CONVERT(date,'20120831'),CONVERT(date,'2012-07-01'),31), (1,2012,CONVERT(date,'20120701'),CONVERT(date,'20120731'),CONVERT(date,'2012-04-01'),91), (1,2012,CONVERT(date,'20120401'),CONVERT(date,'20120430'),CONVERT(date,'2012-03-01'),31), (1,2012,CONVERT(date,'20120301'),CONVERT(date,'20120331'),CONVERT(date,'2012-02-01'),29), (1,2012,CONVERT(date,'20120201'),CONVERT(date,'20120229'),CONVERT(date,'2012-01-01'),31), (1,2012,CONVERT(date,'20120101'),CONVERT(date,'20120131'),CONVERT(date,'2011-12-01'),31), (1,2011,CONVERT(date,'20111201'),CONVERT(date,'20111231'),CONVERT(date,'2011-11-01'),30), (1,2011,CONVERT(date,'20111101'),CONVERT(date,'20111130'),CONVERT(date,'2011-10-01'),31), (1,2011,CONVERT(date,'20111001'),CONVERT(date,'20111031'),CONVERT(date,'2011-09-01'),30), (1,2011,CONVERT(date,'20110901'),CONVERT(date,'20110930'),CONVERT(date,'2011-08-01'),31), (1,2011,CONVERT(date,'20110801'),CONVERT(date,'20110831'),CONVERT(date,'2011-07-01'),31), (1,2011,CONVERT(date,'20110701'),CONVERT(date,'20110731'),CONVERT(date,'2011-05-01'),61), (1,2011,CONVERT(date,'20110501'),CONVERT(date,'20110531'),CONVERT(date,'2011-04-01'),30), (1,2011,CONVERT(date,'20110401'),CONVERT(date,'20110430'),CONVERT(date,'2011-03-01'),31), (1,2011,CONVERT(date,'20110301'),CONVERT(date,'20110331'),CONVERT(date,'2011-02-01'),28), (1,2011,CONVERT(date,'20110201'),CONVERT(date,'20110228'),CONVERT(date,'2010-08-01'),184), (1,2010,CONVERT(date,'20100801'),CONVERT(date,'20100831'),CONVERT(date,'2010-07-01'),31), (1,2010,CONVERT(date,'20100701'),CONVERT(date,'20100731'),CONVERT(date,'2010-06-01'),30), (1,2010,CONVERT(date,'20100601'),CONVERT(date,'20100630'),CONVERT(date,'2010-05-01'),31), (1,2010,CONVERT(date,'20100501'),CONVERT(date,'20100531'),CONVERT(date,'2010-04-01'),30), (1,2010,CONVERT(date,'20100401'),CONVERT(date,'20100430'),NULL,NULL))V(id,year,start_date,end_date,prev_att_month,diff)) SELECT id, MIN(Start_date) AS Start_date, MAX(End_Date) AS End_Date FROM Grps GROUP BY Id, Grp ORDER BY id, Start_date;
Query Sales Group by week no and Month
I have to calculate sales based on WeekNo(Yearly) and Month. Table1: Sales ID SalDate Amount Region 1 2020-12-27 1000 USA 2 2020-12-28 1000 EU 3 2020-12-29 1000 AUS 4 2021-01-01 1000 USA 5 2021-01-02 1000 EU 6 2021-01-05 1000 AUS 7 2020-09-30 1000 EU 8 2020-10-01 1000 AUS Select DateName(Month,SalDate)+' - '+Convert(Varchar(10),Year(SalDate)) Months, 'Week '+ Convert(Varchar(50), DATEPART(WEEK, SalDate)) As Weeks, Sum(Amount) OrderValue From [Sales] Group By DateName(Month,SalDate)+' - '+Convert(Varchar(10),Year(SalDate)), Convert(Varchar(50), DATEPART(WEEK, SalDate)) Months Weeks Total January - 2021 Week 1 2000.00 January - 2021 Week 2 1000.00 December - 2020 Week 53 3000.00 October - 2020 Week 40 1000.00 September - 2020 Week 40 1000.00 But client want to merge Week1 Total with Week 53 And Week2 show as Week1. And Week 40 Merge with Sep 2020. I want result like below Months Weeks Total January - 2021 Week 1 1000.00 December - 2020 Week 53 5000.00 (2000+3000) September - 2020 Week 40 2000.00 (1000+100) Kindly help me to fix this problem.
Error building a SQL query while trying to get order by a day for a period of week
I have an ASP.NET Core application, through controller endpoint I pass #by and #period string values to the SQL query. #by takes one of the following values: day, week #period takes one of the following values: week, month, year When the #period is month or year, then #by is a week, else it's a day. I have the following working query when the #period is a month or a year: SELECT l.region_id AS region_id, 'Region ' + r.region_desc AS region_name, MIN(DATEADD(D, -(DATEPART(WEEKDAY, s.pos_date) - 1), s.pos_date)) AS date_pos, CONVERT(VARCHAR(20), MIN(DATEADD(D, -(DATEPART(WEEKDAY, s.pos_date) - 1), s.pos_date)), 107) AS display_date_pos FROM incent_summary s INNER JOIN location l ON s.store_num = l.store_num INNER JOIN region r ON l.region_id = r.region_id WHERE s.pos_date >= DATEADD(day, #period , CONVERT(date, GETDATE())) AND s.pos_date <= GETDATE() GROUP BY DATEPART (#by, s.pos_date), l.region_id, r.region_desc ORDER BY DATEPART (#by, pos_date), l.region_id, r.region_desc The issue is when the #period is a week, #by is day, and the statement MIN(DATEADD(D, -(DATEPART(WEEKDAY, s.pos_date) - 1), s.pos_date)) AS date_pos returns the same day for all the 7 days. Sample output when #period = year and #by = week: region_id region_name date_pos display_date_pos --------------------------------------------------------------------- 34 Region 43 2019-12-29 00:00:00.000 Dec 29, 2019 50 Region 22 2019-12-29 00:00:00.000 Dec 29, 2019 34 Region 43 2020-01-05 00:00:00.000 Jan 05, 2020 50 Region 22 2020-01-05 00:00:00.000 Jan 05, 2020 34 Region 43 2020-01-12 00:00:00.000 Jan 12, 2020 50 Region 22 2020-01-12 00:00:00.000 Jan 12, 2020 34 Region 43 2020-01-19 00:00:00.000 Jan 19, 2020 50 Region 22 2020-01-19 00:00:00.000 Jan 19, 2020 34 Region 43 2020-01-26 00:00:00.000 Jan 26, 2020 50 Region 22 2020-01-26 00:00:00.000 Jan 26, 2020 Sample output when #period = week and #by = day: region_id region_name date_pos display_date_pos -------------------------------------------------------------------- 34 Region 43 2020-07-12 00:00:00.000 Jul 12, 2020 50 Region 22 2020-07-12 00:00:00.000 Jul 12, 2020 34 Region 43 2020-07-12 00:00:00.000 Jul 12, 2020 50 Region 22 2020-07-12 00:00:00.000 Jul 12, 2020 34 Region 43 2020-07-19 00:00:00.000 Jul 19, 2020 50 Region 22 2020-07-19 00:00:00.000 Jul 19, 2020 34 Region 43 2020-07-19 00:00:00.000 Jul 19, 2020 50 Region 22 2020-07-19 00:00:00.000 Jul 19, 2020 34 Region 43 2020-07-19 00:00:00.000 Jul 19, 2020 50 Region 22 2020-07-19 00:00:00.000 Jul 19, 2020 How can I fix this?
SELECT DATEADD(D, -(DATEPART(WEEKDAY, s.pos_date) - 1), s.pos_date) Will always return the first day of the week because the logic is: "subtract from my date the number of days from sunday and add 1." Sunday: 1 - 1 + 1 = 1 = Sunday Monday: 2 - 2 + 1 = 1 = Sunday . . . Saturday: 7 - 7 + 1 = Sunday That's fine when you want the first Sunday of the year/month/whatever. But the first sunday of every week is always... sunday. But in this case you really just need to take the MIN(s.pos_date) if #period is week. There's probably some crazy way to do this in a single statement using quaternions or something else super mathy, but it's easiest to just use a case statement: MIN ( CASE WHEN '#by' = 'day' THEN s.pos_date ELSE DATEADD(D, -(DATEPART(WEEKDAY, s.pos_date) - 1), s.pos_date) END ) I'm not a C# programmer so I can't tell you the exact way to make sure the string DAY is passed to the query as "DAY" but I'm sure you can handle that part. ALSO IMPORTANT The datepart "day" is day of month, so if you're going to possibly have a span greater than one month (but under a year), use dayofyear.
Insert into Table after processing Week Number SQL
I have the following table: Date Number ----------------------------- 2018-01-01 10 2018-01-04 5 2018-01-10 10 2018-01-20 5 2018-02-01 8 2018-02-03 2 2018-02-28 10 I want to have the following result: WeekNumber Year SumOfNumber ----------------------------------------------- 1 2018 15 2 2018 10 3 2018 5 5 2018 10 9 2018 10 Week day Start from Monday to Sunday. The result should be inserted into a Table. Does anyone have an idea for this? Thank you
Use ISO_WEEK in DATEPART() function select DATEPART(ISO_WEEK, date) WeekNumber, year(date) Year, sum(Number) SumOfNumber from table group by DATEPART(ISO_WEEK, date), year(date)
Pulling Quarters from date range
Please help me how can I break a date range into quarters of a year.Ex date range 1st Jan 2012 to 31st October 2013 should give me a result set of all 8 quarters.The results should be in following format, I am using SQL server 2008 : Quarter Month start Month end 1 Jan-12 Mar-12 2 Apr-12 Jun-12 3 Jul-12 Sep-12 4 Oct-12 Dec-12 1 Jan-13 Mar-13 2 Apr-13 Jun-13 3 Jul-13 Sep-13 4 Oct-13 Oct-13
You'd need to look at the DATEPART(QUARTER,date) and break them up that way. Something akin to this: select datepart(year, dateTarget) as theYear, num as theQuarter, min(dateTarget) as startDate, max(dateTarget) as endDate from numbers join dates on datepart(quarter, dateper) = num where num between 1 and 4 group by datepart(year, dateTarget),num Where the dates table is the table you're looking at, and numbers is, well, a numbers table (something I find pretty useful to just have around).
This gives you quarter start dates for 12 quarrters: with calendar as ( select --DATEFROMPARTS(year(getdate()),1,1) as [start], convert(datetime, convert(char(4), year(getdate()))+'0101') as [start], qtrsBack = 1 union all select dateadd(mm,-3,[start]), qtrsBack+1 from calendar where qtrsback < 12 ) select * from calendar producing: start qtrsBack ---------- ----------- 2013-01-01 1 2012-10-01 2 2012-07-01 3 2012-04-01 4 2012-01-01 5 2011-10-01 6 2011-07-01 7 2011-04-01 8 2011-01-01 9 2010-10-01 10 2010-07-01 11 2010-04-01 12