Double record when both conditions are met SQL - sql

I have a record in my table:
What I need is to create a column with order state: '1' if order was created, '0' if order was cancelled.
So for this example, when there was both creation and cancellation I need two states. The final table should be:
How can I do this?

I think you can simply do a UNION like this:
select OrderCreateDate, OrderCancelDate, ReportDate, 1 as OrderState
from your_table
where orderCreateDate is not null
union all
select OrderCreateDate, OrderCancelDate, ReportDate, 0 as OrderState
from your_table
where orderCancelDate is not null

One way to do this is to join your table multiple times with a constraint on the join to limit your result set; this is an easy way to pivot your data, but it can affect performance.
DECLARE #a TABLE (id INT, createdate date,canceldate date,reportdate DATE)
INSERT INTO #a (id, createdate, canceldate, reportdate)
VALUES (
1, -- id - int
GETDATE(), -- createdate - date
GETDATE(), -- canceldate - date
GETDATE() -- reportdate - date
)
INSERT INTO #a (id, createdate, canceldate, reportdate)
VALUES (
2, -- id - int
GETDATE(), -- createdate - date
null, -- canceldate - date
GETDATE() -- reportdate - date
)
SELECT a.id,a.createdate,a.canceldate,a.reportdate,CASE WHEN a1.id IS NOT NULL THEN '1' ELSE 0 END AS 'createdInd'
,CASE WHEN a2.id IS NOT NULL THEN '1' ELSE 0 END AS 'CancelledInd'
FROM #a a
LEFT JOIN #a a1 ON a.id = a1.id AND a1.createdate IS NOT NULL
LEFT JOIN #a a2 ON a.id = a2.id AND a2.canceldate IS NOT NULL
id createdate canceldate reportdate createdInd CancelledInd
1 2021-04-07 2021-04-07 2021-04-07 1 1
2 2021-04-07 NULL 2021-04-07 1 0

Join to the table a query that returns the values 1 and 0:
SELECT t.*, s.OrderState
FROM tablename AS t
INNER JOIN (SELECT 1 AS OrderState UNION ALL SELECT 0) AS s
ON (s.OrderState = 1 AND t.OrderCreateDate IS NOT NULL)
OR (s.OrderState = 0 AND t.OrderCancelDate IS NOT NULL)

Related

How can I simplify this Query? I need to compare a temp variable value with a column value of multiple rows

I need to compare a temp variable value with a column value of multiple rows and perform Operations based on that.
| intSeqID | Value |
----------------------------
1 | 779.40
2 | 357.38
3 | NULL
4 | NULL
5 | NULL
6 | NULL
7 | NULL
8 | NULL
9 | NULL
10 | NULL
DECLARE #tmpRange NUMERIC(5,2)
SELECT #tmpRange = 636
Here I need to compare the value #tmpRange with Value from TABLE and perform operations based on it.
IF((#tmpRange < (select ISNULL(Value,0) from #tableA intSeqID=1)) AND
(#tmpRange< (select ISNULL(Value,0) from #tableA where intSeqID=2))) AND
(#tmpRange< (select ISNULL(Value,0) from #tableA where intSeqID=3))) AND
(#tmpRange< (select ISNULL(Value,0) from #tableA where intSeqID=9))) AND
(#tmpRange< (select ISNULL(Value,0) from #tableA where intSeqID=10)))
BEGIN
SELECT 'All'
END
ELSE IF ((#tmpRange < (select ISNULL(Value,0) from #tableA intSeqID=1)) AND
(#tmpRange< (select ISNULL(Value,0) from #tableA where intSeqID=2))) AND
(#tmpRange< (select ISNULL(Value,0) from #tableA where intSeqID=3))) AND
(#tmpRange< (select ISNULL(Value,0) from #tableA where intSeqID=9))))
BEGIN
SELECT '10'
END
END
How can i simplify this query to compare values. Or is there any other way to pick the values of multiple rows and compare the same with temp variable.
Here is one fairly simple way to do it:
Create and populate sample table (Please save us this step in your future questions)
DECLARE #tableA as table
(
intSeqID int identity(1,1),
Value numeric(5,2)
)
INSERT INTO #tableA VALUES
(779.40),
(357.38),
(256.32),
(NULL)
Declare and populate the variable:
DECLARE #tmpRange numeric(5, 2) = 636
The query:
;WITH CTE AS
(
SELECT TOP 1 intSeqId
FROM #TableA
WHERE #tmpRange < ISNUll(Value, 0)
ORDER BY Value
)
SELECT CASE WHEN intSeqId =
(
SELECT TOP 1 intSeqId
FROM #TableA
ORDER BY ISNUll(Value, 0)
) THEN 'All'
ELSE CAST(intSeqId as varchar(3))
END
FROM CTE
Result: 1.
See a live demo on rextester.
We can try to refactor your query using aggregations. We almost get away with no subquery except for just one, which is needed to distinguish the two conditions.
SELECT
CASE WHEN SUM(CASE WHEN #tmpRange < Value THEN 1 ELSE 0 END) = 4 AND
#tmpRange < (SELECT Value FROM #tableA WHEREA intSeqID = 10)
THEN 'All'
WHEN SUM(CASE WHEN #tmpRange < Value THEN 1 ELSE 0 END) = 4
THEN '10'
ELSE 'NONE' END AS label
FROM #tableA
WHERE intSeqID IN (1, 2, 3, 9)
You want to find the biggest record in Value, who is also smaller than your variable, correct?
--DECLARE #tableA TABLE (intSeqID tinyint, [Value] decimal(5,2))
--INSERT INTO #tableA SELECT 1, 400 UNION SELECT 2, 300 UNION SELECT 3, 200
--DECLARE #tmpRange decimal(5,2) = 250
SELECT TOP 1 *
FROM (
SELECT TOP 1 CONCAT('', intSeqID) AS intSeqID -- Can't UNION int to varchar.
FROM #tableA
WHERE ISNULL([Value], 0) < #tmpRange
ORDER BY intSeqID ASC
UNION
SELECT 'All' AS [?]
) AS T
ORDER BY intSeqID ASC

SQL query when result is empty

I have a table like this
USER itemnumber datebought (YYYYmmDD)
a 1 20160101
b 2 20160202
c 3 20160903
d 4 20160101
Now I have to show the total number of items bought by each user after date 20160202 (2 february 2016)
I used
SELECT USER, COUNT(itemnumber)<br/>
FROM TABLE<br/>
WHERE datebought >= 20160202<br/>
GROUP BY USER<br>
It gives me results
b 1
c 1
but I want like this
a 0
b 1
c 1
d 0
Please tell me what is the most quick method / efficient method to do that ?
Try like this,
DECLARE #table TABLE
(
[USER] VARCHAR(1),
itemnumber INT,
datebought DATE
)
INSERT INTO #TABLE VALUES
('a',1,'20160101'),
('b',2,'20160202'),
('b',2,'20160202'),
('b',2,'20160202'),
('c',3,'20160903'),
('d',4,'20160101')
SELECT *
FROM #TABLE
SELECT [USER],
Sum(CASE
WHEN datebought >= '20160202' THEN 1
ELSE 0
END) AS ITEMCOUNT
FROM #TABLE
GROUP BY [USER]
Use this
SELECT USER, COUNT(itemnumber)
FROM TABLE
WHERE datebought >= 20160202
GROUP BY USER
Though this query won't be a good idea for the large amount of data:
SELECT USER, COUNT(itemnumber)
FROM TABLE
WHERE datebought >= 20160202
GROUP BY USER
UNION
SELECT DISTINCT USER, 0
FROM TABLE
WHERE datebought < 20160202
USE tempdb
GO
DROP TABLE test1
CREATE TABLE test1(a NVARCHAR(10), ino INT, datebought INT)
INSERT INTO dbo.test1
( a, ino, datebought )
VALUES ( 'a' , 1 , 20160101)
INSERT INTO dbo.test1
( a, ino, datebought )
VALUES ( 'b' , 2 , 20160202)
INSERT INTO dbo.test1
( a, ino, datebought )
VALUES ( 'c' , 3 , 20160903)
INSERT INTO dbo.test1
( a, ino, datebought )
VALUES ( 'd' , 4 , 20160101)
SELECT * FROM dbo.test1
SELECT a, COUNT(ino) OVER(PARTITION BY a) FROM dbo.test1
WHERE datebought>=20160202
UNION ALL
SELECT a, 0 FROM dbo.test1
WHERE datebought<20160202
ORDER BY a

SQL Query that calculates time

Given these tables :
Item
-----
Id
Description
Status
CreatedBy
ItemLog
--------
Id
ItemId
NewStatus
TimeStamp
ChangedBy
Where Itemlog.ItemId = Item.Id, and Status = { "Created", "Pended", "Cancelled", "Completed" }...
How would you write a SQL query to generate the following results :
Item Description ChangeDate NewStatus ChangedBy
1 Test1 2012-01-01 Created User1
1 Test1 2012-01-02 Pended User2
1 Test1 2012-01-03 Completed User2
2 Test2 2012-01-01 Created User2
2 Test2 2012-01-02 Pended User3
2 Test2 2012-01-09 Cancelled User1
3 Test3 2012-01-01 Created User1
3 Test3 2012-01-02 Pended User1
Item CurrentUser CurrentStatus CreatedOn TotalTime TimePended CompletedDate CancelledDate
Test1 User2 Completed 2012-01-01 3 days 1 day 2012-01-03 (null)
Test2 User1 Completed 2012-01-01 9 days 7 days (null) 2012-01-09
Test3 User1 Pended 2012-01-01 35 days 34 days (null) (null)
Which I want to display as a master-detail report in my application.
The first resultset is a simple query with a couple of joins (I haven't included the UserId-UserName tables etc.)
The hard part is the calculation of the total times...
TimeToComplete is the TimeStamp of the last ItemLog minus the TimeStamp of the first ItemLog (where the ItemLog is ordered by date).
TimePended is the sum of the difference in TimeStamps between each change of the status from pended -> something.
Shazam!
create table Item(ID int, Description varchar(200),createdby varchar(20))
create table ItemLog(ID int, ItemID int, NewStatus varchar(200), [TimeStamp] datetime, ChangedBy varchar(20))
insert into Item(ID,Description,CreatedBy) values(1, 'Test 1', 'User1')
insert into Item(ID,Description,CreatedBy) values(2, 'Test 2', 'User1')
insert into Item(ID,Description,CreatedBy) values(3, 'Test 3', 'User1')
insert into ItemLog(ID, ItemID, NewStatus, TimeStamp, ChangedBy)
values(1,1,'Created','1/1/2012','User1')
insert into ItemLog(ID, ItemID, NewStatus, TimeStamp, ChangedBy)
values(2,1,'Pended','1/2/2012','User2')
insert into ItemLog(ID, ItemID, NewStatus, TimeStamp, ChangedBy)
values(3,1,'Completed','1/3/2012','User2')
insert into ItemLog(ID, ItemID, NewStatus, TimeStamp, ChangedBy)
values(4,2,'Created','1/1/2012','User2')
insert into ItemLog(ID, ItemID, NewStatus, TimeStamp, ChangedBy)
values(5,2,'Pended','1/2/2012','User3')
insert into ItemLog(ID, ItemID, NewStatus, TimeStamp, ChangedBy)
values(6,3,'Cancelled','1/9/2012','User1')
insert into ItemLog(ID, ItemID, NewStatus, TimeStamp, ChangedBy)
values(7,3,'Created','1/2/2012','User1')
insert into ItemLog(ID, ItemID, NewStatus, TimeStamp, ChangedBy)
values(8,3,'Pended','1/2/2012','User1')
select * from item i left outer join itemlog il on il.ItemID = i.ID
select
i.Description,
FirstStatusDate=minIL.TimeStamp,
CurrentStatus=maxIL.NewStatus,
CurrentStatusDate=maxIL.TimeStamp,
CurrentUser=maxIL.ChangedBy,
CompletedDate=(select max(TimeStamp) from ItemLog where ItemID=i.ID and NewStatus='Completed'),
[TotalTime (in days)]=case
when
minIL.TimeStamp is not null and maxIL.TimeStamp is not null
then datediff(day,minIL.TimeStamp,maxIL.TimeStamp)
else
convert(int,null)
end,
TimePending=sum(c.Days)
from
Item i
left outer join
(
select
y.ItemID,
y.MinDate,MinItemLogID=min(mn.id),
y.MaxDate,MaxItemLogID=max(mx.id)
from
(
select
ItemID,MinDate=min(timestamp), MaxDate=max(timestamp)
from
ItemLog il group by ItemID
) as y
left outer join ItemLog mn on mn.ItemID=y.ItemID and mn.TimeStamp=y.MinDate
left outer join ItemLog mx on mx.ItemID=y.ItemID and mx.TimeStamp=y.MaxDate
group by
y.ItemID, y.MinDate, y.MaxDate
)
z on z.ItemID = i.ID
left outer join ItemLog minIL on minIL.ID = z.MinItemLogID
left outer join ItemLog maxIL on maxIL.ID = z.MaxItemLogID
left outer join
(
select
p.ItemId,
PendTime=p.TimeStamp,
PendID=p.ID,
Days=datediff(day,p.TimeStamp,
coalesce(
(select min(TimeStamp)
from
ItemLog b
where
b.ItemID = P.ItemID and TimeStamp > p.TimeStamp )
,
getdate()
)
)
from
ItemLog p
where
p.NewStatus='Pended'
) c on c.ItemID = i.ID
group by
i.ID,
i.Description,
minIL.TimeStamp,
maxIL.NewStatus,
maxIL.TimeStamp,
maxIL.ChangedBy
Geez... Does that provide a big performance benefit over something like the following?
ALTER FUNCTION [dbo].[GetItemPendedTime]
(
-- Add the parameters for the function here
#ItemId uniqueidentifier
)
RETURNS INT
AS
BEGIN
-- Declare the return variable here
DECLARE #status INT
DECLARE #timespan datetime
DECLARE #pendTimeMinutes INT = 0
DECLARE #ispend bit = 0
DECLARE #lastTimespan datetime
DECLARE itemlog_queue CURSOR FOR
SELECT Status, Timestamp FROM ItemLog WHERE ItemId = #ItemId
OPEN itemlog_queue;
FETCH NEXT FROM itemlog_queue INTO #status, #timespan
WHILE ##FETCH_STATUS = 0
BEGIN
IF #ispend = 1
BEGIN
SET #pendTimeMinutes += DATEDIFF(minute, #lastTimespan, #timespan)
END
IF #status = 13
BEGIN
SET #ispend = 1
END
ELSE
BEGIN
SET #ispend = 0
END
SET #lastTimespan = #timespan
FETCH NEXT FROM itemlog_queue INTO #status, #timespan
END
CLOSE itemlog_queue
DEALLOCATE itemlog_queue
IF #ispend = 1
BEGIN
SET #pendTimeMinutes = #pendTimeMinutes + DATEDIFF(minute, #lastTimespan, GETDATE())
END
-- Return the result of the function
RETURN #pendTimeMinutes
END
And then call it like
SELECT Description, Status as CurrentStatus, dbo.GetItemPendedTime(ItemId) FROM Items

Query to merge continuous temporal records

I have a table like this:
id START_DATE end_date
1 01/01/2011 01/10/2011
2 01/11/2011 01/20/2011
3 01/25/2011 02/01/2011
4 02/10/2011 02/15/2011
5 02/16/2011 02/27/2011
I want to merge the records where the start_date is just next day of end_date of another record: So the end record should be something like this:
new_id START_DATE end_date
1 01/01/2011 01/20/2011
2 01/25/2011 02/01/2011
3 02/10/2011 02/27/2011
One way that I know to do this will be to create a row based temp table with various rows as dates (each record for one date, between the total range of days) and thus making the table flat.
But there has to be a cleaner way to do this in a single query... e.g. something using row_num?
Thanks guys.
declare #T table
(
id int,
start_date datetime,
end_date datetime
)
insert into #T values
(1, '01/01/2011', '01/10/2011'),
(2, '01/11/2011', '01/20/2011'),
(3, '01/25/2011', '02/01/2011'),
(4, '02/10/2011', '02/15/2011'),
(5, '02/16/2011', '02/27/2011')
select row_number() over(order by min(dt)) as new_id,
min(dt) as start_date,
max(dt) as end_date
from (
select dateadd(day, N.Number, start_date) as dt,
dateadd(day, N.Number - row_number() over(order by dateadd(day, N.Number, start_date)), start_date) as grp
from #T
inner join master..spt_values as N
on N.number between 0 and datediff(day, start_date, end_date) and
N.type = 'P'
) as T
group by grp
order by new_id
You can use a numbers table instead of using master..spt_values.
Try This
Declare #chgRecs Table
(updId int primary key not null,
delId int not null,
endt datetime not null)
While Exists (Select * from Table a
Where Exists
(Select * from table
Where start_date =
DateAdd(day, 1, a.End_Date)))
Begin
Insert #chgRecs (updId, delId , endt)
Select a.id, b.id, b.End_Date,
From table a
Where Exists
(Select * from table
Where start_date =
DateAdd(day, 1, a.End_Date)))
And Not Exists
(Select * from table
Where end_Date =
DateAdd(day, -1, a.Start_Date)))
Delete table Where id In (Select delId from #chgRecs )
Update table set
End_Date = u.endt
From table t join #chgRecs u
On u.updId = t.Id
Delete #delRecs
End
No, was not looking for a loop...
I guess this is a good solution:
taking all the data in a #temp table
SELECT * FROM #temp
SELECT t2.start_date , t1.end_date FROM #temp t1 JOIN #temp t2 ON t1.start_date = DATEADD(DAY,1,t2.end_date)
UNION
SELECT START_DATE,end_date FROM #temp WHERE start_date NOT IN (SELECT t2.START_DATE FROM #temp t1 JOIN #temp t2 ON t1.start_date = DATEADD(DAY,1,t2.end_date))
AND end_date NOT IN (SELECT t1.end_Date FROM #temp t1 JOIN #temp t2 ON t1.start_date = DATEADD(DAY,1,t2.end_date))
DROP TABLE #temp
Please let me know if there is anything better than this.
Thanks guys.
A recursive solution:
CREATE TABLE TestData
(
Id INT PRIMARY KEY,
StartDate DATETIME NOT NULL,
EndDate DATETIME NOT NULL
);
SET DATEFORMAT MDY;
INSERT TestData
SELECT 1, '01/01/2011', '01/10/2011'
UNION ALL
SELECT 2, '01/11/2011', '01/20/2011'
UNION ALL
SELECT 3, '01/25/2011', '02/01/2011'
UNION ALL
SELECT 4, '02/10/2011', '02/15/2011'
UNION ALL
SELECT 5, '02/16/2011', '02/27/2011'
UNION ALL
SELECT 6, '02/28/2011', '03/06/2011'
UNION ALL
SELECT 7, '02/28/2011', '03/03/2011'
UNION ALL
SELECT 8, '03/10/2011', '03/18/2011'
UNION ALL
SELECT 9, '03/19/2011', '03/25/2011';
WITH RecursiveCTE
AS
(
SELECT t.Id, t.StartDate, t.EndDate
,1 AS GroupID
FROM TestData t
WHERE t.Id=1
UNION ALL
SELECT crt.Id, crt.StartDate, crt.EndDate
,CASE WHEN DATEDIFF(DAY,prev.EndDate,crt.StartDate)=1 THEN prev.GroupID ELSE prev.GroupID+1 END
FROM TestData crt
JOIN RecursiveCTE prev ON crt.Id-1=prev.Id
--WHERE crt.Id > 1
)
SELECT cte.GroupID, MIN(cte.StartDate) AS StartDate, MAX(cte.EndDate) AS EndDate
FROM RecursiveCTE cte
GROUP BY cte.GroupID
ORDER BY cte.GroupID;
DROP TABLE TestData;

Select one row per specific time

I have a table that looks like this:
ID UserID DateTime TypeID
1 1 1/1/2010 10:00:00 1
2 2 1/1/2010 10:01:50 1
3 1 1/1/2010 10:02:50 1
4 1 1/1/2010 10:03:50 1
5 1 1/1/2010 11:00:00 1
6 2 1/1/2010 11:00:50 1
I need to query all users where their typeID is 1, but have only one row per 15 mins
For example, the result should be:
1 1 1/1/2010 10:00:00 1
2 2 1/1/2010 10:01:50 1
5 1 1/1/2010 11:00:00 1
6 2 1/1/2010 11:00:50 1
IDs 3 & 4 are not shown because 15 min haven't been passed since the last record for the specific userID.
IDs 1 & 5 are shown because 15 minutes has been passed for this specific userID
Same as for IDs 2 & 6.
How can I do it?
Thanks
Try this:
select * from
(
select ID, UserID,
Max(DateTime) as UpperBound,
Min(DateTime) as LowerBound,
TypeID
from the_table
where TypeID=1
group by ID,UserID,TypeID
) t
where datediff(mi,LowerBound,UpperBound)>=15
EDIT: SINCE MY ABOVE ATTEMPT WAS WRONG, I'm adding one more approach using a Sql table-valued Function that does not require recursion, since, understandable, it's a big concern.
Step 1: Create a table-type as follows (LoginDate is the DateTime column in Shay's example - DateTime name conflicts with a SQL data type and I think it's wise to avoid these conflicts)
CREATE TYPE [dbo].[TVP] AS TABLE(
[ID] [int] NOT NULL,
[UserID] [int] NOT NULL,
[LoginDate] [datetime] NOT NULL,
[TypeID] [int] NOT NULL
)
GO
Step 2: Create the following Function:
CREATE FUNCTION [dbo].[fnGetLoginFreq]
(
-- notice: TVP is the type (declared above)
#TVP TVP readonly
)
RETURNS
#Table_Var TABLE
(
-- This will be our result set
ID int,
UserId int,
LoginTime datetime,
TypeID int,
RowNumber int
)
AS
BEGIN
--We will insert records in this table as we go through the rows in the
--table passed in as parameter and decide that we should add an entry because
--15' had elapsed between logins
DECLARE #temp table
(
ID int,
UserId int,
LoginTime datetime,
TypeID int
)
-- seems silly, but is not because we need to add a row_number column to help
-- in our iteration and table-valued paramters cannot be modified inside the function
insert into #Table_var
select ID,UserID,Logindate,TypeID,row_number() OVER(ORDER BY UserID,LoginDate) AS [RowNumber]
from #TVP order by UserID asc,LoginDate desc
declare #Index int,#End int,#CurrentLoginTime datetime, #NextLoginTime datetime, #CurrentUserID int , #NextUserID int
select #Index=1,#End=count(*) from #Table_var
while(#Index<=#End)
begin
select #CurrentLoginTime=LoginTime,#CurrentUserID=UserID from #Table_var where RowNumber=#Index
select #NextLoginTime=LoginTime,#NextUserID=UserID from #Table_var where RowNumber=(#Index+1)
if(#CurrentUserID=#NextUserID)
begin
if( abs(DateDiff(mi,#CurrentLoginTime,#NextLoginTime))>=15)
begin
insert into #temp
select ID,UserID,LoginTime,TypeID
from #Table_var
where RowNumber=#Index
end
END
else
bEGIN
insert into #temp
select ID,UserID,LoginTime,TypeID
from #Table_var
where RowNumber=#Index and UserID=#CurrentUserID
END
if(#Index=#End)--last element?
begin
insert into #temp
select ID,UserID,LoginTime,TypeID
from #Table_var
where RowNumber=#Index and not
abs((select datediff(mi,#CurrentLoginTime,max(LoginTime)) from #temp where UserID=#CurrentUserID))<=14
end
select #Index=#Index+1
end
delete from #Table_var
insert into #Table_var
select ID, UserID ,LoginTime ,TypeID ,row_number() OVER(ORDER BY UserID,LoginTime) AS 'RowNumber'
from #temp
return
END
Step 3: Give it a spin
declare #TVP TVP
INSERT INTO #TVP
select ID,UserId,[DateType],TypeID from Shays_table where TypeID=1 --AND any other date restriction you want to add
select * from fnGetLoginFreq(#TVP) order by LoginTime asc
My tests returned this:
ID UserId LoginTime TypeID RowNumber
2 2 2010-01-01 10:01:50.000 1 3
4 1 2010-01-01 10:03:50.000 1 1
5 1 2010-01-01 11:00:00.000 1 2
6 2 2010-01-01 11:00:50.000 1 4
How about this, it's fairly straight forward and gives you the result you need:
SELECT ID, UserID, [DateTime], TypeID
FROM Users
WHERE Users.TypeID = 1
AND NOT EXISTS (
SELECT TOP 1 1
FROM Users AS U2
WHERE U2.ID <> Users.ID
AND U2.UserID = Users.UserID
AND U2.[DateTime] BETWEEN DATEADD(MI, -15, Users.[DateTime]) AND Users.[DateTime]
AND U2.TypeID = 1)
The NOT EXISTS restricts to only show records that have no record within 15minutes before them, so you will see the first record in a block rather than one every 15mins.
Edit: Since you want to see one every 15mins this should do without using recursion:
SELECT Users.ID, Users.UserID, Users.[DateTime], Users.TypeID
FROM
(
SELECT MIN(ID) AS ID, UserID,
DATEADD(minute, DATEDIFF(minute,0,[DateTime]) / 15 * 15, 0) AS [DateTime]
FROM Users
GROUP BY UserID, DATEADD(minute, DATEDIFF(minute,0,[DateTime]) / 15 * 15, 0)
) AS Dates
INNER JOIN Users AS Users ON Users.ID = Dates.ID
WHERE Users.TypeID = 1
AND NOT EXISTS (
SELECT TOP 1 1
FROM
(
SELECT MIN(ID) AS ID, UserID,
DATEADD(minute, DATEDIFF(minute,0,[DateTime]) / 15 * 15, 0) AS [DateTime]
FROM Users
GROUP BY UserID, DATEADD(minute, DATEDIFF(minute,0,[DateTime]) / 15 * 15, 0)
) AS Dates2
INNER JOIN Users AS U2 ON U2.ID = Dates2.ID
WHERE U2.ID <> Users.ID
AND U2.UserID = Users.UserID
AND U2.[DateTime] BETWEEN DATEADD(MI, -15, Users.[DateTime]) AND Users.[DateTime]
AND U2.TypeID = 1
)
ORDER BY Users.DateTime
If this doesn't work please post more sample data so that I can see what is missing.
Edit2 same as directly above but just using CTE now instead for improved readability and help improve maintainability, also I improved it to highlighted where you would also restrict the Dates table by whatever DateTime range that you would be restricting to the main query:
WITH Dates(ID, UserID, [DateTime])
AS
(
SELECT MIN(ID) AS ID, UserID,
DATEADD(minute, DATEDIFF(minute,0,[DateTime]) / 15 * 15, 0) AS [DateTime]
FROM Users
WHERE Users.TypeID = 1
--AND Users.[DateTime] BETWEEN #StartDateTime AND #EndDateTime
GROUP BY UserID, DATEADD(minute, DATEDIFF(minute,0,[DateTime]) / 15 * 15, 0)
)
SELECT Users.ID, Users.UserID, Users.[DateTime], Users.TypeID
FROM Dates
INNER JOIN Users ON Users.ID = Dates.ID
WHERE Users.TypeID = 1
--AND Users.[DateTime] BETWEEN #StartDateTime AND #EndDateTime
AND NOT EXISTS (
SELECT TOP 1 1
FROM Dates AS Dates2
INNER JOIN Users AS U2 ON U2.ID = Dates2.ID
WHERE U2.ID <> Users.ID
AND U2.UserID = Users.UserID
AND U2.[DateTime] BETWEEN DATEADD(MI, -15, Users.[DateTime]) AND Users.[DateTime]
AND U2.TypeID = 1
)
ORDER BY Users.DateTime
Also as a performance note, whenever dealing with something that might end up being recursive like this potentially could be (from other answers), you should straight away be considering if you are able to restrict the main query by a date range in general even if it's a whole year or longer range
You can use a recursive CTE for this though I would also evaluate a cursor if the result set is at all large as it may work out more efficient.
I've left out the ID column in my answer. If you really need it it would be possible to add it. It just makes the anchor part of the recursive CTE a bit more unwieldy.
DECLARE #T TABLE
(
ID INT PRIMARY KEY,
UserID INT,
[DateTime] DateTime,
TypeID INT
)
INSERT INTO #T
SELECT 1,1,'20100101 10:00:00', 1 union all
SELECT 2,2,'20100101 10:01:50', 1 union all
SELECT 3,1,'20100101 10:02:50', 1 union all
SELECT 4,1,'20100101 10:03:50', 1 union all
SELECT 5,1,'20100101 11:00:00', 1 union all
SELECT 6,2,'20100101 11:00:50', 1;
WITH RecursiveCTE
AS (SELECT UserID,
MIN([DateTime]) As [DateTime],
1 AS TypeID
FROM #T
WHERE TypeID = 1
GROUP BY UserID
UNION ALL
SELECT UserID,
[DateTime],
TypeID
FROM (
--Can't use TOP directly
SELECT T.*,
rn = ROW_NUMBER() OVER (PARTITION BY T.UserID ORDER BY
T.[DateTime])
FROM #T T
JOIN RecursiveCTE R
ON R.UserID = T.UserID
AND T.[DateTime] >=
DATEADD(MINUTE, 15, R.[DateTime])) R
WHERE R.rn = 1)