【问题标题】:Displaying the difference between rows in the same table in SQL SERVER在 SQL SERVER 中显示同一表中的行之间的差异
【发布时间】:2013-04-15 21:07:05
【问题描述】:

我使用的是 SQL Server 2005。

我有一个表,每次更改某个字段时都会对行进行存档。我必须生成一个报告,显示为每个员工更改的字段。

我的表架构:

tblEmp(empid, name, salary, createddate)

我的表格数据:

Row 1: 1, peter, 1000, 11/4/2012
Row 2: 1, peter, 2000, 11/5/2012
Row 3: 1, pete, 2000, 11/6/2012
Row 4: 1, peter, 4000, 11/7/2012

根据以上员工彼得(员工 id 1)的数据,输出(更改)将是:

结果集:

1, oldsalary: 1000 newsalary: 2000 (changed on 11/5/2012)
1, oldname: peter newname: pete (changed on 11/6/2012)
1, oldname: pete newname: peter, oldsalary:2000, newsalary: 4000 (changed on 11/7/2012)

我正在尝试提出生成上述结果集的 sql。

我尝试做一些类似于此线程中第一个答案的事情:How to get difference between two rows for a column field?

但是,它并没有走到一起,所以想知道是否有人可以提供帮助。

【问题讨论】:

  • 您如何确定哪一行是第 1 行,哪一行是第 2 行等?似乎没有一列指示哪一行先发生,那么 SQL Server 怎么能告诉你呢?它不会跟踪以何种顺序插入了哪些行 - 将一个表视为一袋行。
  • 您链接的问题与您的问题非常相似,“它没有融合在一起”实际上是什么意思?
  • 假设它是有序的。第 1 行更改为第 2 行,第 2 行更改为第 3 行...等等
  • @Prabhu 。 . . SQL 表是没有有序的。您需要有一个明确的排序列——行 ID、创建日期或类似的东西。
  • @Pondlife,我稍微修改了一下,让它显示旧值和新值而不是做减法,但是新值总是NULL,在这种情况下不是

标签: sql sql-server tsql sql-server-2005


【解决方案1】:

您正在逐列查看差异。这建议使用unpivot。以下内容会随着列中的每次更改以及先前的值和日期创建输出:

DECLARE @t TABLE(empid INT,name SYSNAME,salary INT,createddate DATE);

INSERT @t SELECT 1, 'peter', 1000, '20121104'
UNION ALL SELECT 1, 'peter', 2000, '20121105'
UNION ALL SELECT 1, 'pete',  2000, '20121106'
UNION ALL SELECT 1, 'peter', 4000, '20121107';


with cv as (
      select empid, createddate, col, val
      from (select empid, CAST(name as varchar(8000)) as name,
                   CAST(salary as varchar(8000)) as salary, createddate
            from @t
           ) t
      unpivot (val for col in (name, salary)) as unpvt
     ),
    cvr as (
     select cv.*,
            ROW_NUMBER() over (partition by empid, col order by createddate) as seqnum_all
     from (select cv.*, ROW_NUMBER() over (partition by empid, col, thegroup order by createddate) as seqnum_group
           from (select cv.*,
                        (ROW_NUMBER() over (partition by empid, col order by createddate) -
                         ROW_NUMBER() over (partition by empid, col, val order by createddate)
                        ) as thegroup
                 from cv
                ) cv
          ) cv
     where seqnum_group = 1
    ) -- select * from cvr
select cvr.*, cvrprev.val as preval, cvrprev.createddate as prevdate
from cvr left outer join
     cvr cvrprev
     on cvr.empid = cvrprev.empid and
        cvr.col = cvrprev.col and
        cvr.seqnum_all = cvrprev.seqnum_all + 1

【讨论】:

  • 谢谢@Gordon。虽然蒂姆的答案对我有用,但我只是想知道这个答案是否有任何优势。感谢您的宝贵时间。
  • @Prabhu 。 . .这是思考问题的另一种方式。 Tim 的方法是查看相邻的行并进行比较。这种方法查看每一列并确定更改。他们应该返回相似的结果。
【解决方案2】:

也许这些加入 CTE'sROW_NUMBER + CASE

WITH cte  AS 
(
  SELECT empid,
         name,
         salary, 
         rn=ROW_NUMBER()OVER(PARTITION BY empid ORDER BY createddate)
  FROM   tblemp
) 
SELECT oldname=CASE WHEN c1.Name=c2.Name THEN '' ELSE C1.Name END,
       newname=CASE WHEN c1.Name=c2.Name THEN '' ELSE C2.Name END,
       oldsalary=CASE WHEN c1.salary=c2.salary THEN NULL ELSE C1.salary END,
       newsalary=CASE WHEN c1.salary=c2.salary THEN NULL ELSE C2.salary END
FROM cte c1 INNER JOIN cte c2 
ON c1.empid=c2.empid AND c2.RN=c1.RN + 1

Sql-Fiddle Demo

【讨论】:

  • ORDER BY empid 没有做任何有用的事情......这不是关键,那么你怎么知道哪一行得到 rn = 1、rn = 2 等?
  • @AaronBertrand:编辑了我的答案。我还没有看到 OP 同时编辑了他的答案。
【解决方案3】:
DECLARE @t TABLE(empid INT,name SYSNAME,salary INT,createddate DATE);

INSERT @t SELECT 1, 'peter', 1000, '20121104'
UNION ALL SELECT 1, 'peter', 2000, '20121105'
UNION ALL SELECT 1, 'pete',  2000, '20121106'
UNION ALL SELECT 1, 'peter', 4000, '20121107';

;WITH x AS
(
  SELECT empid, name, salary, createddate, rn = ROW_NUMBER() OVER 
  (PARTITION BY empid ORDER BY createddate)
  FROM @t
  -- WHERE empid = 1 -- for example
)
SELECT LTRIM(
  CASE WHEN x.salary <> y.salary THEN 
    'oldsalary: ' + RTRIM(x.salary)
    + ' newsalary: ' + RTRIM(y.salary)
  ELSE '' END
  + CASE WHEN x.name <> y.name THEN 
    ' oldname: ' + x.name
    + ' newname: ' + y.name
  ELSE '' END
  + ' (changed on ' + CONVERT(CHAR(10), y.createddate, 101) + ')')
FROM x INNER JOIN x AS y
ON x.rn = y.rn - 1
AND x.empid = y.empid
AND
(
 x.salary <> y.salary 
 OR x.name <> y.name
);

除非您有一个 where 子句来定位特定的 empid,否则输出不是很有用,除非它还包含 empidSQLfiddle demo

【讨论】:

  • 非常感谢@Aaron,这个解决方案也适用于我。我只是碰巧先尝试了蒂姆的答案。
【解决方案4】:

根据您的解释,在更改此表时创建触发器会更容易,然后使用您期望的结果创建表,因为您在那一刻拥有旧值和新值,所以应该有得出您期望的结果不是问题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-13
    • 1970-01-01
    • 2023-03-08
    相关资源
    最近更新 更多