SQL close close Gaps in data over time - sql

I have a table of play data that I'm using for a prototype. I'm generating the data while I'm at work, but when I leave and my machine goes to sleep, the data generation stops. This has cause large gaps in my collection of items.
I would like to be able to shift the values of each item in the DateTimeCreated collumn of my table so that there isn't a gap of more than 10 minutes between any item and the next generated item.
The structure of the table is like this:
CREATE TABLE [dbo].[Items](
[Id] [uniqueidentifier] NOT NULL,
[DateTimeCreated] [datetimeoffset](7) NOT NULL,
[AuthorId] [uniqueidentifier] NOT NULL,
[Source] [varchar](max) NOT NULL,
[FullText] [varchar](max) NOT NULL,
CONSTRAINT [PK_Items] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
I was thinking about doing this in L2S, but I have over 1 million records, so IDK if that is the best solution (iterating over each item). I know there has to be some way to do this in SQL that will be much faster.

An alternative Ranking-Functions Approach (not 100% tested):
DECLARE #tenMinutes AS INT = 600;
WITH StartingPoints AS
(
SELECT DateTimeCreated, ROW_NUMBER() OVER(ORDER BY DateTimeCreated) AS rownum
FROM dbo.Items AS A
WHERE NOT EXISTS(
SELECT * FROM dbo.Items AS B
WHERE B.DateTimeCreated < A.DateTimeCreated
AND DATEDIFF(SECOND,B.DateTimeCreated, A.DateTimeCreated) BETWEEN 0 AND #tenMinutes
)
),
EndingPoints AS
(
SELECT DateTimeCreated, ROW_NUMBER() OVER(ORDER BY DateTimeCreated) AS rownum
FROM dbo.Items AS A
WHERE NOT EXISTS(
SELECT * FROM dbo.Items AS B
WHERE A.DateTimeCreated < B.DateTimeCreated
AND DATEDIFF(SECOND,A.DateTimeCreated, B.DateTimeCreated) BETWEEN 0 AND #tenMinutes
)
),
Islands AS
(
SELECT S.DateTimeCreated AS start_range,
E.DateTimeCreated AS end_range,
ROW_NUMBER() OVER(ORDER BY S.DateTimeCreated) AS row_num
FROM StartingPoints AS S
JOIN EndingPoints AS E on E.rownum = S.rownum
),
Ofs AS
(
SELECT I2.start_range,
I2.end_range,
I1.end_range AS prev,
DATEDIFF(SECOND, I1.end_range, I2.start_range) AS offset
FROM Islands AS I1
JOIN Islands AS I2 ON I2.row_num = I1.row_num + 1 OR I2.row_num IS NULL
),
CmlOfs AS
(
SELECT O1.start_range,
O1.end_range,
O1.prev,
O1.offset,
(SELECT SUM(O2.offset) FROM Ofs AS O2
WHERE O2.start_range <= O1.start_range) AS cum_offset
FROM Ofs AS O1
),
UpdateQ AS
(
SELECT Items.*, DATEADD(SECOND, -1 * CmlOfs.cum_offset, Items.DateTimeCreated) AS new_value
FROM Items
JOIN CmlOfs ON Items.DateTimeCreated BETWEEN CmlOfs.start_range AND CmlOfs.end_range
)
UPDATE UpdateQ
SET DateTimeCreated = new_value;

Make sure to have an index on DateTimeCreated if you want this to be anything other than a pig.
It also assumes (as you said in your comment) there are few gaps compared to total number of records.
WITH
gap (Start,Finish)
AS
(
SELECT
DateTimeCreated,
(SELECT MIN(DateTimeCreated) FROM items AS lookup WHERE DateTimeCreated > DateTimeCreated)
FROM
items
WHERE
DATEADD(second, 600, DateTimeCreated) < (SELECT MIN(DateTimeCreated) FROM items AS lookup WHERE DateTimeCreated > DateTimeCreated)
UNION ALL
SELECT
MAX(DateTimeCreated),
MAX(DateTimeCreated)
FROM
items
)
,
offset (Start,Finish,Offset)
AS
(
SELECT
[current].Start,
(SELECT MIN(Start) FROM gap WHERE Start > [current].Start),
DATEDIFF(second, Start, Finish) - 600
FROM
gap AS [current]
)
,
cumulative_offset (Start,Finish,Offset)
AS
(
SELECT
[current].Start,
[current].Finish,
SUM([cumulative].Offset)
FROM
offset AS [current]
INNER JOIN
offset AS [cumulative]
ON [cumulative].Start <= [current].Start
)
UPDATE
items
FROM
cumulative_offset
SET
DateTimeCreated = DATEADD(second, -Offset, DateTimeCreated)
INNER JOIN
items
ON items.DateTimeCreated > cumulative.Start
AND items.DateTimeCreated <= cumulative.Finish

Related

Table not updating in single select query

i have a temporary table which i need to update, the first row is updated but the second row updates as null , please help
declare #T Table
(
ID int,
Name nvarchar(20),
rownum int
)
insert into #T(ID,rownum)
select ID, rownum = ROW_NUMBER() OVER(order by id) from testtabel4
select * from testtabel4
update #t
set Name=case when rownum>1 then (select top 1 Name from #T x where x.rownum=(y.rownum-1))
else 'first' end
from #t y
select * from #T
and here the definition of testtabel4
CREATE TABLE [dbo].[testtabel4](
[ID] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](80) NULL,
PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
and here is the output
ID Name
1 first
2 NULL
I think your update would be better written with lag() and an updateable CTE.
with cte as (
select name, lag(name, 1, 'first') over(order by rownum) lag_name
from #t
)
update cte set name = lag_name
With this technique at hand, it is plain to see that don't actually need to feed the table first, then insert into it. You can do both at once, like so:
insert into #t (id, name, rownum)
select
id,
lag(name, 1, 'first') over(order by id),
row_number() over(order by id)
from testtabel4
I am not sure that you even need rownum column anymore, unless it is needed for some other purpose.
You are only inserting two columns in the #T:
insert into #T (ID, rownum)
select ID, ROW_NUMBER() OVER (order by id)
from testtabel4;
You are not inserting name so it is NULL on all rows. Hence, the then part of the case expression will always be NULL.

How to convert Rows to Columns in Sql

I've a table Columns
and a second table Response in which all data is saved.
Now I want to create a SQL View in which the result should be like this
I tried using pivot
select UserId ,FromDate, ToDate, Project, Comment
from
(
select R.UserId ,R.Text , C.ColumnName
from [Columns] C
INNER JOIN Response R ON C.Id=R.ColumnId
) d
pivot
(
max(Text)
for ColumnName in (FromDate, ToDate, Project, Comment)
) piv;
but that didn't worked for me, I also referred this Efficiently convert rows to columns in sql server but was not able to implement it. Any ideas how to achieve the same in SQL View?
Scripts for Tables:
CREATE TABLE [dbo].[Columns](
[Id] [bigint] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](1000) NULL,
[IsActive] [bit] NULL,
CONSTRAINT [PK_Columns] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
insert into [Columns] values('FromDate',1)
insert into [Columns] values('ToDate',1)
insert into [Columns] values('Project',1)
insert into [Columns] values('Comment',1)
CREATE TABLE [dbo].[Response](
[Id] [bigint] IDENTITY(1,1) NOT NULL,
[UserId] [bigint] NOT NULL,
[ColumnId] [bigint] NOT NULL,
[Text] [nvarchar](max) NULL,
[IsActive] [bit] NULL,
CONSTRAINT [PK_Response] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
insert into [Response] values(1,1,'1/1/2012',1)
insert into [Response] values(1,2,'1/2/2012',1)
insert into [Response] values(1,3,'p1',1)
insert into [Response] values(1,4,'c1',1)
insert into [Response] values(2,1,'1/1/2013',1)
insert into [Response] values(2,2,'1/2/2013',1)
insert into [Response] values(2,3,'p2',1)
insert into [Response] values(2,4,'c2',1)
insert into [Response] values(2,1,'1/1/2014',1)
insert into [Response] values(2,2,'1/2/2014',1)
insert into [Response] values(2,3,'p3',1)
insert into [Response] values(2,4,'c3',1)
insert into [Response] values(3,1,'1/1/2015',1)
insert into [Response] values(3,2,'1/2/2015',1)
insert into [Response] values(3,3,'p4',1)
insert into [Response] values(3,4,'c4',1)
Honestly, if the column types aren't going to change, or you only need a subset of them, you could just filter them out and then join on them rather than write a pivot. I wrote it using a cte, but they could just as easily be sub-queries:
;with fd as
(
select
UserID,
[Text] as FromDate,
row_number() over (partition by userID order by ID) as DEDUP
from response
where ColumnID = 1
),
td as
(
select
UserID,
[Text] as ToDate,
row_number() over (partition by userID order by ID) as DEDUP
from response
where ColumnID = 2
),
p as
(
select
UserID,
[Text] as Project,
row_number() over (partition by userID order by ID) as DEDUP
from response
where ColumnID = 3
),
c as
(
select
UserID,
[Text] as Comment,
row_number() over (partition by userID order by ID) as DEDUP
from response
where ColumnID = 4
)
select
fd.*,
td.ToDate,
p.Project,
c.Comment
from fd
inner join td
on fd.UserId = td.UserId
and fd.DEDUP = td.DEDUP
inner join p
on fd.UserId = p.UserId
and fd.DEDUP = p.DEDUP
inner join c
on fd.UserId = c.UserId
and fd.DEDUP = c.DEDUP
Try this. I worked on your answer.
select UserId ,FromDate, ToDate, Project, Comment
from
(
select R.UserId ,R.RText , C.ColumnName
from [Columns] C
INNER JOIN Response R ON C.Id=R.ColumnId
) d
pivot
(
Min(Rtext)
for ColumnName in (FromDate, ToDate, Project, Comment)
) piv
UNION
select UserId ,FromDate, ToDate, Project, Comment
from
(
select R.UserId ,R.RText , C.ColumnName
from [Columns] C
INNER JOIN Response R ON C.Id=R.ColumnId
) d
pivot
(
Max(Rtext)
for ColumnName in (FromDate, ToDate, Project, Comment)
) piv;
You can query like this
;with cte as
(
select r.*,
c.name
from Response r
inner join Columns c
on r.columnid = c.id
)
select
Userid,
max([FromDate]) as [FromDate],
max([ToDate]) as [ToDate],
max([Project]) as [Project],
max([Comment]) as [Comment]
from cte
pivot
(
max(Text) for name in ([FromDate], [ToDate], [Project], [Comment])
) p
group by userid

How can I display repeated values only one time and have '-' if it repeats

I have some subqueries that retreives the same values for each PolicyNumber. How can I substitute repeated value with '-' and only display it one in a top row for each policy?
Right now I have this:
But I need something like this:
SELECT
-------------/* GrossPremium*/
(SELECT ISNULL(SUM(tblFin_InvoiceDetails.AmtBilled), 0)
FROM tblFin_InvoiceDetails WITH (NOLOCK)
WHERE (tblFin_InvoiceDetails.ChargeType = 'P')
AND (tblFin_InvoiceDetails.InvoiceNum = INV.InvoiceNum))
AS GrossPremium
--------------/*CompanyCommissionPercentage*/
,((SELECT ISNULL(SUM(tblFin_InvoiceDetails.MGAAmt), 0)
FROM tblFin_InvoiceDetails
WHERE (tblFin_InvoiceDetails.ChargeType = 'P')
AND (tblFin_InvoiceDetails.InvoiceNum = INV.InvoiceNum))
+
CASE WHEN INV.Remitter = 'B' then
(SELECT ISNULL(SUM(tblFin_InvoiceDetails.RemitterAmt), 0)
FROM tblFin_InvoiceDetails
WHERE (tblFin_InvoiceDetails.ChargeType = 'P')
AND (tblFin_InvoiceDetails.InvoiceNum = INV.InvoiceNum))----------------RemitterCommission
ELSE
(SELECT ISNULL(SUM(tblFin_InvoicedItemsPayees.PayeeAmt), 0)
FROM tblFin_InvoicedItemsPayees
INNER JOIN tblFin_PolicyCharges pc on pc.ChargeCode = tblFin_InvoicedItemsPayees.ChargeCode and pc.chargeType = 'P'
WHERE (tblFin_InvoicedItemsPayees.InvoiceNum = INV.InvoiceNum and tblFin_InvoicedItemsPayees.PayeeGuid = INV.ProducerLocationGuid))
END) * 100 /
NULLIF((SELECT ISNULL(SUM(tblFin_InvoiceDetails.AmtBilled), 0)
FROM tblFin_InvoiceDetails WITH (NOLOCK)
WHERE (tblFin_InvoiceDetails.ChargeType = 'P')
AND (tblFin_InvoiceDetails.InvoiceNum = INV.InvoiceNum)),0)
AS CompanyCommissionPercentage
FROM [tblFin_PayablesWorking] PW
INNER JOIN tblFin_Invoices INV ON PW.InvoiceNumber=INV.InvoiceNum
Well since you do not mention you full query and Table schema, i will give your answer with two simple example.If you want replace your repeated value with - the follow this query(please change the columns name according to your needs).
IF YOU WANT TO SELECT YOUR EXISTING TABLE:
;with ts as (
select S1.[ProductID], row_number() over (partition by S1.[ProductID] order by S1.[ProductID]) as seqnum
from (SELECT [SalesID],[ProductID] FROM [Sales]) AS S1 --Replace 'SELECT [ProductID] FROM [Sales]' with your Subquery and change the column accordingly
)
SELECT
(case when seqnum = 1 then [ProductID] ELSE '-' end) as [ProductID]
FROM ts
FOR USING SUBQUERY:
--CREATE TABLE [dbo].[Sales](
-- [SalesID] [uniqueidentifier] NOT NULL DEFAULT (newid()),
-- [ProductID] [int] NOT NULL,
-- [EmployeeID] [int] NOT NULL,
-- [Quantity] [smallint] NOT NULL,
-- [SaleDate] [datetime] NOT NULL CONSTRAINT [DF_SaleDate] DEFAULT (getdate()),
-- CONSTRAINT [PK_SalesID] PRIMARY KEY CLUSTERED
--(
-- [SalesID] ASC
--)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
--) ON [PRIMARY]
--GO
--INSERT [dbo].[Sales] ([SalesID], [ProductID], [EmployeeID], [Quantity], [SaleDate]) VALUES (N'9498d566-e31b-4ac8-ab54-1c898471fba8', 2, 1, 1, CAST(N'2012-03-01 00:00:00.000' AS DateTime))
--INSERT [dbo].[Sales] ([SalesID], [ProductID], [EmployeeID], [Quantity], [SaleDate]) VALUES (N'69c7dff4-fbac-48d3-ae0a-5027c816acd2', 2, 2, 2, CAST(N'2012-04-01 00:00:00.000' AS DateTime))
--INSERT [dbo].[Sales] ([SalesID], [ProductID], [EmployeeID], [Quantity], [SaleDate]) VALUES (N'a40b9505-4a2c-4186-a89b-88a401248a58', 1, 1, 4, CAST(N'2012-02-01 00:00:00.000' AS DateTime))
--INSERT [dbo].[Sales] ([SalesID], [ProductID], [EmployeeID], [Quantity], [SaleDate]) VALUES (N'04856027-d7ad-40fe-889b-8d933595ffde', 3, 1, 2, CAST(N'2012-02-01 00:00:00.000' AS DateTime))
--INSERT [dbo].[Sales] ([SalesID], [ProductID], [EmployeeID], [Quantity], [SaleDate]) VALUES (N'173be2de-3b80-4a3d-8bcc-a74d0d70b3a9', 3, 2, 1, CAST(N'2012-03-01 00:00:00.000' AS DateTime))
--GO
;with ts as (
SELECT
JOIN1.[SalesID] AS [SalesID]
, JOIN1.[ProductID]
, JOIN1.seqnum AS seqnum
, JOIN2.[EmployeeID], JOIN2.seqnum2 AS seqnum2
FROM
(
select row_number() over (order by S1.[SalesID] asc) as RowNumber
, S1.[SalesID] AS [SalesID]
, S1.[ProductID] AS [ProductID]
, row_number() over (partition by S1.[ProductID] order by S1.[SalesID]) as seqnum
from (SELECT [SalesID],[ProductID] FROM [Sales]) AS S1 --Replace 'SELECT [ProductID] FROM [Sales]' with your Subquery ( For Example GrossPremium) and change the column accordingly. Remember you need some thing common for Iner join, in this case [SalesID]
)AS JOIN1
INNER JOIN
(
select row_number() over (order by S2.[SalesID] asc) as RowNumber
, S2.[SalesID] AS [SalesID]
, S2.[EmployeeID] AS [EmployeeID]
, row_number() over (partition by S2.[EmployeeID] order by S2.[SalesID]) as seqnum2
from (SELECT [SalesID],[EmployeeID] FROM [Sales]) AS S2 --Replace 'SELECT [[SalesID]] FROM [Sales]' with your Subquery ( For Example CompanyCommissionPercentage) and change the column accordingly. Remember you need some thing common for Iner join, in this case [SalesID]
)AS JOIN2
ON JOIN1.[SalesID]=JOIN2.[SalesID]
)
SELECT
(case when seqnum = 1 then [ProductID] ELSE '-' end) as [ProductID]
,(case when seqnum2 = 1 then [EmployeeID] ELSE '-' end) as [EmployeeID]
FROM (Select TOP 10000000 *FROM ts ORDER BY [SalesID] ASC ) AS ts -- Mentioning TOP is Must, or it will give Error
I do know why you are using - instead of NULL, - will take space
if you wanted to do it in SQL (you probably shouldn't it's pretty ugly) you could do something like this using LAG(). It relies on having a field that you can use to sort the records for each Policy Number, in my dummy data below I included a field called RecordID to do this.
SELECT
PolicyNumber
,CASE
WHEN LAG(GrossPremium) OVER(PARTITION BY PolicyNumber ORDER BY RecordID) IS NULL
THEN CAST(GrossPremium AS VARCHAR(MAX))
ELSE '-'
END GrossPremium
,CASE
WHEN LAG(CompanyComissionPercentage) OVER(PARTITION BY PolicyNumber ORDER BY RecordID) IS NULL
THEN CAST(CompanyComissionPercentage AS VARCHAR(MAX))
ELSE '-'
END CompanyComissionPercentage
,CASE
WHEN LAG(RemitterCommissionPercentage) OVER(PARTITION BY PolicyNumber ORDER BY RecordID) IS NULL
THEN CAST(RemitterCommissionPercentage AS VARCHAR(MAX))
ELSE '-'
END RemitterCommissionPercentage
,CASE
WHEN LAG(RemitterCommission) OVER(PARTITION BY PolicyNumber ORDER BY RecordID) IS NULL
THEN CAST(RemitterCommission AS VARCHAR(MAX))
ELSE '-'
END GrossCommission
,CASE
WHEN LAG(RemitterCommission) OVER(PARTITION BY PolicyNumber ORDER BY RecordID) IS NULL
THEN CAST(RemitterCommission AS VARCHAR(MAX))
ELSE '-'
END GrossCommission
FROM
(
-- Dummy data
SELECT
1234 PolicyNumber -- Partition the LAG() on the policy number.
,1 RecordID -- use this to order the LAG() function.
,8749.00 GrossPremium
,18 CompanyComissionPercentage
,10 RemitterCommissionPercentage
,874.90 RemitterCommission
,1574.82 GrossCommission
UNION ALL
SELECT
1234
,2 RecordID
,8749.00
,18
,10
,874.90
,1574.82
UNION ALL
SELECT
5678
,1 RecordID
,8749.00
,18
,10
,874.90
,1574.82
) x;

Please help me to write this query in SQL Server

Consider this table :
dt qnt
---------- -------
1 10
2 -2
3 -4
4 3
5 -1
6 5
How do I create a query to get this result? (res is a running total column):
dt qnt res
---- ----- -----
1 10 10
2 -2 8
3 -4 4
4 3 7
5 -1 6
6 5 11
You can do it using a simple subquery that calculates the sum up to the current row, which should work well on any version of SQL Server;
SELECT dt, qnt,
(SELECT SUM(qnt) FROM Table1 ts WHERE ts.dt <= t1.dt) res
FROM Table1 t1
ORDER BY dt;
An SQLfiddle to test with.
If you're using SQL Server 2012, see Amit's answer for a more efficient query.
If you are Using sql Server 2012 than you can try like this.
Select * ,Sum([qnt]) Over(order by dt) from table1
Sql Fiddle Demo
Use one of this way :
CREATE TABLE [dbo].[T1]
(
[dt] [int] IDENTITY(1, 1)
NOT NULL ,
qnt [int] NULL ,
CONSTRAINT [PK_T1] PRIMARY KEY CLUSTERED ( [dt] ASC )
WITH ( PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON ) ON [PRIMARY]
)
ON [PRIMARY]
GO
INSERT INTO dbo.T1 ( qnt ) VALUES ( 10 )
INSERT INTO dbo.T1 ( qnt ) VALUES ( -2 )
INSERT INTO dbo.T1 ( qnt ) VALUES ( -4 )
INSERT INTO dbo.T1 ( qnt ) VALUES ( 3 )
INSERT INTO dbo.T1 ( qnt ) VALUES ( -1 )
INSERT INTO dbo.T1 ( qnt ) VALUES ( 5 )
GO
SELECT * ,
RunningSum = T1.qnt + COALESCE(( SELECT SUM(qnt)
FROM T1 AS T1Sub
WHERE ( T1Sub.dt < T1.dt )
), 0)
FROM T1
Go
SELECT T1.dt ,
T1.qnt ,
SUM(T1Inner.qnt)
FROM T1
INNER JOIN T1 AS T1Inner ON ( T1.dt >= T1Inner.dt )
GROUP BY T1.dt ,
T1.qnt
ORDER BY T1.dt ,
T1.qnt
GO
SELECT T1.* ,
T2.RunningSum
FROM T1
CROSS APPLY ( SELECT SUM(qnt) AS RunningSum
FROM T1 AS CAT1
WHERE ( CAT1.dt <= T1.dt )
) AS T2
Go
SELECT * ,
RunningSum = ( SELECT SUM(qnt)
FROM T1 AS T1In
WHERE ( T1In.dt <= T1.dt )
)
FROM T1
Go
-- In Sql Server 2012
SELECT * ,
SUM(T1.qnt) OVER ( ORDER BY T1.dt
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW )
AS RunningTotal
FROM T1
Drop Table [dbo].[T1]

sql adjacency list CTE : filter/limit to only complete paths

I have built a function/query that returns [paths] as an adjacency list as shown below and it works well,
however I am trying t figure out how to limit the results to only complete paths, not each iteration of depth(n).
for example, out of the following results shown below, only these are valid, FULL paths I want to return or filter:
S11>S11-LG>V613>V613_Close>B31A>B30>B30A>V30A1>V30A1_Close>B-GARNER>VB1>VB1_Close
S11>S11-LG>V613>V613_Close>B31A>B30>B30A>V30A1>V30A1_Close>B-GARNER>VB1>VB1_Open
S11>S11-LG>V613>V613_Close>B31A>B30>B30A>V30A1>V30A1_Close>B-GARNER>VB1>VB2
S11>S11-LG>V613>V613_Close>B31A>B30>B30A>V30A1>V30A1_Open>V30A2>V30A2_Close>S2-UG
S11>S11-LG>V613>V613_Close>B31A>B30>B30A>V30A1>V30A1_Open>V30A2>V30A2_Open>S1-UG
TEST
select * from fn_Get_SubTreePaths('S11', 11) order by DPath
results
S11>S11-LG
S11>S11-LG>V613
S11>S11-LG>V613>V613_Close
S11>S11-LG>V613>V613_Close>B31A
S11>S11-LG>V613>V613_Close>B31A>B30
S11>S11-LG>V613>V613_Close>B31A>B30>B30A
S11>S11-LG>V613>V613_Close>B31A>B30>B30A>V30A1
S11>S11-LG>V613>V613_Close>B31A>B30>B30A>V30A1>V30A1_Close
S11>S11-LG>V613>V613_Close>B31A>B30>B30A>V30A1>V30A1_Close>B-GARNER
S11>S11-LG>V613>V613_Close>B31A>B30>B30A>V30A1>V30A1_Close>B-GARNER>VB1
S11>S11-LG>V613>V613_Close>B31A>B30>B30A>V30A1>V30A1_Close>B-GARNER>VB1>VB1_Close
S11>S11-LG>V613>V613_Close>B31A>B30>B30A>V30A1>V30A1_Close>B-GARNER>VB1>VB1_Open
S11>S11-LG>V613>V613_Close>B31A>B30>B30A>V30A1>V30A1_Close>B-GARNER>VB1>VB2
S11>S11-LG>V613>V613_Close>B31A>B30>B30A>V30A1>V30A1_Open
S11>S11-LG>V613>V613_Close>B31A>B30>B30A>V30A1>V30A1_Open>V30A2
S11>S11-LG>V613>V613_Close>B31A>B30>B30A>V30A1>V30A1_Open>V30A2>V30A2_Close
S11>S11-LG>V613>V613_Close>B31A>B30>B30A>V30A1>V30A1_Open>V30A2>V30A2_Close>S2-UG
S11>S11-LG>V613>V613_Close>B31A>B30>B30A>V30A1>V30A1_Open>V30A2>V30A2_Open
S11>S11-LG>V613>V613_Close>B31A>B30>B30A>V30A1>V30A1_Open>V30A2>V30A2_Open>S1-UG
In other words - how can I determine only those paths which include the bottom-most item in the traversal / depth ?
Any help would be greatly appreciated! I am reading through Joe Celko's Trees and Hierarchies in SQL for Smarties - but have so far not wrapped my brain around how to limit the results correctly ...
Here is the code;
create function [dbo].[fn_Get_SubTreePaths]( #Start varchar(20), #MaxLevels int)
returns table
as
RETURN(
WITH CTE AS (
SELECT
c.DeviceID AS Start,
CAST(d.DeviceName AS VARCHAR(2000)) AS Path,
c.ConnectedDeviceID,
1 AS Level
FROM Connections c
INNER JOIN Devices d ON d.ID=c.DeviceID
AND d.DeviceName=#Start
UNION ALL
SELECT
r.Start,
CAST(r.Path + '>'+ d.DeviceName AS VARCHAR(2000)),
--CAST(r.Path + ' -> ' + d.DeviceName + '-' + cast(r.SLevel as varchar) AS VARCHAR),
c.ConnectedDeviceID,
Level = r.Level + 1
FROM Connections c
INNER JOIN Devices d ON d.ID=c.DeviceID
and d.DeviceName<>#Start
INNER JOIN CTE r ON c.DeviceID=r.ConnectedDeviceID
AND r.Level < #MaxLevels
)
SELECT
DISTINCT a.DPath
FROM (
SELECT c.Path + '>' + ISNULL(d.DeviceName,'?') AS DPath
FROM CTE c
INNER JOIN Devices d ON d.ID=c.ConnectedDeviceID
) a
)
CREATE TABLE [dbo].[Connections](
[ID] [int] NOT NULL,
[DeviceID] [int] NULL,
[ConnectedDeviceID] [int] NULL
CONSTRAINT [PK_Connections] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
CREATE TABLE [dbo].[Devices](
[ID] [int] IDENTITY(1,1) NOT NULL,
[DeviceName] [varchar](50) NULL
CONSTRAINT [PK_Devices] PRIMARY KEY CLUSTERED
(
[ID] ASC
) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
And the view used by the CTE:
ALTER view [dbo].[vw_DeviceConnections] as
select
c.ID as ID,
c.DeviceID as DeviceID,
d.DeviceName,
d.DeviceType,
c.ConnectedDeviceID as ConnDeviceID,
cd.DeviceName as ConnDeviceName,
cd.DeviceType as ConnDeviceType
from Devices d
inner join Connections c on
d.id=c.DeviceID
inner join Devices cd on
cd.id=c.ConnectedDeviceID
See the answer working on SQLFiddle here.
Here's my strategy: Make your recursive CTE return the current node as well as the node before it. Then you can join that on itself and restrict it to rows whose last node is not used as the parent of another node.
Here's how your function would look (replace 'S11' and 15 with your parameters):
WITH CTE AS (
SELECT
d.ID AS Start,
CAST(d.DeviceName AS VARCHAR(2000)) AS Path,
d.ID AS node,
NULL AS parent,
1 AS Level
FROM Devices d
WHERE d.DeviceName= 'S11'
UNION ALL
SELECT
r.Start,
CAST(r.Path + '>'+ d.DeviceName AS VARCHAR(2000)),
d.ID as node,
r.node as parent,
r.Level + 1 as Level
FROM CTE r
INNER JOIN Connections c ON c.DeviceID = r.node
INNER JOIN Devices d ON d.ID = c.ConnectedDeviceID
WHERE r.Level < 15
),
Trimmed as (
SELECT L.*
FROM CTE L
LEFT JOIN CTE R on L.node = R.parent
WHERE R.parent IS NULL
)
SELECT * FROM Trimmed
Let me know if you'd like clarification about how it works, I can try to explain it better.
try next solution
select
*
from
fn_Get_SubTreePaths('S11', 11) f1
where
(
select
count(*)
from
fn_Get_SubTreePaths('S11', 11) f2
where
charindex(f1.dPath, f2.dPath) > 0
) = 1
order by DPath