比较两行并标识值不同的列

时间:2015-01-28 14:23:59

标签: sql-server sql-server-2008

情况

我们有一个应用程序,我们将机器设置存储在SQL表中。当用户更改机器的参数时,我们创建一个“修订版”,这意味着我们在表中插入一行。此表包含大约200个。 在我们的应用程序中,用户可以查看每个修订版本。

问题

我们想要突出显示自上次修订以来已更改的参数。

问题

是否有一种只使用SQL的方法来获取两行之间差异的列名?

示例

ID | p_x | p_y | p_z
--------------------
11 | xxx | yyy | zzz

12 | xxy | yyy | zzy

查询应返回p_xp_z

修改

该表有200列,而不是行......

MY WAY OUT

我的目的是为这个问题找到一个“一行SQL语句”。

我在下面的答案中看到,它在SQL中更重要。 由于没有针对此问题的简短的SQL解决方案,因此在我们的软件(c#)的后端解决它当然要容易得多!

但由于这不是我问题的真正“答案”,我不会将其标记为已回答。

感谢您的帮助。

4 个答案:

答案 0 :(得分:5)

你说:

 We want to highlight the parameters that have changed since the last revision.

这意味着您希望显示(或报告)使更改的参数突出显示。

如果您要显示所有参数,那么在前端以编程方式执行此操作会容易得多。在编程语言中这将是一个更简单的问题。不幸的是,我不知道你的前端是什么,我不能给你特别的建议。

如果您真的无法在前端执行此操作但必须在数据库查询中接收此信息(您确实说“仅限SQL”),则需要指定您希望数据的格式in。两列记录之间发生变化的列的单列列表?带有标志的列表列表,指示哪些列已更改或未更改?

但这是一种可行的方法,但在此过程中,它会在进行比较之前将所有字段转换为nvarchars:

  1. 使用描述的技术here(免责声明:这是我的博客)将您的记录转换为ID名称 - 值对。
  2. 在ID上将结果数据集加入到自身中,以便您可以比较这些值并打印已更改的值:

     with A as (    
    --  We're going to return the product ID, plus an XML version of the     
    --  entire record. 
    select  ID    
     ,   (
          Select  *          
          from    myTable          
          where   ID = pp.ID                            
          for xml auto, type) as X 
    from    myTable pp )
    , B as (    
    --  We're going to run an Xml query against the XML field, and transform it    
    --  into a series of name-value pairs.  But X2 will still be a single XML    
    --  field, associated with this ID.    
    select  Id        
       ,   X.query(         
           'for $f in myTable/@*          
           return         
           <data  name="{ local-name($f) }" value="{ data($f) }" />      
           ') 
           as X2 from A 
    )
    ,    C as (    
     --  We're going to run the Nodes function against the X2 field,  splitting     
     --  our list of "data" elements into individual nodes.  We will then use    
     -- the Value function to extract the name and value.   
     select B.ID as ID  
       ,   norm.data.value('@name', 'nvarchar(max)') as Name  
       ,   norm.data.value('@value', 'nvarchar(max)') as Value
    from B cross apply B.X2.nodes('/myTable') as norm(data))
    
    -- Select our results.
    
    select *
    from ( select * from C where ID = 123) C1
    full outer join ( select * from C where ID = 345) C2
        on C1.Name = c2.Name
    where c1.Value <> c2.Value 
      or  not (c1.Value is null and c2.Value is null)
    

答案 1 :(得分:0)

这是使用UNPIVOT的一种方式:

;WITH
    cte AS
    (
        SELECT      CASE WHEN t1.p_x <> t2.p_x THEN 1 ELSE 0 END As p_x,
                    CASE WHEN t1.p_y <> t2.p_y THEN 1 ELSE 0 END As p_y,
                    CASE WHEN t1.p_z <> t2.p_z THEN 1 ELSE 0 END As p_z
        FROM        MyTable t1, MyTable t2
        WHERE       t1.ID = 11 AND t2.ID = 12 -- enter the two revisions to compare here
    )

SELECT      *
FROM        cte
UNPIVOT     (
                Changed FOR ColumnName IN (p_x, p_y, p_z)
            ) upvt
WHERE       upvt.Changed = 1

在比较期间,您必须添加代码来处理NULL。如果表中有很多列,也可以动态构建查询。

答案 2 :(得分:0)

  

对于sql server 2012,你可以做类似的事情(复制它   每一栏):

  SELECT iif((p_x != lead(p_x) over(ORDER BY p_x)),
                                       (SELECT COLUMN_NAME 
                                        FROM INFORMATION_SCHEMA.COLUMNS
                                        WHERE TABLE_NAME = 'tbl' 
                                        AND 
                                        TABLE_SCHEMA='schema' 
                                        AND 
                                        ORDINAL_POSITION='1')
                                                 ,NULL)
FROM tbl
  

for sql server 2008试试

DECLARE @x int =11  -- first id
WHILE @x!=(SELECT count(1) FROM tbl)
BEGIN --comparison of two adjacent rows
if (SELECT p_x  FROM tbl WHERE id=@x)!=(SELECT p_x  FROM tbl WHERE id=@x+1)

 BEGIN
 SELECT COLUMN_NAME
 FROM INFORMATION_SCHEMA.COLUMNS
 WHERE TABLE_NAME = 'tbl'  --insert your table
 AND 
 TABLE_SCHEMA='schema'   --insert your schema
 AND 
 ORDINAL_POSITION='1'  --first column 'p_x'
END
set @x=@x+1
END

答案 3 :(得分:0)

我和您有类似的问题。这是我使用unpivot and pivot解决问题的方法。关键是转置数据,以便您可以使用where [11] != [12]

WITH CTE AS (
    SELECT * 
    FROM 
    (
        SELECT ID, colName, val
        FROM tblName
        UNPIVOT
        (
            val
            FOR colName IN ([p_x],[p_y],[p_z])
        ) unpiv
    ) src
    PIVOT
    (
        MAX(val)
        FOR ID IN ([11], [12])
    ) piv
)
SELECT colName
--SELECT *
FROM CTE WHERE [11] != [12]
  

如果表中只有3列,则很容易将[p_x],[p_y],[p_z]放进去,但是显然,键入 200列并不方便。即使您可以使用trick来拖放或复制/粘贴表中的列名,它的体积仍然很大。因此,您需要使用SELECT * EXCEPT技巧和dynamic sql

DECLARE @TSQL NVARCHAR(MAX), @colNames NVARCHAR(MAX)
SELECT @colNames = COALESCE(@colNames + ',' ,'') + [name] 
FROM syscolumns WHERE name  <> 'ID' and id = (SELECT id FROM sysobjects WHERE name = 'tablelName')

SET @TSQL = '
    WITH CTE AS (
        SELECT * 
        FROM 
        (
            SELECT ID, colName, val
            FROM tablelName
            UNPIVOT
            (
                val
                FOR colName IN (' + @colNames + ')
            ) unpiv
        ) src
        PIVOT
        (
            MAX(val)
            FOR ID IN ([11], [12])
        ) piv
    )
    --SELECT colName
    SELECT *
    FROM CTE WHERE [11] != [12]
'
EXEC sp_executesql @TSQL