Wednesday, June 9, 2010

Cursors

How to Perform SQL Server Row-by-Row Operations Without Cursors



USE AdventureWorks
GO
DECLARE @ProductID INT
DECLARE
@getProductID CURSOR
SET
@getProductID = CURSOR FOR
SELECT
ProductID
FROM Production.Product
OPEN @getProductID
FETCH NEXT
FROM @getProductID INTO @ProductID
WHILE @@FETCH_STATUS = 0
BEGIN
PRINT
@ProductID
FETCH NEXT
FROM @getProductID INTO @ProductID
END
CLOSE
@getProductID
DEALLOCATE @getProductID
GO

SQL cursors have been a curse to database programming for many years because of their poor performance. On the other hand, they are extremely useful because of their flexibility in allowing very detailed data manipulations at the row level. Using cursors against SQL Server tables can often be avoided by employing other methods, such as using derived tables, set-based queries, and temp tables. A discussion of all these methods is beyond the scope of this article, and there are already many well-written articles discussing these techniques.

The focus of this article is directed at using non-cursor-based techniques for situations in which row-by-row operations are the only, or the best method available, to solve a problem. Here, I will demonstrate a few programming methods that provide a majority of the cursor’s flexibility, but without the dramatic performance hit.

Let’s begin by reviewing a simple cursor procedure that loops through a table. Then we’ll examine a non-cursor procedure that performs the same task.

if exists (select * from sysobjects where name = N'prcCursorExample')

drop procedure prcCursorExample

go

CREATE PROCEDURE prcCursorExample

AS

/*

** Cursor method to cycle through the Customer table and get Customer Info for each iRowId.

**

** Revision History:

** ---------------------------------------------------

** Date Name Description Project

** ---------------------------------------------------

** 08/12/03 DVDS Create ----

**

*/

SET NOCOUNT ON

-- declare all variables!

DECLARE @iRowId int,

@vchCustomerName nvarchar(255),

@vchCustomerNmbr nvarchar(10)

-- declare the cursor

DECLARE Customer CURSOR FOR

SELECT iRowId,

vchCustomerNmbr,

vchCustomerName

FROM CustomerTable

OPEN Customer

FETCH Customer INTO @iRowId,
@vchCustomerNmbr,
@vchCustomerName

-- start the main processing loop.

WHILE @@Fetch_Status = 0

BEGIN

-- This is where you perform your detailed row-by-row

-- processing.

-- Get the next row.

FETCH Customer INTO @iRowId,
@vchCustomerNmbr,
@vchCustomerName

END

CLOSE Customer

DEALLOCATE Customer

RETURN


As you can see, this is a very straight-forward cursor procedure that loops through a table called CustomerTable and retrieves iRowId, vchCustomerNmbr and vchCustomerName for every row. Now we will examine a non-cursor version that does the exact same thing:

if exists (select * from sysobjects where name = N'prcLoopExample')

drop procedure prcLoopExample

go

CREATE PROCEDURE prcLoopExample

AS

/*

** Non-cursor method to cycle through the Customer table and get Customer Info for each iRowId.

**

** Revision History:

** ------------------------------------------------------

** Date Name Description Project

** ------------------------------------------------------

** 08/12/03 DVDS Create -----

**

*/

SET NOCOUNT ON

-- declare all variables!

DECLARE @iReturnCode int,

@iNextRowId int,

@iCurrentRowId int,

@iLoopControl int,

@vchCustomerName nvarchar(255),

@vchCustomerNmbr nvarchar(10)

@chProductNumber nchar(30)

-- Initialize variables!

SELECT @iLoopControl = 1

SELECT @iNextRowId = MIN(iRowId)

FROM CustomerTable

-- Make sure the table has data.

IF ISNULL(@iNextRowId,0) = 0

BEGIN

SELECT 'No data in found in table!'

RETURN

END

-- Retrieve the first row

SELECT @iCurrentRowId = iRowId,

@vchCustomerNmbr = vchCustomerNmbr,

@vchCustomerName = vchCustomerName

FROM CustomerTable

WHERE iRowId = @iNextRowId

-- start the main processing loop.

WHILE @iLoopControl = 1

BEGIN

-- This is where you perform your detailed row-by-row

-- processing.

-- Reset looping variables.

SELECT @iNextRowId = NULL

-- get the next iRowId

SELECT @iNextRowId = MIN(iRowId)

FROM CustomerTable

WHERE iRowId > @iCurrentRowId

-- did we get a valid next row id?

IF ISNULL(@iNextRowId,0) = 0

BEGIN

BREAK

END

-- get the next row.

SELECT @iCurrentRowId = iRowId,

@vchCustomerNmbr = vchCustomerNmbr,

@vchCustomerName = vchCustomerName

FROM CustomerTable

WHERE iRowId = @iNextRowId

END

RETURN

There are several things to note about the above procedure.

For performance reasons, you will generally want to use a column like "iRowId" as your basis for looping and row retrieval. It should be an auto-incrementing integer data type, along with being the primary key column with a clustered index.

There may be times in which the column containing the primary key and/or clustered index is not the appropriate choice for looping and row retrieval. For example, the primary key and/or clustered index may have already been built on a column using uniqueindentifier as the data type. In such a case, you can usually add an auto-increment integer data column to the table and build a unique index or constraint on it.

The MIN function is used in conjunction with greater than “>” to retrieve the next available iRowId. You could also use the MAX function in conjunction with less than “<” to achieve the same result:

SELECT @iNextRowId = MAX(iRowId)

FROM CustomerTable

WHERE iRowId < @iCurrentRowId

Be sure to reset your looping variable(s) to NULL before retrieving the next @iNextRowId value. This is critical because the SELECT statement used to get the next iRowId will not set the @iNextRowId variable to NULL when it reaches the end of the table. Instead, it will fail to return any new values and @iNextRowId will keep the last valid, non-NULL, value it received, throwing your procedure into an endless loop. This brings us to the next point, exiting the loop.

When @iNextRowId is NULL, meaning the loop has reached the end of the table, you can use the BREAK command to exit the WHILE loop. There are other ways of exiting from a WHILE loop, but the BREAK command is sufficient for this example.

You will notice that in both procedures I have included the comments listed below in order to illustrate the area in which you would perform your detailed, row-level processing.

-- This is where you perform your detailed row-by-row

-- processing.

Quite obviously, your row level processing will vary greatly, depending upon what you need to accomplish. This variance will have the most profound impact on performance.

For example, suppose you have a more complex task which requires a nested loop. This is equivalent to using nested cursors; the inner cursor, being dependent upon values retrieved from the outer one, is declared, opened, closed and deallocated for every row in the outer cursor. (Please reference the DECLARE CURSOR section in SQL Server Books Online for an example of this.) In such a case, you will achieve much better performance by using the non-cursor looping method because SQL is not burdened by the cursor activity.

Page 3 / 3

Here is an example procedure with a nested loop and no cursors:

if exists (select * from sysobjects where name = N'prcNestedLoopExample')

drop procedure prcNestedLoopExample

go

CREATE PROCEDURE prcNestedLoopExample

AS

/*

** Non-cursor method to cycle through the Customer table ** and get Customer Name for each iCustId. Get all
** products for each iCustid.

**

** Revision History:

** -----------------------------------------------------

** Date Name Description Project

** -----------------------------------------------------

** 08/12/03 DVDS Create -----

**

*/

SET NOCOUNT ON

-- declare all variables!

DECLARE @iReturnCode int,

@iNextCustRowId int,

@iCurrentCustRowId int,

@iCustLoopControl int,

@iNextProdRowId int,

@iCurrentProdRowId int,

@vchCustomerName nvarchar(255),

@chProductNumber nchar(30),

@vchProductName nvarchar(255)

-- Initialize variables!

SELECT @iCustLoopControl = 1

SELECT @iNextCustRowId = MIN(iCustId)

FROM Customer

-- Make sure the table has data.

IF ISNULL(@iNextCustRowId,0) = 0

BEGIN

SELECT 'No data in found in table!'

RETURN

END

-- Retrieve the first row

SELECT @iCurrentCustRowId = iCustId,

@vchCustomerName = vchCustomerName

FROM Customer

WHERE iCustId = @iNextCustRowId

-- Start the main processing loop.

WHILE @iCustLoopControl = 1

BEGIN

-- Begin the nested(inner) loop.

-- Get the first product id for current customer.

SELECT @iNextProdRowId = MIN(iProductId)

FROM CustomerProduct

WHERE iCustId = @iCurrentCustRowId

-- Make sure the product table has data for
-- current customer.

IF ISNULL(@iNextProdRowId,0) = 0

BEGIN

SELECT 'No products found for this customer.'

END

ELSE

BEGIN

-- retrieve the first full product row for
-- current customer.

SELECT @iCurrentProdRowId = iProductId,

@chProductNumber = chProductNumber,

@vchProductName = vchProductName

FROM CustomerProduct

WHERE iProductId = @iNextProdRowId

END

WHILE ISNULL(@iNextProdRowId,0) <> 0

BEGIN

-- Do the inner loop row-level processing here.

-- Reset the product next row id.

SELECT @iNextProdRowId = NULL

-- Get the next Product id for the current customer

SELECT @iNextProdRowId = MIN(iProductId)

FROM CustomerProduct

WHERE iCustId = @iCurrentCustRowId

AND iProductId > @iCurrentProdRowId

-- Get the next full product row for current customer.

SELECT @iCurrentProdRowId = iProductId,

@chProductNumber = chProductNumber,

@vchProductName = vchProductName

FROM CustomerProduct

WHERE iProductId = @iNextProdRowId

END

-- Reset inner loop variables.

SELECT @chProductNumber = NULL

SELECT @vchProductName = NULL

SELECT @iCurrentProdRowId = NULL

-- Reset outer looping variables.

SELECT @iNextCustRowId = NULL

-- Get the next iRowId.

SELECT @iNextCustRowId = MIN(iCustId)

FROM Customer

WHERE iCustId > @iCurrentCustRowId

-- Did we get a valid next row id?

IF ISNULL(@iNextCustRowId,0) = 0

BEGIN

BREAK

END

-- Get the next row.

SELECT @iCurrentCustRowId = iCustId,

@vchCustomerName = vchCustomerName

FROM Customer

WHERE iCustId = @iNextCustRowId

END

RETURN

In the above example we are looping through a customer table and, for each customer id, we are then looping through a customer product table, retrieving all existing product records for that customer. Notice that a different technique is used to exit from the inner loop. Instead of using a BREAK statement, the WHILE loop depends directly on the value of @iNextProdRowId. When it becomes NULL, having no value, the WHILE loop ends.

Conclusion

SQL Cursors are very useful and powerful because they offer a high degree of row-level data manipulation, but this power comes at a price: negative performance. In this article I have demonstrated an alternative that offers much of the cursor’s flexibility, but without the negative impact to performance. I have used this alternative looping method several times in my professional career to the benefit of cutting many hours of processing time on production SQL Servers.

Monday, June 7, 2010

Friday, June 4, 2010

New Event Handlers in SharePoint 2010




With SharePoint 2010, there is now a new host of event handlers that Developers can leverage to interject custom code into their sites when end users perform various actions. To be precise, there are 12 new event handlers available. Notice the chart below, highlighting what is new:

List Events

A field was added

A field is being added

A field was removed

A field is being removed

A field was updated

A field is being updated

A list is being added

A list is being deleted

A list was added

A list was deleted

List Item Events

An item is being added

An item is being updated

An item is being deleted

An item is being checked in

An item is being checked out

An item is being unchecked out

An attachment is being added to the item

An attachment is being removed from the item

A file is being moved

An item was added

An item was updated

An item was deleted

An item was checked in

An item was checked out

An item was unchecked out

An attachment was added to the item

An attachment was removed from the item

A file was moved

A file was converted

The list received a context event

List Workflow Events

A workflow is starting

A workflow was started

A workflow was postponed

A workflow was completed

List Email Events

The list received an e-mail message

Feature Events

A feature was activated

A feature is deactivating

A feature was installed

A feature is being upgraded

Web Events

A site collection is being deleted

A site is being deleted

A site is being moved

A site is being provisioned

A site collection was deleted

A site was deleted

A site was moved

A site was provisioned

Of particular interest is the site provisioning event handlers. I believe this greatly opens the doors for corporations to interject their custom site approval or request workflows into SharePoint, and the obvious, run some custom code on a site after it has been provisioned. Notice that now you are no longer depending on "feature stapling" to execute code on a site after it is provisioned. You can now just attach this web event.

If you recall, feature stapling was/is the action of editing the ONET.xml file within a site definition, adding features to the "WebFeatures" element which would auto-activate features upon provisioning. People would use that functionality to execute code upon site provisioning via a feature activating/ed event receiver, but that is now no longer necessary. Much simpler with this new event!!

Another great improvement to event handlers in SharePoint 2010 is with the improvements to Visual Studio 2010, and what it takes to create them. Notice, when you add a new item into a Visual Studio project, three is an Event Receiver template you can choose from:

When you choose that template, you get a dialog box allowing you to choose what event you want to capture, as well as what content type you want to associate the event receiver with:

After you click finish it will stub out all the code for you! Very slick!

Also notice how Visual Studio 2010 will also stub out all the necessary features and Solution Packages necessary to deploy into SharePoint. Literally all you have to do is hit F5, and then navigate into SharePoint and unit test your event receiver. SO COOL:

It is really great to see Microsoft place such a significant effort on enabling Developers to customize the product, as well as make it so easy to do so (which is so easily seen with their improvements with Visual Studio 2010). Thanks MS!


SharePoint 2010 Visual Web Parts


Ever since the sneak peak developer videos were released months ago I’ve been wondering about the implementation of SharePoint 2010’s visual web parts. If your not sure what I mean, with SP 2010 and Visual Studio 2010 you can now create a web part with a design time experience, so you can drag and drop controls etc:

VS2010Designer

Now that the beta is upon us I can finally take a look under the covers at a visual web part, the default project structure looks like:

VS2010WebPart

The project contains a number of new items: Features and Package both relate to the deployment features of Visual Studio 2010 in that you can create SharePoint solutions (aka the Package) and features which can be activated, visual studio will automatically deploy and activate your web parts using this solution and features.

Next we move on to the VisualWebPart1.cs file which contains the secret sauce:

    public class VisualWebPart1 : WebPart
{


// Visual Studio might automatically update this path when you change the Visual Web Part project item.
private const string _ascxPath = @"~/_CONTROLTEMPLATES/TestVisualWebPart/VisualWebPart1/VisualWebPart1UserControl.ascx";

public VisualWebPart1()
{
}

protected override void CreateChildControls()
{
Control control = this.Page.LoadControl(_ascxPath);
Controls.Add(control);
base.CreateChildControls();
}

protected override void RenderContents(HtmlTextWriter writer)
{
base.RenderContents(writer);
}

As you can see, the web part still derives from WebPart, no special VisualWebPart base class, nothing special going on here.

In fact we are using the same techniques and approach that would have worked in Visual Studio 2008, the only difference now is that Visual Studio 2010 has better tooling support for SharePoint 2010 and will deploy the ascx file automatically for us to the _CONTROLTEMPLATES directory as part of the solution.

There are still a few things a web part developer should know, lets look at the case where we want to expose some custom properties on a web part that we want a user to configure via the web interface:

      [System.Web.UI.WebControls.WebParts.WebBrowsable(true),
System.Web.UI.WebControls.WebParts.WebDisplayName("Custom Prop"),
System.Web.UI.WebControls.WebParts.WebDescription(""),
System.Web.UI.WebControls.WebParts.Personalizable(
System.Web.UI.WebControls.WebParts.PersonalizationScope.Shared),
System.ComponentModel.Category("Settings"),
System.ComponentModel.DefaultValue("")
]
public string CustomProp
{
get { return customProp; }
set { customProp = value; }
}

Now if we put this property and attributes on the VisualWebPart1UserControl (in VisualWebPart1UserControl.ascx.cs) we will find that the custom property builder won’t appear (the web interface that lets us set a value to this property).

We have to add the custom property on the VisualWebPart1 class (in VisualWebPart1.cs) :

    public class VisualWebPart1 : WebPart
{

// Visual Studio might automatically update this path when you change the Visual Web Part project item.
private const string _ascxPath = @"~/_CONTROLTEMPLATES/TestVisualWebPart/VisualWebPart1/VisualWebPart1UserControl.ascx";

protected override void CreateChildControls()
{
Control control = this.Page.LoadControl(_ascxPath);
Controls.Add(control);
base.CreateChildControls();
}

protected override void RenderContents(HtmlTextWriter writer)
{
base.RenderContents(writer);
}

[System.Web.UI.WebControls.WebParts.WebBrowsable(true),
System.Web.UI.WebControls.WebParts.WebDisplayName("Custom Prop"),
System.Web.UI.WebControls.WebParts.WebDescription(""),
System.Web.UI.WebControls.WebParts.Personalizable(
System.Web.UI.WebControls.WebParts.PersonalizationScope.Shared),
System.ComponentModel.Category("Settings"),
System.ComponentModel.DefaultValue("")
]
public string CustomProp
{
get { return customProp; }
set { customProp = value; }
}
}

Now we get our custom property builder:

WebPartSettings

Lets assume that we want to pass the user entered value to the Visual component (the usercontrol) we now need to change the visual studio generated code to cast the user control to our visual user control class, rather than the more generic base Control:

       protected override void CreateChildControls()
{
//user control is of type VisualWebPart1UserControl and defined with private scope
userControl = (VisualWebPart1UserControl)this.Page.LoadControl(_ascxPath);
Controls.Add(control);
base.CreateChildControls();
}

From here we can set properties on the userControl variable as normal.

The same principles apply to web part connections, so the connection points need to be defined on the web part class (not the usercontrol). Visual Studio will take care of deploying the ascx file which is still a big win.

I doubt an experienced web part developer would have any issues, but I wonder how many new web part developers will not know that they can make there web parts configurable and connectable given that they will likely only use the Visual Studio lie presented to them?

Thursday, June 3, 2010

SQLServer TSQL CheatSheet

SQLServer TSQL CheatSheet
DECLARE and SET Varibales

DECLARE @Mojo int
SET @Mojo = 1
SELECT @Mojo = Column FROM Table WHERE id=1

IF / ELSE IF / ELSE Statement

IF @Mojo < 1
BEGIN
PRINT 'Mojo Is less than 1'
END
ELSE IF @Mojo = 1
BEGIN
PRINT 'Mojo Is 1'
END
ELSE
BEGIN
PRINT 'Mojo greater than 1'
END

CASE Statement

SELECT Day = CASE
WHEN (DateAdded IS NULL) THEN 'Unknown'
WHEN (DateDiff(day, DateAdded, getdate()) = 0) THEN 'Today'
WHEN (DateDiff(day, DateAdded, getdate()) = 1) THEN 'Yesterday'
WHEN (DateDiff(day, DateAdded, getdate()) = -1) THEN 'Tomorrow'
ELSE DATENAME(dw , DateAdded)
END
FROM Table

Add A Column

ALTER TABLE YourTableName ADD [ColumnName] [int] NULL;

Rename a Column

EXEC sp_rename 'TableName.OldColName', 'NewColName','COLUMN';

Rename a Table

EXEC sp_rename 'OldTableName', 'NewTableName';

Add a Foreign Key

ALTER TABLE Products WITH CHECK
ADD CONSTRAINT [FK_Prod_Man] FOREIGN KEY(ManufacturerID)
REFERENCES Manufacturers (ID);

Add a NULL Constraint

ALTER TABLE TableName ALTER COLUMN ColumnName int NOT NULL;

Set Default Value for Column

ALTER TABLE TableName ADD CONSTRAINT
DF_TableName_ColumnName DEFAULT 0 FOR ColumnName;

Create an Index

CREATE INDEX IX_Index_Name ON Table(Columns)

Check Constraint

ALTER TABLE TableName
ADD CONSTRAINT CK_CheckName CHECK (ColumnValue > 1)

DROP a Column

ALTER TABLE TableName DROP COLUMN ColumnName;
Single Line Comments

SET @mojo = 1 --THIS IS A COMMENT

Multi-Line Comments

/* This is a comment
that can span
multiple lines
*/

Try / Catch Statements

BEGIN TRY
-- try / catch requires SQLServer 2005
-- run your code here
END TRY
BEGIN CATCH
PRINT 'Error Number: ' + str(error_number())
PRINT 'Line Number: ' + str(error_line())
PRINT error_message()
-- handle error condition
END CATCH

While Loop

DECLARE @i int
SET @i = 0
WHILE (@i < 10)
BEGIN
SET @i = @i + 1
PRINT @i
IF (@i >= 10)
BREAK
ELSE
CONTINUE
END

CREATE a Table

CREATE TABLE TheNameOfYourTable (
ID INT NOT NULL IDENTITY(1,1),
DateAdded DATETIME DEFAULT(getdate()) NOT NULL,
Description VARCHAR(100) NULL,
IsGood BIT DEFAULT(0) NOT NULL,
TotalPrice MONEY NOT NULL,
CategoryID int NOT NULL REFERENCES Categories(ID),
PRIMARY KEY (ID)
);

User Defined Function

CREATE FUNCTION dbo.DoStuff(@ID int)
RETURNS int
AS
BEGIN
DECLARE @result int
IF @ID = 0
BEGIN
RETURN 0
END
SELECT @result = COUNT(*)
FROM table WHERE ID = @ID
RETURN @result
END
GO
SELECT dbo.DoStuff(0)

Wednesday, June 2, 2010

Popular Posts

Recent Posts

Unordered List

Text Widget