SQL - Selecting rows with dates before and after column value change - sql

I have a table called test.
In test I have An ID, a value and a date.
The dates are ordered for each ID.
I want to select rows for an ID, before and after a change of value, so the following example table.
RowNum--------ID------- Value -------- Date
1------------------001 ---------1----------- 01/01/2015
2------------------001 ---------1----------- 02/01/2015
3------------------001 ---------1----------- 04/01/2015
4------------------001 ---------1----------- 05/01/2015
5------------------001 ---------1----------- 06/01/2015
6------------------001 ---------1----------- 08/01/2015
7------------------001 ---------0----------- 09/01/2015
8------------------001 ---------0----------- 10/01/2015
9------------------001 ---------0----------- 11/01/2015
10-----------------001 ---------1----------- 12/01/2015
11-----------------001 ---------1----------- 14/01/2015
12------------------002 ---------1----------- 01/01/2015
13------------------002 ---------1----------- 04/01/2015
14------------------002 ---------0----------- 05/01/2015
15------------------002 ---------0----------- 07/01/2015
The result would return rows 6, 7, 9, 10, 13, 14

You could use analytic functions LAG() and LEAD() to access value in preceding and following rows, then check that it does not match value in current row.
SELECT *
FROM (
SELECT RowNum,
ID,
Value,
Date,
LAG(VALUE, 1, VALUE) OVER(ORDER BY RowNum) PrevValue,
LEAD(VALUE, 1, VALUE) OVER(ORDER BY RowNum) NextValue
FROM test)
WHERE PrevValue <> Value
OR NextValue <> Value
Params passed to this functions are
some scalar expression (column name in this case);
offset (1 row before or after);
default value (LAG() will return NULL for first row and LEAD() will return NULL for last row, but they don't seem special in your question, so I used column value as default).

Refer the below one for without using LEAD and LAG:
DECLARE #i INT = 1,
#cnt INT,
#dstvalue INT,
#srcvalue INT
CREATE TABLE #result
(
id INT,
mydate DATE
)
CREATE TABLE #temp1
(
rn INT IDENTITY(1, 1),
id INT,
mydate DATE
)
INSERT INTO #temp1
(id,
mydate)
SELECT id,
mydate
FROM table
ORDER BY id,
mydate
SELECT #cnt = Count(*)
FROM #temp1
SELECT #srcvalue = value
FROM #temp1
WHERE rn = #i
WHILE ( #i <= #cnt )
BEGIN
SELECT #dstvalue = value
FROM #temp1
WHERE rn = #i
IF( #srcvalue = #dstvalue )
BEGIN
SET #i = #i + 1
CONTINUE;
END
ELSE
BEGIN
SET #srcvalue = #dstvalue
INSERT INTO #result
(id,
mydate)
SELECT id,
mydate
FROM #temp
WHERE rn = #i - 1
UNION ALL
SELECT id,
mydate
FROM #temp
WHERE rn = #i
END
SET #i = #i + 1
END
SELECT *
FROM #result

The answer using lag() and lead() is the right answer. If you are using a pre-SQL Server 2012 version, then you can do essentially the same thing using cross apply or a correlated subquery:
select t.*
from test t cross apply
(select top 1 tprev.*
from test tprev
where tprev.date < t.date
order by date desc
) tprev cross apply
(select top 1 tnext.*
from test tnext
where tnext.date > t.date
order by date asc
) tnext
where tprev.value <> tnext.value;

Related

Selecting count of consecutives dates before and after a specified date based on start/end

I'm trying to determine the number of records with consecutive dates (previous record ends on the same date as the start date of the next record) before and after a specified date, and ignore any consecutive records as soon as there is a break in the chain.
If I have the following data:
-- declare vars
DECLARE #dateToCheck date = '2020-09-20'
DECLARE #numRecsBefore int = 0
DECLARE #numRecsAfter int = 0
DECLARE #tempID int
-- temp table
CREATE TABLE #dates
(
[idx] INT IDENTITY(1,1),
[startDate] DATETIME ,
[endDate] DATETIME,
[prevEndDate] DATETIME
)
-- insert temp table
INSERT INTO #dates
( [startDate], [endDate] )
VALUES ( '2020-09-01', '2020-09-04' ),
( '2020-09-04', '2020-09-10' ),
( '2020-09-10', '2020-09-16' ),
( '2020-09-17', '2020-09-19' ),
( '2020-09-19', '2020-09-20' ),
--
( '2020-09-20', '2020-09-23' ),
( '2020-09-25', '2020-09-26' ),
( '2020-09-27', '2020-09-28' ),
( '2020-09-28', '2020-09-30' ),
( '2020-10-01', '2020-09-05' )
-- update with previous records endDate
DECLARE #maxRows int = (SELECT MAX(idx) FROM #dates)
DECLARE #intCount int = 0
WHILE #intCount <= #maxRows
BEGIN
UPDATE #dates SET prevEndDate = (SELECT endDate FROM #dates WHERE idx = (#intCount - 1) ) WHERE idx=#intCount
SET #intCount = #intCount + 1
END
-- clear any breaks in the chain?
-- number of consecutive records before this date
SET #numRecsBefore = (SELECT COUNT(idx) FROM #dates WHERE startDate = prevEndDate AND endDate <= #dateToCheck)
-- number of consecutive records after this date
SET #numRecsAfter = (SELECT COUNT(idx) FROM #dates WHERE startDate = prevEndDate AND endDate >= #dateToCheck)
-- return & clean up
SELECT * FROM #dates
SELECT #numRecsBefore AS numBefore, #numRecsAfter AS numAfter
DROP TABLE #dates
With the specified date being '2020-09-20, I would expect #numRecsBefore = 2 and #numRecsAfter = 1. That is not what I am getting, as its counting all the consecutive records.
There has to be a better way to do this. I know the loop isn't optimal, but I couldn't get LAG() or LEAD() to work. I've spend all morning trying different methods and searching, but everything I find doesn't deal with two dates, or breaks in the chain.
This reads like a gaps-and-island problem. Islands represents rows whose date ranges are adjacent, and you want to count how many records preceed of follow a current date in the same island.
You could do:
select
max(case when #dateToCheck > startdate and #dateToCheck <= enddate then numRecsBefore end) as numRecsBefore,
max(case when #dateToCheck >= startdate and #dateToCheck < enddate then numRecsAfter end) as numRecsAfter
from (
select d.*,
count(*) over(partition by grp order by startdate) as numRecsBefore,
count(*) over(partition by grp order by startdate desc) as numRecsAfter
from (
select d.*,
sum(case when startdate = lag_enddate then 0 else 1 end) over(order by startdate) as grp
from (
select d.*,
lag(enddate) over(order by startdate) as lag_enddate
from #dates d
) d
) d
) d
This uses lag() and a cumulative sum() to define the islands. The a window count gives the number and preceding and following records on the same island. The final step is conditional aggrgation; extra care needs to be taken on the inequalities to take in account various possibilites (typically, the date you search for might not always match a range bound).
Demo on DB Fiddle
I think this is what you are after, however, this does not give the results in your query; I suspect that is because they aren't the expected results? One of the conditional aggregated may also want to be a >= or <=, but I don't know which:
WITH CTE AS(
SELECT startDate,
endDate,
CASE startDate WHEN LAG(endDate) OVER (ORDER BY startDate ASC) THEN 1 END AS IsSame
FROM #dates d)
SELECT COUNT(CASE WHEN startDate < #dateToCheck THEN IsSame END) AS numBefore,
COUNT(CASE WHEN startDate > #dateToCheck THEN IsSame END) AS numAfter
FROM CTE;

How to compare the current month value with last 12 months

I'm trying to compare the current month value with the last 12 months values.
E.g:
6/30/2019 100
5/31/2019 90
4/30/2019 80
3/31/2019 70
2/8/2019 60
1/31/2019 50
12/31/2018 40
11/30/2018 30
10/31/2018 20
9/30/2018 10
8/31/2018 90
7/30/2018 110
Now the current month value(6/30/2019) in 100 then I want to compare these value with the last 12 months values. If the current month value is the maximum when compared with the 12 months then I want to set the flag as "max". In the above example, 110 is the maximum but current month 100 i.e., it is minimum when compared with the last 12 months values then I want to set the flag as "min".
Also, I want to get the date also i.e., if it is minimum then what is the maximum value with the date (expected output "MIN(110 as on 7/30/2018)").
Please provide me any solution to achieve this scenario
expected output "MIN(110 as on 7/30/2018)"
If you want to compare the current value to a rolling 12-month min/max, you can use window functions:
select top (1) t.*,
(case when value = max12_value then 'MAX'
when value = min12_value then 'MIN'
end) as flag
from (select t.*,
max(value) over (order by date rows between 11 preceding and current row) as max12_value,
min(value) over (order by date rows between 11 preceding and current row) as min12_value,
from t
) t
order by date desc
If I understand correctly, here's one way to do it:
-- Create a table variable to hold the sample data
DECLARE #data TABLE (
[Date] DATE,
[Value] INT
)
-- Load the sample data table
DECLARE #i INT = 0;
WHILE #i < 12
BEGIN
INSERT INTO #data ([Date], [Value]) values (EOMONTH(DATEADD(M,-#i, GETDATE())), FLOOR(RAND()*(100-1+1))+10);
SET #i = #i + 1;
END;
-- Select the resulting data set with flags
WITH t as
(
SELECT [Date], [Value],
CASE
WHEN [Value] = (SELECT MAX([Value]) FROM #data) THEN 'MAX'
WHEN [Value] = (SELECT MIN([Value]) FROM #data) THEN 'MIN'
ELSE ''
END AS Flag
FROM #data
)
SELECT 'MAX(' + CAST([Value] as VARCHAR(MAX)) + ' as on ' + CAST([Date] as VARCHAR(MAX)) + ')' FROM t WHERE [Flag] = 'MAX'
This will output:
MAX(109 as on 2018-09-30)
Swap "MIN" for "MAX" and you'll have the minimum value.
This following script will give you output considering the current month value always calculated from GETDATE()
WITH CTE (d, value)
AS
(
SELECT id,value FROM your_table
),
CTE2
AS
(
SELECT DISTINCT
(SELECT Value FROM CTE WHERE d>= CAST(DATEADD(DD,-DATEPART(DD,GETDATE()) + 1,GETDATE()) AS DATE)) current_month_value,
MIN(value) min_value,
MAX(value) max_value
FROM CTE
WHERE d < CAST(DATEADD(DD,-DATEPART(DD,GETDATE()) + 1,GETDATE()) AS DATE)
AND d >= CAST(DATEADD(MM,-13,DATEADD(DD,-DATEPART(DD,GETDATE()) + 1,GETDATE()) ) AS DATE)
)
SELECT
CASE
WHEN current_month_value > max_value THEN 'MAX'
ELSE 'MIN(' + CAST(max_value AS VARCHAR)+ ' AS ON '+ (SELECT TOP 1 CAST(d AS VARCHAR) FROM CTE WHERE Value = max_value)+ ')'
END
FROM CTE2

SQL sum a particular row based on condition?

I am using MS SQL Server. This is the table I have:
Create table tblVal
(
Id int identity(1,1),
Val NVARCHAR(100),
startdate datetime,
enddate datetime
)
--Inserting Records--
Insert into tblVal values(500,'20180907','20191212')
Insert into tblVal values(720,'20190407','20191212')
Insert into tblVal values(539,'20190708','20201212')
Insert into tblVal values(341,'20190221','20190712')
Table as this:
Id |Val |startdate |enddate
--- ----------------------------------------------
1 |500 |2018-09-07 |2019-12-12
2 |720 |2019-04-07 |2019-12-12
3 |539 |2019-07-08 |2020-12-12
4 |341 |2019-02-21 |2019-07-12
This is what I want:
Mon | Total
------------------
Jan | 500
Feb | 841
March | 841
April | 1561
May | 1561
June | 1561
July | 2100
........|.........
I want to sum Val column if it lies in that particular month. For ex. in case of April month it lies between two of the rows. I have to check both the condition start date and end date. and then sum the values.
This is what I have tried:
select *
into #ControlTable
from dbo.tblVal
DECLARE #cnt INT = 0;
while #cnt<12
begin
select sum(CASE
WHEN MONTH(startdate) BETWEEN #cnt and MONTH(enddate) THEN 0
ELSE 0
END)
from #ControlTable
SET #cnt = #cnt + 1;
end
drop table #ControlTable
but from above I was unable to achieve the result.
How do I solve this? Thanks.
I believe you want something like this:
with dates as (
select min(datefromparts(year(startdate), month(startdate), 1)) as dte,
max(datefromparts(year(enddate), month(enddate), 1)) as enddte
from tblVal
union all
select dateadd(month, 1, dte), enddte
from dates
where dte < enddte
)
select d.dte, sum(val)
from dates d left join
tblval t
on t.startdate <= eomonth(dte) and
t.enddate >= dte
group by d.dte
order by d.dte;
This does the calculation for all months in the data.
The results are a bit different from your sample results, but seem more consistent with the data provided.
Here is a db<>fiddle.
Hi if i understand your wall query i think this query can respond :
Create table #tblVal
(
Id int identity(1,1),
Val NVARCHAR(100),
startdate datetime,
enddate datetime
)
--Inserting Records--
Insert into #tblVal values(500,'20180907','20191212')
Insert into #tblVal values(720,'20190407','20191212')
Insert into #tblVal values(539,'20190708','20201212')
Insert into #tblVal values(341,'20190221','20190712')
Create table #tblMonth ( iMonth int)
Insert into #tblMonth values(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12);
select * from #tblVal
select * from #tblMonth
SELECT *, SUM(case when Val is null then 0 else cast (Val as int) end) OVER(ORDER BY iMonth
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) as 'Totaltime'
FROM #tblMonth
LEFT JOIN #tblVal ON MONTH(startdate) = iMonth
ORDER BY iMonth
drop table #tblVal
drop table #tblMonth
Not you have to use SQL Server version 2008 min for use OVER(ORDER BY iMonth
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
Link :
https://learn.microsoft.com/en-us/sql/t-sql/queries/select-over-clause-transact-sql?view=sql-server-2017
If you have older version you can use CTE or JOIN ON select .
DECLARE #outpuTable Table(
MOn INT,
Total nvarchar(MAX)
)
DECLARE #cnt INT = 1;
while (#cnt<=12)
begin
INSERT INTo #outpuTable VALUES(#cnt,
(select ISNULL(sum(CONVERT(INT,Val)),0)
from tblVal
WHERE #cnt BETWEEN MONTH(startdate) and MONTH(enddate) ))
SET #cnt = #cnt + 1;
end
Select * from #outpuTable

Convert Procedural Approach into Set Based Approach in Sql-Server

We are using procedural approach (while loop) for inserting records into a particular table. the insert syntax is like below,
DECLARE #CNT INT = 0,
#WEEK DATE = '2015-11-01',
#FLAG INT
CREATE TABLE #Tmpdata (officeId int,id smallint, weekDate date,startsOn varchar(10),endsOn varchar(10),flag bit);
WHILE (#CNT <7)
BEGIN
SET #WEEK = DATEADD(D,#CNT,#WEEK )
IF EXISTS
(SELECT 1
FROM YEARRANGE
WHERE #WEEK BETWEEN CONVERT(DATE,taxseasonbegin)
AND CONVERT (DATE,taxSeasonEnd)
)
BEGIN
SET #FLAG =1
END
ELSE
BEGIN
SET #FLAG = 0
END
INSERT INTO #Tmpdata
(
officeId,id,weekDate,startsOn,endsOn,flag
)
VALUES
(
5134,#lvCounter,#week,'09:00 AM','05:00 PM',#flag
);
SET #cnt=#cnt+1;
end
(NOTE : TaxSeason is from january to august).
Is it possible to re-write the above logic in set based approach?
This is making a number of assumption because you didn't post ddl or any consumable sample data. Also, there is a variable #lvCounter not defined in your code. This is perfect opportunity to use a tally or numbers table instead of a loop.
declare #lvCounter int = 42;
DECLARE #CNT INT = 0,
#WEEK DATE = '2015-11-01',
#FLAG INT;
WITH
E1(N) AS (select 1 from (values (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))dt(n))
, cteTally(N) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E1
)
select 5134 as officeId
, #lvCounter as Id
, DATEADD(DAY, N - 1, #WEEK) as weekDate
, '09:00 AM' as startsOn
, '05:00 PM' as EndOn
, Flag
from cteTally t
cross apply
(
select CAST(count(*) as bit) as Flag
from YearRange
where DATEADD(Day, t.N, #WEEK) > CONVERT(DATE,taxseasonbegin)
AND DATEADD(Day, t.N, #WEEK) <= CONVERT (DATE,taxSeasonEnd)
) y
where t.N <= 7;
Please can you provide sample data?
You can do something like:
SELECT DateIncrement = SUM(DATEADD(D,#CNT,#WEEK)) OVER (ORDER BY officeID)
FROM...
This gets an incremented date value for each record which you can then check against your start and end dates.
You could try some Kind of this one. This gives you the data I think you Need for your insert. I do not have a table named YEARRANGE so I couldn't test it completely
DECLARE #CNT INT = 0, #WEEK DATE = '2015-11-01', #FLAG INT;
CREATE TABLE #Tmpdata (officeId int,id smallint, weekDate date,startsOn varchar(10),endsOn varchar(10),flag bit);
WITH CTE AS
(
SELECT num AS cnt,
DATEADD(D, SUM(num) OVER(ORDER BY num ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
, #WEEK) AS [week]
FROM
(
SELECT ROW_NUMBER() OVER (ORDER BY nl) -1 AS num
FROM
(SELECT NULL AS nl UNION ALL SELECT NULL AS nl UNION ALL SELECT NULL AS nl UNION ALL SELECT NULL AS nl
UNION ALL SELECT NULL AS nl UNION ALL SELECT NULL AS nl UNION ALL SELECT NULL AS nl
) AS ni
) AS no
)
INSERT INTO #Tmpdata (officeId,id,weekDate,startsOn,endsOn,flag)
SELECT 5134 AS officeID, cnt AS id, [week],'09:00 AM' AS startsOn,'05:00 PM' AS endsOn, COALESCE(A1.flag,0) AS flag
FROM CTE
OUTER APPLY (SELECT 1
FROM YEARRANGE
WHERE [week] BETWEEN CONVERT(DATE,taxseasonbegin)
AND CONVERT (DATE,taxSeasonEnd)
) AS A1(flag);

How can I update extreme columns within range fast?

I have 40 tables that look like following, and each table contains 30 million records.
Table RawData : PK(CaregoryID, Time)
CategoryID Time IsSampled Value
-----------------------------------------------------------
1 2012-07-01 00:00:00.000 0 -> 1 65.36347
1 2012-07-01 00:00:11.000 0 80.16729
1 2012-07-01 00:00:14.000 0 29.19716
1 2012-07-01 00:00:25.000 0 -> 1 7.05847
1 2012-07-01 00:00:36.000 0 -> 1 98.08257
1 2012-07-01 00:00:57.000 0 75.35524
1 2012-07-01 00:00:59.000 0 35.35524
As of now, the IsSampled column is 0 for all records.
I need to update the records, so that for each CategoryID and for each minute range, the records with Max(Value), Min(Value), and the first record should have 1 for IsSampled.
Following is the procedural query I've created, but it takes too long to run. (approx. 2h 30m for each table)
DECLARE #startRange datetime
DECLARE #endRange datetime
DECLARE #endTime datetime
SET #startRange = '2012-07-01 00:00:00.000'
SET #endTime = '2012-08-01 00:00:00.000'
WHILE (#startRange < #endTime)
BEGIN
SET #endRange = DATEADD(MI, 1, #startRange)
UPDATE r1
SET IsSampled = 1
FROM RawData AS r1
JOIN
(
SELECT r2.CategoryID,
MAX(Value) as MaxValue,
MIN(Value) as MinValue,
MIN([Time]) AS FirstTime
FROM RawData AS r2
WHERE #startRange <= [Time] AND [Time] < #endRange
GROUP BY CategoryID
) as samples
ON r1.CategoryID = samples.CategoryID
AND (r1.Value = samples.MaxValue
OR r1.Value = samples.MinValue
OR r1.[Time] = samples.FirstTime)
AND #startRange <= r1.[Time] AND r1.[Time] < #endRange
SET #startRange = DATEADD(MI, 1, #startRange)
END
Is there a way to update these tables faster(presumably in non-procedural way)? Thanks!
I'm not sure what the performance of this will be like, but it's a more set-based approach than your current one:
declare #T table (CategoryID int not null,Time datetime2 not null,IsSampled bit not null,Value decimal(10,5) not null)
insert into #T (CategoryID,Time,IsSampled,Value) values
(1,'2012-07-01T00:00:00.000',0,65.36347),
(1,'2012-07-01T00:00:11.000',0,80.16729),
(1,'2012-07-01T00:00:14.000',0,29.19716),
(1,'2012-07-01T00:00:25.000',0,7.05847),
(1,'2012-07-01T00:00:36.000',0,98.08257),
(1,'2012-07-01T00:00:57.000',0,75.35524),
(1,'2012-07-01T00:00:59.000',0,35.35524)
;with BinnedValues as (
select CategoryID,Time,IsSampled,Value,DATEADD(minute,DATEDIFF(minute,0,Time),0) as TimeBin
from #T
), MinMax as (
select CategoryID,Time,IsSampled,Value,TimeBin,
ROW_NUMBER() OVER (PARTITION BY CategoryID, TimeBin ORDER BY Value) as MinPos,
ROW_NUMBER() OVER (PARTITION BY CategoryID, TimeBin ORDER BY Value desc) as MaxPos,
ROW_NUMBER() OVER (PARTITION BY CategoryID, TimeBin ORDER BY Time) as Earliest
from
BinnedValues
)
update MinMax set IsSampled = 1 where MinPos=1 or MaxPos=1 or Earliest=1
select * from #T
Result:
CategoryID Time IsSampled Value
----------- ---------------------- --------- ---------------------------------------
1 2012-07-01 00:00:00.00 1 65.36347
1 2012-07-01 00:00:11.00 0 80.16729
1 2012-07-01 00:00:14.00 0 29.19716
1 2012-07-01 00:00:25.00 1 7.05847
1 2012-07-01 00:00:36.00 1 98.08257
1 2012-07-01 00:00:57.00 0 75.35524
1 2012-07-01 00:00:59.00 0 35.35524
It could possibly be sped up if the TimeBin column could be added as a computed column to the table and added to appropriate indexes.
It should also be noted that this will mark a maximum of 3 rows as sampled - if the earliest is also the min or max value, it will only be marked once (obviously), but the next nearest min or max value will not be. Also, if multiple rows have the same Value, and that is the min or max value, one of the rows will be selected arbitrarily.
You could rewrite update in the loop to something like:
UPDATE r1
SET IsSampled = 1
FROM RawData r1
WHERE r1.Time >= #startRange and Time < #endRange
AND NOT EXISTS
(
select *
from RawData r2
where r2.CategoryID = r1.CategoryID
and r2.Time >= #startRange and r2.Time < #endRange
and (r2.Time < r1.Time or r2.Value < r1.Value or r2.Value > r1.Value)
)
To get actual performance improvement you need an index on Time column.
Hi try this query.
declare #T table (CategoryID int not null,Time datetime2 not null,IsSampled bit not null,Value decimal(10,5) not null)
insert into #T (CategoryID,Time,IsSampled,Value) values
(1,'2012-07-01T00:00:00.000',0,65.36347),
(1,'2012-07-01T00:00:11.000',0,80.16729),
(1,'2012-07-01T00:00:14.000',0,29.19716),
(1,'2012-07-01T00:00:25.000',0,7.05847),
(1,'2012-07-01T00:00:36.000',0,98.08257),
(1,'2012-07-01T00:00:57.000',0,75.35524),
(1,'2012-07-01T00:00:59.000',0,35.35524)
;WITH CTE as (SELECT CategoryID,CAST([Time] as Time) as time,IsSampled,Value FROM #T)
,CTE2 as (SELECT CategoryID,Max(time) mx,MIN(time) mn,'00:00:00.0000000' as start FROM CTE where time <> '00:00:00.0000000' Group by CategoryID)
update #T SET IsSampled=1
FROM CTE2 c inner join #T t on c.CategoryID = t.CategoryID and (CAST(t.[Time] as Time)=c.mx or CAST(t.[Time] as Time)=c.mn or CAST(t.[Time] as Time)=c.start)
select * from #T
Hi Here is the latest updated query.
Check the query for performance:
declare #T table (CategoryID int not null,Time datetime2 not null,IsSampled bit not null,Value decimal(10,5) not null)
insert into #T (CategoryID,Time,IsSampled,Value) values
(1,'2012-07-01T00:00:00.000',0,65.36347),
(1,'2012-07-01T00:00:11.000',0,80.16729),
(1,'2012-07-01T00:00:14.000',0,29.19716),
(1,'2012-07-01T00:00:25.000',0,7.05847),
(1,'2012-07-01T00:00:36.000',0,98.08257),
(1,'2012-07-01T00:00:57.000',0,75.35524),
(1,'2012-07-01T00:00:59.000',0,35.35524)
;WITH CTE as (SELECT CategoryID,Time,CAST([Time] as Time) as timepart,IsSampled,Value FROM #T)
--SELECT * FROM CTE
,CTE2 as (SELECT CategoryID,Max(value) mx,MIN(value) mn FROM CTE
where timepart <> '00:00:00.0000000' and Time <=DATEADD(MM,1,Time)
Group by CategoryID)
,CTE3 as (SELECT CategoryID,Max(value) mx,MIN(value) mn FROM CTE
where timepart = '00:00:00.0000000' and Time <=DATEADD(MM,1,Time)
Group by CategoryID)
update #T SET IsSampled=1
FROM #T t left join CTE2 c1
on (t.CategoryID = c1.CategoryID and (t.Value = c1.mn or t.Value =c1.mx))
left join CTE3 c3 on(t.CategoryID = c3.CategoryID and t.Value = c3.mx)
where (c1.CategoryID is not null or c3.CategoryID is not null)
select * from #T