I have a table of segments with a beginning point, an ending point, and a value like so:
Bmp | Emp | SomeVal
0 1 1
1 2 1
2 3 2
3 4 2
4 5 1
I would like to merge (summarize) these records so they look like so:
Bmp | Emp | SomeVal
0 2 1
2 4 2
4 5 1
I've simplified my data set for the purpose of this question. The end result is I need unique rows grouped by the SomeVal column (in my real data set, there are about 20 columns) with the segments stitched together from Bmp to Emp, but not overlapping.
I've tried the following:
DECLARE #tbl TABLE (Bmp int, Emp int, SomeVal int)
INSERT INTO #tbl
SELECT 0, 1, 1 UNION
SELECT 1, 2, 1 UNION
SELECT 2, 3, 2 UNION
SELECT 3, 4, 2 UNION
SELECT 4, 5, 1
SELECT MIN(Bmp) AS Bmp, Max(Emp) AS Emp, SomeVal FROM #tbl
GROUP BY SomeVal
Unfortunately, it comes out like so which is wrong:
Bmp | Emp | SomeVal
0 5 1
2 4 2
My query above only works if the values of SomeVal do not repeat. How can I fix my sql?
Minimum required version is SQL 2008.
You may using ROW_NUMBER() function to correlate begin group row with end group row.
DECLARE #tbl TABLE (Bmp int, Emp int, SomeVal int)
INSERT INTO #tbl
SELECT 0, 1, 1 UNION
SELECT 1, 2, 1 UNION
SELECT 2, 3, 2 UNION
SELECT 3, 4, 2 UNION
SELECT 4, 5, 1
;WITH
[Begins] AS
(
SELECT Bmp, SomeVal, ROW_NUMBER() OVER (ORDER BY Bmp) AS OrderNumber
FROM #tbl AS [Begin]
WHERE NOT EXISTS (
SELECT 1
FROM #tbl AS [Prev]
WHERE [Prev].Emp = [Begin].Bmp AND [Prev].SomeVal = [Begin].SomeVal)
),
[Ends] AS
(
SELECT Emp, SomeVal, ROW_NUMBER() OVER (ORDER BY Emp) AS OrderNumber
FROM #tbl AS [End]
WHERE NOT EXISTS (
SELECT 1
FROM #tbl AS [Next]
WHERE [Next].Bmp = [End].Emp AND [Next].SomeVal = [End].SomeVal)
)
SELECT [Begins].Bmp, [Ends].Emp, [Begins].SomeVal
FROM [Begins]
INNER JOIN [Ends]
ON [Begins].OrderNumber = [Ends].OrderNumber;
Here is another option...
IF OBJECT_ID('tempdb..#TestData', 'U') IS NOT NULL
DROP TABLE #TestData;
CREATE TABLE #TestData (
Bmp INT NOT NULL PRIMARY KEY CLUSTERED,
Emp INT NOT NULL,
SomeVal INT NOT NULL
);
INSERT #TestData (Bmp, Emp, SomeVal)
SELECT 0, 1, 1 UNION ALL
SELECT 1, 2, 1 UNION ALL
SELECT 2, 3, 2 UNION ALL
SELECT 3, 4, 2 UNION ALL
SELECT 4, 5, 1;
--==============================================
WITH
cte_GroupStart AS (
SELECT
td.Bmp,
td.Emp,
td.SomeVal,
GroupStart = CASE WHEN td.SomeVal = LAG(td.SomeVal, 1) OVER (ORDER BY td.Bmp) THEN NULL ELSE ROW_NUMBER() OVER (ORDER BY td.Bmp) END
FROM
#TestData td
),
cte_FillGroup AS (
SELECT
gs.Bmp,
gs.Emp,
gs.SomeVal,
AggGroup = MAX(gs.GroupStart) OVER (ORDER BY gs.Bmp) -- ROWS UNBOUNDED PRECEDING is implied and should work as expected 2008
FROM
cte_GroupStart gs
)
SELECT
Bmp = MIN(fg.Bmp),
Emp = MAX(fg.Emp),
fg.SomeVal
FROM
cte_FillGroup fg
GROUP BY
fg.AggGroup,
fg.SomeVal
ORDER BY
fg.AggGroup;
Related
I have a table with the following values
UserID ParentID Levels Path
1 NULL 0 A1
5 1 1 A2
9 5 2 A3
43 9 3 A4
The output should be like followed :
UserID ParentID Levels FinalPath
1 NULL 0 A1/
5 1 1 A1/A2/
9 5 2 A1/A2/A3/
43 9 3 A1/A2/A3/A4/
Thanks in advance for any guidance on this.
Solution using a recusive common table expression.
Sample data
create table users
(
userid int,
parentid int,
levels int,
path nvarchar(100)
);
insert into users (userid, parentid, levels, path) values
(1, NULL, 0, 'A1'),
(5, 1, 1, 'A2'),
(9, 5, 2, 'A3'),
(43, 9, 3, 'A4');
Solution
with cte as
(
select u.userid, u.parentid, u.levels, u.path
from users u
where u.parentid is null
union all
select u.userid, u.parentid, u.levels, convert(nvarchar(100), c.path + '/' + u.path)
from users u
join cte c
on c.userid = u.parentid
)
select c.userid, c.parentid, c.levels, c.path + '/' as FinalPath
from cte c;
Fiddle
Here's a version that both calculates the Level and appends the Path.
Data
drop table if exists dbo.test_table;
go
create table dbo.test_table(
UserID int,
ParentID int,
[Path] varchar(5));
insert dbo.test_table([UserID], [ParentID], [Path]) values
(1,null, 'A1'),
(5,1, 'A2'),
(9,5, 'A3'),
(43,9, 'A4');
Query
;with recur_cte([UserId], [ParentID], h_level, [Path]) as (
select [UserId], [ParentID], 0, cast([Path] as varchar(100))
from dbo.test_table
where [ParentID] is null
union all
select tt.[UserId], tt.[ParentID], rc.h_level+1, cast(concat(tt.[Path], '/', rc.[Path]) as varchar(100))
from dbo.test_table tt join recur_cte rc on tt.[ParentID]=rc.[UserId])
select * from recur_cte;
Results
UserId ParentID h_level Path
1 NULL 0 A1
5 1 1 A1/A2
9 5 2 A1/A2/A3
43 9 3 A1/A2/A3/A4
If I have:
1, 'a'
2, 'a'
3, 'b'
4, 'b'
And I want to select the rows of each 'letter' which the highest 'id' to produce:
2, 'a'
4, 'b'
I can do it like below.
But is it possible to do this without having to wrap the extra SELECT around the whole thing?
declare #t table (id int, txt varchar)
insert into #t (id, txt)
select 1, 'a' union
select 2, 'a' union
select 3, 'b' union
select 4, 'b'
select * from (
select *, row_number() over (partition by txt order by id desc) as row_num
from #t
) z
where row_num = 1
Just use MAX and GROUP BY:
SELECT MAX(id) AS id, txt FROM #t GROUP BY txt;
This groups by the txt column and gets the maximum id for each of these.
Results:
id txt
----------- ----
2 a
4 b
Good morning all
I would appreciate any help you can give me in this subject
I have a table that grows in time with the same Id1
but some time Id2 change , like a historic of a park.
I would like to find the best way with a query to retrieve
the rows where id2 changes and time
example if table contents are
Id1 Id2 time
1 1 10:00
1 1 10:30
1 2 10:40
1 2 10:45
1 2 11:00
1 3 11:45
1 3 12:45
query output would be
Id1 oldId2 newId2 time
1 1 2 10:40
1 2 3 11:45
i have done with a stored procedure, but I was wondering of there is a faster/cleaner way to get this
thanks in advance
You can do this by Ranking functions..
Schema:
CREATE TABLE #TAB (Id1 INT,Id2 INT, timeS TIME )
INSERT INTO #TAB
SELECT 1 AS Id1 , 1 Id2, '10:00' AS timeS
UNION ALL
SELECT 1, 1, '10:30'
UNION ALL
SELECT 1, 2, '10:40'
UNION ALL
SELECT 1, 2, '10:45'
UNION ALL
SELECT 1, 2, '11:00'
UNION ALL
SELECT 1, 3, '11:45'
UNION ALL
SELECT 1, 3, '12:45'
Now do select with ROW_NUMBER and CTE for retrieving previous/next row values.
;WITH CTE
AS (
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS RNO
,ID1
,ID2
,timeS
FROM (
SELECT ROW_NUMBER() OVER (PARTITION BY ID2 ORDER BY TIMES) AS SNO
,*
FROM #TAB
) A
WHERE SNO = 1
)
SELECT C1.Id1
,C1.Id2 AS OLD_ID2
,C2.Id2 AS NEW_ID2
,C2.timeS
FROM CTE C1
LEFT JOIN CTE C2 ON C1.RNO + 1 = C2.RNO
WHERE C2.Id1 IS NOT NULL
Result:
+-----+---------+---------+------------------+
| Id1 | OLD_ID2 | NEW_ID2 | timeS |
+-----+---------+---------+------------------+
| 1 | 1 | 2 | 10:40:00.0000000 |
| 1 | 2 | 3 | 11:45:00.0000000 |
+-----+---------+---------+------------------+
Note: If you want to get Previous/Next Row values into current row, you can use LEAD LAG functions. But they support only in SQL Server 2012+.
The above Left Join with CTE will work for lower versions too.
declare #t table (Id1 int, Id2 int, [time] time)
insert into #t
select 1, 1, '10:00' union
select 1, 1, '10:30' union
select 1, 2, '10:40' union
select 1, 2, '10:45' union
select 1, 2, '11:00' union
select 1, 3, '11:45' union
select 1, 3, '12:45'
select Id1, oldId = (select top 1 id2 from #t where Id1=t.Id1 and Id2 < t.Id2 order by id2, time desc), newId = id2, time = min(time)
from #t t
where id2 > 1
group by Id1, id2
i have done some changes to the code from Shakeer Mirza.
the pratical problem that originated the question in the first place is:
i have a table that represents the history of an equipment. Being machine internal id(Num_TPA).
Each time there is a malfunction, the machine is replaced by another it keeps the same Num_TPA but Serial_number changes
i needed to know what is the historic on internal_id->Num_TPA . the new and the old serial_number , and the date of replacement
and this is what it came out.
;WITH CTE
AS (
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS RNO
,[Num_TPA]
,[Serial_number]
,[Time]
,a.SNO
FROM (
SELECT ROW_NUMBER() OVER (PARTITION BY [Num_TPA]
ORDER BY [Data_Hora_Ficheiro]) AS SNO
,*
FROM tab_values
) A
WHERE SNO > 1
)
SELECT C1.[Num_TPA]
,C1.[Serial_number] AS OLD_ID2
,C2.[Serial_number] AS NEW_ID2
,C2.[Data_Hora_Ficheiro]
,c2.SNO
,c2.RNO
FROM tab_values C1
LEFT JOIN CTE C2 ON (
C1.[Num_TPA] = C2.[Num_TPA]
AND c1.[Serial_number] != c2.[Serial_number]
AND C2.[Time] > c1.TIME
)
WHERE C2.[Num_TPA] IS NOT NULL
AND SNO = 2
UNION
SELECT C1.[Num_TPA]
,C1.[Serial_number] AS OLD_ID2
,C2.[Serial_number] AS NEW_ID2
,C2.[Data_Hora_Ficheiro]
,c2.SNO
,c2.RNO
FROM CTE C1
LEFT JOIN CTE C2 ON (
C1.SNO + 1 = C2.SNO
AND C1.[Num_TPA] = C2.[Num_TPA]
)
WHERE C2.[Num_TPA] IS NOT NULL
AND C2.SNO > 2
is there any possible way to get a unique id for similar kind of number for example
H_ID ROW_ID
1 1
1 2
1 3
2 1
2 2
3 1
3 2
AS SHOWN 1 IS REPEATED 3 TIMES SO NUMBERS ARE 1,2,3 AND THEN AGAIN STARTS WITH 1
Try it's
declare #table table (h_id int)
insert into #table
select 1
insert into #table
select 1
insert into #table
select 1
insert into #table
select 2
insert into #table
select 2
insert into #table
select 3
insert into #table
select 3
select h_id,d
from (select row_number() over(partition by h_id order by h_id ) as d,*
from #table) as source
Reference https://learn.microsoft.com/en-us/sql/t-sql/functions/row-number-transact-sql
You can use ROW_NUMBER() window function
CREATE TABLE #ROW_TEST
(
H_ID INT
)
INSERT INTO #ROW_TEST
SELECT 1
UNION ALL SELECT
1
UNION ALL SELECT
1
UNION ALL SELECT
2
UNION ALL SELECT
2
UNION ALL SELECT
3
UNION ALL SELECT
3
SELECT H_ID,ROW_NUMBER() OVER (PARTITION BY H_ID ORDER BY H_ID) FROM #ROW_TEST
I have following table:
ID ParentID
1 NULL
2 1
3 2
4 NULL
5 4
6 5
7 3
I want to find the first ID of a specific child ID.
Example: ID=7 and the result is 1
ID=6 and the result is 4
How to do it?
You need to do a bit of recursive CTE magic to solve this one.
Given the data in a table variable thusly:
declare #data table(id int, parentid int)
insert into #data
select 1, null
union select 2,1
union select 3,2
union select 4, null
union select 5,4
union select 6,5
union select 7,3
The following should do the trick:
;with recursiveTable as
(
select d.id, d.parentId, 0 as depth
from #data d
where d.id=6 -- Parameterize this
union all
select d.id, d.parentid, r.depth-1
from #data d
inner join recursiveTable r
on d.id = r.parentId
)
select top 1 id
from recursiveTable
order by depth
Plugging 6 in as above returns 4. Changing this to 7 returns 1 as requested.
Try this:
CREATE TABLE childpar(ID int,ParentID int)
INSERT INTO childpar
values(1,NULL),
(2, 1),
(3, 2),
(4, NULL),
(5, 4),
(6, 5),
(7, 3)
DECLARE #inpyID int
SET #inpyID=7
;WITH CTE1 as (
select * from childpar where id=#inpyID
union all
select c2.* from CTE1 c1 inner join childpar c2 on c1.ParentID = c2.ID
)
select top 1 id from CTE1 order by id asc