比较MS SQL中的2行并获取不同的列

时间:2013-09-26 16:41:29

标签: sql-server tsql compare rows dynamic-columns

我已经搜索了一段时间了,但只是不知道是否有一个“银弹”解决方案我想要做什么。我的数据库中有一个表(为了这个讨论,实际的列是无关紧要的)。我希望能够从同一个表中查看2行,并获得2列之间不同的列。

我知道我可以编写一大堆TSQL来实现特定的表,但我希望SQL Server中有一个内置函数(我正在运行2008 R2)可以做到这一点。

我知道CHECKSUM这样的简单函数会告诉我两行是否不同,但我需要哪些列的具体内容不同。

最好我想把它变成UDF并传递表名,以及我要比较的2行的主键。

有任何想法或建议吗?

- 编辑 - 这是我提出的解决方案:

Well, It is certainly not the most elegant solution...but it will work in a pinch.

    CREATE PROCEDURE sp_Compare_Table_Rows
    (
        @TablePK varchar(1000), -- The Name of the Primary Key in that Table
        @TableName varchar(1000), -- The Name of the Table
        @PK1 int, -- The ID of the 1st table
        @PK2 int -- The ID of the 2nd table
    )
    AS
    DECLARE @Holder table
    (
        Column_Name varchar(250),
        Different bit
    )
    INSERT INTO @Holder(Column_Name,Different)
    select 
    COLUMN_NAME,0
    from 
    LPS_DEV.INFORMATION_SCHEMA.COLUMNS
    WHERE
    TABLE_NAME = @TableName
    and ORDINAL_POSITION >1

    DECLARE @LoopedColumnName varchar(250)
    DECLARE @DynamicQuery nvarchar(max)
    DECLARE @ORD1 int
    DECLARE @ORD2 int

    SET @DynamicQuery = ''
    SET @LoopedColumnName = ''
    SET @ORD1 = 0
    SET @ORD2 = 0

    DECLARE MYCUR CURSOR FOR SELECT Column_Name FROM @Holder
    OPEN MYCUR
    FETCH NEXT FROM MYCUR INTO @LoopedColumnName
    WHILE @@FETCH_STATUS = 0
        BEGIN
            SET @DynamicQuery = 'SELECT @Outer= CHECKSUM(' + @LoopedColumnName + ') FROM ' + @TableName + ' WHERE ' + @TablePK + ' = ' + CONVERT(varchar(100),@PK1)
            exec sp_executesql @DynamicQuery, N'@Outer int output',@ORD1 out

            SET @DynamicQuery = 'SELECT @Outer= CHECKSUM(' + @LoopedColumnName + ') FROM ' + @TableName + ' WHERE ' + @TablePK + ' = ' + CONVERT(varchar(100),@PK2)
            exec sp_executesql @DynamicQuery, N'@Outer int output',@ORD2 out

            IF @ORD1 <> @ORD2 
            BEGIN
                UPDATE @Holder SET Different = 1 WHERE Column_Name = @LoopedColumnName
            END     
            FETCH NEXT FROM MYCUR INTO @LoopedColumnName    
        END
    CLOSE MYCUR
    DEALLOCATE MYCUR

    select * from @Holder

3 个答案:

答案 0 :(得分:0)

约翰,你可以使用这样的东西。这将显示列名称 - 设置@tablename和@whereclause。

declare @tablename varchar(1000), 
@cols varchar(max), 
@sqlstmt nvarchar(max),
@whereclause nvarchar(max);
    set @tablename = 'sometable';
    set @whereclause = ' where
        a.col1 = ''SOMEOTHERVALUE'' and a.datecol2 = ''19910101'' and
        b.col2 = ''SOMEVALUE'' and b.datecol2 = ''19910101''
    '
    select @cols = stuff((
        select ', case when a.' + c.name + ' = b.' + c.name 
           + ' then '''' else ''' + c.name + ''' end'
            from sys.columns c
            inner join sys.tables t on c.object_id = t.object_id
            where t.name = @tablename
            for xml path ('')), 1, 1, '')

    set @sqlstmt = 'select ' + @cols + ' from ' + @tablename 
                  + ' a, ' + @tablename + ' b ' + @whereclause
    exec sp_executesql @sqlstmt

答案 1 :(得分:0)

我对Sriram的答案做了一些改进,这是新脚本:

declare @tablename nvarchar(MAX), 
@cols varchar(max) = '', 
@sqlstmt nvarchar(max) = 'DECLARE @T AS TABLE (ColumnName NVARCHAR(MAX), Value NVARCHAR(MAX), Value2 NVARCHAR(MAX))',
@whereclause nvarchar(max) = '';

set @tablename = 'TABLE_NAME';
set @whereclause = ' where a.FIRST_ROW_ID = ''NUMBER'' and b.SECOND_ROW_ID = ''NUMBER'''

select @cols = (
    select ' INSERT INTO @T SELECT ''' + c.name + ''', cast(a.' + c.name + ' as nvarchar(max)), cast(b.' + c.name + ' as nvarchar(max)) from ' + @tablename + ' a, ' + @tablename + ' b ' + @whereclause + ' AND  cast(a.' + c.name + ' as nvarchar(max)) != cast(b.' + c.name + ' as nvarchar(max))'
            from sys.columns c
            inner join sys.tables t on c.object_id = t.object_id
            where t.name = @tablename
            for xml path (''))

set @sqlstmt += @cols + ' SELECT * FROM @T'

exec sp_executesql @sqlstmt

您需要更换&#39; TABLE_NAME&#39;使用您的表并更改where子句变量。

结果如下:

ColumnName | Value  | Value2
----------------------------
Id         | 1      | 2 
Name       | Name 1 | Name 2

答案 2 :(得分:0)

我进一步完善了Sriram Chitturi和Mohamed Noor的答案,还处理了日期时间比较

ALTER PROC [dbo].[IdentifyNotMatchingColumnDataBetween2Rows] (
    @table_name VARCHAR(300) = 'TableName'
    ,@excluded_columns VARCHAR(300) = 'Id,UpdateTimeStamp,InsertTimeStamp,IsDuplicate,NotMatchingColumns'
    ,@compare_row_id_1 INT = 1732340
    ,@compare_row_id_2 INT = 1736803
    )
AS
/*
EXEC [dbo].[IdentifyNotMatchingColumnDataBetween2Rows]
*/
BEGIN
    SET NOCOUNT ON;

    DECLARE @cols VARCHAR(max) = ''
    DECLARE @sqlstmt NVARCHAR(max) = 'DECLARE @T AS TABLE (ColumnName VARCHAR(300), Value NVARCHAR(MAX), Value2 NVARCHAR(MAX))'
    DECLARE @where_clause NVARCHAR(max) = ' WHERE a.Id = ''' + CAST(@compare_row_id_1 as varchar(20)) + ''' and b.Id = ''' + CAST(@compare_row_id_2 as varchar(20)) + ''''

    SELECT @cols = (
            SELECT CASE 
                    WHEN c.system_type_id = 106 --DECIMAL
                        OR c.system_type_id = 56 --INT
                        OR c.system_type_id = 48 --TINYINT
                        OR c.system_type_id = 52 --SMALLINT
                        OR c.system_type_id = 127 --BIGINT
                        OR c.system_type_id = 62 --FLOAT
                        OR c.system_type_id = 108 --NUMERIC
                        THEN ' INSERT INTO @T SELECT ''[' + c.name + ']'', CAST(a.[' + c.name + '] AS NVARCHAR(MAX)), CAST(b.[' + c.name + '] AS NVARCHAR(MAX)) FROM [' + @table_name + '] a WITH(NOLOCK) , [' + @table_name + '] b WITH(NOLOCK) ' + @where_clause + ' AND  CAST(ISNULL(a.[' + c.name + '],' + '-1' + ') AS NVARCHAR(MAX)) != CAST(ISNULL(b.[' + c.name + '],' + '-1' + ') AS NVARCHAR(MAX))'
                    WHEN c.system_type_id IN (61,42) -- DATETIME / DATETIME2
                        THEN ' INSERT INTO @T SELECT ''[' + c.name + ']'', FORMAT(a.[' + c.name + '] ,''yyyyMMddHHmmssffff''), FORMAT(b.[' + c.name + '] ,''yyyyMMddHHmmssffff'') FROM [' + @table_name + '] a WITH(NOLOCK) , [' + @table_name + '] b WITH(NOLOCK) ' + @where_clause + ' AND  FORMAT(ISNULL(a.[' + c.name + '],' + '''' + '1900-01-01' +  '''' + ') ,''yyyyMMddHHmmssffff'') != FORMAT(ISNULL(b.[' + c.name + '],' + '''' + '1900-01-01' +  '''' + ') ,''yyyyMMddHHmmssffff'')'
                    WHEN c.system_type_id = 58 --SMALLDATETIME
                        THEN ' INSERT INTO @T SELECT ''[' + c.name + ']'', FORMAT(a.[' + c.name + '] ,''yyyyMMddHHmmss''), FORMAT(b.[' + c.name + '] ,''yyyyMMddHHmmss'') FROM [' + @table_name + '] a WITH(NOLOCK) , [' + @table_name + '] b WITH(NOLOCK) ' + @where_clause + ' AND  FORMAT(ISNULL(a.[' + c.name + '],' + '''' + '1900-01-01' +  '''' + ') ,''yyyyMMddHHmmss'') != FORMAT(ISNULL(b.[' + c.name + '],' + '''' + '1900-01-01' +  '''' + ') ,''yyyyMMddHHmmss'')'
                    ELSE 
                        ' INSERT INTO @T SELECT ''[' + c.name + ']'', CAST(a.[' + c.name + '] AS NVARCHAR(MAX)), CAST(b.[' + c.name + '] AS NVARCHAR(MAX)) FROM [' + @table_name + '] a WITH(NOLOCK) , [' + @table_name + '] b WITH(NOLOCK) ' + @where_clause + ' AND  CAST(ISNULL(a.[' + c.name + '],' + '''' + '''' + ') AS NVARCHAR(MAX)) != CAST(ISNULL(b.[' + c.name + '],' + '''' + '''' + ') AS NVARCHAR(MAX))'
                    END
            FROM sys.columns c
            INNER JOIN sys.tables t ON c.object_id = t.object_id
            WHERE t.name = @table_name
                AND c.name NOT IN (
                    SELECT items
                    FROM dbo.Split(@excluded_columns, ',')
                    )
            FOR XML path('')
            )

    SET @sqlstmt += @cols + ' SELECT * FROM @T';

    --PRINT @sqlstmt;

    EXEC sp_executesql @sqlstmt
END

要执行

EXECUTE dbo.IdentifyNotMatchingColumnDataBetween2Rows 'TableName','Id,UpdateTimeStamp,InsertTimeStamp,Last Checked,IsDuplicate,NotMatchingColumns,',@row_id_1,@row_id_2;