Get parents and children given a segment in a recursion SQL - sql-server

Given an ID at the beginning or middle or end, I want to look up for all the rows that are linked between them.
With this I found the child elements recursively
declare #T table(
Id int primary key,
Name nvarchar(255) not null,
ParentId int)
insert into #T values
(1, 'A', NULL),
(2, 'B', 1),
(3, 'C', 2),
(4, 'D', NULL),
(5, 'E', 1)
declare #Id int = 2
;with cte as
(
select T.*
from #T as T
where T.Id = #Id
union all
select T.*
from #T as T
inner join cte as C
on T.ParentId = C.Id
)
select *
from cte
If #Id is equals 2, I will get 2 and 3. But I want to retrieve the parent too, in this case 1.
I want to get this:
If id = 1
1 A NULL
2 B 1
3 C 2
5 E 1
If id = 2
1 A NULL
2 B 1
3 C 2
5 E 1
If id = 3
1 A NULL
2 B 1
3 C 2
5 E 1
If id = 4
4 D NULL
IF id = 5
1 A NULL
2 B 1
3 C 2
5 E 1
How can I fix this?

Ok! I've got you:
I created a separate family for testing.
The first thing you need to do is create a query to find the oldest ancestor of your chosen ID. That is the first CTE below. Then you designate that, I called it #Eve and find Eve, all children of Eve.
Here is the example you can play around with
create table UserType (
Id int,
name nvarchar(255),
parentid int)
insert into UserType values
(1, 'A', NULL),
(2, 'B', 1),
(3, 'C', 2),
(4, 'D', NULL),
(5, 'E', 1),
(6, 'F', NULL),
(7, 'G', 6),
(8, 'H', 7);
DECLARE #eve BIGINT; --ancestor
DECLARE #id BIGINT;
SET #id = 8; --This is where you choose
WITH tblParent AS --CTE for oldest ancestor
(
SELECT *
FROM UserType WHERE Id = #id
UNION ALL
SELECT UserType.*
FROM UserType JOIN tblParent ON UserType.Id = tblParent.ParentId
)
select
#eve = (select top 1 id from tblParent order by id);
WITH tblChild AS --CTE for all #eve and all children
(
SELECT *
FROM UserType WHERE id = #eve
UNION ALL
SELECT UserType.* FROM UserType JOIN tblChild ON UserType.ParentId = tblChild.Id
)
SELECT * FROM tblChild
order by id
OPTION(MAXRECURSION 32767)
Props to CodeProject which was quite helpful.

Try something like this.
declare #tbl table( --test table var
Id int,
name nvarchar(255),
parentid int)
insert into #tbl values -- test data
(1, 'A', NULL),
(2, 'B', 1),
(3, 'C', 2),
(4, 'D', NULL),
(5, 'E', 1),
(6, 'F', NULL),
(7, 'G', 6),
(8, 'H', 7);
declare #id int = 7
;with parents as (
select id,name,parentid, 0 lvl
from #tbl
where id=#id
union all
select t.id,t.name,t.parentid, lvl-1
from #tbl t
inner join parents p on t.Id=p.parentid --up the tree
)
,children as (
select id,name,parentid, 0 lvl
from #tbl
where id=#id --select single record
union all
select t.id,t.name,t.parentid, lvl+1
from #tbl t
inner join children c on c.Id=t.parentid -- down the tree
)
select * from parents
union --combine results
select * from children
order by lvl

Related

Recursive CTE with hierarchy data

Sample data:
declare #docs table (docid int, name varchar(10), isfolder int)
declare #hierarchy table (childid int, parent varchar(10))
insert #docs values (1, 'Doc1', 0)
insert #docs values (2, 'Doc2', 0 )
insert #docs values (3, 'Folder1', 1 )
insert #docs values (4, 'Folder2', 1 )
insert #docs values (5, 'SubFolderA', 1 )
insert #docs values (6, 'SubFolderB', 1 )
insert #hierarchy values (1, 5)
insert #hierarchy values (1, 6)
insert #hierarchy values (2, 6)
insert #hierarchy values (5, 3)
insert #hierarchy values (6, 4)
I want to list the data above so I end up with the documents (isfolder = 0) and the folder paths that they are in.
Expected output:
DocId Name Path
--------------------------------------
1 Doc1 Folder1\SubFolderA
1 Doc1 Folder2\SubFolderB
2 Doc2 Folder2\SubFolderB
CTE
I started to write this next CTE as it said this can be used to create my output but I am wrong somewhere
;WITH folderCTE (docid, name) AS
(
-- Anchor member
SELECT docid, CAST(name AS varchar(max)) AS name
FROM #docs
WHERE isfolder = 0
UNION ALL
-- Recursive member that references expression_name.
SELECT d.docid, CAST(folderCTE.Name + '\' + d.name AS varchar(max)) AS name
FROM folderCTE
INNER JOIN #docs d ON d.docid = folderCTE.docid
)
-- references expression name
SELECT *
FROM folderCTE
Can anyone show me how I should be doing a CTE for this?
If you use a CTE which just expands the hierarchy for folders, and a second one to build the hierarchy, you can simply join to this to get the folder for your docs:
declare #docs table (docid int, name varchar(10), isfolder int)
declare #hierarchy table (childid int, parentid varchar(10))
insert #docs values (1, 'Doc1', 0)
insert #docs values (2, 'Doc2', 0 )
insert #docs values (3, 'Folder1', 1 )
insert #docs values (4, 'Folder2', 1 )
insert #docs values (5, 'SubFolderA', 1 )
insert #docs values (6, 'SubFolderB', 1 )
insert #docs values (7, 'SuperFold1', 1 )
insert #hierarchy values (1, 5)
insert #hierarchy values (1, 6)
insert #hierarchy values (2, 6)
insert #hierarchy values (5, 3)
insert #hierarchy values (6, 4)
insert #hierarchy values (3, 7)
;WITH folderCTE
AS
(
select docid, cast(name as nvarchar(max)) as name, parentid
from #docs d
left join #hierarchy h on d.docid=h.childid
where d.isfolder = 1
),
folderHierarchyCTE
as
(
select docid, cast(name as nvarchar(max)) as name
from folderCTE where parentid is null
union all
select d.docid, cast(p.name + '/' + d.name as nvarchar(max))
from folderCTE d
inner join folderHierarchyCTE p on d.parentid = p.docid
)
SELECT d.docid, d.name, f.name
FROM #docs d
inner join #hierarchy h on h.childid=d.docid
inner join folderHierarchyCTE f on h.parentid=f.docid
where d.isfolder = 0

How to get the root in a hierarchy query using SQL Server from any level of Hierarchy

I would like to get the Top most Ancestor (Root) of the hierarchy from any level of data.
The following is my table.
CREATE TABLE #SMGROUP (ID INT NOT NULL, GRP NVARCHAR(40), GRPCLASS INT, PARENTGRP NVARCHAR(40), PARENTGRPCLASS INT)
INSERT INTO #SMGROUP VALUES (1, 'A', 1, NULL,NULL)
INSERT INTO #SMGROUP VALUES (1, 'B', 1, NULL,NULL)
INSERT INTO #SMGROUP VALUES (1, 'C', 1, NULL,NULL)
INSERT INTO #SMGROUP VALUES (1, 'A.1', 2, 'A',1)
INSERT INTO #SMGROUP VALUES (1, 'A.2', 2, 'A',1)
INSERT INTO #SMGROUP VALUES (1, 'A.3', 2, 'A',1)
INSERT INTO #SMGROUP VALUES (1, 'B.1', 2, 'B',1)
INSERT INTO #SMGROUP VALUES (1, 'B.2', 2, 'B',1)
INSERT INTO #SMGROUP VALUES (1, 'A.3.3', 3, 'A.3',2)
INSERT INTO #SMGROUP VALUES (1, 'A.3.3.3', 4, 'A.3.3',3)
INSERT INTO #SMGROUP VALUES (1, 'A.3.3.3.1', 5, 'A.3.3.3',4)
INSERT INTO #SMGROUP VALUES (1, 'B.1.2', 3, 'B.1',2)
INSERT INTO #SMGROUP VALUES (1, 'B.2.1', 3, 'B.2', 2)
SELECT * FROM #SMGROUP
I Would like to have the value of - 'A' if I provide 'A.1' as input, also the return value would be 'A' if I provide 'A.3.3' as input. Also the return would be 'A' if the parameter is 'A.3.3.3.1'
I have written some thing like this, but I am not sure how to continue after this.
;WITH items AS (
SELECT G.GRP ,CAST('' AS NVARCHAR(30)) AS ParentGroup,
0 AS Level
FROM #SMGROUP G
WHERE G.PARENTGRP IS NULL
UNION ALL
SELECT G.GRP, CAST(G.PARENTGRP AS NVARCHAR(30)) AS ParentGroup
, Level + 1
FROM #SMGROUP G
INNER JOIN items itms ON itms.GRP = G.PARENTGRP
)
SELECT * FROM items
You are on the right direction, you just need one last push.
Instead of using a "standard" recursive cte that traverse from root to leaf nodes, you "reverse" the process and traverse from the input node back to the root.
Then it's simply a top 1 with level desc in the order by clause:
DECLARE #GRP NVARCHAR(40) = 'A.3.3.3.1';
WITH items AS (
SELECT G.GRP,
ISNULL(G.PARENTGRP, '') AS ParentGroup,
0 AS Level
FROM #SMGROUP G
WHERE G.GRP = #GRP
UNION ALL
SELECT G.GRP,
G.PARENTGRP,
Level + 1
FROM #SMGROUP G
INNER JOIN items itms
ON itms.ParentGroup = G.GRP
)
SELECT TOP 1 Grp
FROM items
ORDER BY Level DESC

I want to convert some data to pivot in SQL Server with join and dynamic

The table, at last, is my target.
This is my demo database
create database pvtestDb;
go
use pvtestDb;
go
create table custTransaction
(
id int,
custNum int,
value nvarchar(50)
)
go
create table customers
(
id int,
custName nvarchar(50)
)
insert into Customers(id, custName)
values (1, 'aaa'), (2, 'bbb'), (3, 'ccc'), (4, 'ddd'),
(5, 'eee'), (6, 'fff'), (7, 'ggg'), (8, 'hhh'), (9, 'iii')
insert into custTransaction (id, custNum, value)
values (1, 3, 'a'), (1, 4, 'b'), (1, 5, 'c'),
(2, 3, 'd'), (2, 4, 'e'), (2, 6, 'f'),
(3, 3, 'g'), (3, 8, 'h'), (3, 9, 'i')
select * from customers
select * from custTransaction
select custName, custNum, value
from customers
join custTransaction on custTransaction.id = customers.id
I tried code like this but not worked at all
SELECT
custNum, [a], [b], [c], [d]
FROM
customers
JOIN
custTransaction ON custTransaction.id = customers.id
PIVOT
(COUNT([custName])
FOR [custName] IN ([a], [b], [c], [d])) AS p
I need to join between the two tables in first.
Any hints would be appreciated as I am stuck with this situation
Here's approach with dynamic SQL
declare #customers varchar(8000)
declare #sql varchar(8000)
select #customers = stuff((
select ',' + quotename(custName)
from customers
for xml path('')
), 1, 1, '')
set #sql = 'select
id, ' + #customers + '
from (
select
ct.id, c.custName, ct.value
from
customers c
join custTransaction ct on ct.custNum = c.id
) t
pivot (
max(value) for custName in (' + #customers + ')
) p'
exec (#sql)
Output
id aaa bbb ccc ddd eee fff ggg hhh iii
----------------------------------------------------------------
1 NULL NULL a b c NULL NULL NULL NULL
2 NULL NULL d e NULL f NULL NULL NULL
3 NULL NULL g NULL NULL NULL NULL h i

Alternative for this Cursor SQL

How can I do this without the cursor:
SET NOCOUNT ON;
DECLARE #VAR_A BIGINT, #VAR_B TINYINT;
DECLARE _CURSOR CURSOR FOR
SELECT A, B FROM MY_TABLE
OPEN _CURSOR
FETCH NEXT FROM _CURSOR
INTO #VAR_A, #VAR_B
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT TOP 2 A, B, C, ROW_NUMBER() OVER (ORDER BY A DESC) AS ROW_NUM
INTO #TMP FROM MY_TABLE_2
WHERE A = #VAR_A AND X = 0 ORDER BY A DESC
IF ((SELECT COUNT(*) FROM #TMP) = 1) BEGIN
UPDATE MY_TABLE
SET Y = (SELECT B FROM #TMP WHERE ROW_NUM = 1)
WHERE A = #VAR_A
END ELSE IF (#VAR_B = 7) BEGIN
UPDATE MY_TABLE
SET Y = (SELECT B FROM #TMP WHERE ROW_NUM = 2),
Z = (SELECT C FROM #TMP WHERE ROW_NUM = 2)
WHERE A = #VAR_A
END ELSE BEGIN
UPDATE MY_TABLE
SET Y = (SELECT B FROM #TMP WHERE ROW_NUM = 2)
WHERE A = #VAR_A
END
DROP TABLE #TMP
FETCH NEXT FROM _CURSOR
INTO #VAR_A, #VAR_B
END
CLOSE _CURSOR;
DEALLOCATE _CURSOR;
I have a PRODUCT table, this table has a status column, the last status of the product. I have another table, the PRODUCT_HISTORY, that have all information changed on product, including the older status. For each product, I need to get the last status and insert it on a new field on product table. Like this:
This should do the trick...
IF OBJECT_ID('tempdb..#Product', 'U') IS NOT NULL
DROP TABLE #Product;
CREATE TABLE #Product (
Id INT NOT NULL PRIMARY KEY CLUSTERED,
[Description] VARCHAR(20) NOT NULL,
[Status] INT NOT NULL,
OlderStatus INT NULL
);
INSERT #Product (Id, [Description], [Status]) VALUES
(1, 'Product A', 5),
(2, 'Product B', 7),
(3, 'Product C', 4),
(4, 'Product D', 3),
(5, 'Product E', 0);
IF OBJECT_ID('tempdb..#ProductHistory', 'U') IS NOT NULL
DROP TABLE #ProductHistory;
CREATE TABLE #ProductHistory (
Id INT NOT NULL PRIMARY KEY CLUSTERED,
[Date] DATE NOT NULL,
Status INT NOT NULL,
ProductId INT NOT NULL
);
INSERT #ProductHistory (Id, [Date], [Status], ProductId) VALUES
(1, '2017-01-01', 0, 1),
(2, '2017-01-02', 1, 1),
(3, '2017-01-03', 5, 1),
(4, '2017-01-04', 0, 2),
(5, '2017-01-05', 1, 2),
(6, '2017-01-06', 5, 2),
(7, '2017-01-07', 7, 2),
(8, '2017-01-08', 0, 3),
(9, '2017-01-09', 4, 3),
(10, '2017-01-10', 0, 4),
(11, '2017-01-11', 3, 4),
(12, '2017-01-12', 0, 5);
--===================================================
-- the actual solution...
WITH
cte_PH AS (
SELECT
ph.Id, ph.Date, ph.Status, ph.ProductId,
RN = ROW_NUMBER() OVER (PARTITION BY ph.ProductId ORDER BY ph.Date DESC)
FROM
#ProductHistory ph
)
UPDATE p SET
p.OlderStatus = ISNULL(ph.Status, 0)
FROM
#Product p
LEFT JOIN cte_PH ph
ON p.id = ph.ProductId
AND ph.RN = 2;
----------------------------------------
SELECT * FROM #Product p;
Results...
Id Description Status OlderStatus
----------- -------------------- ----------- -----------
1 Product A 5 1
2 Product B 7 5
3 Product C 4 0
4 Product D 3 0
5 Product E 0 0

T-SQL SELECT query to return combined result of multiple tables

I am wondering if it is possible and more efficient to do something that I am presently doing in code, to do in T-SQL instead.
I have a database with courses. Each course can have different offerings which are variations of the course at different locations and at different awards.
Here's my (simplified) database structure and some sample data:
CREATE TABLE tblCourse (CourseId int, CourseName varchar(50))
CREATE TABLE tblOffering (OfferingId int, CourseId int, LocationId int, AwardId int)
CREATE TABLE tblLocation (LocationId int, LocationName varchar(50))
CREATE TABLE tblAward (AwardId int, AwardName varchar(50))
INSERT INTO tblCourse VALUES (1, 'Course A')
INSERT INTO tblCourse VALUES (2, 'Course B')
INSERT INTO tblOffering VALUES (1, 1, 1, 1)
INSERT INTO tblOffering VALUES (2, 1, 2, 1)
INSERT INTO tblOffering VALUES (3, 1, 3, 1)
INSERT INTO tblOffering VALUES (4, 1, 1, 2)
INSERT INTO tblOffering VALUES (5, 2, 3, 1)
INSERT INTO tblLocation VALUES (1, 'Location A')
INSERT INTO tblLocation VALUES (2, 'Location B')
INSERT INTO tblLocation VALUES (3, 'Location C')
INSERT INTO tblAward VALUES (1, 'Award A')
INSERT INTO tblAward VALUES (2, 'Award B')
What I want to retrieve from SQL is a single row for each course/award combination. Each row would have columns for each location and whether a course of that CourseId/AwardId combination was available. There would be now rows for course/award combinations that have no offerings.
The required result, from the sample data, would be a recordset like this:
CourseId | CourseName | AwardId | AwardName | LocationA | LocationB | LocationC
---------+------------+---------+-----------+-----------+-----------+----------
1 | Course A | 1 | Award A | True | True | True
1 | Course A | 2 | Award B | True | NULL | NULL
2 | Course B | 1 | Award A | NULL | NULL | True
(NULL could also be False)
At present I am doing a simple SELECT statement with various JOINS which gives me multiple rows for each course/award combination, then I loop through all rows in my code and build the required result. However, I don't think this is so efficient as I also need to page results.
I think I could do this fairly easily in a stored procedure by creating a temporary table and a bunch of separate queries, but I don't think that would be too efficient. Wondering if there is a better way of doing it in T-SQL???
So to clarify, what I am looking for is a T-SQL query or stored procedure that will produce the above sample recordset, and which I could adapt paging to.
NB. I am using SQL Server 2008
For Dynamic columns:
DECLARE #COLUMNS VARCHAR(max)
,#query varchar(1024)
,#True varchar(6)
SELECT #COLUMNS =
COALESCE(
#Columns + ',[' + L.LocationName + ']',
'[' + L.LocationName +']'
)
FROM tblLocation L
SELECT #True = '''True'''
SELECT #QUERY = 'SELECT C.CourseName
,A.AwardName
, pvt.*
FROM (SELECT O.OfferingID AS OID
,O.AwardID AS AID
,O.CourseID AS CID
,L.LocationName AS LID
FROM tblOffering O Inner Join tblLocation L on L.LocationID = O.LocationID) AS S
PIVOT
(
count(oID) For LID IN (' +#COLUMNS+ ')
) As pvt
inner join tblCourse C on C.CourseID = CID
inner join tblAward A on A.AwardID = pvt.AID'
EXEC (#QUERY)
GO
This will produce a paginated version of your example results:
declare #tblCourse as table (CourseId int, CourseName varchar(50))
declare #tblOffering as table (OfferingId int, CourseId int, LocationId int, AwardId int)
declare #tblLocation as table (LocationId int, LocationName varchar(50))
declare #tblAward as table (AwardId int, AwardName varchar(50))
INSERT INTO #tblCourse VALUES (1, 'Course A')
INSERT INTO #tblCourse VALUES (2, 'Course B')
INSERT INTO #tblOffering VALUES (1, 1, 1, 1)
INSERT INTO #tblOffering VALUES (2, 1, 2, 1)
INSERT INTO #tblOffering VALUES (3, 1, 3, 1)
INSERT INTO #tblOffering VALUES (4, 1, 1, 2)
INSERT INTO #tblOffering VALUES (5, 2, 3, 1)
INSERT INTO #tblLocation VALUES (1, 'Location A')
INSERT INTO #tblLocation VALUES (2, 'Location B')
INSERT INTO #tblLocation VALUES (3, 'Location C')
INSERT INTO #tblAward VALUES (1, 'Award A')
INSERT INTO #tblAward VALUES (2, 'Award B') -- This had id 1 in your example.
-- Set the following parameters to control paging:
declare #PageSize as Int = 5
declare #PageNumber as Int = 1
; with CourseAwardSummary as (
select distinct C.CourseId, C.CourseName, A.AwardId, A.AwardName,
case when exists ( select 42 from #tblOffering where CourseId = C.CourseId and AwardId = A.AwardId and LocationId = 1 ) then 'True' end as LocationA,
case when exists ( select 42 from #tblOffering where CourseId = C.CourseId and AwardId = A.AwardId and LocationId = 2 ) then 'True' end as LocationB,
case when exists ( select 42 from #tblOffering where CourseId = C.CourseId and AwardId = A.AwardId and LocationId = 3 ) then 'True' end as LocationC
from #tblCourse as C inner join
#tblOffering as O on O.CourseId = C.CourseId inner join
#tblAward as A on A.AwardId = O.AwardId
),
CourseAwardSummaryRows as (
select *, Row_Number() over ( order by CourseName, AwardName ) as RowNumber
from CourseAwardSummary
)
select CourseId, CourseName, AwardId, AwardName, LocationA, LocationB, LocationC
from CourseAwardSummaryRows
where ( #PageNumber - 1 ) * #PageSize + 1 <= RowNumber and RowNumber <= #PageNumber * #PageSize
order by CourseName, AwardName
The following query does this by joining and aggregating the offering table, and then joining the result to the course and award tables:
select c.CourseId, c.CourseName, oa.AwardId, oa.AwardName,
oa.LocationA, oa.LocationB, oa.LocationC
from tblCourse c left outer join
(select o.CourseId, o.AwardId, a.awardName
max(case when LocationName = 'Location A' then 'true' end) as LocationA,
max(case when LocationName = 'Location B' then 'true' end) as LocationB,
max(case when LocationName = 'Location C' then 'true' end) as LocationC
from tblOffering o join
tblLocation l
on o.LocationId = l.LocationId join
tblAward a
on a.awardID = o.AwardId
group by o.CourseId, o.AwardId, a.awardName
) oa
on oa.CourseId = c.CourseId

Resources