CTE to build hierarchy from source table - sql

I am trying to build a CTE or just some query that takes hierarchial data from one table and insert it into another table that might have a different index. I thought this would be simple, but for some reason I'm getting stuck. I can't get the output to read correctly on the 'RequiredID' with the newly seeded index. Btw I'm working in SQL Server 2012.
Below I've provided proof of concept code to demonstrate what I'm going for. The actual SQL is more complex, but this illustrates the point.
This is what I've got so far:
DECLARE #Table TABLE (ID INT, Code NVARCHAR(50), RequiredID INT);
INSERT INTO #Table (ID, Code, RequiredID) VALUES
(1, 'Physics', NULL),
(2, 'Advanced Physics', 1),
(3, 'Nuke', 2),
(4, 'Health', NULL);
DECLARE #DefaultSeed TABLE (ID INT, Code NVARCHAR(50), RequiredID INT);
WITH hierarchy
AS (
--anchor
SELECT t.ID , t.Code , t.RequiredID
FROM #Table AS t
WHERE t.RequiredID IS NULL
UNION ALL
--recursive
SELECT t.ID
, t.Code
, h.ID
FROM hierarchy AS h
JOIN #Table AS t
ON t.RequiredID = h.ID
)
INSERT INTO #DefaultSeed (ID, Code, RequiredID)
SELECT ID
, Code
, RequiredID
FROM hierarchy
OPTION (MAXRECURSION 10)
DECLARE #NewSeed TABLE (ID INT IDENTITY(10, 1), Code NVARCHAR(50), RequiredID INT)
--this is where I get stuck - I can't get the requiredID to read like below
INSERT INTO #NewSeed (Code, RequiredID)
SELECT Code, RequiredID
FROM #DefaultSeed
--I'm trying to get #NewSeed should read like the following...
[ID] [Code] [RequiredID]
10....Physics..........NULL
11....Health...........NULL
12....AdvancedPhysics..10
13....Nuke.............12
SELECT *
FROM #NewSeed
Any help would be greatly appreciated!

You can use OUTPUT in combination with Merge to get a Mapping from ID's to new ID's.
The essential part:
--this is where you got stuck
Declare #MapIds Table (aOldID int,aNewID int)
;MERGE INTO #NewSeed AS TargetTable
Using #DefaultSeed as Source on 1=0
WHEN NOT MATCHED then
Insert (Code,RequiredID)
Values
(Source.Code,Source.RequiredID)
OUTPUT Source.ID ,inserted.ID into #MapIds;
Update #NewSeed Set RequiredID=aNewID
from #MapIds
Where RequiredID=aOldID
and the whole example:
DECLARE #Table TABLE (ID INT, Code NVARCHAR(50), RequiredID INT);
INSERT INTO #Table (ID, Code, RequiredID) VALUES
(1, 'Physics', NULL),
(2, 'Advanced Physics', 1),
(3, 'Nuke', 2),
(4, 'Health', NULL);
DECLARE #DefaultSeed TABLE (ID INT, Code NVARCHAR(50), RequiredID INT);
WITH hierarchy
AS (
--anchor
SELECT t.ID , t.Code , t.RequiredID
FROM #Table AS t
WHERE t.RequiredID IS NULL
UNION ALL
--recursive
SELECT t.ID
, t.Code
, h.ID
FROM hierarchy AS h
JOIN #Table AS t
ON t.RequiredID = h.ID
)
INSERT INTO #DefaultSeed (ID, Code, RequiredID)
SELECT ID
, Code
, RequiredID
FROM hierarchy
OPTION (MAXRECURSION 10)
DECLARE #NewSeed TABLE (ID INT IDENTITY(10, 1), Code NVARCHAR(50), RequiredID INT)
Declare #MapIds Table (aOldID int,aNewID int)
;MERGE INTO #NewSeed AS TargetTable
Using #DefaultSeed as Source on 1=0
WHEN NOT MATCHED then
Insert (Code,RequiredID)
Values
(Source.Code,Source.RequiredID)
OUTPUT Source.ID ,inserted.ID into #MapIds;
Update #NewSeed Set RequiredID=aNewID
from #MapIds
Where RequiredID=aOldID
/*
--#NewSeed should read like the following...
[ID] [Code] [RequiredID]
10....Physics..........NULL
11....Health...........NULL
12....AdvancedPhysics..10
13....Nuke.............12
*/
SELECT *
FROM #NewSeed

Related

Sql Server While Loop with Changing Condition

I have a User Table in my database that contains two fields
user_id
manager_id
I am trying to construct a query to list all of the manager_ids that are associated with a user_id in a hierarchical structure.
So if i give a user_id, i will get that users manager, followed by that persons manager all the way to the very top.
So far i have tried but it doesnt give what i need:
WITH cte(user_id, manager_id) as (
SELECT user_id, manager_id
FROM user
WHERE manager_id=#userid
UNION ALL
SELECT u.user_id, u.manager_id,
FROM user u
INNER JOIN cte c on e.manager_id = c.employee_id
)
INSERT INTO #tbl (manager_id)
select user_id, manager_id from cte;
If anyone can point me in the right direction that would be great.
I thought about a While loop but this may not be very efficient and im not too sure how to implement that.
OP asked for a while loop, and while (ha, pun) this may not be the best way... Ask and you shall receive. (:
Here is sample data I created (in the future, please provide this):
CREATE TABLE #temp (userID int, managerID int)
INSERT INTO #temp VALUES (1, 3)
INSERT INTO #temp VALUES (2, 3)
INSERT INTO #temp VALUES (3, 7)
INSERT INTO #temp VALUES (4, 6)
INSERT INTO #temp VALUES (5, 7)
INSERT INTO #temp VALUES (6, 9)
INSERT INTO #temp VALUES (7, 10)
INSERT INTO #temp VALUES (8, 10)
INSERT INTO #temp VALUES (9, 10)
INSERT INTO #temp VALUES (10, 12)
INSERT INTO #temp VALUES (11, 12)
INSERT INTO #temp VALUES (12, NULL)
While Loop:
CREATE TABLE #results (userID INT, managerID INT)
DECLARE #currentUser INT = 1 -- Would be your parameter!
DECLARE #maxUser INT
DECLARE #userManager INT
SELECT #maxUser = MAX(userID) FROM #temp
WHILE #currentUser <= #maxUser
BEGIN
SELECT #userManager = managerID FROM #temp WHERE userID = #currentUser
INSERT INTO #results VALUES (#currentUser, #userManager)
SET #currentUser = #userManager
END
SELECT * FROM #results
DROP TABLE #temp
DROP TABLE #results
Get rid of this column list in your CTE declaration that has nothing to do with the columns you are actually selecting in the CTE:
WITH cte(employee_id, name, reports_to_emp_no, job_number) as (
Just make it this:
WITH cte as (
I recommend recursive solution:
WITH Parent AS
(
SELECT * FROM user WHERE user_id=#userId
UNION ALL
SELECT T.* FROM user T
JOIN Parent P ON P.manager_id=T.user_id
)
SELECT * FROM Parent
To see demo, run following:
SELECT * INTO #t FROM (VALUES (1,NULL),(2,1),(3,2),(4,1)) T(user_id,manager_id);
DECLARE #userId int = 3;
WITH Parent AS
(
SELECT * FROM #t WHERE user_id=#userId
UNION ALL
SELECT T.* FROM #t T
JOIN Parent P ON P.manager_id=T.user_id
)
SELECT * FROM Parent

How to insert keyvalue as a row in table?

I have table variable which contains records in terms of key,value pair of a table. where key is the column of table and value is the value for that column
below is table variable
DECLARE #Table TABLE(
FieldName varchar(100),
FieldValue varchar(max))
insert into #Table values('Title','NewFrom')
insert into #Table values('Points','4')
insert into #Table values('createdby','5')
I have above values in UsersInformation.This is physical table of sql server
has following column.
UserID int autoincrement(identity)
Title nvarchar(100),
Points int,
createdby int
I want that all values of #Table should be as a single row of UserInformation table.
How can be using sql server?
Have not tested but this should work
INSERT INTO UsersInformation (Title, Points, createdby)
SELECT Title, Points, createdby FROM (SELECT FieldName, FieldValue
FROM #Table) AS SourceTable
PIVOT (MAX(FieldValue) FOR FieldName IN ([Points], [Title], [createdby])) AS PivotTable;
Based on your comment you will need to add a key to your source #table
DECLARE #Table TABLE(
id int,
FieldName varchar(100),
FieldValue varchar(max))
insert into #Table values(1,'Title','NewFrom')
insert into #Table values(1,'Points','4')
insert into #Table values(1,'createdby','5')
insert into #Table values(2,'Title','NewFrom2')
insert into #Table values(2,'Points','44')
insert into #Table values(2,'createdby','55')
insert into #Table values(3,'Title','NewFrom3')
insert into #Table values(3,'Points','444')
Then you can run the query:
SELECT [Title], [Points], [createdby]
FROM #Table
PIVOT
(
max(FieldValue) FOR [FieldName] IN ([Title], [Points], [createdby])
) AS P

How to get The QueryString Without EXEC and Print Function

I have other table #table3 where the #qur will be stored and using that #qur i want to retrieve the data.
so it is possible to get data without set all query in other variable and execute this query directly.
this string of #qur not fixed it will different for different person.
and yes i use sql server 2010
CREATE TABLE #Table1
([Name] varchar(5), [DateVal] date, [TimeVal] time, [Item] varchar(5))
;
INSERT INTO #Table1
([Name], [DateVal], [TimeVal], [Item])
VALUES
('Lisa', '2015-04-21', '10:20:06', 'Item1'),
('John', '2015-04-21', '10:25:30', 'Item2'),
('Peter', '2015-03-18', '13:35:32', 'Item3'),
('Ralf', '2015-04-03', '09:26:52', 'Item4')
;
CREATE TABLE #Table2
([ID] int, [Name] varchar(5))
;
INSERT INTO #Table2
([ID],[Name])
VALUES
(1,'Lisa' ),
(2,'John' ),
(3,'Peter'),
(4,'Ralf')
;
DECLARE #qur VARCHAR(2000)='([Item] in (''Item1,Item2'')) and [Name]=''Lisa'') '
SELECT DateVal FROM #Table1
WHERE [Name] in (SELECT [Name] FROM #Table2)
AND #qur
Maybe You can use the query bellow in SQLCMD mode:
:setvar qur "and ([Item] in ('Item1','Item2')) and [Name]='Lisa' "
SELECT DateVal FROM #Table1
WHERE [Name] in (SELECT [Name] FROM #Table2)
$(qur)

INSERT multiple rows and OUTPUT original (source) values

I would like to INSERT multpile rows (using INSERT SELECT), and OUTPUT all the new and old IDs into a "mapping" table.
How can I get the original ID (or any source values) in the OUTPUT clause? I don't see a way to get any source values there.
Here is a minimal code example:
-- create some test data
declare #t table (id int identity, name nvarchar(max))
insert #t ([name]) values ('item 1')
insert #t ([name]) values ('another item')
-- duplicate items, storing a mapping from src ID => dest ID
declare #mapping table (srcid int, [newid] int)
insert #t ([name])
output ?????, inserted.id into #mapping-- I want to use source.ID but it's unavailable here.
select [name] from #t as source
-- show results
select * from #t
select * from #mapping
My actual scenario is more complex, so for example I cannot create a temp column on the data table in order to store a "original ID" temporarily, and I cannot uniquely identify items by anything other than the 'ID' column.
Interesting question. For your example, a possible cheat is to depend on the fact that you are doubling the number of rows. Assuming that rows are never deleted and the [id] column remains dense:
-- create some test data
declare #t table (id int identity, name nvarchar(max))
insert #t ([name]) values ('item 1')
insert #t ([name]) values ('another item')
-- duplicate items, storing a mapping from src ID => dest ID
declare #mapping table (srcid int, [newid] int)
declare #Rows as Int = ( select Count(42) from #t )
insert #t ([name])
output inserted.id - #Rows, inserted.id into #mapping
select [name] from #t as source order by source.id -- Note 'order by' clause.
-- show results
select * from #t
select * from #mapping

Find lowest common parent in recursive SQL table

Suppose I have a recursive table (e.g. employees with managers) and a list of size 0..n of ids. How can I find the lowest common parent for these ids?
For example, if my table looks like this:
Id | ParentId
---|---------
1 | NULL
2 | 1
3 | 1
4 | 2
5 | 2
6 | 3
7 | 3
8 | 7
Then the following sets of ids lead to the following results (the first one is a corner case):
[] => 1 (or NULL, doesn't really matter)
[1] => 1
[2] => 2
[1,8] => 1
[4,5] => 2
[4,6] => 1
[6,7,8] => 3
How to do this?
EDIT: Note that parent isn't the correct term in all cases. It's the lowest common node in all paths up the tree. The lowest common node can also be a node itself (for example in the case [1,8] => 1, node 1 is not a parent of node 1 but node 1 itself).
Kind regards,
Ronald
Here's one way of doing it; it uses a recursive CTE to find the ancestry of a node, and uses "CROSS APPLY" over the input values to get the common ancestry; you just change the values in #ids (table variable):
----------------------------------------- SETUP
CREATE TABLE MyData (
Id int NOT NULL,
ParentId int NULL)
INSERT MyData VALUES (1,NULL)
INSERT MyData VALUES (2,1)
INSERT MyData VALUES (3,1)
INSERT MyData VALUES (4,2)
INSERT MyData VALUES (5,2)
INSERT MyData VALUES (6,3)
INSERT MyData VALUES (7,3)
INSERT MyData VALUES (8,7)
GO
CREATE FUNCTION AncestorsUdf (#Id int)
RETURNS TABLE
AS
RETURN (
WITH Ancestors (Id, ParentId)
AS (
SELECT Id, ParentId
FROM MyData
WHERE Id = #Id
UNION ALL
SELECT md.Id, md.ParentId
FROM MyData md
INNER JOIN Ancestors a
ON md.Id = a.ParentId
)
SELECT Id FROM Ancestors
);
GO
----------------------------------------- ACTUAL QUERY
DECLARE #ids TABLE (Id int NOT NULL)
DECLARE #Count int
-- your data (perhaps via a "split" udf)
INSERT #ids VALUES (6)
INSERT #ids VALUES (7)
INSERT #ids VALUES (8)
SELECT #Count = COUNT(1) FROM #ids
;
SELECT TOP 1 a.Id
FROM #ids
CROSS APPLY AncestorsUdf(Id) AS a
GROUP BY a.Id
HAVING COUNT(1) = #Count
ORDER BY a.ID DESC
Update if the nodes aren't strictly ascending:
CREATE FUNCTION AncestorsUdf (#Id int)
RETURNS #result TABLE (Id int, [Level] int)
AS
BEGIN
WITH Ancestors (Id, ParentId, RelLevel)
AS (
SELECT Id, ParentId, 0
FROM MyData
WHERE Id = #Id
UNION ALL
SELECT md.Id, md.ParentId, a.RelLevel - 1
FROM MyData md
INNER JOIN Ancestors a
ON md.Id = a.ParentId
)
INSERT #result
SELECT Id, RelLevel FROM Ancestors
DECLARE #Min int
SELECT #Min = MIN([Level]) FROM #result
UPDATE #result SET [Level] = [Level] - #Min
RETURN
END
GO
and
SELECT TOP 1 a.Id
FROM #ids
CROSS APPLY AncestorsUdf(Id) AS a
GROUP BY a.Id, a.[Level]
HAVING COUNT(1) = #Count
ORDER BY a.[Level] DESC
After doing some thinking and some hints in the right direction from Marc's answer (thanks), I came up with another solution myself:
DECLARE #parentChild TABLE (Id INT NOT NULL, ParentId INT NULL);
INSERT INTO #parentChild VALUES (1, NULL);
INSERT INTO #parentChild VALUES (2, 1);
INSERT INTO #parentChild VALUES (3, 1);
INSERT INTO #parentChild VALUES (4, 2);
INSERT INTO #parentChild VALUES (5, 2);
INSERT INTO #parentChild VALUES (6, 3);
INSERT INTO #parentChild VALUES (7, 3);
INSERT INTO #parentChild VALUES (8, 7);
DECLARE #ids TABLE (Id INT NOT NULL);
INSERT INTO #ids VALUES (6);
INSERT INTO #ids VALUES (7);
INSERT INTO #ids VALUES (8);
DECLARE #count INT;
SELECT #count = COUNT(1) FROM #ids;
WITH Nodes(Id, ParentId, Depth) AS
(
-- Start from every node in the #ids collection.
SELECT pc.Id , pc.ParentId , 0 AS DEPTH
FROM #parentChild pc
JOIN #ids i ON pc.Id = i.Id
UNION ALL
-- Recursively find parent nodes for each starting node.
SELECT pc.Id , pc.ParentId , n.Depth - 1
FROM #parentChild pc
JOIN Nodes n ON pc.Id = n.ParentId
)
SELECT n.Id
FROM Nodes n
GROUP BY n.Id
HAVING COUNT(n.Id) = #count
ORDER BY MIN(n.Depth) DESC
It now returns the entire path from the lowest common parent to the root node but that is a matter of adding a TOP 1 to the select.