跨不同行的值 - 将它们组合成 1 行

Posted

技术标签:

【中文标题】跨不同行的值 - 将它们组合成 1 行【英文标题】:Values Across Different Rows - combine them into 1 row 【发布时间】:2022-01-01 05:00:50 【问题描述】:

我在一个表中有以下结果,我需要得到的是频率列向下到适当的行,因此频率列中的 NULL 值被替换为适当的值。注意 CustomerCode 不同的值。

任何人都知道如何做到这一点,并在这样做时删除没有 Type 和 TypeAmount 的频率行?

CustomerCode    Frequency   Type    TypeAmount
C12345          Monthly     NULL    NULL
C12345          NULL        A1      5.00
C12345          NULL        A2      20.00
C12345          Fortnightly NULL    NULL
C12345          NULL        A1      5.00
C12345          NULL        A2      20.00
C56789          Fortnightly NULL    NULL
C56789          NULL        A1      50.00

期望的输出

CustomerCode    Frequency   Type    TypeAmount
C12345          Monthly     A1      5.00
C12345          Monthly     A2      20.00
C12345          Fortnightly A1      5.00
C12345          Fortnightly A2      20.00
C56789          Fortnightly A1      50.00

样本数据

Create Table #Data
(
    CustomerCode varchar(50),
    Frequency varchar(50) NULL,
    Type varchar(50) NULL,
    TypeAmount money NULL
)

insert into #Data
(
    CustomerCode,
    Frequency,
    Type,
    TypeAmount 
)
select
    'C12345',
    'Monthly',
    NULL,
    NULL
union all
select
    'C12345',
    NULL,
    'A1',
    '5.00'
union all
select
    'C12345',
    NULL,
    'A2',
    '20.00'
union all
select
    'C12345',
    'Fornightly',
    NULL,
    NULL
union all
select
    'C12345',
    NULL,
    'A1',
    '5.00'
union all
select
    'C12345',
    NULL,
    'A2',
    '20.00'
union all
select
    'C56789',
    'Fornightly',
    NULL,
    NULL
union all
select
    'C56789',
    NULL,
    'A1',
    '50.00'

select * from #Data

【问题讨论】:

所需的输出会有所帮助。 @Arun - 当然,完成 我已经发布了答案。看看吧! 这行得通吗? 感谢@arun - 我目前正在测试,8 分钟后它仍在针对真实数据运行。 【参考方案1】:

您的数据必须有一些已定义的顺序,否则您将无法执行此查询。我通过将您的数据插入到带有标识列的临时表中来创建订单以供您参考。我假设您的源数据中定义了一些基本顺序。只需将其替换为我的代理键列 [ID]

DROP TABLE IF EXISTS #Data

Create Table #Data
(
    ID int Identity(1,1),
    CustomerCode varchar(50),
    Frequency varchar(50) NULL,
    Type varchar(50) NULL,
    TypeAmount money NULL
)

INSERT INTO #Data (CustomerCode,Frequency,[Type],TypeAmount )
VALUES ('C12345','Monthly',NULL,NULL)
,('C12345',NULL,'A1','5.00')
,('C12345',NULL,'A2','20.00')
,('C12345','Fornightly',NULL,NULL)
,('C12345',NULL,'A1','5.00')
,('C12345',NULL,'A2','20.00')
,('C56789','Fornightly',NULL,NULL)
,('C56789',NULL,'A1','50.00')

Select *
FROM #Data

SELECT A.ID
    ,B.Frequency
    ,A.Type
    ,A.TypeAmount
from #Data as A
Cross Apply 
(   /*Grab most recent preceding row that has frequency populated*/
    SELECT Top (1) DTA.Frequency
    From #Data AS DTA
    Where A.CustomerCode = DTA.CustomerCode
    AND DTA.ID < A.ID
    AND DTA.Frequency IS NOT NULL
    Order by DTA.ID DESC
) AS B
WHERE A.Frequency IS NULL

如果性能是一个问题,建议在执行您的选择之前创建一个这样的索引:

Create Index ix on #Data(CustomerCode,ID) Include (Frequency)

【讨论】:

【参考方案2】:

RECURSIVE CTE 应该可以解决问题:

With cte AS
(
SELECT customerCode, frequency, type, TypeAmount, rn
FROM 
    (
     SELECT *, ROW_NUMBER()OVER(PARTITION BY CustomerCode ORDER BY CustomerCode) AS rn
     FROM #data
    ) AS d
WHERE Frequency IS NOT NULL

UNION ALL

SELECT d2.customerCode, cte.frequency, d2.type, d2.TypeAmount, d2.rn
From 
     (
      SELECT *, ROW_NUMBER()OVER(PARTITION BY CustomerCode ORDER BY CustomerCode) AS rn
      FROM #data
     ) AS d2
INNER JOIN cte
  ON d2.rn=cte.rn+1
  AND d2.CustomerCode=cte.CustomerCode
WHERE d2.Frequency IS NULL
)

SELECT * 
FROM cte 
WHERE Type IS NOT NULL 
  AND TypeAmount IS NOT NULL
ORDER BY CustomerCode, rn;

结果:

customerCode frequency type TypeAmount rn
C12345 Monthly A1 5.0000 2
C12345 Monthly A2 20.0000 3
C12345 Fornightly A1 5.0000 5
C12345 Fornightly A2 20.0000 6
C56789 Fornightly A1 50.0000 2

查询说明:

    创建rownumber,以便我们可以参考上一行来获取frequency CTE 的第一部分将获取 NON NULL 频率,第二部分将获取 NULL 频率 将两者与前 1 行连接以获得各自的Frequency

在此处查看DEMO

【讨论】:

感谢 Arun,它似乎产生了正确的结果。但是,当它针对数万条记录运行时,它非常慢。您可以提出任何更改建议吗? @Philip,我想这不能更快,因为我们在可能的地方使用了 JOINS。但是,创建ROW_NUMBER可以在CTE开始之前完成(这样子查询会减少) PARTITION BY CustomerCode ORDER BY CustomerCode 是完全不确定的,每次可能返回不同的结果。它工作一次或一百次的事实并不意味着它会一直工作。您需要另一列来排序 查理脸是正确的,需要在数据上定义顺序。 @Arun 不需要递归,它引入了不必要的复杂性和性能挑战。只需要一个简单的 CROSS APPLY 来查找最后填充的频率值(见我的答案)【参考方案3】:
;with cte1 as
(select CustomerCode,Frequency,ROW_NUMBER()over( order by 
customercode)rn,Type,TypeAmount from #Data a ),
cte2 as (   select * from cte1 where  Frequency is not null)
select row_number()over(order by 
cte2.rn)sno,cte1.CustomerCode,cte2.Frequency,cte1.Type,cte1.TypeAmount from cte1
inner join cte2 on cte1.CustomerCode=cte2.CustomerCode
where cte1.Type is not null
group by cte1.CustomerCode,cte2.Frequency,cte1.Type,cte1.TypeAmount,cte2.rn
order by 1

【讨论】:

以上是关于跨不同行的值 - 将它们组合成 1 行的主要内容,如果未能解决你的问题,请参考以下文章

将不同形状的 CSV 组合成一个 CSV

如何将来自不同查询的值(计数)组合成一个查询

是否可以将与不同行名关联的值添加在一起,同时保持其他行不变?

将两个系列组合成一个数据框行明智

将不同大小的数组组合成一个对象?

从多个链接中获取 url 参数并将它们组合成一个 url