Split function in SQL Server 2008

前端 未结 4 1437
孤城傲影
孤城傲影 2020-11-28 15:29

I have Table1 with columns like this:

+--+------+
|ID|Name  |
+--+------+
|1 |MSSQL |
+--+------+
|2 |MySQl |
+--+------+
|3 |Oracle|
+--+------         


        
相关标签:
4条回答
  • 2020-11-28 15:57
    --Here it goes:
    
    ----------------
    -- FieldCount --
    ----------------
    CREATE FUNCTION [dbo].[FieldCount](@S VARCHAR(8000), @Separator VARCHAR(10))
      RETURNS INT
    AS
    
    BEGIN
    
      /*
      @Author: Leonardo Augusto Rezende Santos
      @Contact: http://www.linkedin.com/pub/leonardo-santos/0/2b1/890
      */
    
      DECLARE @Ptr INT, @p INT, @LenS INT, @LenSep INT, @Result INT
    
      IF @Separator = ' ' 
        BEGIN
          SET @S = REPLACE(@S, ' ', '|-|')
          SET @Separator = '|-|'
        END
    
      WHILE CHARINDEX(@Separator + @Separator, @S) > 0
        SET @S = Replace(@S, @Separator + @Separator, @Separator + '_-_' + @Separator)
      IF @S <> ''
        SET @Result = 1
      ELSE
        BEGIN
          SET @Result = 0
          RETURN(@Result)
        END
      SET @Ptr = 0
      SET @LenS = LEN(@S)
      SET @LenSep = LEN(@Separator)
      SET @p = CHARINDEX(@Separator, @S)
      WHILE @p > 0
        BEGIN
          SET @Result = @Result + 1
          SET @Ptr = @Ptr + @p + @LenSep
          SET @p = CHARINDEX(@Separator, SUBSTRING(@S, @Ptr, @LenS - @Ptr + 1))
        END
    
      RETURN(@Result)
    
    END
    
    --------------
    -- GetField --
    --------------
    CREATE FUNCTION [dbo].[GetField](@S VARCHAR(8000), @Separator VARCHAR(10), @Field INT)
      RETURNS VARCHAR(8000)
    AS
    
    BEGIN
    
      /*
      @Author: Leonardo Augusto Rezende Santos
      @Contact: http://www.linkedin.com/pub/leonardo-santos/0/2b1/890
      */
    
      DECLARE @Ptr INT, @p INT, @LenS INT, @LenSep INT, @Fld INT, @Result VARCHAR(8000)
    
      IF @Separator = ' ' 
        BEGIN
          SET @S = REPLACE(@S, ' ', '|-|')
          SET @Separator = '|-|'
        END
    
      IF @Field > dbo.FieldCount(@S, @Separator)
        BEGIN
          SET @Result = ''
          RETURN(@Result)
        END
      SET @Fld = 1
      SET @Ptr = 1
      SET @LenS = LEN(@S)
      SET @LenSep = LEN(@Separator)
      SET @p = CHARINDEX(@Separator, @S)
      WHILE (@p > 0) and (@Fld < @Field)
        BEGIN
          SET @Fld = @Fld + 1
          SET @Ptr = @Ptr + @p + @LenSep - 1
          SET @p = CHARINDEX(@Separator, SUBSTRING(@S, @Ptr, @LenS - @Ptr + 1))
        END
      IF (@p = 0) and (@Fld = @Field)
        SET @p = @LenS - @Ptr + 2
      SET @Result = SUBSTRING(@S, @Ptr, @p - 1)
    
      RETURN(@Result)
    
    END
    
    /* USAGE*/
    
    select dbo.FieldCount('A1 A2 A3 A4 A5', ' ')
    
    --It will return 5
    
    select dbo.GetField('A1 A2 A3 A4 A5', ' ', 3)
    
    --It will return 'A3'
    
    select dbo.GetField('A1/A2/A3/A4/A5', '/', 3)
    
    --It will return 'A3'
    
    --Hope it works for you.
    
    --Leonardo Augusto
    
    0 讨论(0)
  • 2020-11-28 16:01

    No splitting, also no XML Path, but achieves the right result.

    ;with cte as (
        select *, cast(null as varchar(1024)) as str, cast(0 as int) as ID
        from Table2
    
        union all
    
        select DatabaseName, (case when DatabaseName like cast(t.ID as varchar(32)) + ',%' 
                                        or DatabaseName like '%,' + cast(t.ID as varchar(32)) + ',%'
                                        or DatabaseName like '%,' + cast(t.ID as varchar(32)) 
                                        or DatabaseName = cast(t.ID as varchar(32)) then cast(isnull(str, '') + ',' + t.Name as varchar(1024)) else str end), cte.ID + 1 as ID
        from cte
        inner join Table1 t on cte.ID + 1 = t.ID
    )
    select DatabaseName, (case when str like ',%' then substring(str, 2, len(str)) else null end) as str
    from cte c
    where ID = (select max(ID) from cte where DatabaseName = c.DatabaseName)
    
    0 讨论(0)
  • 2020-11-28 16:03

    You are asking for a split function but you do not have to split your values to get the result you want.

    This query builds the comma separated names list in a correlated subquery using the for xml trick to concatenate values. It uses like to figure out what values to use from Table1 for each row in Table2.

    select (
           select ', '+T1.Name
           from Table1 as T1
           where ','+T2.Databasename+',' like '%,'+cast(T1.ID as varchar(10))+',%'
           for xml path(''), type
           ).value('substring(text()[1], 3)', 'varchar(max)') as Databasenames
    from Table2 as T2
    

    SQL Fiddle

    0 讨论(0)
  • 2020-11-28 16:10

    First, your best solution is to not store data in a comma-separated list in your database. You should consider fixing the table structure.

    If you cannot alter the table structure, then you will need to split the data in the list to rows to assign the correct name. Once the data is split then you can concatenate the data back into the list.

    There are many different split function that you can find online but here is a version that I typically use:

    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;
    

    To get your result, I would start by applying the split function and a row_number() since I do not see a unique key associated with each row. If you have a unique key on each row then you will not need the row_number():

    ;with cte as
    (
      select rn, name, id
      from
      (
        select row_number() over(order by (select 1)) rn,
          databasename
        from table2
      ) t2
      cross apply dbo.split(t2.databasename, ',') i
      inner join table1 t1
        on i.items = t1.id
    ) 
    select *
    from cte
    

    This query breaks your comma-separated list into the following:

    | RN |   NAME | ID |
    --------------------
    |  1 |  MSSQL |  1 |
    |  1 | Oracle |  3 |
    |  2 |  MySQl |  2 |
    |  3 |  MSSQL |  1 |
    |  3 |  MySQl |  2 |
    

    Once you have the data in multiple rows with the correct name, then you can use STUFF() and FOR XML PATH to concatenate it into the list. You full query would be similar to this:

    ;with cte as
    (
      select rn, name, id
      from
      (
        select row_number() over(order by (select 1)) rn,
          databasename
        from table2
      ) t2
      cross apply dbo.split(t2.databasename, ',') i
      inner join table1 t1
        on i.items = t1.id
    ) 
    select  
      STUFF(
             (SELECT ', ' + c2.name
              FROM cte c2
              where c1.rn = c2.rn
              order by c2.id
              FOR XML PATH (''))
              , 1, 1, '') Databasename
    from cte c1
    group by c1.rn
    order by c1.rn;
    

    See SQL Fiddle with Demo.

    The result of the full query is:

    |   DATABASENAME |
    ------------------
    |  MSSQL, Oracle |
    |          MySQl |
    |   MSSQL, MySQl |
    
    0 讨论(0)
提交回复
热议问题