Preorder Tree Traversal (left/right tree) Moving Node by index MSSQL2005

Posted by admin on August 16th, 2008

Next Stored Procedure in MSSQL2005 moves a Node (with all its children) in a left-right tree (Preorder Tree Traversal). It can be moved in the same tree or to another tree. The two tables are what you need to understand the Stored Procedure. The Stored Procedure uses one Function (you can easily copy paste the code from the function into the SP).

treestructures Preorder Tree Traversal (left/right tree) Moving Node by index MSSQL2005

trees Preorder Tree Traversal (left/right tree) Moving Node by index MSSQL2005

sp_movetreestructure :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
/****** 
 * Object:  StoredProcedure [dbo].[sp_MoveTreeStructure]    
 * Script Date: 08/16/2008 19:45:40 
 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[sp_MoveTreeStructure]
	@TreeStructureId INT,		-- TreeStructure (Node) to be moved
	@ParentTreeStructureId INT,	-- Target TreeStructure to be moved to
	@INDEX INT				-- Place where TreeStructure should be moved to
AS
BEGIN
	SET NOCOUNT ON;
 
	SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
 
	DECLARE @TranName VARCHAR(20);
	SELECT @TranName = 'MoveTreeStructure';
 
	BEGIN TRANSACTION @TranName
 
	BEGIN TRY	
 
		DECLARE @OriginalVolume INT
		DECLARE @DiffTarget INT
 
		DECLARE @OriginalLeft INT
		DECLARE @OriginalRight INT
		DECLARE @OriginalTreeId INT
 
		DECLARE @TargetLeft INT
		DECLARE @TargetRight INT
		DECLARE @TargetTreeId INT
		DECLARE @TargetTreeStructureId INT
 
		DECLARE @LastPlace bit
		SET   	@LastPlace = 0
 
		/******************************************************/	
 
		/**
		 * If @ParentTreeStructureId is NULL, we asume that 
		 * movement takes place under the same Folder
		 */
 
			IF (@ParentTreeStructureId IS NULL)
				BEGIN
					SELECT @ParentTreeStructureId = dbo.fn_Edumatic3_GetParentTreeStructure(@TreeStructureId)
				END
 
		/******************************************************/	
 
 
		/**
		 * If Movement is under same ParentTreeStructure and forward, 
		 * then Index needs to be +1
		 * For example, if the first child wants to be moved to 
		 * second place, you would give an index of 1
		 * saying the node wants to be inserted between first 
		 * and second child. Because the moved node is the
		 * first child, it really needs to be inserted between child 2 
		 * and 3. Child 2 will become child 1, the inserted
		 * child (used to be child 1) will become child 2 and child 3 
		 * stays 3. Difficult to explain... Draw it and you'll see.
		 */
 
			IF @ParentTreeStructureId = dbo.fn_Edumatic3_GetParentTreeStructure(@TreeStructureId) 
				AND	@INDEX >= [dbo].[fn_Edumatic3_GetIndexOf](@TreeStructureId)
				BEGIN
					SET @INDEX = @INDEX + 1
				END
 
		/******************************************************/	
 
		/**
		 * Retrieve the original left/right and tree values
		 */
 
			SELECT	@OriginalLeft = LeftValue, @OriginalRight = RightValue, @OriginalTreeId = TreeId
			FROM	TreeStructures 
			WHERE	TreeStructureId = @TreeStructureId
 
		/******************************************************/	
 
		/**
		 * Retrieve the target left/right and tree values. 
		 * Here there's an exception to be caught.
		 * If the node is to be moved to the last place, 
		 * the @TargetTreeStructureId will be -1. We look for the
		 * Node before which the moved node will be inserted. 
		 * In this case there's is no last Node.
		 * If this happens, we take the last Node and insert the 
		 * Node to be moved after this last node instead of before.
		 */
 
			SET @TargetTreeStructureId = dbo.[fn_Edumatic3_GetChildTreeStructure](@ParentTreeStructureId, @INDEX)
 
			IF @TargetTreeStructureId <> -1
				BEGIN
					-- Retrieve the target left/right and tree values
					SELECT	@TargetLeft = LeftValue, 
							@TargetRight = RightValue, 
							@TargetTreeId = TreeId
					FROM	TreeStructures 
					WHERE	TreeStructureId = @TargetTreeStructureId
				END
			ELSE
				BEGIN
					SET @TargetTreeStructureId = dbo.[fn_Edumatic3_GetChildTreeStructure](@ParentTreeStructureId, (@Index-1))
 
					SELECT	@TargetLeft = LeftValue, 
							@TargetRight = RightValue, 
							@TargetTreeId = TreeId
					FROM	TreeStructures 
					WHERE	TreeStructureId = @TargetTreeStructureId
 
					SET   	@LastPlace = 1
				END
 
			SET   	@OriginalVolume = @OriginalRight - @OriginalLeft + 1
 
		/******************************************************/	
 
		/**
		 * Setting the Left and Right Values of TreeStructure 
		 * that needs to be replaced to - Left/Right Value
		 * This needs to be done because these Left and Right 
		 * values may not be changed by queries further down
		 * the road. At the end they are being made positive again. 
		 * Draw some cases if you want this to be clearder to you.
		 */
 
			UPDATE	TreeStructures 
			SET   	LeftValue = -LeftValue,
					RightValue = -RightValue
			WHERE	TreeId = @OriginalTreeId
					AND LeftValue >= @OriginalLeft
					AND RightValue <= @OriginalRight
 
		/******************************************************/	
 
		/**
		 * If the Node is not inserted to the last place, 
		 * then we insert it before the TargetTreeStructure
		 * If it is to be inserted to the last place, 
		 * we insert it after the TargetTreeStructure
		 */
		IF @LastPlace = 0
			BEGIN
				-- Update + volume left/right >= target
				UPDATE	TreeStructures 
				SET   	LeftValue = LeftValue + @OriginalVolume
				WHERE	TreeId = @TargetTreeId 
						AND LeftValue >= @TargetLeft
 
				UPDATE	TreeStructures 
				SET   	RightValue = RightValue + @OriginalVolume  
				WHERE	TreeId = @TargetTreeId 
						AND RightValue >= @TargetLeft
 
				SET   	@DiffTarget = @TargetLeft - @OriginalLeft
			END
		ELSE
			BEGIN
				-- Update + volume left/right >= target
				UPDATE	TreeStructures 
				SET   	LeftValue = LeftValue + @OriginalVolume
				WHERE	TreeId = @TargetTreeId 
						AND LeftValue > @TargetRight
 
				UPDATE	TreeStructures 
				SET   	RightValue = RightValue + @OriginalVolume  
				WHERE	TreeId = @TargetTreeId 
						AND RightValue > @TargetRight
 
				SET   	@DiffTarget = @TargetRight + 1 - @OriginalLeft
			END
 
		/******************************************************/	
 
		/**
		 * For debugging purposes
		 */
 
				--	SELECT	LTRIM(STR(@OriginalVolume)) AS OrignalVolume, 
				--			LTRIM(STR(@OriginalLeft)) AS OriginalLeft,
				--			LTRIM(STR(@OriginalRight)) AS OriginalRight,
				--			LTRIM(STR(@TargetLeft)) AS TargetLeft,
				--			LTRIM(STR(@TargetRight)) AS TargetRight,
				--			LTRIM(STR(@LastPlace)) AS LastPlace,
				--			LTRIM(STR(@Index)) AS [Index],
				--			LTRIM(STR(@DiffTarget)) AS DiffTarget,
				--			LTRIM(STR(@TargetTreeStructureId)) AS TargetTreeStructureId
 
		/******************************************************/
 
		/**
		 * Updating the left and right values of the volume that needs to be transported.
		 * They become non negative again.
		 */
 
			UPDATE	TreeStructures 
			SET   	LeftValue = -LeftValue + @DiffTarget, 
					RightValue = -RightValue + @DiffTarget,
					TreeId = @TargetTreeId
			WHERE	LeftValue <= -@OriginalLeft 
					AND RightValue >= -@OriginalRight 
					AND TreeId = @OriginalTreeId
 
		/******************************************************/
 
		/**
		 * Closing the gap in the original tree (can be the same as target tree)
		 */
 
			UPDATE	TreeStructures 
			SET   	LeftValue = LeftValue - @OriginalVolume
			WHERE	TreeId = @OriginalTreeId
					AND LeftValue > @OriginalRight
 
			UPDATE	TreeStructures 
			SET   	RightValue = RightValue - @OriginalVolume
			WHERE	TreeId = @OriginalTreeId
					AND RightValue > @OriginalRight
 
		/******************************************************/
 
		COMMIT TRANSACTION @TranName
 
		/******************************************************/
 
	END TRY
 
	BEGIN CATCH
 
		/* 	
		 *	If an error occured we Roll Back the transaction 
		 *	and raise an error that VS can catch 
		*/
 
		ROLLBACK TRANSACTION @TranName
		RAISERROR 50001 'Error while trying to move. Rollback has been executed.'
 
		/******************************************************/
	END CATCH
END

fn_edumatic3_getchildtreestructure:
Gets the id of the child node at a give index of a given parent node.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/****** 
 * Object:  UserDefinedFunction [dbo].[fn_GetChildTreeStructure]    
 * Script Date: 08/16/2008 14:03:45 
******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION [dbo].[fn_Edumatic3_GetChildTreeStructure] 
(
	@ParentTreeStructureId INT,
	@INDEX INT
)
RETURNS INT
AS
BEGIN
	DECLARE @TreeStructureId INT
	DECLARE @RightValue INT
	DECLARE @RootLeftValue INT
	DECLARE @RootRightValue INT
	DECLARE @TreeId INT
 
	SELECT	@RootLeftValue = LeftValue, 
			@RootRightValue = RightValue, 
			@TreeId = TreeId
	FROM 	TreeStructures
	WHERE	TreeStructureId = @ParentTreeStructureId
 
	SELECT	@RightValue = RightValue, 
			@TreeStructureId = TreeStructureId 
	FROM 	TreeStructures 
	WHERE	TreeId = @TreeId AND 
			LeftValue = @RootLeftValue + 1
 
	WHILE (@RightValue < (@RootRightValue - 1) AND @INDEX > 0) 
		BEGIN
			SELECT	@RightValue = RightValue, 
					@TreeStructureId = TreeStructureId 
			FROM 	TreeStructures 
			WHERE	TreeId = @TreeId AND 
					LeftValue = @RightValue + 1	
 
			SET   	@INDEX = @INDEX -1	
		END
 
	IF @INDEX <> 0
		-- Either negative index was passed or index was bigger than number of childs
		BEGIN
			SET @TreeStructureId = -1
		END
 
	RETURN @TreeStructureId
 
END

Preorder Tree Traversal (left/right tree) Inserting Node by index MSSQL2005

Posted by admin on August 16th, 2008

Next Stored Procedure in MSSQL2005 inserts a Node in a left-right tree (Preorder Tree Traversal). It gives back the id of the inserted Node. The two tables are what you need to understand the Stored Procedure. The Stored Procedure uses one Function (you can easily copy paste the code from the function into the SP).

treestructures Preorder Tree Traversal (left/right tree) Inserting Node by index MSSQL2005

trees Preorder Tree Traversal (left/right tree) Inserting Node by index MSSQL2005

sp_inserttreenodeandreturntreestructureid:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
/****** 
    Object:  StoredProcedure [dbo].[sp_InsertTreeNodeAndReturnTreeStructureId]    
    Script Date: 08/16/2008 09:29:07 
******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[sp_InsertTreeNodeAndReturnTreeStructureId]
	(
		@TreeStructureId INT,    -- Parent Node
		@TreeId INT,                -- TreeId (in case no TreeStructures exist yet for the Tree)
		@INDEX INT                  -- Index where to be inserted (if null, insert at last place)
	)
AS
BEGIN
	SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
	DECLARE @TranName NVARCHAR(MAX)
	SELECT @TranName = 'InsertTreeNodeAndReturnTreeStructureId';
	BEGIN TRANSACTION @TranName
	BEGIN TRY
 
		DECLARE @RightValue INT
		DECLARE @TargetTreeStructureId INT
		IF (@TreeStructureId IS NULL)
		   -- Tree exists but no TreeStructures yet for Tree 
		    BEGIN
		        SET @RightValue = 1
		    END
		ELSE
		    BEGIN
			IF(@TreeId IS NULL)
			    -- With TreeStructureId it's easy to get the TreeId
			    BEGIN
			        SELECT     @TreeId = TreeId 
			        FROM         TreeStructures 
			        WHERE       TreeStructureId = @TreeStructureId
			    END
			IF (@INDEX IS NULL OR @INDEX = -1)
			    BEGIN
			        -- Node will be inserted at last place
			        SELECT	@RightValue = RightValue
			        FROM	        TreeStructures
			        WHERE	TreeStructureId = @TreeStructureId
			    END
			ELSE
			    BEGIN
			        SET @TargetTreeStructureId = 
                                    dbo.[fn_Edumatic3_GetChildTreeStructure](@TreeStructureId, @INDEX)		
			        IF(@TargetTreeStructureId <> -1)
				    BEGIN
				        SELECT  @RightValue = LeftValue
				        FROM	    TreeStructures
				        WHERE   TreeStructureId = dbo.[fn_GetChildTreeStructure](@TreeStructureId, @INDEX)
				    END
					ELSE
					    BEGIN
					        SELECT	@RightValue = RightValue
					        FROM  	TreeStructures
					        WHERE	TreeStructureId = @TreeStructureId
					    END
			    END
 
			-- Move TreeStructure row
			UPDATE  TreeStructures 
			SET       RightValue = RightValue + 2 
			WHERE   RightValue > (@RightValue - 1) 
			AND       TreeId = @TreeId
 
			UPDATE  TreeStructures 
			SET       LeftValue = LeftValue + 2 
			WHERE   LeftValue > (@RightValue - 1) 
			AND       TreeId = @TreeId
		   END
 
	       -- Insert new TreeStructure row
	       INSERT 
	       INTO     TreeStructures (TreeId, LeftValue, RightValue) 
	       VALUES  (@TreeId, @RightValue, @RightValue + 1)
 
	       COMMIT TRANSACTION @TranName
 
	       RETURN @TreeStructureId
	END TRY
 
	BEGIN CATCH
 
		/* If an error occured we Roll Back the transaction and 
                    raise an error that VS can catch */
 
		ROLLBACK TRANSACTION @TranName
		RAISERROR 50001 'Error sp_InsertTreeNodeAndReturnTreeStructureId. 
                                         Rollback has been executed.'
 
		/*************************************************************/	
 
	END CATCH
 
END

fn_edumatic3_getchildtreestructure:
Gets the id of the child node at a give index of a given parent node.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/****** 
    Object:  UserDefinedFunction [dbo].[fn_GetChildTreeStructure]    
    Script Date: 08/16/2008 14:03:45 
******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION [dbo].[fn_Edumatic3_GetChildTreeStructure] 
(
	@ParentTreeStructureId INT,
	@INDEX INT
)
RETURNS INT
AS
BEGIN
	DECLARE @TreeStructureId INT
	DECLARE @RightValue INT
	DECLARE @RootLeftValue INT
	DECLARE @RootRightValue INT
	DECLARE @TreeId INT
 
	SELECT	@RootLeftValue = LeftValue, 
			@RootRightValue = RightValue, 
			@TreeId = TreeId
	FROM 	TreeStructures
	WHERE	TreeStructureId = @ParentTreeStructureId
 
	SELECT	@RightValue = RightValue, 
			@TreeStructureId = TreeStructureId 
	FROM 	TreeStructures 
	WHERE	TreeId = @TreeId AND 
			LeftValue = @RootLeftValue + 1
 
	WHILE (@RightValue < (@RootRightValue - 1) AND @INDEX > 0) 
		BEGIN
			SELECT	@RightValue = RightValue, 
					@TreeStructureId = TreeStructureId 
			FROM 	TreeStructures 
			WHERE	TreeId = @TreeId AND 
					LeftValue = @RightValue + 1	
 
			SET   	@INDEX = @INDEX -1	
		END
 
	IF @INDEX <> 0
		-- Either negative index was passed or index was bigger than number of childs
		BEGIN
			SET @TreeStructureId = -1
		END
 
	RETURN @TreeStructureId
 
END

Serializing Transactions in MSSQL2005

Posted by admin on August 13th, 2008

With our left-right implementation of trees (preorder tree traversal) it’s important to have a look at different updates at the same time. We have found some problems and to resolve this we need to make some transactions Serializable. Have googled some time for this and read the Concurrency chapter of Martin Fowlers “Patterns of enterprise application architecture” book. There’s a part explaining “Reducing Transaction Isolation for Liveness” that explains this well.

Check this link for isolation levels in MSSQL2005.

There are four levels

  • Read Uncommitted
  • Read Committed
  • Repeatable Read
  • Serializable

Use serializable isolation if you want to be sure of correctness. It does mess up your liveness so be aware of that. Look at each transaction to decide which isolation level is best suited.

Example in MSSQL2005:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/****** Object:  StoredProcedure [dbo].[sp_Edumatic3_MoveTreeStructure]   
Script Date: 08/14/2008 20:00:37 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[sp_Edumatic3_MoveTreeStructure]
    @TreeStructureId INT,
    @ParentTreeStructureId INT,
    @INDEX INT
AS
BEGIN
    SET NOCOUNT ON;
 
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
 
    DECLARE @TranName VARCHAR(20);
    SELECT @TranName = 'MoveTreeStructure';
 
    BEGIN TRANSACTION @TranName
 
        BEGIN TRY	
            ...
 
            COMMIT TRANSACTION @TranName
 
        END TRY
 
    BEGIN CATCH
 
        /* If an error occured we Roll Back the transaction and raise an error that VS can catch */
 
        ROLLBACK TRANSACTION @TranName
        RAISERROR 50001 'Error while trying to move. Rollback has been executed.'
 
    END CATCH
END

Managing Hierarchical Data in a DataBase using ‘preorder tree traversal algorithm’

Posted by admin on May 4th, 2008

I will not go and explain the different ways of storing hierarchical data in a Database. This link provides some information on the topic together with a lot of other resources you can find on the internet. I’m using the preorder tree traversal algorithm. If you have more loading than writing to do, and you want to load the full tree very fast, the preorder tree traversal algorithm is ideal. However, if your tree gets very large (some 100.000 nodes in my case), then the preorder tree traversal algorithm isn’t that ideal anymore because it’s impossible to load the whole tree and send it to Flex (I must say we haven’t actually tried to do send the whole tree, but my guess is that Flex will either crash or get freezed for some minutes). We use lazy loading (if you load a Branch, only it’s children are also loaded, but not the children’s children). Loading however only the children of a branch is not easy with the preorder tree traversal algorithm. Or you need to do a self join or multiple sql queries.

Our implementation of the left right tree also has versions of nodes that it keeps. The consequence of this was that loading the children of a node took a couple of seconds. When a branch had for example 100 children, it could take up to 30 seconds, which of course is way too much. With some searching on indexes in SQL2005 a managed to reduce it to less than a second, which is really a great improvement.

This is a piece of the database design:

leftrightdatabasepicture Managing Hierarchical Data in a DataBase using preorder tree traversal algorithm

There are some other relations that I’ll leave away. The TreeStructures table holds the structure of different Trees (TreeId). The TreeNodes contain the data linked to a certain TreeStructure (branch, leaf). In this way, a TreeStructure can be linked to different TreeNodes, which in our case are versions. If a TreeNodes is edited and saved in Flex, then a new version is created. This means of course that when the children of a treestructure are queried, we also need to go and search for the latest treenode version of each treestructure child. Without an extra index, this is where everyting goes incredible slow. I’ve added a index on TreeStructureId (Table TreeNodes) and the result was wauwwwwww! Now it’s fun to work with the application in Flex. Saving a TreeNode should be a little bit slower now because SQL needs to update this extra index, but it’s so little that it can’t be noticed. And since saving a node from Flex is asynchronous, the user experience doesn’t suffer on it. Loading however has to go fast for a good usability!

In the future we will support different trees for a user. In this case the trees will not be as big as 100.000 branches and we could try sending the whole tree at once to Flex. Then we would have the full advantages of the preorder tree traversal algorithm.

pixel Managing Hierarchical Data in a DataBase using preorder tree traversal algorithm

Copyright © 2007 Lieven Cardoen. All rights reserved.