【问题标题】:SQL Server : Merging Several Rows of Data into a Single RowSQL Server:将多行数据合并为一行
【发布时间】:2013-06-25 11:11:14
【问题描述】:

我要做的是合并几行数据,以从 Transact-SQL 或 SSIS 中显示为单行。比如:

制作:

REF  ID   Title Surname     Forename    DOB          Add1            Postcode
------------------------------------------------------------------------------------------    
D    10   MR    KINGSTON    NULL        15/07/1975   3 WATER SQUARE  NULL
T    10   NULL  NULL        BOB         NULL         NULL            NULL
T    10   MRS   NULL        NULL        NULL         NULL            TW13 7DT

进入这个:

REF  ID   Title Surname    Forename   DOB          Add1            Postcode
----------------------------------------------------------------------------------    
D    10   MRS   KINGSTON   BOB        15/07/1975   3 WATER SQUARE  TW13 7DT

所以我所做的是将值合并在一起,忽略空值。 (D = 数据;T = 更新)

欢迎提出任何建议。

谢谢。

【问题讨论】:

  • 在合并时你给什么优先权?在您选择 MRS 而不是 MR 的标题中。
  • @DinupKandel T = Update
  • 如何处理同一列的多次更新?
  • 我会优先考虑 MRS,因为它是第二笔交易,因此会覆盖 MR。
  • 如何确定第二笔交易?请不要告诉我是它,因为它是最底部的,您需要一个类似时间戳或 id 的列来“排序”来描述它是哪一个

标签: sql sql-server sql-server-2008 tsql ssis


【解决方案1】:

这会起作用,但由于没有标识或日期时间列 - 无法找到哪个更新行较新。因此,如果同一列有更多更新,我只需按字母/数字顺序 (MIN) 取第一个。

WITH CTE AS 
(
    SELECT ID, REF, MIN(Title) Title, MIN(Surname) Surname, MIN(Forename) Forename, MIN(DOB) DOB, MIN(Add1) Add1, MIN(Postcode) Postcode
    FROM Table1
    GROUP BY id, REF
)
SELECT 
    d.REF
  , d.ID
  , COALESCE(T.Title, d.TItle) AS Title
  , COALESCE(T.Surname, d.Surname) AS Surname
  , COALESCE(T.Forename, d.Forename) AS Forename
  , COALESCE(T.DOB, d.DOB) AS DOB
  , COALESCE(T.Add1, d.Add1) AS Add1
  , COALESCE(T.Postcode, d.Postcode) AS Postcode
FROM CTE d 
INNER JOIN CTE t ON d.ID = t.ID AND d.REF = 'D' AND t.REF = 't'

SQLFiddle DEMO

如果可以添加标识列,我们可以重写CTE部分使其更准确。

编辑:

如果我们有标识列,并且 CTE 被重写为递归,实际上可以删除查询的整个其他部分。

WITH CTE_RN AS 
(
    --Assigning row_Numbers based on identity - it has to be done since identity can always have gaps which would break the recursion
    SELECT *, ROW_NUMBER() OVER (PARTITION BY ID ORDER BY IDNT DESC) RN FROM dbo.Table2
)
,RCTE AS 
(
    SELECT  ID ,
            Title ,
            Surname ,
            Forename ,
            DOB ,
            Add1 ,
            Postcode ,
            RN FROM CTE_RN WHERE RN = 1 -- taking the last row for each ID
    UNION ALL
    SELECT r.ID,
        COALESCE(r.TItle,p.TItle), --Coalesce will hold prev value if exist or use next one
        COALESCE(r.Surname,p.Surname),
        COALESCE(r.Forename,p.Forename),
        COALESCE(r.DOB,p.DOB),
        COALESCE(r.Add1,p.Add1),
        COALESCE(r.Postcode,p.Postcode),
        p.RN
    FROM RCTE r
    INNER JOIN CTE_RN p ON r.ID = p.ID AND r.RN + 1 = p.RN --joining the previous row for each id
)
,CTE_Group AS 
(
    --rcte now holds both merged and unmerged rows, merged is max(rn)
    SELECT ID, MAX(RN) RN FROM RCTE
    GROUP BY ID  
)
SELECT r.* FROM RCTE r
INNER JOIN CTE_Group g ON r.ID = g.ID AND r.RN = g.RN

SQLFiddle DEMO

【讨论】:

  • @Nitz 不要忘记接受答案。请注意,如果列多次更改值,这并不总是有效
  • 如果我在主表上有一个标识列,CTE 会是什么样子?
  • @Nitz:我已经编辑了答案以包含该解决方案。在开始重写 CTE 之后 - 最后它变成了几乎完全不同的解决方案。不再需要加入 D 和 T - 因为我猜 T 的 IDNT 总是比 D 更新。
  • 有没有办法为所有字段执行此操作而无需输入它们?像 SELECT * UNION ALL SELECT r.ID COALESCE *
【解决方案2】:

我添加了一个标识列 id2 以使逻辑工作。

declare @t table(id2 int identity(1,1), 
REF char(1),
ID int,
Title varchar(10),
Surname varchar(10),
Forename varchar(10),
DOB date, 
Add1 varchar(15),
Postcode varchar(10)
)

insert @t values

('D',10, 'MR', 'KINGSTON', NULL, '19750715', '3 WATER SQUARE', NULL),
('T',10, NULL, NULL, 'BOB', NULL, NULL, NULL),
('T',10, 'MRS', NULL, NULL, NULL, NULL, 'TW13')

select Ref, t2.Title, t3.Surname, t4.Forename, t5.Dob, t6.Add1, t7.PostCode from @t t1
outer apply (select top 1 Title from @t where t1.id = id and Title is not null
order by id2 desc) t2
outer apply (select top 1 Surname from @t where t1.id = id and Surname is not null
order by id2 desc) t3
outer apply (select top 1 Forename from @t where t1.id = id and Forename is not null
order by id2 desc) t4
outer apply (select top 1 DOB from @t where t1.id = id and DOB is not null
order by id2 desc) t5
outer apply (select top 1 add1 from @t where t1.id = id and add1 is not null
order by id2 desc) t6
outer apply (select top 1 postcode from @t where t1.id = id and postcode is not null
order by id2 desc) t7
where Ref = 'D'

结果:

Ref Title  Surname  Forename  Dob         Add1            PostCode
D   MRS    KINGSTON BOB       1975-07-15  3 WATER SQUARE  TW13

【讨论】:

    【解决方案3】:

    你可以试着用光标来做:

    BEGIN
      declare @Title sometype, @Surname sometype, @Forename sometype, @DOB sometype, @Add1 sometype, @Postcode sometype --vars to fetch the crusor into
      declare @rTitle sometype, @rSurname sometype, @rForename sometype, @rDOB sometype, @rAdd1 sometype, @rPostcode sometype --vars to keep the result
    
      DECLARE mycur CURSOR FOR  
        SELECT Title,Surname,Forename,DOB,Add1,Postcode
          FROM t1
          WHERE where id = 10 --or some parameter if you have a procedure
          ORDER BY REF -- add another column here if you decide to create one (e.g. date_created)
    
      OPEN mycur   
      FETCH NEXT FROM mycur INTO @Title, @Surname, @Forename, @DOB, @Add1, @Postcode 
    
      WHILE @@FETCH_STATUS = 0   
      BEGIN   
        SET @rTitle = isnull(@Title,@rTitle) -- update the result with the new value unless the new one is null
        ... -- repeat for all the variables
      END   
    
      CLOSE mycur
      DEALLOCATE mycur
    
    --here use all the result variables for whatever you wish
    END
    

    ORDER BY REF 在数据之后进行更新,因为基本上是 T > D

    【讨论】:

      猜你喜欢
      • 2011-12-21
      • 2012-05-27
      • 2018-02-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-04-22
      • 2017-09-24
      • 2016-04-27
      相关资源
      最近更新 更多