join comma delimited data column

后端 未结 6 1468
日久生厌
日久生厌 2020-11-27 06:30

my table1 is :

T1

col1    col2
 C1     john
 C2     alex
 C3     piers
 C4     sara

and so table 2:

T2

         


        
相关标签:
6条回答
  • 2020-11-27 07:09

    Here's a way of splitting the data without a function, then using the standard XML PATH method for getting the CSV list:

    with CTE as
    (
      select T2.col1
        , T1.col2
      from T2
        inner join T1 on charindex(',' + T1.col1 + ',', ',' + T2.col2 + ',') > 0
    )
    select T2.col1
      , col2 = stuff(
          (
            select ',' + CTE.col2
            from CTE
            where T2.col1 = CTE.col1
            for xml path('')
          )
          , 1
          , 1
          , ''
        )
    from T2
    

    SQL Fiddle with demo.

    As has been mentioned elsewhere in this question it is hard to query this sort of denormalised data in any sort of efficient manner, so your first priority should be to investigate updating the table structure, but this will at least allow to get the results you require.

    0 讨论(0)
  • 2020-11-27 07:09

    If you wanted to do this task in oracle we can use listagg and can accomplish this easily.

    A possible equivalent available in SQL Server for listagg is Stuff

    So using stuff you can try with following query:

    SELECT T2.Col1,
           Stuff((SELECT ',' + CAST(T1.Col2 AS VARCHAR(100))
                   FROM T1
                  WHERE T2.Col2 LIKE T1.Col1
                    FOR Xml Path('')),
                 1,
                 1,
                 '')
      FROM T2
    
    0 讨论(0)
  • 2020-11-27 07:09

    First write a table value function for split col2 on tbl2.

    CREATE FUNCTION [dbo].[Split](@String varchar(100), @Delimiter char(1))       
    returns @temptable TABLE (items VARCHAR(5))       
    as       
    begin       
        declare @idx int       
        declare @slice VARCHAR(5)
    
        select @idx = 1       
            if len(@String)<1 or @String is null  return       
    
        while @idx!= 0       
        begin       
            set @idx = charindex(@Delimiter,@String)       
            if @idx!=0       
                set @slice = left(@String,@idx - 1)       
            else       
                set @slice = @String       
    
            if(len(@slice)>0)  
                insert into @temptable(Items) values(@slice)       
    
            set @String = right(@String,len(@String) - @idx)       
            if len(@String) = 0 break       
        end   
    return       
    end  
    
    Go
    
    ;WITH    SplitList
              AS ( SELECT   T2.Col1 ,
                            T1.Col2
                   FROM     T2
                            CROSS APPLY dbo.Split(T2.Col2, ',') S
                            INNER JOIN T1 ON T1.Col1 = S.Items
                 )
        SELECT  T2.Col1 ,
                STUFF(( SELECT  ', ' + SplitList.Col2
                        FROM    SplitList
                        WHERE   SplitList.Col1 = T2.Col1
                      FOR
                        XML PATH('')
                      ), 1, 2, '')
        FROM    T2       
    
    0 讨论(0)
  • 2020-11-27 07:14

    Ideally, your best solution would be to normalize Table2 so you are not storing a comma separated list.

    Once you have this data normalized then you can easily query the data. The new table structure could be similar to this:

    CREATE TABLE T1
    (
      [col1] varchar(2), 
      [col2] varchar(5),
      constraint pk1_t1 primary key (col1)
    );
    
    INSERT INTO T1
        ([col1], [col2])
    VALUES
        ('C1', 'john'),
        ('C2', 'alex'),
        ('C3', 'piers'),
        ('C4', 'sara')
    ;
    
    CREATE TABLE T2
    (
      [col1] varchar(2), 
      [col2] varchar(2),
      constraint pk1_t2 primary key (col1, col2),
      constraint fk1_col2 foreign key (col2) references t1 (col1)
    );
    
    INSERT INTO T2
        ([col1], [col2])
    VALUES
        ('R1', 'C1'),
        ('R1', 'C2'),
        ('R1', 'C4'),
        ('R2', 'C3'),
        ('R2', 'C4'),
        ('R3', 'C1'),
        ('R3', 'C4')
    ;
    

    Normalizing the tables would make it much easier for you to query the data by joining the tables:

    select t2.col1, t1.col2
    from t2
    inner join t1
      on t2.col2 = t1.col1
    

    See Demo

    Then if you wanted to display the data as a comma-separated list, you could use FOR XML PATH and STUFF:

    select distinct t2.col1, 
      STUFF(
             (SELECT distinct ', ' + t1.col2
              FROM t1
              inner join t2 t
                on t1.col1 = t.col2
              where t2.col1 = t.col1
              FOR XML PATH ('')), 1, 1, '') col2
    from t2;
    

    See Demo.

    If you are not able to normalize the data, then there are several things that you can do.

    First, you could create a split function that will convert the data stored in the list into rows that can be joined on. The split function would be similar to this:

    CREATE FUNCTION [dbo].[Split](@String varchar(MAX), @Delimiter char(1))       
    returns @temptable TABLE (items varchar(MAX))       
    as       
    begin      
        declare @idx int       
        declare @slice varchar(8000)       
    
        select @idx = 1       
            if len(@String)<1 or @String is null  return       
    
        while @idx!= 0       
        begin       
            set @idx = charindex(@Delimiter,@String)       
            if @idx!=0       
                set @slice = left(@String,@idx - 1)       
            else       
                set @slice = @String       
    
            if(len(@slice)>0)  
                insert into @temptable(Items) values(@slice)       
    
            set @String = right(@String,len(@String) - @idx)       
            if len(@String) = 0 break       
        end   
    return 
    end;
    

    When you use the split, function you can either leave the data in the multiple rows or you can concatenate the values back into a comma separated list:

    ;with cte as
    (
      select c.col1, t1.col2
      from t1
      inner join 
      (
        select t2.col1, i.items col2
        from t2
        cross apply dbo.split(t2.col2, ',') i
      ) c
        on t1.col1 = c.col2
    ) 
    select distinct c.col1, 
      STUFF(
             (SELECT distinct ', ' + c1.col2
              FROM cte c1
              where c.col1 = c1.col1
              FOR XML PATH ('')), 1, 1, '') col2
    from cte c
    

    See Demo.

    A final way that you could get the result is by applying FOR XML PATH directly.

    select col1, 
    (
      select ', '+t1.col2
      from t1
      where ','+t2.col2+',' like '%,'+cast(t1.col1 as varchar(10))+',%'
      for xml path(''), type
    ).value('substring(text()[1], 3)', 'varchar(max)') as col2
    from t2;
    

    See SQL Fiddle with Demo

    0 讨论(0)
  • 2020-11-27 07:16

    This task cannot be solved with standard SQL. In Oracle I would write a stored function (PL/SQL) to parse the Name-ID-string (T2 col2) and resolve the names. Don't know if that's possible in Transact-SQL, but it's glorious inefficient.

    T2 is a badly designed, not normalized table. That's the problem. If you would normalize it, so that you have one line per Name-ID (col 2 in T2), you can get the list of names with a simple join of the two tables. To generate the desired output format (comma delimited) you need to write something else than SQL - maybe a stored procedure or something else that iterates over the resultset.

    0 讨论(0)
  • 2020-11-27 07:21

    If you are like me and you are a stickler for CTE's especially recursive CTE's as supposed to STUFF and XML Path:

    DECLARE @T1 TABLE (
        col1 CHAR(2),
        col2 VARCHAR(10)
    )
    INSERT INTO @T1
    VALUES  ('C1', 'john'),
            ('C2', 'alex'),
            ('C3', 'piers'),
            ('C4', 'sara');
    
    DECLARE @T2 TABLE (
        col1 CHAR(2),
        col2 CHAR(100)
    )
    INSERT INTO @T2
    VALUES  ('R1', 'C1,C2,C4'),
            ('R2', 'C3,C4'),
            ('R3', 'C1,C4');
    
    WITH T2Sorted AS (
        SELECT col1, col2, RN = ROW_NUMBER() OVER (ORDER BY col1) FROM @T2
    ), CTERecursionOnT2 AS (
        SELECT RN, col1, col2, 0 AS PrevCharIndex, CHARINDEX(',', col2, 1) AS NextCharIndex FROM T2Sorted
        UNION ALL
        SELECT a.RN, a.col1, a.col2, b.NextCharIndex, CHARINDEX(',', a.col2, b.NextCharIndex + 1) 
        FROM T2Sorted a
        JOIN CTERecursionOnT2 b ON a.RN = b.RN
        WHERE b.NextCharIndex > 0
    ), CTEIndividualCol2Items AS (
        SELECT *, SUBSTRING(col2, PrevCharIndex + 1, CASE WHEN NextCharIndex = 0 THEN LEN(col2) ELSE NextCharIndex - 1 END - PrevCharIndex) AS itemCol2 
        FROM CTERecursionOnT2
    ), CTELookupT1 AS (
        SELECT a.col1, b.col2, RN = ROW_NUMBER() OVER (PARTITION BY a.col1 ORDER BY a.PrevCharIndex)
        FROM CTEIndividualCol2Items a
        JOIN @T1 b ON a.itemCol2 = b.col1
    ), CTERecursionOnLookupT1 AS (
        SELECT col1, CAST(col2 AS VARCHAR(MAX)) AS col2, RN
        FROM CTELookupT1 
        WHERE RN = 1
    
        UNION ALL
    
        SELECT a.col1, b.col2 + ',' + a.col2, a.RN
        FROM CTELookupT1 a
        JOIN CTERecursionOnLookupT1 b ON a.col1 = b.col1 AND a.RN = b.RN + 1
    ), CTEFinal AS (
        SELECT *, RNDesc = ROW_NUMBER() OVER (PARTITION BY col1 ORDER BY RN DESC)
        FROM CTERecursionOnLookupT1
    )
    SELECT col1, col2
    FROM CTEFinal
    WHERE RNDesc = 1
    ORDER BY col1
    

    Obviously you could break up the first recursion part into separate functions as the already agreed solution has suggested i.e. CTERecursionOnT2 and thus CTEIndividualCol2Items can be your alternative Split function (I would include the order id as well), and thus:

    ;WITH CTEIndividualCol2Items AS (
        SELECT a.col1, b.value as itemCol2, b.id AS PrevCharIndex
        FROM @T2 a
        CROSS APPLY (
            SELECT id, items FROM dbo.Split(a.col2, ',')
        ) b
    ) ...
    

    and you split function:

    CREATE FUNCTION dbo.Split(@String varchar(100), @Delimiter char(1))
    RETURNS TABLE
    AS
    RETURN 
    (
        WITH CTERecursion AS (
            SELECT id = 1, PrevCharIndex = 0, NextCharIndex = CHARINDEX(@Delimiter, @String, 1)
            UNION ALL
            SELECT id + 1, NextCharIndex, CHARINDEX(@Delimiter, @String, NextCharIndex + 1) FROM CTERecursion WHERE NextCharIndex > 0
        )
        SELECT Id, items = SUBSTRING(@String, PrevCharindex + 1, (CASE WHEN NextCharIndex = 0 THEN LEN(@String) ELSE NextCharIndex - 1 END) - PrevCharIndex)
        FROM CTERecursion
        WHERE @String > ''
    )
    
    0 讨论(0)
提交回复
热议问题