Search code examples
sqlsql-servert-sqlsql-server-2017

Values Across Different Rows - combine them into 1 row


I have the following results across a table, and what I need to get is the Frequency column down onto the appropriate rows, so the NULL values in the Frequency column are replaced with the appropriate value. Noting the CustomerCode different values.

Anyone know how this could be done, and in doing so removing the Frequency row that doesn't have the Type and 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

Desired Output

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

Sample Data

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

Solution

  • There must be some defined order to your data or else you cannot perform this query. I created an order by inserting your data into a temp table with an identity column for your reference. I'm assuming there is some underlying order defined in your source data. Just swap that out with my surrogate key column [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
    

    If performance is an issue, recommend creating an index like so before executing your select:

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