Search code examples
t-sqlsql-server-2017

STRING_AGG with specific criteria


I have a dilemma, I'm able to convert the following Sample data:

declare @table table
(
    iTermID int
,   cTermDesc1 varchar(30)
,   cTermDesc2 varchar(30)
,   cTermDesc3 varchar(30)
,   cTermDesc4 varchar(30)
,   cTermDesc5 varchar(30)
,   cTermDesc6 varchar(30)
,   cTermDesc7 varchar(30)
)

insert into @table
(
    iTermID
,   cTermDesc1
,   cTermDesc2
,   cTermDesc3
,   cTermDesc4
,   cTermDesc5
,   cTermDesc6
,   cTermDesc7
)
values
    (1,'Current','30 Days','60 Days','90 Days','120 Days','150 Days','180 Days')
,   (3,'Current','30 Days','60 Days','90 Days','120 Days','150 Days','180 Days')
,   (4,'Current','7 Days','14 Days','21 Days','28 Days','35 Days','42 Days')
,   (5,'Current','14 Days','28 Days','42 Days','56 Days','70 Days','84 Days');

with this script:

with cte as (
select
cTermDesc1+','+cTermDesc2+','+cTermDesc3+','+cTermDesc4+','+cTermDesc5+','+cTermDesc6+','+cTermDesc7    term
from    @table
)
,   data as (
select  distinct
Value
from    cte
outer apply string_split(term,',')
)
,   retur as (
select
Value
,
case Value
when 'Current'  then 1
when '7 Days'   then 2
when '14 Days'  then 3
when '21 Days'  then 4
when '28 Days'  then 5
when '35 Days'  then 6
when '42 Days'  then 7
when '56 Days'  then 8
when '70 Days'  then 9
when '84 Days'  then 10
when '30 Days'  then 11
when '60 Days'  then 12
when '90 Days'  then 13
when '120 Days' then 14
when '150 Days' then 15
when '180 Days' then 16
else 17 end sort
from    data
)

select
string_agg(quotename(Value),',') within group (order by sort desc)
from    retur

to look like this (expected results) (fiddle):

[180 Days],[150 Days],[120 Days],[90 Days],[60 Days],[30 Days],[84 Days],[70 Days],[56 Days],[42 Days],[35 Days],[28 Days],[21 Days],[14 Days],[7 Days],[Current]

Expected Results

I require to get the same results as my expected results above, but without using a CASE.

I tried altering my CTE to this:

with cte as (
select
'1-'+cTermDesc1+','+'2-'+cTermDesc2+','+'3-'+cTermDesc3+','+'4-'+cTermDesc4+','+'5-'+cTermDesc5+','+'6-'+cTermDesc6+','+'7-'+cTermDesc7 term
from    @table
)
,   data as (
select  distinct
Value
from    cte
outer apply string_split(term,',')
)
,   retur as (
select
    substring(Value,charindex('-',Value,1)+1,20)    Value
,   left(Value,1) sort
from    data
)

select
string_agg(quotename(Value),',') within group (order by sort desc)
from    retur

But now I don't get the same order as my expected results above.

Results:

[180 Days],[42 Days],[84 Days],[150 Days],[35 Days],[70 Days],[120 Days],[28 Days],[56 Days],[21 Days],[42 Days],[90 Days],[14 Days],[28 Days],[60 Days],[14 Days],[30 Days],[7 Days],[Current]

Incorrect Results

fiddle

Is there maybe an easier way I can achieve this?

PLEASE NOTE THAT I ONLY NEED ONE "CURRENT" RETURNED

EDIT

Here is my sample data with the Code included:

declare @table table
(
    iTermID int
,   Code        varchar(30)
,   cTermDesc1  varchar(30)
,   cTermDesc2  varchar(30)
,   cTermDesc3  varchar(30)
,   cTermDesc4  varchar(30)
,   cTermDesc5  varchar(30)
,   cTermDesc6  varchar(30)
,   cTermDesc7  varchar(30)
)

insert into @table
(
    iTermID
,   Code
,   cTermDesc1
,   cTermDesc2
,   cTermDesc3
,   cTermDesc4
,   cTermDesc5
,   cTermDesc6
,   cTermDesc7
)
values
    (1,'MNTH-INV','Current','30 Days','60 Days','90 Days','120 Days','150 Days','180 Days')
,   (3,'MNTH-STM','Current','30 Days','60 Days','90 Days','120 Days','150 Days','180 Days')
,   (4,'7 Days','Current','7 Days','14 Days','21 Days','28 Days','35 Days','42 Days')
,   (5,'14 Days','Current','14 Days','28 Days','42 Days','56 Days','70 Days','84 Days')

Solution

  • Here's a generic version that will always put the values from MNTH-INV and MNTH-STM in sequence before the values for 7 days and 14 days. It gives the correct sequence of values for the inputs in the question.

    It's hard to be sure whether this is correct in all circumstances, since the rules for defining the order aren't explicitly given in the question/comments.

    (All of the logic here has been written without CASE since when I was working on it I hadn't seen the comment that explains why the OP wants to avoid CASE)

    ;WITH grpCTE
    AS
    (
        SELECT  v.grp,
                t.cTermDesc1,t.cTermDesc2,t.cTermDesc3,t.cTermDesc4,t.cTermDesc5,t.cTermDesc6,t.cTermDesc7
        FROM    @table AS t
        JOIN    (VALUES (1,1),(3,1), (4,2), (5,2)) AS v(iTermID, grp)
        ON      v.iTermID = t.iTermID
    ) 
    ,valuesCTE
    AS
    (
    
        SELECT grp, cTermDesc1 AS dayCount FROM grpCTE
        UNION
        SELECT grp, cTermDesc2 AS dayCount FROM grpCTE
        UNION                              
        SELECT grp, cTermDesc3 AS dayCount FROM grpCTE
        UNION                              
        SELECT grp, cTermDesc4 AS dayCount FROM grpCTE
        UNION                              
        SELECT grp, cTermDesc5 AS dayCount FROM grpCTE
        UNION                              
        SELECT grp, cTermDesc6 AS dayCount FROM grpCTE
        UNION                              
        SELECT grp, cTermDesc7 AS dayCount FROM grpCTE
    )
    ,aggCTE
    AS
    (
        SELECT STRING_AGG(QUOTENAME(dayCount),',') WITHIN GROUP (ORDER BY grp, TRY_CAST(LEFT(dayCount,3) AS INT) desc) AS retur
        FROM    valuesCTE
        WHERE TRY_CAST(LEFT(dayCount,3) AS INT) IS NOT NULL
    )
    SELECT CONCAT(retur, ',[' + v.dayCount + ']') AS retur
    FROM aggCTE AS a
    OUTER 
    APPLY (SELECT TOP(1) dayCount FROM  valuesCTE WHERE dayCount = 'Current') AS v