【问题标题】:Split function in SQL Server 2008SQL Server 2008 中的拆分功能
【发布时间】:2013-02-16 14:10:12
【问题描述】:

我有Table1 有这样的列:

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

Table2,我有一个类似的专栏

+------------+
|Databasename|
+------------+
|1,3         |
+------------+
|2           |
+------------+
|1,2         |
+------------+

我的输出应该是:

+------------+
|Databasename|
+------------+
|MSSQL,Oracle|
+------------+
|MySQL       |
+------------+
|MSSQL,MYSQL |
+------------+

我怎么得到这个,我需要查询这个..

【问题讨论】:

  • 在数据库中存储逗号分隔的列表不是一个好习惯。对此的查询将非常可怕。
  • 搜索Split Function in SQL你会得到很多。
  • 为什么人们一直这样做?这与使用姓氏作为唯一键一样基本错误。

标签: sql sql-server sql-server-2008 tsql


【解决方案1】:

首先,您最好的解决方案是不要将数据存储在数据库中以逗号分隔的列表中。您应该考虑修复表结构。

如果您无法更改表结构,则需要将列表中的数据拆分为行以分配正确的名称。拆分数据后,您可以将数据连接回列表中。

您可以在网上找到许多不同的split 函数,但这里是我通常使用的一个版本:

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;

为了获得您的结果,我将首先应用 split 函数和 row_number(),因为我没有看到与每一行关联的唯一键。如果您在每一行上都有一个唯一键,那么您将不需要 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

此查询将您以逗号分隔的列表分成以下内容:

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

一旦您的多行数据具有正确的name,您就可以使用STUFF()FOR XML PATH 将其连接到列表中。您的完整查询将与此类似:

;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;

SQL Fiddle with Demo

完整查询的结果是:

|   DATABASENAME |
------------------
|  MSSQL, Oracle |
|          MySQl |
|   MSSQL, MySQl |

【讨论】:

    【解决方案2】:

    您要求使用拆分功能,但您不必拆分您的值以获得您想要的结果。

    此查询使用for xml 技巧连接值,在相关子查询中构建逗号分隔的名称列表。它使用like 来确定从Table1 中为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

    【讨论】:

      【解决方案3】:

      没有拆分,也没有 XML Path,但达到了正确的结果。

      ;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)
      

      【讨论】:

        【解决方案4】:
        --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
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多