Data Object dependencies

Posted by admin on November 15th, 2008

I’ve seen that working with stored procedures in MSSQL2005 sometimes raises a lot of questions in me in how to maintain all those stored procedures. I was wondering if there was a way of checking Data Object Dependencies, such as stored procedures that depend on a table.

I’ve gotten some good answers on StackOverflow.

Apparently there is a database engine stored procedure and two dynamic management functions you can use to do this like:

  1. sp_depends: Displays information about database object dependencies, such as the views and procedures that depend on a table or view, and the tables and views that are depended on by the view or procedure. References to objects outside the current database are not reported. –> will be removed in future versions of mssqlserver
  2. sys.dm_sql_referencing_entities: Returns one row for each entity in the current database that references another user-defined entity by name. A dependency between two entities is created when one entity, called the referenced entity, appears by name in a persisted SQL expression of another entity, called the referencing entity. For example, if a user-defined type (UDT) is specified as the referenced entity, this function returns each user-defined entity that reference that type by name in its definition. The function does not return entities in other databases that may reference the specified entity. This function must be executed in the context of the master database to return a server-level DDL trigger as a referencing entity.
  3. sys.dm_sql_referenced_entities: Returns one row for each user-defined entity referenced by name in the definition of the specified referencing entity. A dependency between two entities is created when one user-defined entity, called the referenced entity, appears by name in a persisted SQL expression of another user-defined entity, called the referencing entity. For example, if a stored procedure is the specified referencing entity, this function returns all user-defined entities that are referenced in the stored procedure such as tables, views, user-defined types (UDTs), or other stored procedures.

Also very usefull is SysComments :

SELECT DISTINCT Object_Name(ID)
FROM SysComments
WHERE text LIKE '%Table%'
AND text LIKE '%Column%'

Apparently this SysComments table is a sql server 200 system table included for backward compatibility. Instead you should be using sys.sql_modules, which is a view object.

Writing tests however seems to me the most reliable way of ensuring that nothing was broken when changing an object in the database.

Lieven Cardoen aka Johlero

<

SQL Compare Ignore Collations

Posted by admin on September 30th, 2008

Go to Edit Project and in the Options tab check the Collations Checkbox under Ignore.

That’s it!

Otherwise SQL Compare will rebuild all tables that have different Collation… Takes longer!

<

Mapping to Relational Databases – Behaviour Problem

Posted by admin on August 26th, 2008

The Behavioral Problem

There are the structural aspects (how tables relate to objects) but there’s also a behavioral problem which is harder to solve. How will you get the objects to load and save themselves in the database? If you load a lot of objects into memory, you will have to make sure that the database you are working with stays consistent and synchronized with your memory model. This is not an easy job because for instance sometimes you need to create a row first before you can modify another (because you need the id of the created row). Concurrency is also a big issue here!

The Unit of Work pattern can solve both of these problems. It keeps track of all objects read from the database together with all objects that are modified. It also knows how to make updates to database. The programmer then tells the Unit of Work to commit. It is the controller of the database mapping.

An Identity Map is needed to keep a record of every row you read. If you read some data, you can check this Identity Map to know if you don’t allready have it.

When using a Domain Model, usually you will load linked objects together. Without Lazy Load however, this would mean that enormous object graphs would be loaded out of the database. Lazy Load relies on having a placeholder for a reference to an object. If you try to follow the placeholder, the real object will get pulled from the database. A good example here is when in Flex we have to load big trees. If you have a tree with 100.000 folders, loading them all together would take a while. If we load a folder, we give back its children but not the children of the children. So a depth 1 is returned.

In a previous project created with Flex/Fluorine Gateway/.NET C#/SQL2005 I just save the whole model. Having to program a Unit of Work keeping track of changes would have taken me too long. I realize that if the project would be used by thousand concurrent users, changes would have to be made. For now, it isn’t a problem, but I can imagine that working out a Unit of Work would not be an easy job. In the current project a similar problem arises when editing exams. An exam consists of some components that contain items (exercises). A Unit of Work would have to keep track of deleting items, moving items, changing items, changing components, … Now we just save the whole Exam object (this means mapping it to DTO’s in Flex, sending it to the service layer, converting it to Table Data Gateway, deleting the Exam and saving the Table Data Gateway). A fast solution, but it actually takes 30 seconds to do it… No need to say we’ll have to change this in the future.

<

Mapping to Relational Databases – Patterns

Posted by admin on August 25th, 2008

A big part in the role of the data source layer is talking to a database, most of the times a relational database. I have done some reading about how to implement this data source layer because it can become pretty messy in big projects. It’s also hard to refactor once a choice of pattern is made so it’s important to make the right choice from the beginning. For my current project I’ll look into some patterns, ideas, new technologies and write down a resume here.

Architectural patterns

They define how the domain logic talks to the database. In my experience there should be data access layer (.NET, Java, …) developers and database developers. Most of the times a programmer also does the database side as a result of which they don’t become database experts. When you have data access layer developers and database developers, you will want to seperate SQL from the code, so that a database developer can have a look at the SQL. In this way, a Database administrator can understand how best to tune it and how to arrange indexes.

We had a performance problem with lazy loading a tree. It went so slow that usability was down to nothing. The trees are saved as left right trees in the database (preorder tree traversal algorithm). Normally the advantage of these trees are that loading the whole tree goes very fast. However, for the lazy loading, we only need the children of the node we are loading, and this took a very long time. After searching a bit on indexes in SQL, I managed to improve the performance to a level that usability is really very good now. Lazy loading the trees is now a pleasure.

A good way to do this is to have classes based on the tables (one class for each table). These classes form a gateway to the table. Developers who specialize in the database have a clear place to go.

  1. Row Data Gateway: An instance for each row that’s returned by a query.
  2. Table Data Gateway: Single object for each table in the database. This provides methods to query the database that return a Record Set.

This will only work for applications where the domain model is allmost equal to the database structure, having one class for each table.

A Table Data Gateway can also be used to organise Stored Procedures. You could treat the Stored Procedures as Tables and have a Table Data Gateway to wrap the calls to the stored procedures.

Active Record is having a Row Data Gateway and then adding domain logic to the class. Again, if your Domain Model corresponds closely to the database structure, this option is usefull. However, if your Domain Model becomes more and more complicated, the Active Record approach will break down sooner or later. As you start to implement inheritance, OO patterns, stategies, … in your Domain Model, things will get out of control. Another thing is that O/R mapping tools generate classes that you will need to extend to add Domain Logic. Eventually sending these objects to Flex will get messy as you will have to exclude certain properties that do not need to be send. A solution is to isolate your Domain Model from the database by making an indirection layer responsible for the mapping between domain objects and db tables. This is called a Data Mapper and it completly isolates the two layers.

There are tools that create these patterns(O/R mapping tools). I have used MyGeneration together with Entity Spaces and SubSonic (both Table & Row Data Gateways I think). Both were very helpfull but not ideal. They are third party tools and when you change the database you need to recreate all the classes. The biggest problem is that if the database changes, compiling the project will be no problem. At runtime however problems will start and errors will follow. All the projects that I’ve done are RIA using Flex and Remote Calls to the Server. This means having DTO’s. The classes created by Entity Spaces and SubSonic are too large and complicated too send to Flex. So I had to create Mappers from Entity Spaces/SubSonic to the DTO’s, which made it complicated. I’m kind of hoping that LINQ solves this. In the current project I’m working on the complexity is even bigger. I’m using Stored Procedures to retrieve data and SubSonic to save data and sometimes retrieve data. This means I have to convert DataSets and SubSonic Objects to DTO’s before sending them to Flex. Now we are also porting the model from Flex to .NET, which means that converters also need to be able to convert datasets and subsonic objects to the Model. It gets kind of complicated and I’m searching for a good solution here.

Another thing are OO databases. I haven’t experimented with them yet, but the whole problem of mapping the database to the model would disappear with a OO database (I think). The whole mapping thing is only relevant because of the fundamental difference between objects and relations. Objects hold a reference to another object while tables hold a primary id to another record. I guess not many projects use OO databases, maybe because relational databases have been around for a long time and are proven technology.

<

SubSonic IsDeleted Flag

Posted by admin on August 23rd, 2008

When using SubSonic you have support for deleting a record instead of destroying it. Deleting would be having a flag Deleted or IsDeleted set to true. Destroying removes the record from the Table.

SubSonic adds logic to the classes if a Table contiains a Deleted or IsDeleted column (a non null bit column). Setting this Flag to true is very easy (SubSonic 2.1):

C#:
  1. Dal.Channels.Delete(channelId);

If the table doesn't have a Deleted or IsDeleted flag, the record will be removed.

Getting all the channels in SubSonic would normally be done like this:

C#:
  1. ChannelsCollection channelColl =
  2. new ChannelsCollection ().Where(Channels.Column.IsDeleted, false).Load();

With SubSonic 2.1 howver the same result is gotten using just:

C#:
  1. ChannelsCollection userColl = new ChannelsCollection ().Load();

<

Preorder Tree Traversal (left/right tree) Deleting children of a Node MSSQL2005

Posted by admin on August 23rd, 2008

This Stored Procedure deletes the children of a node in a left/right tree (Preorder Tree Traversal). The two tables are what you need to understand the Stored Procedure. This SP is allmost identical to the sp_deletetreestructure.

TreeStructures Table

Trees Table

deletetreestructurechildren :

SQL:
  1. /******
  2. Object:  StoredProcedure [dbo].[sp_DeleteTreeStructureChildren]
  3. Script Date: 08/17/2008 19:12:16
  4. ******/
  5. SET ANSI_NULLS ON
  6. GO
  7. SET QUOTED_IDENTIFIER ON
  8. GO
  9. CREATE PROCEDURE [dbo].[sp_DeleteTreeStructureChildren]
  10. (
  11. @TreeStructureId int -- Id TreeStructure which children should be deleted
  12. )
  13. AS
  14. BEGIN
  15.     SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
  16.  
  17.     DECLARE @TranName NVARCHAR(MAX)
  18.     SELECT @TranName = 'DeleteTreeStructureChildren';
  19.     BEGIN TRANSACTION @TranName
  20.  
  21.     BEGIN TRY
  22.  
  23.         DECLARE @RightValue int
  24.         DECLARE @LeftValue int
  25.         DECLARE @Width int
  26.         DECLARE @TreeId int
  27.  
  28.         SELECT  @RightValue = (RightValue - 1),
  29.                 @LeftValue = (LeftValue + 1),
  30.                 @Width = (RightValue - LeftValue) - 1,
  31.                 @TreeId = TreeId
  32.         FROM    TreeStructures
  33.         WHERE   (TreeStructureId = @TreeStructureId)
  34.  
  35.         DELETE
  36.         FROM    TreeStructures
  37.         WHERE   (LeftValue BETWEEN @LeftValue AND @RightValue)
  38.         AND     TreeId = @TreeId
  39.  
  40.         UPDATE  TreeStructures
  41.         SET     RightValue = RightValue - @Width
  42.         WHERE   RightValue&gt; @RightValue
  43.         AND     TreeId = @TreeId
  44.  
  45.         UPDATE  TreeStructures
  46.         SET     LeftValue = LeftValue - @Width
  47.         WHERE   LeftValue&gt; @RightValue
  48.         AND     TreeId = @TreeId
  49.  
  50.     COMMIT TRANSACTION @TranName
  51.  
  52.     END TRY
  53.  
  54.     BEGIN CATCH
  55.  
  56.     /*
  57.     If an error occured we Roll Back the transaction
  58.     and raise an error that VS can catch
  59.     */
  60.  
  61.     ROLLBACK TRANSACTION @TranName
  62.     RAISERROR 50001 'Error in sp_DeleteTreeStructureChildren.
  63.     Rollback has been executed.'
  64.  
  65.     /**********************************************************/
  66.  
  67.     END CATCH
  68. END

<

Preorder Tree Traversal (left/right tree) Deleting a Node MSSQL2005

Posted by admin on August 17th, 2008

This Stored Procedure deletes a node in a left/right tree (Preorder Tree Traversal) and all its children. The two tables are what you need to understand the Stored Procedure.

TreeStructures Table

Trees Table

sp_deletetreestructure :

SQL:
  1. /******
  2.     Object:  StoredProcedure [dbo].[sp_DeleteTreeStructure]   
  3.     Script Date: 08/17/2008 19:12:16
  4. ******/
  5. SET ANSI_NULLS ON
  6. GO
  7. SET QUOTED_IDENTIFIER ON
  8. GO
  9. CREATE PROCEDURE [dbo].[sp_DeleteTreeStructure]
  10. (
  11.     @TreeStructureId int -- Id TreeStructure to be deleted
  12. )
  13. AS
  14. BEGIN
  15.     SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
  16.    
  17.     DECLARE @TranName NVARCHAR(MAX)
  18.     SELECT @TranName = 'DeleteTreeStructure';
  19.     BEGIN TRANSACTION @TranName
  20.    
  21.     BEGIN TRY
  22.  
  23.         DECLARE @RightValue int
  24.         DECLARE @LeftValue int
  25.         DECLARE @Width int
  26.         DECLARE @TreeId int
  27.  
  28.  
  29.        
  30.         SELECT  @RightValue = RightValue,
  31.                 @LeftValue = LeftValue,
  32.                 @Width = (RightValue - LeftValue) + 1,
  33.                 @TreeId = TreeId
  34.         FROM    TreeStructures
  35.         WHERE   (TreeStructureId = @TreeStructureId)
  36.        
  37.         DELETE
  38.         FROM    TreeStructures
  39.         WHERE   (LeftValue BETWEEN @LeftValue AND @RightValue)
  40.                 AND TreeId = @TreeId
  41.  
  42.        
  43.         UPDATE  TreeStructures
  44.         SET     RightValue = RightValue - @Width
  45.         WHERE   RightValue> @RightValue
  46.                 AND TreeId = @TreeId
  47.        
  48.         UPDATE  TreeStructures
  49.         SET     LeftValue = LeftValue - @Width
  50.         WHERE   LeftValue> @RightValue
  51.                 AND TreeId = @TreeId
  52.  
  53.         COMMIT TRANSACTION @TranName
  54.  
  55.     END TRY
  56.  
  57.     BEGIN CATCH
  58.        
  59.         /*
  60.             If an error occured we Roll Back the transaction   
  61.             and raise an error that VS can catch
  62.          */
  63.  
  64.         ROLLBACK TRANSACTION @TranName
  65.         RAISERROR 50001 'Error in sp_DeleteTreeStructure.
  66.                         Rollback has been executed.'
  67.  
  68.         /**********************************************************/   
  69.  
  70.     END CATCH
  71. END

<

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 Table

Trees Table

sp_movetreestructure :

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

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

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

Trees Table

sp_inserttreenodeandreturntreestructureid:

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

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

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

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

<

Copyright © 2007 Johlero – Cardoen Lieven. All rights reserved.