Santry Technology Solutions, Content Management, DotNetNuke, SharePoint Consulting
Register | Login
Tuesday, February 09, 2010

Sections
  
About Us
  
Partners
Downloads
  
 WWWCoder.com Resource Directory

Recursive SQL User Defined Functions
9/25/2003 12:41:31 PM

In this article we will cover the new feature of SQL Server 2000 and the ability to create user defined functions. We will focus on creating recursive queries in this article to extend upon previous articles.

Being the developer for this directory, I had a need to perform several recursive methods. Any time you are creating a tree like data structure like directories, org charts, etc. You will need to perform some kind of recursion. In this example, I wanted to be able to provide a count of all descendents sites under a particular category as you see on WWWCoder.com when navigating the site. Each category contains a count of descendent sites of all categories under that specific category.

In the past there were several ways to perform this, one simple method was to create a field in the categories table that would get incremented each time a site was added. This was accomplished using a recursive method in the ASP.Net code that would create a new connection to the database each time a new record was added. It did eliminate the need to call a recursive function for each time a person requested a page to display the category navigation. Basically the method performed would accept a category id and a string value to increment or decrement the count contained in the site count field:

       
Public Sub UpdateParentCount(ByVal ParentID As Integer, ByVal AddSubtract As String)
 Dim SQLQ As String = "UPDATE Categories Set SiteCount = SiteCount " & AddSubtract & " 1"
 If AddSubtract = "+" Then 'add a new icon.
  SQLQ = SQLQ & ", DateSiteAdded = '" & Date.Today & "'"
 End If
 SQLQ = SQLQ & " WHERE CategoryID=" & ParentID
 Dim secondConnection As New SqlConnection(GetDBConnectionString)
 Dim secondCommand As New SqlCommand(SQLQ, secondConnection)
 secondCommand.CommandType = CommandType.Text
 secondConnection.Open()
 secondCommand.ExecuteNonQuery()
 secondConnection.Close()
 secondConnection = Nothing
 Dim SQLQ2 As String = "SELECT ParentID FROM Categories Where CategoryID = " & ParentID
 Dim myConnection As New SqlConnection(GetDBConnectionString)
 Dim myCommand As New SqlCommand(SQLQ2, myConnection)
 myConnection.Open()
 Dim result As SqlDataReader = myCommand.ExecuteReader(CommandBehavior.CloseConnection)
 If result.Read Then
  UpdateParentCount(result("ParentID"), AddSubtract)
 End If
 myConnection.Close()
 myConnection = Nothing
End Sub


This wasn't too bad of solution, since I was able to eliminate the hits on the database for the count. The problem here is a count can become incorrect if any changes are performed outside of the application.

The solution for me had to meet the following requirements: reduce traffic between the Web server and the database server, and make sure an accurate count is always available in the database regardless of what modifies the records that it contains. In order to accomplish this, the count method was moved out of the ASP.Net code and into the database. SQL Server 2000 supports User Defined Functions that can be called from a stored procedure, in addition, the function can be recursive up to 32 levels. Since I can't see a need to go beyond 32 levels deep of categories, I opted to use the functions for creating the count. Here is an example of using a function from within a stored procedure:

 

ALTER procedure GetCategories

@ParentID   int

as

BEGIN
SELECT 
    CategoryID, CategoryName, Path, SiteCount, DateSiteAdded, 
    ParentID, SortColumn, dbo.CountChildren(CategoryID, 0) 
    As CulCount
FROM 
    Categories  
WHERE 
    ParentID = @ParentID
ORDER BY 
    SortColumn, CategoryName
END

You'll notice in the SQL stored procedure's select statement there is a call to a function called CountChildren, in this function we pass the category id of the current category and the current cumulative count of the sites within the category.
ALTER FUNCTION dbo.CountChildren
(@id int, @cChildren int) 
RETURNS bigint 
AS
BEGIN

IF EXISTS (SELECT     
    Sites.SiteCatID
FROM         
    dbo.Categories 
INNER JOIN
    dbo.Sites 
ON 
    dbo.Categories.CategoryID = dbo.Sites.SiteCatID
WHERE 
    dbo.Categories.ParentID = @id OR dbo.Sites.SiteCatID = @id)
BEGIN 
   SET @cChildren = @cChildren + (
     SELECT 
        Count(SiteCatID) 
        FROM 
            Sites 
        WHERE 
            SiteCatID = @id AND SiteActive = 1)
  SELECT 
              @cChildren = dbo.CountChildren(CategoryID, @cChildren) 
            FROM 
              Categories 
            WHERE 
              ParentID = @id 
END 
  RETURN @cChildren 
END 


As you can see the function calls itself just as a recursive function in VB.Net would do, each time incrementing the cumulative count of all descendents of a particular category. In the end we have all the information generated on the SQL machine, and then it returns what we need without having to call a recursive method in the ASP.Net page and generate all the additional database calls over the network.

By: Patrick Santry, Microsoft MVP (ASP/ASP.NET), developer of this site, author of books on Web technologies, and member of the DotNetNuke core development team. If you're interested in the services provided by Patrick, visit his company Website at Santry.com.

Related Articles
   Related Document Descendant Count Using ASP.NET Code


Page Options:
format for printing  Format for Printer
email article  Email Page
add to your favorites   Add to Favorites
How would you rate the quality of this content?
Poor - - Excellent
Comments?
Overall Rating:
Comments Left:
Left on 9/30/2009 10:55:47 AM by Anonymous
Comments: Excellent! Really helped.
Left on 9/30/2009 10:55:01 AM by Anonymous
Comments: Excellent!
Left on 9/30/2008 12:56:23 AM by Anonymous
Comments: i love u jaanu
No ratings available.
Left on 9/28/2006 3:20:33 AM by Anonymous
Comments:
Left on 6/13/2006 12:20:16 AM by Anonymous
Comments: CategoryID int 4 0
CategoryName varchar 50 1
ParentCategoryID int 4 1
LevelID int 4 1
<%
' find and print category path....
' Biju
function printpath(catid,symbol) 
tempsymbol=symbol
SET RSlevel =Server.CreateObject("ADODB.recordset")
SET RScategory =Server.CreateObject("ADODB.recordset")
sqlselect="SELECT LevelID FROM TR_Category where categoryid='"&catid&"'"

RSlevel.open sqlselect,conn,3,3
if not RSlevel.eof then
level=RSlevel("LevelID")&""
end if
i=0
RSlevel.close
while i<=int(level)
sqlcategory="SELECT  ParentCategoryID,CategoryName FROM TR_Category where categoryid='"&catid&"'"
    RScategory.open sqlcategory,conn,3,3
if not RScategory.eof then
if i=int(level) then
symbol=""
end if
  if i<>int("0") then
linkstr=""&RScategory("CategoryName")&""
else
linkstr=  RScategory("CategoryName")
end if
str=str&linkstr&symbol


catid=RScategory("ParentCategoryID")
RScategory.close
end if
i=i+1

wend

tempstr=split(str,tempsymbol)
tempstrlen=ubound(tempstr)
for i=int(tempstrlen) to 0 step -1
str1=str1&tempsymbol&tempstr(i)

next
str1=right(str1,len(str1)-1)

printpath=str1
end function
s= printpath (160,"$" )
Response.Write s

%>
Left on 3/1/2006 5:22:15 PM by Anonymous
Comments: Can we call a SQL user defined function from an asp.net page ?
Left on 2/28/2006 6:18:51 AM by Anonymous
Comments: poor
Left on 6/4/2005 12:56:57 AM by Anonymous
Comments: This worked perfect!!! Thank you so much! How long did it take you to code this?
No ratings available.
Left on 5/4/2005 2:17:56 PM by Anonymous
Comments: I'm glad to see it's doable.
No ratings available.
Left on 4/3/2005 5:49:31 AM by Anonymous
Comments: Great, used it for http://diabete.pagineinfo.it
Left on 2/9/2005 5:51:56 AM by Anonymous
Comments: A Nice One.
Left on 1/13/2005 6:30:33 AM by Anonymous
Comments: Not bad at all...Gives me a good starting point for solving my problem
Left on 7/11/2004 6:24:28 AM by Anonymous
Comments: Thank very much... This was just what i needed being a ASP 3.0/VB6 programmer. - Aniruddha Nadkarni.
Left on 11/4/2003 2:40:36 PM by Anonymous
Comments: SQL triggers can increment a count. Just do an update whenever an insert or delete occurs. You would still have to handle the move though, this article accounts for everything since the count is always live.
No ratings available.
Left on 10/7/2003 1:54:43 PM by Anonymous
Comments: can sql server triggers to increment/decr. the count?
Left on 10/6/2003 5:13:18 AM by Anonymous
Comments: A more generic apporach would be helpful for less competant coders
  

 Latest Articles
  

 Latest News
  

 

Spotlight
Syndication

 


 


Digg This
 


DotNetNuke Platinum Benefactor

  
 

 Terms Of Use | Privacy Statement
 Copyright 2008 - Santry Technology Solutions, Box 172, Girard, PA 16417, (814) 774-0970