Split date into month and year based on number of months passed in stored procedure into a temp table - sql

I have a stored procedure, where takes number of numbers as a parameter. I do my query with where clause like this
select salesrepid, month(salesdate), year(salesdate), salespercentage
from SalesRecords
where salesdate >= DATEADD(month, -#NumberOfMonths, getdate())
So for example, if #NumberOFmonths passed = 3 and based on todays date,
It should bring, september 9, october 10 and november 11 in my resultset. My query brings it but request is I need to return null for those salesrep who doesnt have a value for a month,
for example:
salerepid month year salespercentage
232 9 2020 80%
232 10 2020 null
232 11 2020 90%
how can I achieve this ? Right now the query brings back only two records and does not bring october data as no value is there, but i want it to return october with null value.

If I follow you correctly, you can generate all start of months within the target interval, and cross join that with the table to generate all possible combinations. Then you can bring the table with a left join:
with all_dates as (
select datefromparts(year(getdate()), month(getdate()), 1) salesdate, 0 lvl
union all
select dateadd(month, - lvl - 1, salesdate), lvl + 1
from all_dates
where lvl < #NumberOfMonths
)
select r.salesrepid, d.salesdate , s.salespercentage
from all_dates d
cross join (select distinct salesrepid from salesrecords) r
left join salesrecord s
on s.salesrepid = r.salesrepid
and s.salesdate >= d.salesdate
and s.salesdate < dateadd(month, 1, d.salesdate )
Your original query and result imply that there is at most one record per sales rep and month, so this works under the same assumption. If that's not the case (which would somehow make more sense), you would need aggregation in the outer query.

Declare #numberofmonths int = 3;
with all_dates as (
select datefromparts(year(getdate()), month(getdate()), 1) dt, 0 lvl
union all
select dateadd(month, - lvl - 1, dt), lvl + 1
from all_dates
where lvl < 3
)
select * from all_dates
This gives me following result:
2020-11-01 0
2020-10-01 1
2020-08-01 2
2020-05-01 3
I want only:
2020-11-01 0
2020-10-01 1
2020-09-01 2

Related

Splitting a multi year contract row into multiple rows covering 1 year/365 days from start to end date

I have contract no 1 covering from 8/1/2022 to 7/31/2024, I need all the active contracts to split into multiple rows covering 365 days/year per row.
Existing table - EX- contract 1 is from 8/1/2022 to 7/31/2024,
I need contract 1 to be split from 8/1/2022 to 7/31/2023 in row1 and in ro2 contract 1 to be split from 8/1/2023 to 7/31/2024(2nd year of contract) like wise third year in row3 from 8/1/2024 to 7/31/2025.
Contract
start_date
End_date
1
8/1/2022
7/31/2024
23
8/7/2022
8/8/2023
26
6/8/2022
6/9/2025
I need above table to split like
S.No
Contract
start_date
End_date
1
1
8/1/2022
7/31/2023
2
1
8/1/2023
7/31/2024
3
23
8/7/2022
8/8/2023
4
26
6/8/2022
6/7/2023
5
26
6/8/2023
6/7/2024
6
26
6/8/2024
6/7/2025
The following will determine the maximum possible years for any contract, generate a sequence of numbers 0..N, calculate each potential full-year for each contract, and then trim the results to reflect the actual contract end-date.
-- Upper bound on number of years
DECLARE #MaxYears INT = (
SELECT 1 + MAX(DATEDIFF(year, Start_date, End_date))
FROM COntracts
)
;WITH NumTable AS (
SELECT 0 AS N
UNION ALL
SELECT N + 1
FROM NumTable
WHERE N < #Maxyears
)
SELECT
C.Contract,
D.Start_Date,
CASE WHEN D.End_date < C.End_date THEN D.End_date ELSE C.End_date END AS End_date
FROM Contracts C
CROSS APPLY (
SELECT
DATEADD(year, N.N, C.Start_date) AS Start_date,
DATEADD(day, -1, DATEADD(year, N.N + 1, C.Start_date)) AS End_date
FROM NumTable N
) D
WHERE D.Start_date <= C.End_date
ORDER BY C.Contract, D.Start_Date
If using SQL Server 2022, the NumTable CTE can be replaced with a GENERATE_SERIES() function, and the CASE expression can be replaced with LEAST().
SELECT C.Contract, D.Start_Date, LEAST(D.End_date, C.End_date) AS End_date
FROM Contracts C
CROSS APPLY (
SELECT
DATEADD(year, S.Value, C.Start_date) AS Start_date,
DATEADD(day, -1, DATEADD(year, S.Value + 1, C.Start_date)) AS End_date
FROM GENERATE_SERIES(0, DATEDIFF(year, C.Start_date, C.End_date)) S
) D
WHERE D.Start_date <= C.End_date
ORDER BY C.Contract, D.Start_Date
Results:
Contract
Start_Date
End_date
1
2022-08-01
2023-07-31
1
2023-08-01
2024-07-31
23
2022-08-07
2023-08-06
23
2023-08-07
2023-08-08
26
2022-06-08
2023-06-07
26
2023-06-08
2024-06-07
26
2024-06-08
2025-06-07
26
2025-06-08
2025-06-09
The above is slightly different from OP requested results, but I believe this to be correct based on the stated requirements.
See this db<>fiddle.
Edge case: If a contract starts on 29 February of a leap year, the calculations will define the start if each following year as either the 28th or 29th with a potential for a later contract year having a 366 day period from 28 February through 28 February of a later leap year.
You may try the following recursive CTE:
with cte as
(
select Contract, start_date sd, end_date ed, DATEADD(year, 1, start_date) new_ed
from table_name
where end_date >= GETDATE() -- get only active contracts
union all
select Contract, new_ed, ed, DATEADD(year, 1, new_ed)
from cte
where new_ed < DATEADD(day, -1, ed)
)
select ROW_NUMBER() over (order by Contract, sd) [S.No],
Contract,
sd start_date,
iif(YEAR(ed) = YEAR(new_ed), ed, DATEADD(day, -1, new_ed)) end_date
from cte
order by Contract, sd
See demo

MS-SQL how to add missing month in a table values

I have a table with the following entries,
ID
date
Frequency
1
'2012-04-30'
5
1
'2012-06-30'
4
1
'2012-07-31'
25
2
'2012-04-30'
7
2
'2012-05-31'
4
2
'2012-06-30'
1
2
'2012-07-31'
6
I need to add missing month and the date which gets added should be the last date of that month with frequency value as 0.
The expected output is
ID
date
Frequency
1
'2012-04-30'
5
1
'2012-05-31'
0
1
'2012-06-30'
4
1
'2012-07-31'
25
2
'2012-04-30'
7
2
'2012-05-31'
4
2
'2012-06-30'
1
2
'2012-07-31'
6
I need to add missing month and the date which gets added should be the last date of that
I would suggest recursive CTEs:
with cte as (
select id, date, frequency,
lead(date) over (partition by id order by date) as next_date
from t
union all
select id, eomonth(date, 1), 0, next_date
from cte
where eomonth(date, 1) < dateadd(day, -1, next_date)
)
select id, date, frequency
from cte
order by id, date;
The anchor part of the CTE calculates the end date for a given row. The recursive part then just keeps adding months to fill in the missing rows (and none if there are none). The use of eomonth(date, 1) is just a handy way of getting the last day of the next month.
Here is a db<>fiddle.
If you have all dates in the table, you can also use cross join to generate the rows and then left join to bring in the existing data:
select i.id, d.date, coalesce(t.frequency, 0) as frequency
from (select distinct id from t) i cross join
(select distinct date from t) d left join
t
on i.id = t.id and d.date = t.date
order by i.id, d.date;
If you have a large amount of data, you can compare performance. This may be a case where a recursive CTE is faster than alternative methods.

For each quarter between two dates, add rows quarter by quarter in SQL SERVER

I have a table, with types int, datetime, datetime:
id start date end date
-- ---------- ----------
1 2019-04-02 2020-09-17
2 2019-08-10 2020-08-10
Here is create/insert:
CREATE TABLE dbo.something
(
id int,
[start date] datetime,
[end date] datetime
);
INSERT dbo.something(id,[start date],[end date])
VALUES(1,'20190402','20200917'),(2,'20190810','20200810');
What is a SQL query that can produce these results:
id Year Quarter
-- ---- ----------
1 2019 2
1 2019 3
1 2019 4
1 2020 1
1 2020 2
1 2020 3
2 2019 3
2 2019 4
2 2020 1
2 2020 2
2 2020 3
Just use a recursive CTE. This version switches to counting quarters from year 0:
with cte as (
select id,
year(start_date) * 4 + datepart(quarter, start_date) - 1 as yyyyq,
year(end_date) * 4 + datepart(quarter, end_date) - 1 as end_yyyyq
from t
union all
select id, yyyyq + 1, end_yyyyq
from cte
where yyyyq < end_yyyyq
)
select id, yyyyq / 4 as year, (yyyyq % 4) + 1 as quarter
from cte;
Here is a db<>fiddle.
If you cannot make another reference table/etc, you can use DATEDIFF (and DATEPART) using quarters, and then some simple date arithmetic.
The logic below is simply to find, for each startdate, the first quarter and then the number of additional quarters to get to the maximum. Then do a SELECT where the additional quarters are added to the startdate, to get each quarter.
The hardest part of the query to understand imo is the WITH numberlist section - all this does is generate a series of integers between 0 and the maximum number of quarters difference. If you already have a numbers table, you can use that instead.
Key code part is below, and here's a full DB_Fiddle with some additional test data.
CREATE TABLE #yourtable (id int, startdate date, enddate date)
INSERT INTO #yourtable (id, startdate, enddate) VALUES
(1, '2019-04-02', '2020-09-17'),
(2, '2019-08-10', '2020-08-20')
; WITH number_list AS
-- list of ints from 0 to maximum number of quarters
(SELECT n
FROM (SELECT ones.n + 10*tens.n AS n
FROM (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) ones(n),
(VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) tens(n)
) AS a
WHERE n <= (SELECT MAX(DATEDIFF(quarter,startdate,enddate)) FROM #yourtable)
)
SELECT id,
YEAR(DATEADD(quarter, number_list.n, startdate)) AS [Year],
DATEPART(quarter, DATEADD(quarter, number_list.n, startdate)) AS [Quarter]
FROM (SELECT id, startdate, DATEDIFF(quarter,startdate,enddate) AS num_additional_quarters FROM #yourtable) yt
CROSS JOIN number_list
WHERE number_list.n <= yt.num_additional_quarters
DROP TABLE #yourtable
First create a date dimension table which contains date, corresponding quarter and year. Then use below query to get the result. Tweak column and table name according to your schema.
with q_date as
(
select 1 as id, '2019-04-02' :: date as start_date, '2020-09-17' :: date as end_date
UNION ALL
select 2 as id, '2019-08-10' :: date as start_date, '2020-08-10' :: date as end_date
)
select qd.id, dd.calendar_year, dd.calendar_quarter_number
from dim_date dd, q_date qd
where dd.date_dmk between qd.start_date and qd.end_date
group by qd.id, dd.calendar_year, dd.calendar_quarter_number
order by qd.id, dd.calendar_year, dd.calendar_quarter_number;

Join table with dates list - include reference on NULLs

More brain freeze moments from me. I'm sure this will be an easy one.
I have two tables. One is a list of part usage by week. This is called TransactionsPerWeek and looks like this:
ItemPK xWeek xYear TotalQty
1234 2 2019 65
1234 4 2019 15
1234 5 2019 50
I also have a DateList table that has week numbers and years in it
xWeek xYear
1 2019
2 2019
3 2019
etc.
When I right join the two together on week and year I get
ItemPK xWeek xYear TotalQty
NULL 1 2019 0
1234 2 2019 65
NULL 3 2019 0
1234 4 2019 15
1234 5 2019 50
What I need is to have the ItemPK on every line, even if the TotalQty is 0. So in effect, I need:
ItemPK xWeek xYear TotalQty
1234 1 2019 0
1234 2 2019 65
1234 3 2019 0
1234 4 2019 15
1234 5 2019 50
This is my code...
SELECT itemfk,
dates.year,
dates.week,
isnull(transactionsperweek.TotalQty,0) as TotalQty
from (
SELECT iit.ItemFK,
year(iit.transactiondate) xYear,
datepart(wk,iit.transactiondate) xWeek,
abs(sum(iit.quantity)) TotalQty
from iteminventorytransaction iit
INNER JOIN ItemInventoryTransactionType iitt on ItemInventoryTransactionTypePK = iit.ItemInventoryTransactionTypeFK
where iit.itemfk = 5311
and iit.ItemInventoryTransactionTypeFK in (10,8)
and iit.TransactionDate BETWEEN
-- 1 year up to the sunday of last week
DateAdd(wk,-51,DATEADD(day,-1 - (DATEPART(weekday, GETDATE()) + ##DATEFIRST - 2) % 7,GETDATE()))
AND DATEADD(day,-1 - (DATEPART(weekday, GETDATE()) + ##DATEFIRST - 2) % 7,GETDATE())
AND Quantity < 0
group by iit.itemfk,
year(iit.transactiondate),
datepart(wk,iit.transactiondate)
) transactionsPerWeek
RIGHT JOIN (
select year,
week
from DatesList
where date > DateAdd(wk,-51,DATEADD(day,-1 - (DATEPART(weekday, GETDATE()) + ##DATEFIRST - 2) % 7,GETDATE()))
AND date < DATEADD(day,-1 - (DATEPART(weekday, GETDATE()) + ##DATEFIRST - 2) % 7,GETDATE())
group by year,
week
) Dates ON dates.week = transactionsPerWeek.xWeek
AND dates.year = transactionsPerWeek.xYear
where week not in (52,53)
Hope this is clear enough. Thanks in advance.
You can use recursive cte :
with cte as (
select 1 as id, max(xWeek) as maxwk
from TransactionsPerWeek
union all
select id + 1, maxwk
from cte c
where c.id < maxwk
)
select coalesce(wk.ItemPK, wk1.ItemPK) as ItemPK, c.id as xWeek, wk.xYear, wk.TotalQty
from cte c left join
TransactionsPerWeek wk
on wk.xWeek = c.id outer apply
( select top (1) wk1.ItemPK
from TransactionsPerWeek wk1
where wk1.xWeek >= c.id and wk1.xWeek is not null
order by wk1.xWeek
) wk1;
Ok, so I did what #larnu suggested and cross joined the item with the dates, then left joined it to the transactionsperweek table and it worked. Thank you.
This is my code now;
SELECT itempk, week, year
, ISNULL(transactionsPerWeek.TotalQty,0) as TotalQty
from item
CROSS JOIN
(
select year, week from DatesList where date >
DateAdd(wk,-51,DATEADD(day,-1 - (DATEPART(weekday, GETDATE()) + ##DATEFIRST - 2) % 7,GETDATE()))
AND date <
DATEADD(day,-1 - (DATEPART(weekday, GETDATE()) + ##DATEFIRST - 2) % 7,GETDATE())
group by year, week
) dates
LEFT JOIN
(
SELECT iit.ItemFK, year(iit.transactiondate) xYear, datepart(wk,iit.transactiondate) xWeek, abs(sum(iit.quantity)) TotalQty from iteminventorytransaction iit
INNER JOIN ItemInventoryTransactionType iitt on ItemInventoryTransactionTypePK = iit.ItemInventoryTransactionTypeFK
where iit.itemfk = 5311 and iit.ItemInventoryTransactionTypeFK in (10,8)
and iit.TransactionDate BETWEEN
-- 1 year up to the sunday of last week
DateAdd(wk,-51,DATEADD(day,-1 - (DATEPART(weekday, GETDATE()) + ##DATEFIRST - 2) % 7,GETDATE()))
AND
DATEADD(day,-1 - (DATEPART(weekday, GETDATE()) + ##DATEFIRST - 2) % 7,GETDATE())
AND Quantity < 0
group by iit.itemfk, year(iit.transactiondate), datepart(wk,iit.transactiondate)
) transactionsPerWeek
ON itempk = transactionsperweek.ItemFK and transactionsPerWeek.xYear = dates.year and transactionsPerWeek.xWeek = dates.week
where itempk = 5311
Use a cross join to generate the rows and a left join to bring in the results you already have.
Your question explicitly states that you have two tables. Hence, I don't know what your SQL code is doing, because it is not referencing those tables. So, based on the description:
select i.ItemPK, d.xWeek, d.xYear,
coalesce(TotalQty, 0) as TotalQty
from (select distinct itemPK from TransactionsPerWeek
) i cross join
DateList d left join
TransactionsPerWeek t
on t.itemPK = i.itemPK and
t.xWeek = d.xWeek and
t.xYear = d.xYear;
Of course if the "tables" are really subqueries, then I would recommend using CTEs and still this basic query structure.

Get count of orders created monthly

I'm trying to list the total number of orders for the last 12 rolling months (not including the current month).
This is my query:
Select
Year(CreatedOn)*100+Month(CreatedOn) YearMonth,
Count(*) OrderCount
From Orders
Where DateDiff(MM,CreatedOn,GetUTCDate()) Between 1 And 12
Group By Year(CreatedOn), Month(CreatedOn)
Order By YearMonth
As expected, I am getting the results correctly. However, when there are no orders in a specific month, the month is excluded from the result completely. I would like to show that month with 0. See sample result:
201809 70
201810 8
201811 53
201812 67
201901 15
201902 13
201903 10
201905 12
201908 9
See the missing months 201904, 201906 and 201907. There should be a total of 12 rows.
The query should be executable within a sub-query using For XML Path so that I can get a comma separated list of orders in the last 12 months.
How can I accomplish this?
You need to generate the rows that you want somehow. One method uses a recursive CTE:
with dates as (
select Year(getdate())*100+Month(getdate()) as yearmonth,
1 as n, datefromparts(year(getdate()), month(getdate()), 1) as yyyymm
union all
select year(dateadd(month, -1, yyyymm)) * 100 + month(dateadd(month, -1, yyyymm),
n + 1,
dateadd(month, -1, yyyymm)
from cte
where n < 12
),
q as (
<your query here>
)
select d.yearmonth, coalesce(q.orders, 0) as orders
from dates d left join
q
on d.yearmonth = q.yearmonth;
Check this-
WITH R(N) AS
(
SELECT 1
UNION ALL
SELECT N+1
FROM R
WHERE N < 12
)
SELECT REPLACE(LEFT(CAST (DATEADD(MONTH,DATEDIFF(MONTH,0,(DATEADD(MONTH,-N,GetUTCDate()))),0) AS DATE),7),'-','') AS [YearMonth],ISNULL(o.OrderCount,0) as OrderCount
FROM R A
LEFT JOIN
(
Select
Year(CreatedOn)*100+Month(CreatedOn) YearMonth,
Count(*) OrderCount
From Orders
Where DateDiff(MM,CreatedOn,GetUTCDate()) Between 1 And 12
Group By Year(CreatedOn), Month(CreatedOn)
) O ON O.YearMonth=REPLACE(LEFT(CAST (DATEADD(MONTH,DATEDIFF(MONTH,0,(DATEADD(MONTH,-N,GetUTCDate()))),0) AS DATE),7),'-','')
Order By REPLACE(LEFT(CAST (DATEADD(MONTH,DATEDIFF(MONTH,0,(DATEADD(MONTH,-N,GetUTCDate()))),0) AS DATE),7),'-','');