Home > front end >  SQL Row roll up and concatenate strings
SQL Row roll up and concatenate strings

Time:11-12

I have a table like this in SQL Server:

ID Rank Tag Name Size Seq
1 1 One 14 7
2 2 A Two 12 4
3 2 B Three 0 5
4 2 C Four 0 6
5 3 Five 8 1
6 4 A Six 18 2
7 4 B Seven 0 3
  • "ID" is an identity field.
  • Rank is an always increasing integer that does the same job as "Tag"
  • "Tag" groups rows together into Single or Multiple type rows: if "Tag" is blank or null the row is selected by itself (single). If 'A', 'B' 'C' or 'D' they must all be rolled up to one 'A' (Multiple) row by concatenating the "Name" fields separated by commas.
  • The "Size" value for the rolled-up rows is the value for the 'A' row. For all rows with 'B','C' or 'D' Size is zero and on rollup will take on the previous 'A' value.
  • Finally, the Seq field is a user-specified sort order value the final ORDER BY Field.

So, I need the following output:

Tag Name Size
Five 8
A Six,Seven 18
A Two,Three,Four 12
One 14

I know I need sub queries and some combinations of GROUP BY and/or PARTITION BY, plus ROW_OVER to roll up the rows. I've tried all combinations I can think of with no success. There must be TSQL query to do this without resorting to cursors. Can anyone help? Thanks in advance.

CodePudding user response:

select   min(tag)               as tag
        ,string_agg(name, ', ') as name
        ,sum(size)              as size
from     t
group by rank
tag name size
null One 14
A Two, Three, Four 12
null Five 8
A Six, Seven 18

Fiddle

CodePudding user response:

Looking through the requirements, I seems we are grouping by the rank column. Additionally, some of the fields are determined by row with an A for the tag value, which isn't always present.

With that in mind, we need to start by selecting a "seed" row for each rank. The easiest way to do this is using the row_number() function to sort within each rank and then pick the first row:

SELECT *
FROM (
    SELECT *, row_number() over (partition by rank order by case when tag = 'A' then 0 when coalesce(tag,'') = '' then 1 else 2 end, tag) as rn
    FROM [table]
) t
WHERE rn = 1

Now we know which row to use as the base for each rank, but that's not enough. We also need to know about other name values for the same rank. We can find this if we take the first query and JOIN it back to the same table based on rank again, and then use GROUP BY STRING_AGG() to get our final fields. For convenience, I'll use the first query as a CTE:

WITH base_rows AS (
    SELECT rank, size, tag, seq, rn
    FROM (
        SELECT *, row_number() over (partition by rank order by case when tag = 'A' then 0 when coalesce(tag,'') = '' then 1 else 2 end, tag) as rn
        FROM [table]
    ) t
    WHERE rn = 1
)
SELECT b.tag, string_agg(t.name,',') names, b.size
FROM base_rows b
INNER JOIN [table] t ON t.rank = b.rank
GROUP BY b.rank, b.tag, b.size, b.seq
ORDER BY b.Seq

See it work here:

https://dbfiddle.uk/jt7m_EpK

  • Related