Monday, March 31, 2008

And finally Wayne's Codesmith version for all procs prefaced with 'aspnet_':

<%@ CodeTemplate Language="C#" TargetLanguage="C#"  %>
<%@ Property Name="SourceDatabase" Type="SchemaExplorer.DatabaseSchema" %>
<%@ Assembly Name="SchemaExplorer" %>
<%@ Assembly Name="System.Data" %>
<%@ Assembly Name="System.Design" %>
<%@ Import Namespace="SchemaExplorer" %>
<%@ Import Namespace="System.Data" %>
USE [<%=SourceDatabase.Name%>]

GO

<%

foreach(CommandSchema command in SourceDatabase.Commands)
{
 %>
 
 <%
 if ((command.Name.Length>7) && (command.Name.Substring(0,7) == "aspnet_"))
 {
%>
IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[<%=command.Name%>]') AND type in (N'P', N'PC'))
DROP PROCEDURE [dbo].[<%=command.Name%>]

GO

<%
 }
}
%>

ASP.NET | CodeSmith | Dina | T-SQL
Monday, March 31, 2008 7:09:06 PM (Pacific Standard Time, UTC-08:00)  #    Disclaimer  |  Comments [0]  | 
 Sunday, March 30, 2008

After generating all CRUD procs with several different naming prefixes, my database was looking messy. So I knew what I wanted to produce but I needed to get rid of all these other procs. So how to quickly drop all the procs. Wayne said he had a codesmith template for it but I couldn't wait. I google'd and found another person that had the same problem after a heavy CodeSmith NetTiers session. His method was not too different from my post from yesterday. I did my final gen of procs via NetTiers but instead of going into the database, sent them to a file. I opened the file to find the following code which should have been both obvious in that NetTiers would need it and where I should look for it:

BEGIN

DECLARE @procedureName SYSNAME

DECLARE c CURSOR FOR

SELECT name FROM sysobjects WHERE type = 'P' AND objectproperty(id, 'IsMSShipped') = 0

AND name LIKE 'NetTiers_%' AND name NOT LIKE 'WW_{0}_%'

OPEN c

FETCH NEXT FROM c INTO @procedureName

WHILE @@FETCH_STATUS = 0

BEGIN

EXEC('DROP PROC ' + @procedureName)

FETCH NEXT FROM c INTO @procedureName

END

CLOSE c

DEALLOCATE c

END

 

Sunday, March 30, 2008 7:02:01 PM (Pacific Standard Time, UTC-08:00)  #    Disclaimer  |  Comments [0]  | 
 Saturday, March 29, 2008

In Sql server query window, execute query that selects all stored procedures with the prefix you want (such as procedures that start with 'cust'):

SELECT 'DROP PROCEDURE ' + Name FROM sys.objects

WHERE type in (N'P', N'PC')

and name like 'cust_%'

 

Right-click the query window and set results to text. This gives you a results window with a single column of drop prodecures. Select all the text, copy to a new query window and execute.

Saturday, March 29, 2008 8:12:22 PM (Pacific Standard Time, UTC-08:00)  #    Disclaimer  |  Comments [0]  | 
 Tuesday, February 26, 2008

Here is a little code that will create a class that you can use with Enterprise Library caching.  Basically it allows you to define a WHERE clause where any object in that WHERE clause changes (across a single table) then the cache will expire the object in the cache.  Think of it as a way to store a List<> of object that represent rows in a table in the cache and be able to detect if any row changes so that you can drop the cached list.  LastUpdate is a timestamp column that needs to be on every row.  This works since timestamp column type is always incremented by one, which means that SELECT MAX(LastUpdate) will tell you the maximum change across all rows.

using System;
using System.Collections.Generic;
using System.Text;
using System.Data.SqlClient;
using System.Data;
using System.Configuration;

using Microsoft.Practices.EnterpriseLibrary.Caching;
using Microsoft.Practices.EnterpriseLibrary.Caching.Properties;

namespace ERS.Base
{
    /// <summary>
    /// 
    /// </summary>
    public class TableSqlDependency : ICacheItemExpiration
    {
        String _tableName = String.Empty;
        String _connectionString = String.Empty;
        String _where = String.Empty;
        Byte[] _lastUpdate = null;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="connectionString"></param>
        /// <param name="tableName"></param>
        public TableSqlDependency(String connectionString, String tableName)
        {
            _connectionString = connectionString;
            _tableName = tableName;
        }

        public TableSqlDependency(String connectionString,
            String tableName,
            String where)
        {
            _connectionString = connectionString;
            _tableName = tableName;
            _where = where;
        }

        // Summary:
        //     Specifies if item has expired or not.
        //
        // Returns:
        //     Returns true if the item has expired, otherwise false.
        public bool HasExpired()
        {
            Byte[] lastUpdate = LastUpdate;

            if ((lastUpdate == null) && (_lastUpdate != null))
                return (true);

            if ((lastUpdate != null) && (_lastUpdate == null))
                return (true);

            if ((lastUpdate == null) && (_lastUpdate == null))
                return (false);

            if (lastUpdate.Length != _lastUpdate.Length)
                return (true);

            for (int i = 0; i < lastUpdate.Length; i++)
                if (lastUpdate[i] != _lastUpdate[i])
                    return (true);

            return (false);
        }

        private Byte[] LastUpdate
        {
            get
            {
                Byte[] lastUpdate = null;

                using (SqlConnection sqlConnection =
                    new SqlConnection(
                        ConfigurationManager.ConnectionStrings[_connectionString]
                        .ConnectionString))
                {
                    String sql = String.Empty;

                    if (!String.IsNullOrEmpty(_where))
                        sql = String.Format(
                            "SELECT MAX([LastUpdate]) 'Max' FROM [{0}] WHERE {1}",
                            _tableName, _where);
                    else
                        sql = String.Format(
                            "SELECT MAX([LastUpdate]) 'Max' FROM [{0}]",
                            _tableName);

                    using (SqlCommand sqlCommand =
                        new SqlCommand(sql, sqlConnection))
                    {
                        sqlConnection.Open();

                        sqlCommand.CommandType = CommandType.Text;

                        using (SqlDataReader sqlDataReader =
                            sqlCommand.ExecuteReader())
                        {
                            if (!sqlDataReader.HasRows)
                                lastUpdate = null;

                            sqlDataReader.Read();

                            lastUpdate = (Byte[])sqlDataReader["Max"];
                        }
                    }
                }

                return (lastUpdate);
            }
        }

        //
        // Summary:
        //     Called to give the instance the opportunity to
        //        initialize itself from information
        //     contained in the CacheItem.
        //
        // Parameters:
        //   owningCacheItem:
        //     CacheItem that owns this expiration object
        public void Initialize(CacheItem owningCacheItem)
        {
            _lastUpdate = LastUpdate;
        }

        //
        // Summary:
        //     Called to tell the expiration that the CacheItem to which
        //        this expiration
        //     belongs has been touched by the user
        public void Notify()
        {
        }
    }
}

{6230289B-5BEE-409e-932A-2F01FA407A92}

.net | C# | T-SQL | Wayne
Tuesday, February 26, 2008 10:44:49 AM (Pacific Standard Time, UTC-08:00)  #    Disclaimer  |  Comments [0]  | 
 Friday, January 25, 2008

Will be presenting at the Seattle Code Camp https://seattle.codecamp.us/ this Saturday.  My talk is entitled "Kick Your Hash" and I will attempt to bridge the cryptography gap between code and theory by showing SQL Server and .NET code, real life examples, and practical correct uses for Hashing.

Example 1:

SELECT HashBytes('MD5','password')

SELECT HashBytes('MD5',CONVERT(varchar(max),'password'))

SELECT HashBytes('MD5',CONVERT(nvarchar(max),'password'))

Example 2a:

ALTER PROC CheckLogin
@Login varchar(50),
@Password varchar(50),
@Valid bit OUTPUT
AS

SET NOCOUNT ON

SELECT *
FROM [User]
WHERE @Login = [User].[Login] AND [User].Hash = 
    HashBytes('MD5',CONVERT(varchar(max),[User].Prefix) + @Password)

IF (@@ROWCOUNT>0)
    SET @Valid = 1
ELSE 
    SET @Valid = 0

Example 2b:

CREATE TABLE [dbo].[User](
    [UserId] [uniqueidentifier] NOT NULL CONSTRAINT [DF_User_UserId]  DEFAULT (newid()),
    [Login] [varchar](50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
    [Hash] [varbinary](16) NOT NULL,
    [Prefix] [uniqueidentifier] NOT NULL,
 CONSTRAINT [PK_User] PRIMARY KEY CLUSTERED 
(
    [UserId] ASC
)WITH (PAD_INDEX  = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]

Example 2c:

DECLARE @Prefix uniqueidentifier
SET @Prefix = NewId()

INSERT INTO [User]
(
    [Login],
    Hash,
    Prefix )
VALUES(
    'wayne',
    HashBytes('MD5',CONVERT(varchar(max),@Prefix) + 'password'),
    @Prefix)

Example 3:

CREATE PROC ChangePassword
@Login varchar(50),
@OldPassword varchar(50),
@NewPassword varchar(50)
AS

UPDATE [User]
SET Hash = HashBytes('MD5',CONVERT(varchar(max),[User].Prefix) + @NewPassword)
WHERE @Login = [User].[Login] AND [User].Hash = 
    HashBytes('MD5',CONVERT(varchar(max),[User].Prefix) + @OldPassword)

Example 4a:

CREATE PROC AddData
@Data varbinary(max)
AS

INSERT INTO Data
(
    Data,
    Hash,
    [Size]
)
VALUES
(
    @Data,
    HashBytes('MD5',@Data),
    DATALENGTH(@Data)
)

Example 4b:

CREATE PROC FindData
@Data varbinary(max),
@Id uniqueidentifier OUTPUT
AS

DECLARE @Hash varbinary(16)
SET @Hash = HashBytes('MD5',@Data)

DECLARE @Length bigint
SET @Length = DATALENGTH(@Data)

SELECT @Id = DataId
FROM Data
WHERE @Hash = Hash AND @Length = Size
{6230289B-5BEE-409e-932A-2F01FA407A92}
 
T-SQL | Wayne
Friday, January 25, 2008 11:28:56 AM (Pacific Standard Time, UTC-08:00)  #    Disclaimer  |  Comments [0]  | 
 Monday, January 14, 2008

My brother called, he had forgotten a WHERE clause and had lost some data.  I felt really bad for him -- the kind of sinking feeling you get when someone you know has a death in the family.  I can relate -- I have forgotten a few WHERE clauses in the past couple of years.  Right after he called I checked my database backups to make sure they where still running every night.

This reminded me of one of my favorite interview questions: "Tell me about the last time you forgot a WHERE clause?"  I am looking for that sinking recognition that takes place when someone knows the question and can relate.  And if they have never forgotten a WHERE clause -- I just know they haven't programmed much T-SQL, cause we all do it from time to time.  I also want to hear how they recovered their data and what they are doing differently to prevent the problem.  A developer I know at work always writes the WHERE clause before he writes the DELETE or UPDATE line just to prevent the aforementioned.

Maybe SQL Management tool should have an optional warning that makes you confirm that you want to run a DELETE or UPDATE when there is no WHERE clause?

{6230289B-5BEE-409e-932A-2F01FA407A92}

 

T-SQL | Wayne
Monday, January 14, 2008 7:34:38 PM (Pacific Standard Time, UTC-08:00)  #    Disclaimer  |  Comments [0]  | 
 Friday, January 11, 2008

Lately I have doing a lot of SSRS and overall SQL work for a project.  In returning some data to process server-side my data retrieval method was returning null* which means that an exception was thrown. I did a little digging around and then decided to execute the stored procedure manually (i.e. Management Studio).  After debugging in Visual Studio and seeing what the values were and that they were in fact getting passed to the stored procedure I used the same signature to execute the proc in Management Studio.  Hmm, no dice.   My error message was of a "Divide-By-Zero" fashion.  This is the first time that I have ever seen this.  A little reading on the subject and I come to find out that the field used as the denominator allowed nulls.

Enter NULLIF. This is what saved the day.  This function will compare two values and return a null value if the expressions are equal.  For example, take the following dummy stored procedure.

CREATE Procedure SharesCalculateSharePrice
(
   
@shareId int,
   
@numberOfUnits decimal
)

AS

DECLARE @totalPrice decimal

SET @totalPrice = ( SELECT TotalPrice FROM Shares WHERE ShareId = @shareId )
DECLARE @returnValue decimal

SET @returnValue = ( @numberOfUnits / @totalPrice )
SELECT @returnValue 

Pretty basic. I'm selecting the total price from a table and using as the denominator.  I pass in number of units. To get price per unit (share price) I divide one by the other. What if the @totalPrice is 0? Then I get the dreaded Divide-By-Zero error.  Using NULLIF, we can make a trivial aleration to the above procedure and return a NULL value instead.

CREATE Procedure SharesCalculateSharePrice
(
   
@shareId int,
   
@numberOfUnits decimal
)

AS

DECLARE @totalPrice decimal

SET @totalPrice = ( SELECT SUM(TotalPrice) FROM Shares WHERE ShareId = @shareId )
DECLARE @returnValue decimal

SET @returnValue = ( @numberOfUnits / NULLIF(@totalPrice, 0) )
SELECT @returnValue 

Easy. If @totalPrice is 0 we will substitute with a null value.  This way the returnValue will be NULL and we can process accordingly from our .NET application.

public static object ExecuteScalar(List<IDbDataParameter> parameters, string commandText)
{
   using (DBManager manager = new DBManager(_provider, _connectionString))
   {
      manager.Open();
      manager.CreateParameters(parameters.Count);

      for (int i = 0; i < parameters.Count; ++i)
         manager.AddParameters(i, parameters[i].ParameterName, parameters[i].Value);

      object returnValue = manager.ExecuteScalar(CommandType.StoredProcedure, commandText);

      return returnValue == DBNull.Value || returnValue == null ? -1 : returnValue;
   }

There could be even more useful ways to use NULLIF but for me this has worked out fine.

T-SQL | Will
Friday, January 11, 2008 8:41:10 AM (Pacific Standard Time, UTC-08:00)  #    Disclaimer  |  Comments [2]  | 
 Thursday, January 10, 2008

If you have some legacy tables you where you want to convert from having a primary key of int to a primary key of uniqueidentifier, you need to do a lot of work.  Here are the steps:

1) Drop all forigen keys contraints that points to the table being modified.

2) Drop all views that reference the table being modified.

3) Drop all indexes that use the primary key or any forigen key column that points to the table being modified.

4) Rename all the old forigen key column that points to the table being modified, so that we can add a new column with the original name of type uniqueidentifier.

5) Drop the primary key contraints on the table being modified.

6) Add a new forigen key column to all tables where there was a forigen key column that points to the table being modified of the same name with a type of uniqueidentifier.  Make then NULL

7) Rename the primary key of the table being modified -- the int column.

8) Add a new column with the original primary key name of type of uniqueidentifier.

9) Fill all rows with the new primary key column using the function NewId()

10) Update all the new forigen key column with the new primary key using the old forigen key column bound to the old primary key on the new table.

11) Alter all the new forigen key column to NOT NULL where the original forigen key column was NULL.

12) Rebuild all indexes, including the primary key index on the new columns

13) Rebuild all forigen keys.

14) Rebuild all views.

Good Luck

{6230289B-5BEE-409e-932A-2F01FA407A92}

T-SQL | Wayne
Thursday, January 10, 2008 4:02:23 PM (Pacific Standard Time, UTC-08:00)  #    Disclaimer  |  Comments [0]  | 
 Wednesday, January 09, 2008

When you start to use GUIDs (uniqueidentifiers) as primary keys in T-SQL one of the first questions is how to replace @@IDENITY in your primary key column.  A typical table with int as a primary key might look like this:

CREATE T1(ID int PRIMARY KEY IDENTITY, Name varchar(50))

When you insert into this table and want the primary key of the row that you inserted you typically do this:

DECLARE @ID int

INSERT INTO T1 (Name) values ('Tom Smith')

SET @ID = @@IDENTITY

However, when you use uniqueidentifiers as primary keys you don't declare them as IDENTITY, here is how you would build this table:

CREATE T1(ID uniqueidentifier PRIMARY KEY , Name varchar(50))

And here is what the INSERT looks like:

DECLARE @Id uniqueidentifier

SET @Id = NewId()

INSERT INTO T1 (ID, Name) VALUES (@Id, 'Tom Smith')

When using uniqueidentifiers you create them ahead of the insertion and SQL Server doesn't keep track of the process for you.

{6230289B-5BEE-409e-932A-2F01FA407A92}

T-SQL | Wayne
Wednesday, January 09, 2008 10:13:09 AM (Pacific Standard Time, UTC-08:00)  #    Disclaimer  |  Comments [1]  | 
 Tuesday, January 08, 2008

Lets take a quick look at INNER JOINs in T-SQL.  This select statement is a very typical:

SELECT *
FROM Products
   INNER JOIN Category ON Products.CategoryId = Category.CategoryId
WHERE Products.AccountId = @AccountId

However, it can be rewritten to be much faster:

SELECT *
FROM Category
   INNER ON Products JOIN Products.CategoryId = Category.CategoryId AND
      Products.AccountId = @AccountId

Here is why: In the first statement all the rows in the products table are being joined to all the rows in the category table, producing a bigger table, that is then walked to find the correct account.  This might make a very large table after the join if there are many accounts and the category table is very wide.  SQL Server has to deal with this large in query table being produced.  However, in the second statement only the products that are in the required account are joined to the category table -- significantly reducing the join. 

It is important to note you can only reduce the size of the join on the tables you are joining too (not the first table).  So you might need to rotate the first table.  In the case above the category table is likely smaller then the products table and doesn't have accounts so I rotated it up.

{6230289B-5BEE-409e-932A-2F01FA407A92}

 

 

T-SQL | Wayne
Tuesday, January 08, 2008 10:04:37 AM (Pacific Standard Time, UTC-08:00)  #    Disclaimer  |  Comments [1]  | 
 Monday, January 07, 2008

One question I ask when interviewing is what types of T-SQL statements are the worst for performance, I am looking for answers like the below (not in any order):

  • Cursors
  • IN clauses
  • Large INNER JOINs

What this tells me about the person is that they are conscience of the code impact they have on the server.  Someone that doesn't know the answer will write anything that works just to get their job done -- which means that I will later have to clean-up their stuff.

{6230289B-5BEE-409e-932A-2F01FA407A92}

T-SQL | Wayne
Monday, January 07, 2008 9:52:08 AM (Pacific Standard Time, UTC-08:00)  #    Disclaimer  |  Comments [1]  | 
 Sunday, January 06, 2008

Been doing some interviewing lately and finding that people think they are really good at T-SQL (claiming they are a 6 out of 10), however almost none of them write their T-SQL by hand.  If you are using the WYSIWYG query builder you are a 3 out of 10, or less.  Here is one of my favorite interview problems that comes up in real life (my life) a lot.

Given a table like this:

CREATE T1 (ID int PRIMARY KEY, Name varchar(50))

That has duplicate rows in the name column, write a query to "clean" the duplicates leaving only on row per name.  So if "Tom Smith" shows up in two rows (ID: 2, and 4), the query would delete either rows with ID 2 or 4 and leave the other one.  My favorite answer is:

DELETE T1
WHERE NOT T1.ID IN (SELECT MIN(T1.ID) FROM T1 GROUP BY Name)

This includes an IN clause where is terrible for performance -- however the best solution I know.  Most people try it with a cursor which is much harder.

Note:  This is how you "clean" a table of duplicates before you add a unique index to the name column.  A typical scenario when you have a lookup table that has some extra data and you are normalizing the database.

{6230289B-5BEE-409e-932A-2F01FA407A92}

 

T-SQL | Wayne
Sunday, January 06, 2008 9:49:25 AM (Pacific Standard Time, UTC-08:00)  #    Disclaimer  |  Comments [0]  | 
 Wednesday, January 02, 2008

In my opinion SQL Server 2005 is full of holes, and my definition is functionality that is available doesn't span the whole gamete of the server.  For example, aggregates don't work with GUIDs (call uniqueidentifiers in SQL).  Here is non-working code that I would like to see work:

CREATE #Temp(Id uniqueidentifier, Name varchar(50))

SELECT MAX(Id)
FROM #Temp

DROP TABLE #Temp

Why doesn't this work?  GUIDs are just 128-bit numbers, they are all unique and one comes before the other, there should be a MAX and MIN.  This does work:

CREATE #Temp(Id uniqueidentifier, Name varchar(50)) 

SELECT MAX(CONVERT(varchar(41),Id))
FROM #Temp

DROP TABLE #Temp

However, I don't want the extra overhead of converting each GUID to a String.

 

 

GUID | T-SQL | Wayne
Wednesday, January 02, 2008 12:05:09 PM (Pacific Standard Time, UTC-08:00)  #    Disclaimer  |  Comments [0]  |