Closure Table INSERT statement including the level/distance column - sql

I'm referring Bill Karwin's presentation in order to implement a closure table which will help me manage hierarchies. Unfortunately, the presentation does not show how I could insert/update the Level column mentioned on slide 67; this would have been very useful. I've been giving it a thought but I couldn't come up with something concrete that I could test. Here's what I got so far:
create procedure USP_OrganizationUnitHierarchy_AddChild
#ParentId UNIQUEIDENTIFIER,
#NewChildId UNIQUEIDENTIFIER
AS
BEGIN
INSERT INTO [OrganizationUnitHierarchy]
(
[AncestorId],
[DescendantId],
[Level]
)
SELECT [AncestorId], #NewChildId, (here I need to get the count of ancestors that lead to the currently being selected ancestor through-out the tree)
FROM [OrganizationUnitHierarchy]
WHERE [DescendantId] = #ParentId
UNION ALL SELECT #NewChildId, #NewChildId
END
go
I am not sure how I could do that. Any ideas?

You know that for Parent = self you have Level = 0 and when you copying paths from ancestor, you're just increasing Level by 1:
create procedure USP_OrganizationUnitHierarchy_AddChild
#ParentId UNIQUEIDENTIFIER,
#NewChildId UNIQUEIDENTIFIER
AS
BEGIN
INSERT INTO [OrganizationUnitHierarchy]
(
[AncestorId],
[DescendantId],
[Level]
)
SELECT [AncestorId], #NewChildId, [Level] + 1
FROM [OrganizationUnitHierarchy]
WHERE [DescendantId] = #ParentId
UNION ALL
SELECT #NewChildId, #NewChildId, 0
END

Related

Conversion of CTE to temp table in SQL Server to get All Possible Parents

I have one user table in which I maintain parent child relationship and I want to generate the result with all user id along with its parentid and all possible hierarchical parents as comma separated strings.
My table structure is as follows.
CREATE TABLE [hybarmoney].[Users](
[ID] [bigint] IDENTITY(1,1) NOT NULL,
[USERID] [nvarchar](100) NULL,
[REFERENCEID] [bigint] NULL
)
and I am getting the result using the below CTE
;WITH Hierarchy (
ChildId
,ChildName
,ParentId
,Parents
)
AS (
SELECT Id
,USERID
,REFERENCEID
,CAST('' AS VARCHAR(MAX))
FROM hybarmoney.Users AS FirtGeneration
WHERE REFERENCEID = 0
UNION ALL
SELECT NextGeneration.ID
,NextGeneration.UserID
,Parent.ChildId
,CAST(CASE
WHEN Parent.Parents = ''
THEN (CAST(NextGeneration.REFERENCEID AS VARCHAR(MAX)))
ELSE (Parent.Parents + ',' + CAST(NextGeneration.REFERENCEID AS VARCHAR(MAX)))
END AS VARCHAR(MAX))
FROM hybarmoney.Users AS NextGeneration
INNER JOIN Hierarchy AS Parent ON NextGeneration.REFERENCEID = Parent.ChildId
)
SELECT *
FROM Hierarchy
ORDER BY ChildId
OPTION (MAXRECURSION 0)
But I have the limitation of MAXRECURSION and when I googled, I got to know that temp tables are an alternative solution but I was not able to do the same
and also i don't want to get all possible top parents, for my purpose I want to find 15 levels of hierarchical parents for each users. Is it possible to use temp tables for my purpose if possible how.
What you could do, in order to get only N levels of your CTE is to create an additional column where you keep track of each level.
;WITH Hierarchy (
ChildId
,ChildName
,ParentId
,LEVEL
,Parents
)
AS (
SELECT Id
,USERID
,REFERENCEID
,0 AS LEVEL
,CAST('' AS VARCHAR(MAX))
FROM hybarmoney.Users AS FirtGeneration
WHERE REFERENCEID = 0
UNION ALL
SELECT NextGeneration.ID
,NextGeneration.UserID
,Parent.ChildId
,LEVEL+1 AS LEVEL
,CAST(CASE
WHEN Parent.Parents = ''
THEN (CAST(NextGeneration.REFERENCEID AS VARCHAR(MAX)))
ELSE (Parent.Parents + ',' + CAST(NextGeneration.REFERENCEID AS VARCHAR(MAX)))
END AS VARCHAR(MAX))
FROM hybarmoney.Users AS NextGeneration
INNER JOIN Hierarchy AS Parent ON NextGeneration.REFERENCEID = Parent.ChildId
)
SELECT *
FROM Hierarchy
WHERE LEVEL <= 15
ORDER BY ChildId
OPTION (MAXRECURSION 0)
This works, assuming I understood correctly your following statement: "for my purpose I want to find 15 levels of hierarchical parents for each users", where you actually meant 15 levels of hierarchical parents for a single user (in your case REFERENCEID=0).
If you want this to generate a list of 15 hierarchical parents for each user in your hybarmoney.Users table, then move your CTE in a table valued function and implement a similar solution as explained here.
I got the same result using the flowing query if there may be better solution
Create PROC UspUpdateUserAndFiftenLevelIDs (#UserID BIGINT)
AS
BEGIN
DECLARE #REFERENCEIDString NVARCHAR(max)
SET #REFERENCEIDString = ''
DECLARE #ReferenceID BIGINT
SET #ReferenceID = #UserID
DECLARE #Count INT
SET #Count = 0
WHILE (#count < 15)
BEGIN
SELECT #ReferenceID = U.REFERENCEID
,#REFERENCEIDString = #REFERENCEIDString + CASE
WHEN #REFERENCEIDString = ''
THEN (CAST(U.REFERENCEID AS VARCHAR(100)))
ELSE (',' + CAST(U.REFERENCEID AS VARCHAR(MAX)))
END
FROM hybarmoney.Users U
WHERE U.ID = #ReferenceID
SET #Count = #Count + 1
END
SELECT #UserID
,#REFERENCEIDString
END

Is there a way to avoid this loop in SQL Server 2008 R2?

I've often seen that expert users advise to try avoid loops at database level (Reference here). I have a short block of code where i can't see other way to achieve the task without the use of a loop. The task is very simple but is there a way to avoid the loop?
DECLARE #id INT = 1
DECLARE #auxId INT
WHILE #id IS NOT NULL
BEGIN
SET #auxId = #id
SELECT #id = id_next_version FROM task WHERE id_task = #id
END
SELECT #aux
Explanation of the code:
I have a table where there are tasks and some rows are updates of other tasks, so I have a column where are the id of the next version. What I want is to find the id of the last version of a task.
EDIT:
Table structure
CREATE TABLE task
(
id_task INT IDENTITY(1,1) NOT NULL,
task NVARCHAR(50) NULL,
id_next_version INT NULL
)
You are traversing a graph -- probably a tree structure actually. You can do this with a recursive CTE:
with cte as (
select id_task, id_next_version, 1 as lev
from task
where id_task = #id
union all
select t.id_task, t.id_next_version, cte.lev + 1
from task t join
cte
on t.id_task = cte.id_next_version
)
select top 1 *
from cte
order by lev desc;
I'm not sure that this is more elegant than your loop. It should be an iota faster because you are only passing in one query.
Here is a SQL Fiddle illustrating the code.

Querying up tree for a particular value

I'm a bit of a SQL novice, so I could definitely use some assistance hashing out the general design of a particular query. I'll be giving a SQL example of what I'm trying to do below. It may contain some syntax errors, and I do apologize for that- I'm just trying to get the design down before I go running and testing it!
Side note- I have 0 control over the design scheme, so redesign is not an option. My example tables may have an error due to oversight on my part, but the overall design scheme of bottom-up value searching will remain the same. I'm querying an existing database filled with tons of data already in it.
The scenario is this: There is a tree of elements. Each element has an ID and a parent ID (table layouts below). Parent ID is a recursive foreign key to itself. There is a second table that contains values. Each value has an elementID that is a foreign key to the element table. So to get the value of a particular variable for a particular element, you must join the two tables.
The variable hierarchy goes Bottom-Up by way of inheritance. If you have an element and want to get its variable value, you first look at that element. If it doesn't have a value, then check the element's parent. If that doesn't check the parent's parent- all the way to the top. Every variable is guaranteed to have a value by the time you reach the top! (if I search for variableID 21- I know that 21 will exist. If not at the bottom, then definitely at the top) The lowest element on the tree gets priority, though- if the bottom element has a value for that variable, don't go any farther up!
The tables would look roughly like this:
Element_Table
--------------
elementID (PK)
ParentID (FK to elementID)
Value_Table
--------------
valueID (PK)
variableID
value (the value that we're looking for)
elementID (FK to Element_Table.elementID)
So, what I'm looking to do is create a function that cleanly (key word here. Nice, clean and efficient code) search, bottom-up, across the tree looking for a variable value. Once I find it- return that value and move on!
Here is an example of what I'm thinking:
CREATE FUNCTION FindValueInTreeBottomUp
(#variableID int, #element varchar(50))
RETURNS varchar(50)
AS
BEGIN
DECLARE #result varchar(50)
DECLARE #ID int
DECLARE #parentID int
SET #result = NULL, #ID = #element
WHILE (#result IS NULL)
BEGIN
SELECT #result = vals.value, #parentID = eles.ParentID
FROM Value_Table vals
JOIN Element_Table eles
ON vals.elementID = eles.elementID
WHERE eles.elementID = #ID AND vals.variableID = #variableID
IF(#result IS NULL)
#ID = #parentID
CONTINUE
ELSE
BREAK
END
RETURN #result
END
Again, I apologize if there are any syntactical errors. Still a SQL novice and haven't run this yet! I'm especially a novice at functions- I can query all day, but functions/sprocs are still rather new to me.
So, SQL gurus out there- can you think of a better way to do this? The design of the tables won't be changing; I have NO control over that. All I can do is produce the query to check the already existing design.
I think you could do something like this (it's untested, have to try it in sql fiddle):
;with cte1 as (
select e.elementID, e.parentID, v.value
from Element_Table as e
left outer join Value_Table as v on e.elementID = e.elementID and v.variableID = #variableID
), cte2 as (
select v.value, v.parentID, 1 as aDepth
from cte1 as v
where v.elementID = #elementID
union all
select v.value, v.parentID, c.aDepth + 1
from cte2 as c
inner join cte1 as v on v.elementID = c.ParentID
where c.value is null
)
select top 1 value
from cte2
where value is not null
order by aDepth
test infrastructure:
declare #Elements table (ElementID int, ParentID int)
declare #Values table (VariableID int, ElementID int, Value nvarchar(128))
declare #variableID int, #elementID int
select #variableID = 1, #elementID = 2
insert into #Elements
select 1, null union all
select 2, 1
insert into #Values
select 1, 1, 'test'
;with cte1 as (
select e.elementID, e.parentID, v.value
from #Elements as e
left outer join #Values as v on e.elementID = e.elementID and v.variableID = #variableID
), cte2 as (
select v.value, v.parentID, 1 as aDepth
from cte1 as v
where v.elementID = #elementID
union all
select v.value, v.parentID, c.aDepth + 1
from cte2 as c
inner join cte1 as v on v.elementID = c.ParentID
where c.value is null
)
select top 1 value
from cte2
where value is not null
order by aDepth

SQL Server : recursive update statement

I'm somewhat new to SQL and I am trying to figure out the best way of doing this without hardcoding update statements in SQL Server 2012.
Basically I have a hierarchical table of companies (think of a supply chain) with columns (CompanyID, ParentID, ParentPriceAdj, CompanyPriceAdj). Each company gets assigned a price adjustment by their parent that modifies a list price in the PartMaster table and final price gets calculated by cascading the adjustments from parent to child.
If a parents price adjustment gets updated, I want that to reflect on all of his child companies and so forth
aka:
When updating a CompanyPriceAdj for a given updatedcompanyID, I want to recursively find the child CompanyID's (ParentId = updatedCompanyID) and update their ParentPriceAdj to ParentCompany's (parentPriceAdj * (1 + CompanyPriceAdj))
CompanyId ParentID ParentPriceAdj CompanyPriceAdj
5 6 0.96 .10
6 8 1 .20
7 6 0.96 .15
8 11 1 0
10 6 0.96 0
11 12 1 0
I was thinking of using a stored procedure that updates then repeatedly calls itself for every child that was just updated and then subsequently updates his children.... until the company has no children
I've tried looking around couldn't find any examples like this
This is what I have right now
ALTER PROCEDURE [dbo].[UpdatePricing]
#updatedCompanyID int, #PriceAdj decimal
AS
BEGIN
SET NOCOUNT ON;
WHILE (Select CompanyID From CompanyInfo Where ParentID = #updatedCompanyID) IS NOT NULL
UPDATE CompanyInfo
SET ParentPriceAdj = #PriceAdj * (1+CompanyPriceAdj),
#updatedCompanyId = CompanyID,
#PriceAdj = CompanyPriceAdj
WHERE ParentID = #updatedCompanyID
--- some method to call itself again for each (#updatedCompanyID, #PriceAdj)
END
Recursive CTE can be used to walk hierarchy, something like:
ALTER PROCEDURE [dbo].[UpdatePricing]
(
#companyID int,
#PriceAdj decimal
)
as
begin
set nocount on
update CompanyInfo
set CompanyPriceAdj = #PriceAdj
where CompanyID = #companyID
;with Hierarchy(CompanyID, ParentID, InPriceAdj, OutPriceAdj)
as (
select D.CompanyID, D.ParentID, cast(D.ParentPriceAdj as float),
cast(D.ParentPriceAdj as float) * cast(1 + D.CompanyPriceAdj as float)
from CompanyInfo D
where CompanyID = #companyID
union all
select D.CompanyID, D.ParentID,
H.OutPriceAdj, H.OutPriceAdj * (1 + D.CompanyPriceAdj)
from Hierarchy H
join CompanyInfo D on D.ParentID = H.CompanyID
)
update D
set D.ParentPriceAdj = H.InPriceAdj
from CompanyInfo D
join Hierarchy H on H.CompanyID = D.CompanyID
where
D.CompanyID != #companyID
end
You can use WITH expression in t-sql to get all parent records for given child record. And can update each record in record set accordingly with your logic.
Here are links for WITH expression --
http://msdn.microsoft.com/en-us/library/ms175972.aspx
http://msdn.microsoft.com/en-us/library/ms186243(v=sql.105).aspx
Have you looked at using a CURSOR? It's a bit of overhead, but it may give you what you need. In case you're not familiar, below is a basic outline. It allows you pull each company ID one at a time, make your changes, and move on. You can nest them, include them in loops or include loops in them, whatever you need to go through a list performing actions until there are no actions left needing attention.
declare #updatedCompanyID Varchar(5)
DECLARE D_cursor CURSOR FOR Select updatedCompanyID from
OPEN D_cursor
FETCH NEXT FROM D_cursor into #updatedCompanyID
WHILE ##FETCH_STATUS =0
BEGIN
--Run Your Code
WHERE parentID = #updatedCompanyID
FETCH NEXT FROM D_cursor into #updatedCompanyID
END
Close D_cursor
DEALLOCATE D_cursor
Hope this is helpful. I apologize if I've misunderstood.

SQL with clause dynamic where parameter

I have a tree-style database with the following structure:
Table fields:
NodeID int
ParentID int
Name varchar(40)
TreeLevel int
I would like to use a variable #NodeID in the first part of the with clause to don't get all the table just start from the piece I'm interested in (see where Parent=#ParentID and comment).
with RecursionTest (NodeID,ParentID,ThemeName)
as
(
--if i remove the where from here it spends too much time (the tree is big)--
select Nodeid,ParentID,Name from TreeTable where ParentID=#ParentID
union all
select T0.Nodeid,
T0.ParentID,
T0.Name
from
TreeTable T0
inner join RecursionTest as R on T0.ParentID = R.NodeID
)
select * from RecursionTest
This throws some errors, but my question is:
Is possible to pass a variable to a with clause ?
Thanks a lot in advance.
Best regards.
Jose
Yes.
declare #ParentID int
set #ParentID = 10;
with RecursionTest (NodeID,ParentID,ThemeName) ....
You could wrap the whole thing up in a parameterised inline TVF as well. Example of this last approach.
CREATE FUNCTION dbo.RecursionTest (#ParentId INT)
RETURNS TABLE
AS
RETURN
(
WITH RecursionTest (NodeID,ParentID,ThemeName)
AS
(
/*... CTE definition goes here*/
)
SELECT NodeID,ParentID,ThemeName
FROM RecursionTest
)
GO
SELECT NodeID,ParentID,ThemeName
FROM dbo.RecursionTest(10)
OPTION (MAXRECURSION 0)
Unfortunately <11g this will throw an ORA-32033 - unsupported column aliasing, as this functionality is not supported < that version