【问题标题】:Values Across Different Rows - combine them into 1 row跨不同行的值 - 将它们组合成 1 行
【发布时间】: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 分钟后它仍在针对真实数据运行。

标签: sql sql-server tsql sql-server-2017


【解决方案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

    查询说明:

    1. 创建rownumber,以便我们可以参考上一行来获取frequency
    2. CTE 的第一部分将获取 NON NULL 频率,第二部分将获取 NULL 频率
    3. 将两者与前 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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-16
      相关资源
      最近更新 更多