SQL Server dynamic pivot table with dates as columns using CTE as my table? [duplicate] - sql

This question already has answers here:
SQL Server dynamic PIVOT query?
(9 answers)
Closed 12 months ago.
So I have 36 month worth of data in my CTE, and the database is still active storing new data daily.
In my analysis, I just need 6 months worth of data from getdate()
So my question is that how do I make the month derived from calendar month the column of my table?
So for end of March 2022, this is the view that I should see:
id
name
10/01/21
11/01/21
12/01/21
01/01/22
02/01/22
03/01/2022
1
John
3
0
1
0
0
2
2
Mary
6
1
2
1
1
2
3
Angelo
1
5
3
2
2
0
4
Diane
3
2
0
1
0
6
So for the end of April 2022, this is the view that I should see:
id
name
11/01/21
12/01/21
01/01/22
02/01/22
03/01/2022
04/01/22
1
John
0
1
0
0
2
7
2
Mary
1
2
1
1
2
2
3
Angelo
5
0
0
0
0
3
4
Diane
2
0
1
0
6
4

You can't have a dynamic PIVOT without dynamic SQL. I find it's easiest to break it up into parts.
Get the last 6 months
DECLARE #thisMonth date, #firstMonth date;
SET #thisMonth = DATEFROMPARTS(YEAR(getdate()), MONTH(getdate()), 1);
SET #firstMonth = DATEADD(MONTH, -5, #thisMonth);
;WITH m(m) AS
(
SELECT #firstMonth
UNION ALL
SELECT DATEADD(MONTH, 1, m) FROM m
WHERE m < #thisMonth
)
SELECT m FROM m ORDER BY m DESC;
Output:
m
2022-03-01
2022-02-01
2022-01-01
2021-12-01
2021-11-01
2021-10-01
Figure out what manual query you need. Given this sample data:
CREATE TABLE dbo.JetSales
(
ID int,
Name nvarchar(32),
SalesDate date
);
INSERT dbo.JetSales(ID, Name, SalesDate) VALUES
(1,N'John','20211005'),(1,N'John','20211016'),(1,N'John','20211031'),
(2,N'Mary','20211007'),(2,N'Mary','20211013'),
(3,N'Tank','20211009');
I think you want a query like this (yes, you can accomplish this specific task with PIVOT too, but PIVOT doesn't cover some other scenarios, and it also requires pre-aggregation in this case... so I think conditional aggregation is better):
SELECT ID, Name,
[10/01/2021] = SUM(CASE WHEN SalesDate >= '20211001'
AND SalesDate < '20211101' THEN 1 ELSE 0 END),
[11/01/2021] = SUM(CASE WHEN SalesDate >= '20211101'
AND SalesDate < '20211201' THEN 1 ELSE 0 END),
[12/01/2021] = SUM(CASE WHEN SalesDate >= '20211201'
AND SalesDate < '20220101' THEN 1 ELSE 0 END),
[01/01/2022] = SUM(CASE WHEN SalesDate >= '20220101'
AND SalesDate < '20220201' THEN 1 ELSE 0 END),
[02/01/2022] = SUM(CASE WHEN SalesDate >= '20220201'
AND SalesDate < '20220301' THEN 1 ELSE 0 END),
[03/01/2022] = SUM(CASE WHEN SalesDate >= '20220301'
AND SalesDate < '20220401' THEN 1 ELSE 0 END)
FROM dbo.JetSales AS js GROUP BY ID, Name;
Which you can build as follows:
DECLARE #thisMonth date, #firstMonth date;
SET #thisMonth = DATEFROMPARTS(YEAR(getdate()), MONTH(getdate()), 1);
SET #firstMonth = DATEADD(MONTH, -5, #thisMonth);
DECLARE #sql nvarchar(max) = N'SELECT ID, Name';
;WITH m(m) AS
(
SELECT #firstMonth
UNION ALL
SELECT DATEADD(MONTH, 1, m) FROM m
WHERE m < #thisMonth
)
SELECT #sql += N',
' + QUOTENAME(CONVERT(char(10), m, 101))
+ N' = SUM(CASE WHEN SalesDate >= '
+ QUOTENAME(CONVERT(char(8), m, 112), char(39)) + N'
AND SalesDate < '
+ QUOTENAME(CONVERT(char(8), DATEADD(MONTH, 1, m), 112), char(39))
+ N' THEN 1 ELSE 0 END)'
FROM m;
SET #sql += N'
FROM dbo.JetSales AS js GROUP BY ID, Name;';
SELECT #sql;
EXEC sys.sp_executesql #sql;
Working example: db<>fiddle
If you really want to use PIVOT explicitly, you can, it's just a lot more cumbersome. Here's the query you want to end up with:
;WITH src AS
(
SELECT ID, Name, m = CONVERT(char(10),
DATEFROMPARTS(YEAR(SalesDate), Month(SalesDate), 1), 101)
FROM dbo.JetSales
WHERE SalesDate >= #firstMonth
),
agg AS
(
SELECT ID, Name, m, c = COUNT(*)
FROM src GROUP BY ID, Name, m
)
SELECT ID, Name,
[10/01/2021] = COALESCE([10/01/2021], 0),
[11/01/2021] = COALESCE([11/01/2021], 0),
[12/01/2021] = COALESCE([12/01/2021], 0),
[01/01/2022] = COALESCE([01/01/2022], 0),
[02/01/2022] = COALESCE([02/01/2022], 0),
[03/01/2022] = COALESCE([03/01/2022], 0)
FROM agg PIVOT (SUM(c) FOR m IN (
[10/01/2021],[11/01/2021],[12/01/2021],
[01/01/2022],[02/01/2022],[03/01/2022]
)) AS p;
To get there:
DECLARE #thisMonth date, #firstMonth date;
SET #thisMonth = DATEFROMPARTS(YEAR(getdate()), MONTH(getdate()), 1);
SET #firstMonth = DATEADD(MONTH, -5, #thisMonth);
DECLARE #col1 nvarchar(max) = N'',
#col2 nvarchar(max) = N'',
#sql nvarchar(max) = N';WITH src AS
(
SELECT ID, Name, m = CONVERT(char(10),
DATEFROMPARTS(YEAR(SalesDate), Month(SalesDate), 1), 101)
FROM dbo.JetSales
WHERE SalesDate >= #firstMonth
),
agg AS
(
SELECT ID, Name, m, c = COUNT(*)
FROM src GROUP BY ID, Name, m
)
SELECT ID, Name,';
;WITH m(m) AS
(
SELECT #firstMonth
UNION ALL
SELECT DATEADD(MONTH, 1, m) FROM m
WHERE m < #thisMonth
),
x(x) AS
(
SELECT QUOTENAME(CONVERT(char(10), m, 101)) FROM m
)
SELECT
#col1 += STRING_AGG(CONCAT(N'
', x, N' = COALESCE(', x, ',0)'),N','),
#col2 += STRING_AGG(x, N',
')
FROM x;
SET #sql += #col1 + N'
FROM agg PIVOT (SUM(c) FOR m IN ('
+ #col2 + N'
)) AS p;';
SELECT #sql;
EXEC sys.sp_executesql #sql, N'#firstMonth date', #firstMonth;
Another fiddle here: db<>fiddle

Related

Count of days for rest of month returning incorrect value for EOM date

The below code when run for the last day of the month it is giving me a week day count of 1 when it should be 0 - how can I fix it?
Declare #EndDate DateTime = '03-31-2021'
;WITH mycte AS (
SELECT #EndDate + 1 DateValue
UNION ALL
SELECT DateValue + 1
FROM mycte
WHERE DateValue < EOMONTH(#EndDate)
)
select
count(case when datepart(dw, DateValue) = 1 then 1 end) SunCount
, count(case when datepart(dw, DateValue) = 7 then 1 end) SatCount
, count(case when datepart(dw, DateValue) between 2 and 6 then 1 end) WeekCount
from mycte
Your initial CTE is actually creating a date of 2021-04-01 which is a week day, so that's where your count of 1 is coming from. If you want to restrict the counts to just the month in question, you could add a WHERE clause to your end query like this. This way, you get zeros for all counts.
Declare #EndDate DateTime = '03-31-2021'
;WITH mycte AS (
SELECT #EndDate + 1 DateValue
UNION ALL
SELECT DateValue +1
FROM mycte
WHERE DateValue < EOMONTH(#EndDate)
)
select
count(case when datepart(dw, DateValue) = 1 then 1 end) SunCount
, count(case when datepart(dw, DateValue) = 7 then 1 end) SatCount
, count(case when datepart(dw, DateValue) between 2 and 6 then 1 end) WeekDayCount
from mycte
where DATEPART(MM,DateValue)=DATEPART(MM,#EndDate)

My count CTE returning blanks, how can I get it return as 0?

CTE created to count the number of days left from today's date to end of current month. So my report for today (30 March 2021) did not count tomorrow's date 31 March 2021.
declare #DespatchTo Date = '03-30-2021'
WITH mycte AS
(
SELECT CAST(Convert(date,getdate()) AS DATETIME) DateValue
UNION ALL
SELECT DateValue + 1
FROM mycte
WHERE DateValue < DATEADD(d, -1, DATEADD(m, DATEDIFF(m, 0, #DespatchTo) + 1, 0)) --03-31-2021
)
SELECT SUN.Count as SunCount, SAT.Count as SatCount, WK.Count as WeekCount
FROM
(SELECT count(*) as Count
FROM mycte
WHERE DatePart("w",DateValue) = 1
group by DatePart("w",DateValue))
As SUN,
(SELECT count(*) as Count
FROM mycte
WHERE DatePart("w",DateValue) = 7
group by DatePart("w",DateValue))
As SAT,
(SELECT distinct SUM(COUNT(*)) OVER() AS Count
FROM mycte
WHERE DatePart("w",DateValue) > 1 AND DatePart("w",DateValue) < 7
group by DatePart("w",DateValue))
As WK
Which returns blank/null results. How can I return as 0?
here is what you need to do:
;WITH mycte AS (
SELECT GETDATE() DateValue
UNION ALL
SELECT DateValue + 1
FROM mycte
WHERE DateValue < EOMONTH(GETDATE())
)
select
count(case when datepart(dw, DateValue) = 1 then 1 end) SUN
, count(case when datepart(dw, DateValue) = 7 then 1 end) SAT
, count(case when datepart(dw, DateValue) between 2 and 6 then 1 end) WK
from mycte
if you want to exclude today, you can adjust cte :
;WITH mycte AS (
SELECT GETDATE() + 1 DateValue
WHERE GETDATE() <> EOMONTH(GETDATE())
UNION ALL
SELECT DateValue + 1
FROM mycte
WHERE DateValue < EOMONTH(GETDATE())
)
select
count(case when datepart(dw, DateValue) = 1 then 1 end) SUN
, count(case when datepart(dw, DateValue) = 7 then 1 end) SAT
, count(case when datepart(dw, DateValue) between 2 and 6 then 1 end) WK
from mycte

SQL- Date Diff- # of week in each month between two date periods

Problem: Display in columns the number of weeks in each month between two date periods (out to three months is fine for now). If possible, from the current day (Dynamic)
Where I currently am:
SELECT Q3.[Begin Date]
,Q3.[End Date]
,Q3.Diff_in_Year
,sum(CASE
WHEN Q3.Year_Counter = 0
THEN datediff(mm, Q3.y_start, Q3.y_end) + 1
ELSE 0
END) Y1
,sum(CASE
WHEN Q3.Year_Counter = 1
THEN datediff(mm, Q3.y_start, Q3.y_end) + 1
ELSE 0
END) Y2
,sum(CASE
WHEN Q3.Year_Counter = 2
THEN datediff(mm, Q3.y_start, Q3.y_end) + 1
ELSE 0
END) Y3
,sum(CASE
WHEN Q3.Year_Counter = 3
THEN datediff(mm, Q3.y_start, Q3.y_end) + 1
ELSE 0
END) Y4
,sum(CASE
WHEN Q3.Year_Counter = 4
THEN datediff(mm, Q3.y_start, Q3.y_end) + 1
ELSE 0
END) Y5
,sum(CASE
WHEN Q3.Year_Counter = 5
THEN datediff(mm, Q3.y_start, Q3.y_end) + 1
ELSE 0
END) Y6
,sum(CASE
WHEN Q3.Year_Counter = 6
THEN datediff(mm, Q3.y_start, Q3.y_end) + 1
ELSE 0
END) Y7
,sum(CASE
WHEN Q3.Year_Counter = 7
THEN datediff(mm, Q3.y_start, Q3.y_end) + 1
ELSE 0
END) Y8
,sum(CASE
WHEN Q3.Year_Counter = 8
THEN datediff(mm, Q3.y_start, Q3.y_end) + 1
ELSE 0
END) Y9
,sum(CASE
WHEN Q3.Year_Counter = 9
THEN datediff(mm, Q3.y_start, Q3.y_end) + 1
ELSE 0
END) Y10
FROM (
SELECT Q1.[Begin Date]
,Q1.[End Date]
,Q1.years Diff_in_Year
,Q2.number AS Year_Counter
,(
CASE
WHEN Q2.number = 0
THEN Q1.[Begin Date]
ELSE dateadd(yy, datediff(yy, 0, dateadd(yy, q2.number, q1.[Begin Date])), 0)
END
) AS y_Start
,(
CASE
WHEN ((Q1.years - 1) = Q2.number)
THEN Q1.[End Date]
ELSE DATEADD(yy, DATEDIFF(yy, 0, dateadd(yy, q2.number + 1, q1.[Begin Date]) + 1), - 1)
END
) AS y_End
,Year(Q1.[Begin Date]) + Q2.number YearInYYYY
FROM (
SELECT [Begin Date]
,[End Date]
,DATEDIFF(year, [Begin Date], [End Date]) + 1 AS years
FROM my dates
) Q1
INNER JOIN master..spt_values Q2 ON Q2.type = 'P'
AND Q2.number < Q1.years
) Q3
GROUP BY Q3.[Begin Date]
,Q3.[End Date]
,q3.Diff_in_Year
How the current code works: Given a date range, the number of months in each year between two dates. IE 1/1/2014 - 1/18/2015 would give two columns "2014" and 2015" the value of 2014 is 12 and the value of 2015 is 1 signifying that there are 13 months between the specified dates.
What I am hoping to achieve is something similar to
Start Date End Date Month 1 Month 2 Month 3
-----------------------------------------------------
1/1/2014 3/8/2014 4 4 1
Dynamic SQL solutions aside (search for dynamic pivot in TSQL), I whipped up a couple of answers. Since your question is unclear whether you want weeks or months, I put together a quick one for each.
Months Example Here:
declare #startdate date = '1/1/2014', #enddate date = '3/1/2015'
select p.*
from (
select #startdate as StartDate, #enddate as EndDate, right(convert(varchar,(dateadd(mm,RowID-1,#startdate)),105),4) [Group]
from (
select *, row_number()over(order by name) as RowID
from master..spt_values
) d
where d.RowID <= datediff(mm, #startdate, #enddate)
) t
pivot (
count([Group]) for [Group] in (
[2014],[2015]
)
) p
Weeks Example Here:
declare #startdate date = '1/1/2014', #enddate date = '3/1/2015'
select p.*
from (
select #startdate as StartDate, #enddate as EndDate, right(convert(varchar,(dateadd(ww,RowID-1,#startdate)),105),7) [Group]
from (
select *, row_number()over(order by name) as RowID
from master..spt_values
) d
where d.RowID <= datediff(ww, #startdate, #enddate)
) t
pivot (
count([Group]) for [Group] in (
[01-2014]
, [02-2014]
, [03-2014]
, [04-2014]
, [05-2014]
, [06-2014]
, [07-2014]
, [08-2014]
, [09-2014]
, [10-2014]
, [11-2014]
, [12-2014]
, [01-2015]
, [02-2015]
, [03-2015]
)
) p

sql timesheet count by day for the week

my table looks like this:
select clocktime, for_UID, in1_out0 from timeclockentries
clocktime for_UID in1_out0
2011-08-07 15:13:58.390 user193 1
2011-08-07 21:09:45.093 user193 0
2011-08-09 14:10:00.000 user193 1
2011-08-09 20:10:00.000 user193 0
I want the results to look like (assuming start of week is Saturday), separated by COLUMNS named 'day1', 'day2', etc.... (but for readability, i've typed them out with linefeeds) :
day1 day2 day3
1900-01-01 00:00:00.000 1900-01-01 05:55:46.700 1900-01-01 00:00:00.000
day4 day5 day6
1900-01-01 06:00:00.000 1900-01-01 00:00:00.000 1900-01-01 00:00:00.000
day7
1900-01-01 00:00:00.000
(i'm using sql2005)
below is what i'm using for a single day:
CREATE PROCEDURE [dbo].[sp_gethoursbyday]
#whichforUID varchar(20),
#whichdate datetime
AS
BEGIN
;WITH CTE as(
SELECT
DENSE_RANK() over (Partition by for_UID , in1_out0 Order by clocktime) id,
clocktime,
for_UID,
in1_out0
FROM
kdhcastle.dbo.timeclockentries tc
WHERE
tc.for_UID = #whichforUID
and month(tc.[clocktime]) = month(#whichdate)
and day(tc.[clocktime]) = day(#whichdate)
and year(tc.[clocktime]) = year(#whichdate)
)
SELECT
Cast(cast(sum(
cast(outTime.clocktime as float) - cast(inTime.clocktime as float)
)as datetime) as datetime) as 'hoursbydy'
FROM
CTE inTime
INNER JOIN CTE outTime
ON inTime.for_UID = outTime.for_UID
AND inTime.id = outTime.id
AND inTime.in1_out0 = 1
and outTime.in1_out0 = 0
END
SELECT
SUM(CASE WHEN DayOfWeek = 1 THEN Duration ELSE 0 END) AS Day1,
SUM(CASE WHEN DayOfWeek = 2 THEN Duration ELSE 0 END) AS Day2,
SUM(CASE WHEN DayOfWeek = 3 THEN Duration ELSE 0 END) AS Day3,
SUM(CASE WHEN DayOfWeek = 4 THEN Duration ELSE 0 END) AS Day4,
SUM(CASE WHEN DayOfWeek = 5 THEN Duration ELSE 0 END) AS Day5,
SUM(CASE WHEN DayOfWeek = 6 THEN Duration ELSE 0 END) AS Day6,
SUM(CASE WHEN DayOfWeek = 7 THEN Duration ELSE 0 END) AS Day7
FROM
(
SELECT
DATEDIFF(DAY, '2011 Jan 01', clocktime) % 7 + 1 AS DayOfWeek,
CAST(MAX(clocktime) - MIN(clocktime) AS FLOAT) AS Duration
FROM
yourTable
GROUP BY
for_UID,
DATEDIFF(DAY, '2011 Jan 01', clocktime)
)
AS [data]
This is more verbose but my focus was (a) to avoid repeating expressions and (b) to simulate all of the input parameters intended to be fed to the stored procedure so that the results are filtered on the desired user / date. Note that the #whichdate parameter is reeled back to the preceding Saturday at midnight, regardless of which day of the week it is or what time is associated with it.
Input parameters:
DECLARE #whichdate DATETIME;
SET #whichdate = '2011-08-08T12:34:00';
DECLARE #whichforUID VARCHAR(32);
SET #whichforUID = 'user193';
Body (just comment out the DECLARE #t / INSERT #t lines, and change #t in the first CTE to the real table name:
SET #whichdate = DATEADD(DAY, -DATEPART(WEEKDAY, #whichdate), #whichdate);
SET #whichdate = DATEADD(DAY, 0, DATEDIFF(DAY, 0, #whichdate));
DECLARE #t TABLE(clocktime DATETIME, for_UID VARCHAR(32), in1_out0 BIT);
INSERT #t SELECT '2011-08-07 15:13:58.390','user193',1
UNION ALL SELECT '2011-08-07 21:09:45.093','user193',0
UNION ALL SELECT '2011-08-09 14:10:00.000','user193',1
UNION ALL SELECT '2011-08-09 20:10:00.000','user193',0;
WITH s(dw, ct, in1_out0) AS
(
SELECT 1 + (DATEDIFF(DAY, '2011-01-01', clocktime) % 7),
clocktime, in1_out0 FROM #t
where for_UID = #whichforUID
AND clocktime >= #whichdate
AND clocktime < DATEADD(DAY, 7, #whichdate)
),
d(dw, min_ct, max_ct) AS
(
SELECT dw,
MIN(CASE WHEN in1_out0 = 1 THEN ct ELSE NULL END),
MAX(CASE WHEN in1_out0 = 0 THEN ct ELSE NULL END)
FROM s GROUP BY dw
),
x AS
(
SELECT d = DATEADD(MILLISECOND, DATEDIFF(MILLISECOND, min_ct, max_ct), 0),
dw FROM d
),
pvt AS (
SELECT * FROM x PIVOT
(MAX(d) FOR dw IN ([1],[2],[3],[4],[5],[6],[7])) AS p
)
SELECT
day1 = COALESCE([1], '19000101'),
day2 = COALESCE([2], '19000101'),
day3 = COALESCE([3], '19000101'),
day4 = COALESCE([4], '19000101'),
day5 = COALESCE([5], '19000101'),
day6 = COALESCE([6], '19000101'),
day7 = COALESCE([7], '19000101')
FROM pvt;

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

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.