hard tsql problem - how many row values are in a sequential order - sql

lets say I have a table with
date,personid
1/1/2001 1
1/2/2001 3
1/3/2001 2
1/4/2001 2
1/5/2001 5
1/6/2001 5
1/7/2001 6
and I'm going to either update 1/2/2001 or 1/5/2001 with personid 2 but before I can update I have to make sure it passes a rule that says you can't have a person three days in a row.
how can i solve this in a mssql stored procedure?
update: It also need to solve this layout as well where I'd update 1/5/2001
date,personid
1/1/2001 1
1/2/2001 3
1/3/2001 2
1/4/2001 2
1/5/2001 1
1/6/2001 2
1/7/2001 2
1/8/2001 5
1/9/2001 5
1/10/2001 6

I've assumed that date is unique let me know if that is not the case!
DECLARE #basedata TABLE ([date] UNIQUE DATE,personid INT)
INSERT INTO #basedata
SELECT GETDATE()+1, 2 union all
SELECT GETDATE()+2, 3 union all
SELECT GETDATE()+3, 2 union all
SELECT GETDATE()+4, 2 union all
SELECT GETDATE()+5, 5 union all
SELECT GETDATE()+6, 5 union all
SELECT GETDATE()+7, 6
DECLARE #date date = GETDATE()+5
DECLARE #personid int = 2
;WITH T AS
(
SELECT TOP 2 [date],personid
FROM #basedata
WHERE [date] < #date
ORDER BY [date] DESC
UNION ALL
SELECT #date, #personid
UNION ALL
SELECT TOP 2 [date],personid
FROM #basedata
WHERE [date] > #date
ORDER BY [date]
),T2 AS
(
SELECT *,
ROW_NUMBER() OVER (ORDER BY [date]) -
ROW_NUMBER() OVER (PARTITION BY personid ORDER BY [date]) AS Grp
FROM T
)
SELECT COUNT(*) /*Will return a result if that date/personid
would cause a sequence of 3*/
FROM T2
GROUP BY personid,Grp
HAVING COUNT(*) >=3

There is a third case not listed, it is the between date case. I included it in the solution below.
The output is
PersonId TrackDate UnallowedBefore UnallowedAfter
----------- ---------- --------------- --------------
2 01/04/2001 01/02/2001 01/05/2001
5 01/06/2001 01/04/2001 01/07/2001
6 01/08/2001 01/08/2001 01/08/2001
USE tempdb
GO
IF OBJECT_ID('PersonDates') IS NOT NULL DROP TABLE PersonDates
CREATE TABLE PersonDates
(
PersonId int NOT NULL,
TrackDate datetime NOT NULL
)
INSERT INTO PersonDates
(
TrackDate,
PersonId
)
SELECT '1/1/2001', 1
UNION ALL
SELECT '1/2/2001', 3
UNION ALL
SELECT '1/3/2001', 2
UNION ALL
SELECT '1/4/2001', 2
UNION ALL
SELECT '1/5/2001', 5
UNION ALL
SELECT '1/6/2001', 5
UNION ALL
SELECT '1/7/2001', 6
UNION ALL
SELECT '1/8/2001', 2
UNION ALL
SELECT '1/9/2001', 6
SELECT
P.PersonId,
TrackDate = CONVERT(varchar(10), DATEADD(day, 1, P.TrackDate), 101),
T.UnallowedBefore,
T.UnallowedAfter
FROM
PersonDates P
CROSS APPLY
(
SELECT TOP 1
UnallowedAfter = CASE
WHEN DATEDIFF(day, P.TrackDate, TrackDate) = 1
THEN CONVERT(varchar(10), DATEADD(day, 1, TrackDate), 101)
ELSE CONVERT(varchar(10), DATEADD(day, -1, TrackDate), 101)
END,
UnallowedBefore = CASE
WHEN DATEDIFF(day, P.TrackDate, TrackDate) = 1
THEN CONVERT(varchar(10), DATEADD(day, -2, TrackDate), 101)
ELSE CONVERT(varchar(10), DATEADD(day, -1, TrackDate), 101)
END
FROM
PersonDates
WHERE
PersonId = P.PersonId
AND
DATEDIFF(day, P.TrackDate, TrackDate) IN (1,2)
) T

SET #TargetDate = '1/2/2001'
SELECT #ForwardCount = COUNT(*) FROM table WHERE ([date] BETWEEN #TargetDate AND DATEADD(dd, 2, #TargetDate)) WHERE PersonID = #PersonID
SELECT #BackwardCount = COUNT(*) FROM table WHERE ([date] BETWEEN #TargetDate AND DATEADD(dd, -2, #TargetDate)) WHERE PersonID = #PersonID
SELECT #BracketCount = COUNT(*) FROM table WHERE ([date] BETWEEN DATEADD(dd, -1, #TargetDate) AND DATEADD(dd, 1, #TargetDate)) WHERE PersonID = #PersonID
IF (#ForwardCount < 2) AND (#BackwardCount < 2) AND (#BracketCount < 2)
BEGIN
-- Do your update here
END

Here's my parametrised solution:
WITH nearby AS (
SELECT
date,
personid = CASE date WHEN #date THEN #personid ELSE personid END
FROM atable
WHERE date BETWEEN DATEADD(day, -#MaxInARow, #date)
AND DATEADD(day, #MaxInARow, #date)
),
nearbyGroups AS (
SELECT
*,
Grp = DATEDIFF(day, 0, date) -
ROW_NUMBER() OVER (PARTITION BY personid ORDER BY date)
FROM nearby
)
UPDATE atable
SET personid = #personid
WHERE date = #date
AND NOT EXISTS (
SELECT Grp
FROM nearbyGroups
GROUP BY Grp
HAVING COUNT(*) > #MaxInARow
)
#date represents the date for which the personid column should be updated. #personid is the new value to be stored. #MaxInARow is the maximum number of days in a row for which the same personid is allowed to be stored.

Related

Keep last n business days records from today date in SQL Server

How can we keep last n business days records from today date in this table:
Suppose n = 7
Sample Data:
Table1:
Date
----------
2021-11-29
2021-11-30
2021-12-01
2021-12-02
2021-12-03
2021-12-04
2021-12-05
2021-12-06
2021-12-07
2021-12-08
2021-12-09
2021-12-10
2021-12-11
2021-12-12
2021-12-13
Based on this table data we want output like below. It should delete all the rows before the 03-Dec or data for last 7 business days.
Date
-------
2021-12-03
2021-12-06
2021-12-07
2021-12-08
2021-12-09
2021-12-10
2021-12-13
Note: It's fine if we keep data for Saturday, Sunday in between business days.
I tried this query
DECLARE #n INT = 7
SELECT * FROM Table1
WHERE [date] < Dateadd(day, -((#n + (#n / 5) * 2)), Getdate())
but Saturday, Sunday logic doesn't fit here with my logic. Please suggest better approach.
You can get the 7th working day from today as
select top(1) cast(dateadd(d, -n + 1, getdate()) as date) d
from (
select n
, sum (case when datename(dw, dateadd(d, -n + 1, getdate())) not in ('Sunday', 'Saturday') then 1 end) over(order by n) wdn
from (
values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11)
)t0(n)
) t
where wdn = 7
order by n;
Generally using on-the-fly tally for a #n -th day
declare #n int = 24;
with t0(n) as (
select n
from (
values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10)
) t(n)
), tally as (
select top(#n + (#n/5 +1)*2) row_number() over(order by t1.n) n
from t0 t1, t0 t2, t0 t3
)
select top(1) cast(dateadd(d, -n + 1, getdate()) as date) d
from (
select n
, sum (case when datename(dw, dateadd(d, -n + 1, getdate())) not in ('Sunday', 'Saturday') then 1 end) over(order by n) wdn
from tally
) t
where wdn = #n
order by n;
You can use CTE to mark target dates and then delete all the others from the table as follows:
; With CTE As (
Select [Date], Row_number() Over (Order by [Date] Desc) As Num
From tbl
Where DATEPART(weekday, [Date]) Not In (6,7)
)
Delete From tbl
Where [Date] Not In (Select [Date] From CTE Where Num<=7)
If the number of business days in the table may be less than 7 and you need to bring the total number of days to 7 by adding days off, try this:
Declare #n Int = 7
; With CTE As (
Select [Date], IIF(DATEPART(weekday, [Date]) In (6,7), 0, 1) As IsBusinessDay
From tbl
)
Delete From tbl
Where [Date] Not In (Select Top(#n) [Date] From CTE Order By IsBusinessDay Desc, [Date] Desc)
If there is only one date for each day, you can simply do this:
SELECT TOP 7 [Date] FROM Table1
WHERE
[Date] < GETDATE() AND DATENAME(weekday, [DATE]) NOT IN ('Saturday', 'Sunday')
ORDER BY
[DATE] DESC

Fill up date gap by month

I have table of products and their sales quantity in months.
Product Month Qty
A 2018-01-01 5
A 2018-02-01 3
A 2018-05-01 5
B 2018-08-01 10
B 2018-10-01 12
...
I'd like to first fill in the data gap between each product's min and max dates like below:
Product Month Qty
A 2018-01-01 5
A 2018-02-01 3
A 2018-03-01 0
A 2018-04-01 0
A 2018-05-01 5
B 2018-08-01 10
B 2018-09-01 0
B 2018-10-01 12
...
Then I would need to perform an accumulation of each product's sales quantity by month.
Product Month total_Qty
A 2018-01-01 5
A 2018-02-01 8
A 2018-03-01 8
A 2018-04-01 8
A 2018-05-01 13
B 2018-08-01 10
B 2018-09-01 10
B 2018-10-01 22
...
I fumbled over the "cross join" clause, however it seems to generate some unexpected results for me. Could someone help to give a hint how I can achieve this in SQL?
Thanks a lot in advance.
I think a recursive CTE is a simple way to do this. The code is just:
with cte as (
select product, min(mon) as mon, max(mon) as end_mon
from t
group by product
union all
select product, dateadd(month, 1, mon), end_mon
from cte
where mon < end_mon
)
select cte.product, cte.mon, coalesce(qty, 0) as qty
from cte left join
t
on t.product = cte.product and t.mon = cte.mon;
Here is a db<>fiddle.
Hi i think this example can help you and perform what you excepted :
CREATE TABLE #MyTable
(Product varchar(10),
ProductMonth DATETIME,
Qty int
);
GO
CREATE TABLE #MyTableTempDate
(
FullMonth DATETIME
);
GO
INSERT INTO #MyTable
SELECT 'A', '2019-01-01', 214
UNION
SELECT 'A', '2019-02-01', 4
UNION
SELECT 'A', '2019-03-01', 50
UNION
SELECT 'B', '2019-01-01', 214
UNION
SELECT 'B', '2019-02-01', 10
UNION
SELECT 'C', '2019-04-01', 150
INSERT INTO #MyTableTempDate
SELECT '2019-01-01'
UNION
SELECT '2019-02-01'
UNION
SELECT '2019-03-01'
UNION
SELECT '2019-04-01'
UNION
SELECT '2019-05-01'
UNION
SELECT '2019-06-01'
UNION
SELECT '2019-07-01';
------------- FOR NEWER SQL SERVER VERSION > 2005
WITH MyCTE AS
(
SELECT T.Product, T.ProductMonth AS 'MMonth', T.Qty
FROM #MyTable T
UNION
SELECT T.Product, TD.FullMonth AS 'MMonth', 0 AS 'Qty'
FROM #MyTable T, #MyTableTempDate TD
WHERE NOT EXISTS (SELECT 1 FROM #MyTable TT WHERE TT.Product = T.Product AND TD.FullMonth = TT.ProductMonth)
)
-- SELECT * FROM MyCTE;
SELECT Product, MMonth, Qty, SUM( Qty) OVER(PARTITION BY Product ORDER BY Product
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) as 'TotalQty'
FROM MyCTE
ORDER BY Product, MMonth ASC;
DROP TABLE #MyTable
DROP TABLE #MyTableTempDate
I have other way to perform this in lower SQL Server Version (like 2005 and lower)
It's a SELECT on SELECT if it's your case let me know and i provide some other example.
You can create the months with a recursive CTE
DECLARE #MyTable TABLE
(
ProductID CHAR(1),
Date DATE,
Amount INT
)
INSERT INTO #MyTable
VALUES
('A','2018-01-01', 5),
('A','2018-02-01', 3),
('A','2018-05-01', 5),
('B','2018-08-01', 10),
('B','2018-10-01', 12)
DECLARE #StartDate DATE
DECLARE #EndDate DATE
SELECT #StartDate = MIN(Date), #EndDate = MAX(Date) FROM #MyTable
;WITH dates AS (
SELECT #StartDate AS Date
UNION ALL
SELECT DATEADD(Month, 1, Date)
FROM dates
WHERE Date < #EndDate
)
SELECT A.ProductID, d.Date, COALESCE(Amount,0) AS Amount, COALESCE(SUM(Amount) OVER(PARTITION BY A.ProductID ORDER BY A.ProductID, d.Date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW),0) AS Total
FROM
(
SELECT ProductID, MIN(date) as DateStart, MAX(date) as DateEnd
FROM #MyTable
GROUP BY ProductID -- As I read in your comments that you need different min and max dates per product
) A
JOIN dates d ON d.Date >= A.DateStart AND d.Date <= A.DateEnd
LEFT JOIN #MyTable T ON A.ProductID = T.ProductID AND T.Date = d.Date
ORDER BY A.ProductID, d.Date
Try this below
IF OBJECT_ID('tempdb..#Temp') IS NOT NULL
DROP TABLE #Temp
;WITH CTE(Product,[Month],Qty)
AS
(
SELECT 'A','2018-01-01', 5 UNION ALL
SELECT 'A','2018-02-01', 3 UNION ALL
SELECT 'A','2018-05-01', 5 UNION ALL
SELECT 'B','2018-08-01', 10 UNION ALL
SELECT 'D','2018-10-01', 12
)
SELECT ct.Product,[MonthDays],ct.Qty
INTO #Temp
FROM
(
SELECT c.Product,[Month],
ISNULL(Qty,0) AS Qty
FROM CTE c
)ct
RIGHT JOIN
(
SELECT -- This code is to get month data
CONVERT(VARCHAR(10),'2018-'+ RIGHT('00'+CAST(MONTH(DATEADD(MM, s.number, CONVERT(DATETIME, 0)))AS VARCHAR),2) +'-01',120) AS [MonthDays]
FROM master.dbo.spt_values s
WHERE [type] = 'P' AND s.number BETWEEN 0 AND 11
)DT
ON dt.[MonthDays] = ct.[Month]
SELECT
MAX(Product)OVER(ORDER BY [MonthDays])AS Product,
[MonthDays],
ISNULL(Qty,0) Qty,
SUM(ISNULL(Qty,0))OVER(ORDER BY [MonthDays]) As SumQty
FROM #Temp
Result
Product MonthDays Qty SumQty
------------------------------
A 2018-01-01 5 5
A 2018-02-01 3 8
A 2018-03-01 0 8
A 2018-04-01 0 8
A 2018-05-01 5 13
A 2018-06-01 0 13
A 2018-07-01 0 13
B 2018-08-01 10 23
B 2018-09-01 0 23
D 2018-10-01 12 35
D 2018-11-01 0 35
D 2018-12-01 0 35
First of all, i would divide month and year to get easier with statistics.
I will give you an example query, not based on your table but still helpful.
--here i create the table that will be used as calendar
Create Table MA_MonthYears (
Month int not null ,
year int not null
PRIMARY KEY ( month, year) )
--/////////////////
-- here i'm creating a procedure to fill the ma_monthyears table
declare #month as int
declare #year as int
set #month = 1
set #year = 2015
while ( #year != 2099 )
begin
insert into MA_MonthYears(Month, year)
select #month, #year
if #month < 12
set #month=#month+1
else
set #month=1
if #month = 1
set #year = #year + 1
end
--/////////////////
--here you are the possible result you are looking for
select SUM(Ma_saledocdetail.taxableamount) as Sold, MA_MonthYears.month , MA_MonthYears.year , item
from MA_MonthYears left outer join MA_SaleDocDetail on year(MA_SaleDocDetail.DocumentDate) = MA_MonthYears.year
and Month(ma_saledocdetail.documentdate) = MA_MonthYears.Month
group by MA_SaleDocDetail.Item, MA_MonthYears.year , MA_MonthYears.month
order by MA_MonthYears.year , MA_MonthYears.month

how to find out days from another table

DECLARE #Daystaken TABLE(
Application VARCHAR(20),
StatusId VARCHAR(2),
Flag INT,
Createddate Datetime)
INSERT INTO #Daystaken (CreatedDate)VALUES
('2015-03-06 17:59:59.410'),
('2015-03-02 17:59:59.410')
select DATEDIFF(DD,MIN(CreatedDate),GETDATE())D from #Daystaken
DECLARE #holiday TABLE(holiday Datetime)
INSERT INTO #holiday ( holiday )VALUES
( '2014-04-06 17:59:59.410'),
( '2014-06-06 17:59:59.410'),
( '2015-05-05 17:59:59.410'),
( '2015-05-01 17:59:59.410'),
( '2013-01-06 17:59:59.410'),
( '2015-02-03 17:59:59.410'),
( '2011-02-01 17:59:59.410')
i have got days count from DaysTaken table Now i need to Substract days from Holidays table.For Example daystaken table have 122 days in that days
3 days holidays till present date came from holidays table.So i need to minus the result and Show 119 Days
Will this do:
DECLARE #minDate AS DATE
SELECT #minDate = MIN(Createddate) FROM #Daystaken;
SELECT DATEDIFF(DAY, #minDate, GETDATE()) - COUNT(*) FROM #holiday
WHERE
CAST(holiday AS DATE) >= #minDate
AND CAST(holiday AS DATE) <= CAST(GETDATE() AS DATE)
Here is another one that does not make use of any variable and can be turned easily into an ITVF:
WITH E1(N) AS(
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
),
E2(N) AS(SELECT 1 FROM E1 a CROSS JOIN E1 b),
E4(N) AS(SELECT 1 FROM E2 a CROSS JOIN E2 b),
Tally(N) AS(
SELECT TOP (DATEDIFF(DAY, #minDate, GETDATE()))
ROW_NUMBER() OVER(ORDER BY (SELECT NULL))
FROM E4
),
CteMinDate(minDate) AS(
SELECT CAST(MIN(CreatedDate) AS DATE) FROM #Daystaken
)
SELECT
COUNT(*)
FROM Tally t
CROSS JOIN CteMinDate md
WHERE
DATEADD(DAY, N - 1, md.minDate) <= CAST(GETDATE() AS DATE)
AND NOT EXISTS(
SELECT 1
FROM #holiday
WHERE CAST(holiday AS DATE) = DATEADD(DAY, N - 1, md.minDate)
)

Work out how many days it took from one status to another : SQL

Please feast your eyes on this current structure of our DB.
Our DBA is currently away for the next two weeks, I have very limited SQL knowledge, I like to stay with the UI and middle tier.
What we are trying to figure out is how can we do the following, we need to write a query to calculate the average period (in days) all commissions have taken to transition from ‘Verified’ to ‘Paid’ for a single dealer, currently the status are
Created
Verified
Rejected
Awaiting Payment
Paid
Refunded
I think this query needs to aim directly at the Commission History Table?
I'm not sure how I would go about writing such query due to the fact my knowledge on SQL is limited...
Any help would be great.
Here's a method to achieve what you're after, although it might not be the most efficient. It seems to me that it's more of a one off query you are looking to run, rather than something that you're going to run on a frequent enough to impact database performance.
Test Table Setup:
CREATE TABLE Commission
(
CommissionId INT,
DealerId INT
)
CREATE TABLE CommissionHistory
(
CommissionId INT,
ActionDate DATETIME,
NewPaymentStatusId INT
)
Insert Dummy Data - 5 Commissions for 1 Dealer:
INSERT INTO dbo.Commission
( CommissionId ,
DealerId
)
VALUES ( 1 , 1 ),
( 2 , 1 ),
( 3 , 1 ),
( 4 , 1 ),
( 5 , 1 ),
INSERT INTO dbo.CommissionHistory
( CommissionId ,
ActionDate ,
NewPaymentStatusId
)
VALUES ( 1 , GETDATE() -25, 1 ),
( 1 , GETDATE() -21, 2 ),
( 1 , GETDATE() -18, 3 ),
( 1 , GETDATE() -16, 4 ),
( 1 , GETDATE() -5, 5 ),
( 2 , GETDATE() -10, 1 ),
( 2 , GETDATE() -9, 2 ),
( 2 , GETDATE() -8, 3 ),
( 2 , GETDATE() -7, 4 ),
( 2 , GETDATE() -6, 5 ),
( 3 , GETDATE() -10, 1 ),
( 3 , GETDATE() -8, 2 ),
( 3 , GETDATE() -6, 3 ),
( 3 , GETDATE() -4, 4 ),
( 3 , GETDATE() -2, 5 ),
( 3 , GETDATE() -25, 6 ),
( 4 , GETDATE() -10, 1 ),
( 4 , GETDATE() -7, 2 ),
( 4 , GETDATE() -6, 3 ),
( 4 , GETDATE() -4, 4 ),
( 4 , GETDATE() -1, 5 ),
( 5 , GETDATE() -1, 1 ),
( 5 , GETDATE() -1, 2 )
So with the dummy data, Commissions 1, 2 &, 4 are classified as valid records as they have status 2 and 5. 3 is excluded as it is refunded and 5 is excluded as it's not paid.
To generate the averages I wrote the below query:
-- set the required dealer id
DECLARE #DealerId INT = 1
-- return all CommissionId's in to a temp table that have statuses 2 and 5, but not 6
SELECT DISTINCT CommissionId
INTO #DealerCommissions
FROM dbo.CommissionHistory t1
WHERE CommissionId IN (SELECT CommissionId
FROM dbo.Commission
WHERE DealerId = #DealerId)
AND NOT EXISTS (SELECT CommissionId
FROM dbo.CommissionHistory t2
WHERE t2.NewPaymentStatusId = 6 AND t2.CommissionId = t1.CommissionId)
AND EXISTS (SELECT CommissionId
FROM dbo.CommissionHistory t2
WHERE t2.NewPaymentStatusId = 2 AND t2.CommissionId = t1.CommissionId)
AND EXISTS (SELECT CommissionId
FROM dbo.CommissionHistory t2
WHERE t2.NewPaymentStatusId = 5 AND t2.CommissionId = t1.CommissionId)
-- use the temp table to return average difference between the MIN & MAX date
;WITH cte AS (
SELECT CommissionId FROM #DealerCommissions
)
SELECT AVG(CAST(DaysToCompletion AS DECIMAL(10,8)))
FROM (
SELECT DATEDIFF(DAY, MIN(ch.ActionDate), MAX(ch.ActionDate)) DaysToCompletion
FROM cte
INNER JOIN dbo.CommissionHistory ch ON ch.CommissionId = cte.CommissionId
GROUP BY ch.CommissionId
) AS averageDays
-- remove temp table
DROP TABLE #DealerCommissions
For every commission in history table you could get the max verified date and min paid date, assuming paid date always later than verified date. Then you can join commission table to group by dealer id to get the average duration in days.
with comm as(
select
commissionid,
max(case NewPamentStatus when 'Verified' then ActionDate else null end) as verified_date,
min(case NewPamentStatus when 'Paid' then ActionDate else null end) as paid_date
--using max or min just incase that same status will be recorded more than one time.
from
CommissionHistory
group by
commistionid
)
select
c.DealerId,
avg(datediff(day,comm.verified_date,comm.paid_date))
from
comm
inner join
commission c
on c.commissionid = comm.commissionid
where
datediff(day,comm.verified_date,comm.paid_date)>0
-- to get rid off the commissions with paid date before the verified date or in same day
group by
c.DealerId

Get all dates in date range in SQL Server

I got this example from one StackOverflow question that was asked but I couldn't get it work according to my need.
WITH DateTable
AS
(
SELECT CAST('20110101' as Date) AS [DATE]
UNION ALL
SELECT DATEADD(dd, 1, [DATE]) FROM DateTable
WHERE DATEADD(dd, 1, [DATE]) < cast('20110131' as Date)
)
SELECT dt.[DATE] FROM [DateTable] dt
Input-
ID | FromDate | ToDate
=============================
1 | 2011-11-10 | 2011-11-12
2 | 2011-12-12 | 2011-12-14
Output -
SN | Dates |
==================
1 | 2011-11-10 |
2 | 2011-11-11 |
3 | 2011-11-12 |
4 | 2011-12-12 |
5 | 2011-12-13 |
6 | 2011-12-14 |
See this code works fine for static dates. But in my case I have a table containing three columns Id, FromDate, ToDate. Now I want to convert each range in the every row to individual dates.
I cannot get the above example to work in case if the range comes from the table and obviously this query has to run for every row in the range table, which is another confusing challenge.
Please help.
With a little help of a numbers table.
declare #T table
(
ID int identity primary key,
FromDate date,
ToDate date
)
insert into #T values
('2011-11-10', '2011-11-12'),
('2011-12-12', '2011-12-14')
select row_number() over(order by D.Dates) as SN,
D.Dates
from #T as T
inner join master..spt_values as N
on N.number between 0 and datediff(day, T.FromDate, T.ToDate)
cross apply (select dateadd(day, N.number, T.FromDate)) as D(Dates)
where N.type ='P'
Try on SE Data
create table Dates (Id int, FromDate date, ToDate date)
insert into Dates values (1, '2011-11-10', '2011-11-12')
insert into Dates values (2, '2011-12-12', '2011-12-14')
with DateTable as
(
select FromDate as Dt, ToDate
from Dates
union all
select DATEADD(D, 1, Dt), ToDate
from DateTable
where DATEADD(D, 1, Dt) <= ToDate
)
select ROW_NUMBER() over (order by Dt) as SN, Dt as Dates
from DateTable
order by Dt
What about this?
--DROP TABLE #Test
CREATE TABLE #Test(ID int, FromDate datetime, ToDate datetime)
INSERT INTO #Test VALUES (1, '2011-11-10', '2011-11-12')
INSERT INTO #Test VALUES (2, '2011-12-12', '2011-12-14')
;
WITH DateTable
AS
(
SELECT ID, FromDate, ToDate, 0 AS Seed FROM #Test
UNION ALL
SELECT ID, DATEADD(dd, 1, FromDate), ToDate, Seed + 1
FROM DateTable
WHERE DATEADD(dd, 1, FromDate) <= ToDate
)
SELECT --*
ROW_NUMBER() OVER (ORDER BY ID, Seed) SN, FromDate AS Dates
FROM DateTable