Blog

  • Compare contents of two tables.

    SET QUOTED_IDENTIFIER ON 
    GO
    SET ANSI_NULLS ON
    GO

    if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[sp_CompareTables]') and OBJECTPROPERTY(id, N'IsProcedure') = 1)
    drop procedure [dbo].[sp_CompareTables]
    GO

    CREATE PROCEDURE sp_CompareTables
    @table1 sysname,
    @table2 sysname,
    @ColumnList varchar(8000) = '*',
    @Summary bit = 0,
    @Detail bit = 1,
    @TableNameHeader varchar(30) = '[Table Name]',
    @CounterHeader varchar(30) = '[Counter]',
    @rc int = null out
    AS
    ------------------------------------------------------------------------------
    --Compare the contents of two tables
    --@table1 and @table2 are the table names
    --@ColumnList is the list of column names, by default all columns will be compared
    --@Summary - set this to 1 it you want summary information
    --@Detail - set this to 1 (the default) if you want the details of the differences
    --@TableNameHeader - this is used as the column heading the a column that contains the table name
    --@CounterHeader - this is used as the column heading the a column that contains a row count
    --(this is used when there may be multiple identical rows
    --@rc - the number of rows that are different
    --Note: this SP can "live" in the master DB
    ------------------------------------------------------------------------------
    DECLARE @err integer,
    @SQL1 varchar(8000),
    @SQL2 varchar(8000),
    @SQL3 varchar(8000),
    @SQL4 varchar(8000),
    @SQLTop varchar(8000),
    @SQLBot varchar(8000),
    @SQLTop2 varchar(8000),
    @SQLBot2 varchar(8000),
    @ColumnGrouping varchar(8000),
    @indexlist varchar(8000)

    SET NOCOUNT ON

    -- a bit of validation first
    IF object_id(@table1) IS NULL
    BEGIN
    RAISERROR ('Table ''%s'' does not exist', 11, 1, @table1) WITH NOWAIT
    RETURN
    END

    IF object_id(@table2) IS NULL
    BEGIN
    RAISERROR ('Table ''%s'' does not exist', 11, 1, @table2) WITH NOWAIT
    RETURN
    END

    IF @table1 NOT LIKE '%.%'
    BEGIN
    SET @table1 = DB_NAME() + '..' + @table1
    END

    IF @table2 NOT LIKE '%.%'
    BEGIN
    SET @table2 = DB_NAME() + '..' + @table2
    END

    -- let's get a list of columns that have different collations ...
    CREATE TABLE #CollationColumns (name sysname)

    DECLARE @SQL varchar(8000)

    set @SQL = 'INSERT #CollationColumns SELECT a.name
    FROM %db1..syscolumns a
    INNER JOIN %db2..syscolumns b
    ON a.name = b.name COLLATE database_default
    WHERE a.id = object_id(''%t1'')
    AND b.id = object_id(''%t2'')
    AND a.collationid b.collationid'
    SET @SQL = replace(@SQL, '%db1', PARSENAME(@table1, 3))
    SET @SQL = replace(@SQL, '%db2', PARSENAME(@table2, 3))
    SET @SQL = replace(@SQL, '%t1', @table1)
    SET @SQL = replace(@SQL, '%t2', @table2)

    EXEC (@SQL)

    -- If the first table is in the current database
    -- we can look to see if there is a unique index
    IF PARSENAME(@table1, 3) = DB_NAME()
    BEGIN
    SET @indexlist = NULL

    SELECT @indexlist = COALESCE(@indexlist + ', ', '') + '[' + c.name + ']'
    FROM syscolumns c
    INNER JOIN sysindexkeys k ON c.colid = k.colid
    AND k.id = OBJECT_ID(@table1)
    AND c.id = k.id
    INNER JOIN sysindexes i ON i.id = k.id
    AND i.indid = k.indid
    WHERE i.indid = ( SELECT TOP 1 i.indid -- get the first unique index (favouring the clustered one)
    FROM sysindexes
    WHERE id = OBJECT_ID(@table1)
    AND INDEXPROPERTY(i.id,i.name, 'IsUnique') = 1
    AND INDEXPROPERTY(i.id,i.name, 'IsStatistics') = 0
    ORDER BY INDEXPROPERTY(i.id,i.name, 'IsClustered') DESC)
    ORDER BY k.keyno
    END

    IF @indexlist IS NULL AND @Summary = 1
    BEGIN
    RAISERROR ('Summary is not availabe as table ''%s'' has no unique index', 10, 1, @table1) WITH NOWAIT
    SET @Summary = 0
    END

    -- Let's generate a list of columns ...
    IF @ColumnList = '*' or @ColumnList = ''
    BEGIN
    -- we can only do this in the current database (this could be enhanced though)
    IF PARSENAME(@table1, 3) DB_NAME()
    BEGIN
    RAISERROR ('Table ''%s'' is not in the current database', 11, 1, @table1) WITH NOWAIT
    RETURN
    END
    SET @ColumnList = NULL
    SET @ColumnGrouping = NULL
    -- get a comma delimited list
    -- convert text and netxt to varchar(8000)
    -- convert image to binary
    SELECT @ColumnList = COALESCE(@ColumnList + ', ', '') + CASE
    WHEN systypes.name IN ( 'text' ,'ntext')
    THEN 'CONVERT(VARCHAR(8000),' + '[' + syscolumns.name + ']' + ') AS '
    WHEN systypes.name ='image'
    THEN 'CONVERT(BINARY,' + '[' + syscolumns.name + ']' + ') AS '
    ELSE ''
    END
    + '[' + syscolumns.name + ']'
    -- if the column has different collation the just use a database default
    + CASE WHEN syscolumns.name IN (SELECT name COLLATE database_default FROM #CollationColumns)
    THEN ' collate database_default AS [' + syscolumns.name + ']'
    ELSE ''
    END
    FROM syscolumns INNER JOIN systypes ON syscolumns.xusertype = systypes.xusertype
    WHERE ID = object_id(@table1)
    ORDER BY colid

    -- we need a similar list of columns for the GROUP BY clause
    SELECT @ColumnGrouping = COALESCE(@ColumnGrouping + ', ', '') + CASE
    WHEN systypes.name in ( 'text' ,'ntext')
    THEN 'convert(varchar(8000),' + '[' + syscolumns.name + ']' + ')'
    WHEN systypes.name = 'image'
    THEN 'convert(binary,' + '[' + syscolumns.name + ']' + ')'
    ELSE '[' + syscolumns.name + ']'
    END
    FROM syscolumns
    INNER JOIN systypes on syscolumns.xusertype = systypes.xusertype
    WHERE id = object_id(@table1)
    ORDER BY colid
    END
    ELSE
    BEGIN
    SET @ColumnGrouping = @ColumnList
    END

    DECLARE @Counter1 varchar(8000)
    DECLARE @Counter2 varchar(8000)

    -- if we have a unique index then we don't need a row count.
    IF @indexlist IS NULL
    BEGIN
    SET @Counter1 = 'COUNT (*) AS ' + @CounterHeader + ', '
    SET @Counter2 = @CounterHeader + ', '
    END
    ELSE
    BEGIN
    SET @Counter1 = ''
    SET @Counter2 = ''
    END

    -- here's the wizzy SQL (I will explain it sometime)
    SET @SQL1 = 'SELECT ' + @Counter1 + '''' + @table1 + ''' AS ' + @TableNameHeader + ', ' + @ColumnList + ' FROM ' + @Table1 + char(13)
    SET @SQL2 = ' GROUP BY ' + @ColumnGrouping + char(13)
    SET @SQL3 = ' UNION ALL SELECT ' + @Counter1 + '''' + @table2 + ''' AS ' + @TableNameHeader + ', ' + @ColumnList + ' FROM ' + @Table2 + char(13)
    SET @SQL4 = ' GROUP BY ' + @ColumnGrouping + char(13)
    SET @SQLTop = 'SELECT ' + @Counter2 + ' Max(' + @TableNameHeader + ') AS ' + @TableNameHeader + ', ' + @ColumnList + ' FROM (' + char(13)
    SET @SQLBot = ') A ' + CHAR(13) + ' GROUP BY ' + @Counter2 + @ColumnGrouping + CHAR(13) + ' HAVING COUNT(*) 2'
    SET @SQLTop2 = 'SELECT ' + @indexlist +
    ' , CASE COUNT(*) when 2 ' +
    ' THEN ''Different'' ' +
    ' ELSE ''Only in '' + max(' + @TableNameHeader + ') ' +
    ' END AS [Details] ' +
    ' FROM ( '
    SET @SQLBot2 = ') B GROUP BY ' + @indexlist

    -- now output the details
    IF @Detail = 1
    BEGIN
    EXEC (@SQLTop + @SQL1 + @SQL2 + @SQL3 + @SQL4 + @SQLBot)
    SET @rc = @@ROWCOUNT
    END

    IF @Summary = 1
    BEGIN
    EXEC (@SQLTop2 + @SQLTop + @SQL1 + @SQL2 + @SQL3 + @SQL4 + @SQLBot + @SQLBot2)
    SET @rc = @@ROWCOUNT
    END

    IF @rc = 0
    BEGIN
    RAISERROR ('Tables ''%s'' and ''%s'' are the same', 10, 1, @table1, @table2) WITH NOWAIT
    END
    ELSE
    BEGIN
    RAISERROR ('Tables ''%s'' and ''%s'' contain %d ''different'' rows', 10, 1, @table1, @table2, @rc) WITH NOWAIT
    END


    SET NOCOUNT OFF
    GO
    SET QUOTED_IDENTIFIER OFF
    GO
    SET ANSI_NULLS ON
    GO

  • Compare 2 database tables

    SET QUOTED_IDENTIFIER ON
    GO
    SET ANSI_NULLS ON
    GO
    IF EXISTS (select * from dbo.sysobjects where id = object_id(N'[dbo].[sp_CompareDBTables]') and OBJECTPROPERTY(id, N'IsProcedure') = 1)
    drop procedure [dbo].[sp_CompareDBTables]
    GO

    CREATE PROC sp_CompareDBTables
    @targetDB sysname,
    @Summary bit = 0,
    @Detail bit = 1
    --****************************************
    --@targetDB: we'll compare the current database with this one
    --@Summary: do we want summary output?
    --@Detail: do we want detail output?

    --calls sp_CompareTables for each table in the current table

    --Note: this SP can "live" in the master DB
    --*****************************************/
    AS
    IF DB_ID(@targetDB) IS NULL
    BEGIN
    RAISERROR('The target database cannot be found.', 10, 1) WITH NOWAIT
    END

    DECLARE @SQL varchar(4000)

    SET @SQL = 'exec sp_CompareTables ''?'', ''' + @targetDB + '.?'', @Summary = ' + convert(varchar(1) ,@Summary) + ', @Detail = ' + convert(varchar(1) ,@Detail)

    EXEC sp_msforeachtable @SQL

    GO
    SET QUOTED_IDENTIFIER OFF
    GO
    SET ANSI_NULLS ON
    GO
  • Finding missing numbers in a sequence

    From http://www.sqlservercentral.com/articles/Datetime+Manipulation/61822/

    Find Missing Numbers

    It is obvious that, to find missing numbers, we need to have a table or resultset which contains all the numbers. Then we can apply a NOT IN clause and identify the missing numbers.

    If you are wondering, why I needed missing numbers, here is the reason. In one of my applications, there is an entity that we call coordinator. Each coordinator has a 4 digit numeric code. The code starts at 1000 and ends at 9999. (There is no chance that we will have more than 9000 coordinators at any point of time, so a 4 digit code is sufficient). When the user creates a new coordinator, the system automatically generates a new coordinator number. However, the user can still edit it. The system will allow the user to edit the coordinator number (during a new entry) as long as it does not produce a duplicate code.

    With the above functionality, we needed a way to reuse un-used/missing numbers. When we create a new coordinator, we need to find the lowest unused coordinator code. For this purpose we needed a way to identify the missing numbers.

    As mentioned earlier in this article, I did not want to keep a temp table. I want to generate the numbers on the fly. There are several ways to do this. I like to use a CTE to do this.

    In one of my previous articles, I had presented a way to generate a sequence of numbers by using a CTE. It was fast. But a faster code fragment was posted by Kathi Kellenberger in the discussion forum. It was taken from Itzik Ben-Gan’s book “Inside Microsoft SQL Server 2005 T-SQL Querying”. Here is the code fragment which generates 1 million records in the fraction of a second. [code]

    WITH
    L0 AS (SELECT 1 AS C UNION ALL SELECT 1), --2 rows
    L1 AS (SELECT 1 AS C FROM L0 AS A, L0 AS B),--4 rows
    L2 AS (SELECT 1 AS C FROM L1 AS A, L1 AS B),--16 rows
    L3 AS (SELECT 1 AS C FROM L2 AS A, L2 AS B),--256 rows
    L4 AS (SELECT 1 AS C FROM L3 AS A, L3 AS B),--65536 rows
    L5 AS (SELECT 1 AS C FROM L4 AS A, L4 AS B),--4294967296 rows
    num AS (SELECT ROW_NUMBER() OVER(ORDER BY C) AS N FROM L5)
    SELECT N FROM NUM WHERE N <= 1000000;

    This code is VERY VERY fast and I find it to be the best candidate for generating a sequence of numbers on the fly. I wanted to re-use this code and hence I created a function that takes a Minimum and Maximum value and returns a resultset containing sequence numbers within the given range. [code]

    CREATE FUNCTION dbo.GetNumbers
    (
    @Start BIGINT,
    @End BIGINT
    )
    RETURNS @ret TABLE(Number BIGINT)
    AS
    BEGIN
    WITH
    L0 AS (SELECT 1 AS C UNION ALL SELECT 1), --2 rows
    L1 AS (SELECT 1 AS C FROM L0 AS A, L0 AS B),--4 rows
    L2 AS (SELECT 1 AS C FROM L1 AS A, L1 AS B),--16 rows
    L3 AS (SELECT 1 AS C FROM L2 AS A, L2 AS B),--256 rows
    L4 AS (SELECT 1 AS C FROM L3 AS A, L3 AS B),--65536 rows
    L5 AS (SELECT 1 AS C FROM L4 AS A, L4 AS B),--4294967296 rows
    num AS (SELECT ROW_NUMBER() OVER(ORDER BY C) AS N FROM L5)

    INSERT INTO @ret(Number)
    SELECT N FROM NUM WHERE N BETWEEN @Start AND @End
    RETURN
    END

    Once we have the function to generate the sequence number, we can easily write the code to identify the missing numbers. [code]

        1 SELECT MIN(Number)

        2 FROM dbo.GetNumbers(1000, 9999)

        3 WHERE Number NOT IN (SELECT CoordinatorID FROM Coordinators)

    Find Missing Dates

    I guess the requirement to find missing dates must be more common than the missing number requirement I mentioned above. To find the missing dates (Dates at which no sales took place, Dates at which a given employ was absent etc) we need to have a table or resultset which contains all the dates within a given range.

    The following example shows how to generate a sequence of dates using the same logic we discussed earlier. [code]

    --Generate a sequence of all the dates for the month of October, 2007

    SELECT
    CAST('2007-10-01' AS DATETIME) + Number-1
    FROM dbo.GetNumbers(1, 30)
    --Generate a sequence of all the dates for year 2007

    SELECT
    CAST('2007-01-01' AS DATETIME) + Number-1
    FROM dbo.GetNumbers(1, 365)

    Once we have a sequence of dates, we can easily apply a NOT IN clause and find the missing dates based on the specific requirement.

  • Are you sitting comfortably?

    …Then we shall begin…

    Blogs are brilliant. There are a ton of brilliant contributors out there that make learning new technologies so simple. So i guess that it’s high time that I stop leeching so much information and start giving something back too.. I don’t expect too much interaction, this is more about logging my thoughts and findings, and if someone else can benefit from that, then great!

    So this blog is going to be about anything I wan (it’s mine after all)… expect information on .NET coding, Silverlight, WCF, ASP.NET (MVC, jQuery, Dynamic Data etc), Photography and so on.

    ..the name… well it appeared to be one of only a few that remained..damn bloggers…

  • Debugging Replication Errors

    use distribution
    --------------------------------------------------------------------------------
    select top 100 e.*, * from MSrepl_commands c inner join MSrepl_errors e on c.xact_seqno =e.xact_seqno order by e.id desc
    --------------------------------------------------------------------------------
    exec sp_replmonitorhelpsubscription @publisher=NULL, @publication_type=0
    --------------------------------------------------------------------------------
    exec sp_helpdistpublisher
    --------------------------------------------------------------------------------
    exec distribution..sp_replmonitorsubscriptionpendingcmds @publisher=pubservername, @subscriber=subservername, @publisher_db='pubdbname', @subscriber_db='subdbname', @publication='pubname', @subscription_type=0
    --------------------------------------------------------------------------------
    exec sp_replqueuemonitor @publisher = pubservername, @publisherdb = 'pubdbname', @publication = 'pubname', @tranid = NULL, @queuetype = 0
    --------------------------------------------------------------------------------
    exec sp_replshowcmds
  • Dropping Replication

    when struggling to drop a publication or subscription from the API, try one of these:

    use [SysTest_1]

    exec sp_dropsubscription @ignore_distributor =1,@publication = N'all', @subscriber=N'all',@article = N'all'

    exec sp_droppublication @publication = N'all',@ignore_distributor =1

    exec sp_removedbreplication 'systest_1'
  • Trusted Logins

    Trusted logins with IIS and IE6.

    Tools | Internet Options | Security Tab | Intranet Zone | Security Levels | Custom Level

    Scroll to the bottom of the list and make sure that User Authentication | Logon is set appropriately.

  • Moving VSTS Load Test Results Databases

    Email from James … :

    Here  is  the full set of steps that you need to go through to view them.  The basic problem is that the connection string to the load test repository is hardcoded into the trx files.

    1. Restore the results database to your local SQL Server 2005 instance.  In theory SQL Server Express (included with the trial DVD that I gave to Andrew) should do the job, but thinking about it you might find a problem restoring due to the file size limitation (depending on what the file sizes were set to on the results server).  In this case you will need to get hold of a copy of SQL Server 2005 Standard Edition.
    2. Create a dummy load test in Visual Studio 2005 (this can just be an empty unit test added to a load test). 
    3. From the ‘Test’ menu, select the ‘Administer Test Controllers’ option.  Under the ‘Load Test Results Store’ section, enter the correct connection details for your SQL Server instance.
    4. Run the dummy load test for a few seconds.  Find the resulting trx file and open it with notepad.  Search for the tag and copy the value (e.g. LAs4ogoKlDc7tK1quR7UVHrc5d598CfIS8AxIDzjz9YaPiknRPEua16x0ZFwpUM6s5UYUAzzrmtpTf26WVVVFVWiAL1CMaOP).
    5. In each of the trx files that you took away from the lab, find the and replace the value with that copied in step 4.
    6. Open the trx file in Visual Studio and right click on the entry in the Test Results window and select ‘View Test Results Database’. The graphs should now be populated from the database.

    The extraction of the resultsRepositoryConnectString value must be done when the database being referenced is moved, or the name of the database is changed.

  • Moving VSTS Load Test Results Databases

    Email from MS:

    Here  is  the full set of steps that you need to go through to view them.  The basic problem is that the connection string to the load test repository is hardcoded into the trx files.

    1. Restore the results database to your local SQL Server 2005 instance.  In theory SQL Server Express (included with the trial DVD that I gave to Andrew) should do the job, but thinking about it you might find a problem restoring due to the file size limitation (depending on what the file sizes were set to on the results server).  In this case you will need to get hold of a copy of SQL Server 2005 Standard Edition.

    2. Create a dummy load test in Visual Studio 2005 (this can just be an empty unit test added to a load test). 

    3. From the ‘Test’ menu, select the ‘Administer Test Controllers’ option.  Under the ‘Load Test Results Store’ section, enter the correct connection details for your SQL Server instance.

    4. Run the dummy load test for a few seconds.  Find the resulting trx file and open it with notepad.  Search for the tag and copy the value (e.g. LAs4ogoKasfgad5T354G25Ga7UVHrc5d598CfIS8xxxxFQW4R34xknRPEua16x0ZFwpUM6s5UYUAzzrmtpTf26Wxxx54Q4QRF).

    5. In each of the trx files that you took away from the lab, find the and replace the value with that copied in step 4.

    6. Open the trx file in Visual Studio and right click on the entry in the Test Results window and select ‘View Test Results Database’. The graphs should now be populated from the database.

    The extraction of the resultsRepositoryConnectString value must be done when the database being referenced is moved, or the name of the database is changed.