Find top consecutive rows where a column value is equal between them - sql

I need to get all the consecutive top row where a column value is equal between them
my table is:
CREATE TABLE [dbo].[Items](
[Id] [int] NOT NULL,
[IdUser] [int] NOT NULL,
[CreatedDate] [datetime] NOT NULL,
[SomeData] nvarchar(50) NOT NULL);
and i want the top rows (ordered by Id desc) with the same IdUser
if table data is:
Id IdUser CreatedDate SomeData
--- ------- ------------------------ --------
1 1 2017-09-21T09:42:01.407Z sdafsasfa
2 1 2017-09-21T09:42:01.407Z sdafsasfa
4 2 2017-09-21T09:42:01.41Z sdafsasfa
5 3 2017-09-21T09:42:01.41Z sdafsasfa
7 3 2017-09-21T09:42:01.413Z sdafsasfa
8 3 2017-09-21T09:42:01.413Z sdafsasfa
9 10 2017-09-21T09:42:01.417Z sdafsasfa
11 11 2017-09-21T09:42:01.417Z sdafsasfa
12 2 2017-09-21T09:42:01.42Z sdafsasfa
15 2 2017-09-21T09:42:01.42Z sdafsasfa
I want :
Id IdUser CreatedDate SomeData
--- ------- ------------------------ --------
12 2 2017-09-21T09:42:01.42Z sdafsasfa
15 2 2017-09-21T09:42:01.42Z sdafsasfa
if table data is:
Id IdUser CreatedDate SomeData
--- ------- ------------------------ --------
1 1 2017-09-21T09:42:01.407Z sdafsasfa
2 1 2017-09-21T09:42:01.407Z sdafsasfa
4 2 2017-09-21T09:42:01.41Z sdafsasfa
I want :
Id IdUser CreatedDate SomeData
--- ------- ------------------------ --------
4 2 2017-09-21T09:42:01.41Z sdafsasfa
SqlFiddle

you can try this query:
select I.*
from
[dbo].[Items] I
JOIN
(select top 1 Id, IdUser from [dbo].[Items] order by Id desc)I2
on I.Iduser=I2.Iduser
order by Id desc;-- this can be removed to remove ordering by Id Desc
updated fiddle link

You could use LAG and SUM() OVER() like this
DECLARE #Items as Table
(
[Id] [int] NOT NULL,
[IdUser] [int] NOT NULL,
[CreatedDate] [datetime] NOT NULL,
[SomeData] nvarchar(50) NOT NULL
);
INSERT INTO #Items
(
Id,
IdUser,
CreatedDate,
SomeData
)
VALUES
( 1 , 1 ,getdate(),'sdafsasfa'),
( 2 , 1 ,getdate(),'sdafsasfa'),
( 4 , 2 ,getdate(),'sdafsasfa'),
( 5 , 3 ,getdate(),'sdafsasfa'),
( 7 , 3 ,getdate(),'sdafsasfa'),
( 8 , 3 ,getdate(),'sdafsasfa'),
( 9 , 10,getdate(),'sdafsasfa'),
( 11, 11,getdate(),'sdafsasfa'),
( 12, 2 ,getdate(),'sdafsasfa'),
( 15, 2 ,getdate(),'sdafsasfa')
;WITH temp AS
(
SELECT *,
CASE
WHEN lag(i.IdUser) over(ORDER BY i.Id) = i.IdUser THEN 0
ELSE 1
END as ChangingPoint
FROM #Items i
),
temp1 AS
(
SELECT
*,
sum(t.ChangingPoint) OVER(ORDER BY t.Id) as GroupId
FROM temp t
)
SELECT TOP 1 WITH TIES
t.Id,
t.IdUser,
t.CreatedDate,
t.SomeData
FROM temp1 t
ORDER BY GroupId DESC
See demo here: http://rextester.com/PHWWU96232

Assuming you want last rows with highest CreateDate and same IdUser then DENSE_RANK will help
SELECT id, iduser, CreatedDate, somedata
FROM (
SELECT id, iduser, CreatedDate, somedata,
DENSE_RANK() OVER (ORDER BY CreatedDate desc, IdUser) ord
FROM [dbo].[Items]) t
WHERE t.ord = 1
The equivalent SQL query is
SELECT *
FROM Items t1
WHERE NOT EXISTS (
SELECT *
FROM Items t2
WHERE t2.createddate > t1.createddate or
(t2.createddate = t1.createddate and t2.iduser < t1.iduser)
)
demo

Despite TriV's solution works fine I ended up using a modified Radim Bača's solution (his solution dont work as i need) because it is faster IMO
SELECT id, iduser, createddate, somedata
FROM Items t1
WHERE NOT EXISTS (
SELECT 1
FROM Items t2
WHERE t2.id > t1.id and t2.iduser <> t1.iduser );
SQLFiddle

select I.*
from
[dbo].[Items1] I
JOIN
(select top 1 Id, IdUser,CreatedDate from [dbo].[Items1] order by Id desc)I2
on I.CreatedDate=I2.CreatedDate
order by Id desc;-- this can be removed to remove ordering by Id Desc

Related

Case statements: SQL Server

id date value
------------------
1 1 null
1 2 a
1 3 b
1 4 null
2 1 null
2 2 null
2 3 null
2 4 null
2 5 null
If value is null in all id's then max of date of that id and if we have value then max of date with value id.
Required output is:
id date value
-----------------
1 3 b
2 5 null
Typical method for this type of problem is row_number(). You can create a CASE expression to define a priority:
select id,
date,
value
from (
select id,
date,
value,
row_number() over (partition by id order by case when value is not null then 1 else 2 end asc, date desc) rn
from UnnamedTable
) t1
where t1.rn = 1
Sql Fiddle Demo
WITH cte as (
SELECT id,
[date],
[value],
ROW_NUMBER() OVER (PARTITION BY [ID] ORDER BY [value] DESC, [date] DESC) as rn
FROM Table1
)
SELECT *
FROM cte
WHERE rn = 1

SQL Server 2008 Group Based on a Sequence

I'm struggling to find if this is possible to use SQL Server 2008 to assign a sequence without having to use cursors. Let's say I have the following table which defines a driver's driving route going from one location to another (null means he is going from home):
RouteID SourceLocationID DestinationLocationID DriverID Created Updated
------- ---------------- --------------------- -------- ------- -------
1 NULL 219 1 10:20 10:23
2 219 266 1 10:21 10:24
3 266 NULL 1 10:22 10:25
4 NULL 54 2 10:23 10:26
5 54 NULL 2 10:24 10:27
6 NULL 300 1 10:25 10:28
7 300 NULL 1 10:26 10:29
I want to group the records between the rows where sourceLID is NULL and the destinationLID is null, so I get the following (generating a sequence number for each grouping set):
DriverID DestinationLocationID TripNumber
-------- --------------------- ----------
1 219 1 (his first trip)
1 266 1
1 300 2 (his second trip)
2 54 1
Is there a way I could use GROUP BY here rather than cursors?
a quick try:
with cte as
( select DestinationLocationID
, DriverID
, tripid = row_number()
over ( partition by driverid
order by DestinationLocationID)
from table1
where sourcelocationid is NULL
UNION ALL
select table1.DestinationLocationID
, table1.DriverID
, cte.tripid
from table1
join cte on table1.SourceLocationID=cte.DestinationLocationID
and table1.DriverID=cte.DriverID
where cte.DestinationLocationID is not null
)
select * from cte
Try this:
select driverid, destinationlocationid, count(destinationlocationid) from
(
select driverid, destinationlocationid from table1 where sourcelocationid is NULL
union all
select driverid, sourcelocationid from table1 where destinationlocationid is NULL
)A group by driverid, destinationlocationid
Try this,
Declare #t table(RouteID int, SourceLocationID int,DestinationLocationID int
,DriverID int,Created time, Updated time)
insert into #t
values(1, NULL, 219, 1, '10:20','10:23'),
(2 ,219,266, 1, '10:21','10:24'),
(3,266, NULL, 1, '10:22','10:25'),
(4, NULL, 54, 2, '10:23','10:26'),
(5,54, NULL, 2, '10:24','10:27'),
(6,NULL,300, 1, '10:25','10:28'),
(7,300,NULL, 1, '10:26','10:29')
;
WITH CTE
AS (
SELECT *
,ROW_NUMBER() OVER (
PARTITION BY DriverID ORDER BY Created
) RN
FROM #t
)
,CTE1
AS (
SELECT *
,1 TripNumber
FROM CTE
WHERE RN = 1
UNION ALL
SELECT A.*
,CASE
WHEN A.SourceLocationID IS NULL
THEN B.TripNumber + 1
ELSE B.TripNumber
END
FROM CTE1 B
INNER JOIN CTE A ON B.DriverID = A.DriverID
WHERE A.RN > B.RN
)
SELECT DISTINCT DestinationLocationID
,DriverID
,TripNumber
FROM CTE1
WHERE DestinationLocationID IS NOT NULL
ORDER BY DriverID
Use a correlated sub-query to count previous trips, plus 1 to get this trip number.
select DriverID,
DestinationLocationID,
(select count(*) + 1
from routes t2
where t1.DriverID = t2.DriverID
and t1.RouteID > t2.RouteID
and DestinationLocationID IS NULL) as TripNumber
from routes t1
where DestinationLocationID IS NOT NULL
order by DriverID, DestinationLocationID;
Executes like this:
SQL>select DriverID,
SQL& DestinationLocationID,
SQL& (select count(*) + 1
SQL& from routes t2
SQL& where t1.DriverID = t2.DriverID
SQL& and t1.RouteID > t2.RouteID
SQL& and DestinationLocationID IS NULL) as TripNumber
SQL&from routes t1
SQL&where DestinationLocationID IS NOT NULL
SQL&order by DriverID, DestinationLocationID;
DriverID DestinationLocationID TripNumber
=========== ===================== ============
1 219 1
1 266 1
1 300 2
2 54 1
4 rows found

Group and tally values for each record in SQL [duplicate]

This question already has answers here:
How to use GROUP BY to concatenate strings in SQL Server?
(22 answers)
Closed 8 years ago.
Im trying to run a select statement to group records having similar IDs but also tally the values from another column for each master ID. So for example below. The result for each line will be the first instance unique ID and the 2 names shown from each record separated by semi colon. Thanks in advance.
Current set
ID Name Cnt
-------------------------------- ----------------- ---
0001D72BA5F664BE129B6AB5744E2BE0 Talati, Shilpa 1
0001D72BA5F664BE129B6AB5744E2BE0 Weaver, Larry 1
0007EAB7CE9A3F2F95D2D63D0BBD08A9 St-Hilaire, Edith 1
0007EAB7CE9A3F2F95D2D63D0BBD08A9 Talati, Shilpa 1
Result:
0001D72BA5F664BE129B6AB5744E2BE0 Talati, Shilpa; Weaver, Larry
The easiest way to solve this in SQL Server is:
select masterId, min(name) + '; ' + max(name)
from table t
group by masterId;
Here's one way using a recursive common table expression. Given a table like this:
create table dbo.Fizzbuzz
(
id int not null identity(1,1) primary key clustered ,
group_id int not null ,
name varchar(50) not null ,
cnt int not null ,
)
containing this data
id group_id name cnt
-- -------- ------ ---
1 1 Bob 3
2 1 Carol 5
3 1 Ted 6
4 1 Alice 16
5 2 Harold 72
6 2 Maude 28
This query
with recursive_cte as
(
select group_id = t.group_id ,
row = t.row ,
name = convert(varchar(8000),t.name) ,
cnt = t.cnt
from ( select * ,
row = row_number() over (
partition by group_id
order by id
)
from dbo.Fizzbuzz
) t
where t.row = 1
UNION ALL
select group_id = prv.group_id ,
row = nxt.row ,
name = convert(varchar(8000), prv.name + ' and ' + nxt.name ) ,
cnt = prv.cnt + nxt.cnt
from recursive_cte prv
join ( select * ,
row = row_number() over (
partition by group_id
order by id
)
from dbo.Fizzbuzz
) nxt on nxt.group_id = prv.group_id
and nxt.row = prv.row + 1
)
select group_id = t.group_id ,
total = t.cnt ,
names = t.name
from ( select * ,
rank = rank() over (
partition by group_id
order by row desc
)
from recursive_cte
) t
where rank = 1
order by group_id
produces the following output
group_id cnt name
-------- --- -------------------------------
1 30 Bob and Carol and Ted and Alice
2 100 Harold and Maude
One should note however, that the depth of recursion is bounded in SQL Server.
SELECT
t1.ID,
(SELECT Name + '; '
FROM yourtable t2
WHERE t1.ID = t2.ID
for xml path('')) as Name
FROM yourtable t1
GROUP BY t1.ID

Create indexed view

My table structure is below :
MyTable (ID Int, AccID1 Int, AccID2 Int, AccID3 int)
ID AccID1 AccID2 AccID3
---- -------- -------- --------
1 12 2 NULL
2 4 12 1
3 NULL NULL 5
4 7 NULL 1
I want to create indexed view with below output :
ID Level Value
---- ----- -------
1 1 12
1 2 2
2 1 4
2 2 12
2 3 1
3 3 5
4 1 7
4 3 1
EDIT :
My table is very huge and I want to have above output.
I can Get my query such as below :
Select ID,
Case StrLevel
When 'AccID1' Then 1
When 'AccID2' Then 2
Else 3
End AS [Level],
AccID as Value
From (
Select A.ID, A.AccID1, A.AccID2, A.AccID3
From MyTable A
)as p
UNPIVOT (AccID FOR [StrLevel] IN (AccID1, AccID2, AccID3)) AS unpvt
or
Select *
from (
select MyTable.ID,
num.n as [Level],
Case Num.n
When 1 Then MyTable.AccID1
When 2 Then MyTable.AccID2
Else MyTable.AccID3
End AS AccID
from myTable
cross join (select 1
union select 2
union select 3)Num(n)
)Z
Where Z.AccID IS NOT NULL
or
Select A.ID,
2 AS [Level],
A.AccID1 AS AccID
From MyTable A
Where A.AccID1 IS NOT NULL
Union
Select A.ID,
2 AS [Level],
A.AccID2
From MyTable A
Where A.AccID2 IS NOT NULL
Union
Select A.ID,
3 AS [Level],
A.AccID3
From MyTable A
Where A.AccID3 IS NOT NULL
But Above query is slow and I want to have indexed view to have better performance.
and in indexed view I can't use UNION or UNPIVOT or CROSS JOIN in indexed view.
What if you created a Numbers table to essentially do the work of your illegal CROSS JOIN?
Create Table Numbers (number INT NOT NULL PRIMARY KEY)
Go
Insert Numbers
Select top 30000 row_number() over (order by (select 1)) as rn
from sys.all_objects s1 cross join sys.all_objects s2
go
Create view v_unpivot with schemabinding
as
Select MyTable.ID,
n.number as [Level],
Case n.number
When 1 Then MyTable.AccID1
When 2 Then MyTable.AccID2
Else MyTable.AccID3
End AS AccID
From dbo.Mytable
Join dbo.Numbers n on n.number BETWEEN 1 AND 3
go
Create unique clustered index pk_v_unpivot on v_unpivot (ID, [Level])
go
Select
ID,
[Level],
AccID
From v_unpivot with (noexpand)
Where AccID IS NOT NULL
Order by ID, [Level]
The WHERE AccID IS NOT NULL must be part of the query because derived tables are not allowed in indexed views.

INSERT INTO statement that copies rows and auto-increments non-identity key ID column

Given a table that has three columns
ID (Primary Key, not-autoincrementing)
GroupID
SomeValue
I am trying to write a single SQL INSERT INTO statement that will make a copy of every row that has one GroupID into a new GroupID.
Example beginning table:
ID | GroupID | SomeValue
------------------------
1 | 1 | a
2 | 1 | b
Goal after I run a simple INSERT INTO statement:
ID | GroupID | SomeValue
------------------------
1 | 1 | a
2 | 1 | b
3 | 2 | a
4 | 2 | b
I thought I could do something like:
INSERT INTO MyTable
( [ID]
,[GroupID]
,[SomeValue]
)
(
SELECT (SELECT MAX(ID) + 1 FROM MyTable)
,#NewGroupID
,[SomeValue]
FROM MyTable
WHERE ID = #OriginalGroupID
)
This causes a PrimaryKey violation since it will end up reusing the same Max(ID)+1 value multiple times as it seems.
Is my only recourse to a bunch of INSERT statements in a T-SQL WHILE statement that has an incrementing Counter value?
I also don't have the option of turning the ID into an auto-incrementing Identity column since that would breaking code I don't have source for.
Instead of + 1, add the row number. I also fixed the error in your WHERE clause (should be GroupID =, not ID =):
INSERT INTO MyTable
( [ID]
,[GroupID]
,[SomeValue]
)
(
SELECT
(SELECT MAX(ID) FROM MyTable) + ROW_NUMBER() OVER (ORDER BY GroupId),
#NewGroupID,
[SomeValue]
FROM MyTable
WHERE GroupID = #OriginalGroupID
)
WITH q AS
(
SELECT *,
(
SELECT MAX(id)
FROM mytable
) + ROW_NUMBER() OVER () AS nid
FROM mytable
WHERE groupID = 1
)
INSERT
INTO mytable (id, groupid, somevalue)
SELECT nid, 2, somevalue
FROM q
Use this:
INSERT INTO MyTable
( [ID]
,[GroupID]
,[SomeValue]
)
SELECT ROW_NUMBER() OVER() + X.MaxID
,#NewGroupID
,[SomeValue]
FROM MyTable
CROSS JOIN (SELECT MAX(ID) AS MaxID FROM MyTable) X
WHERE GroupID = #OriginalGroupID