SQL - 用于将用分号(;)分隔的单列字符串转换为多列的动态SQL查询

时间:2015-12-31 05:48:38

标签: sql sql-server sql-server-2008 sql-server-2008-r2

动态SQL查询,将以分号(;)分隔的单列字符串转换为多列。

ID           Rollcode            Rack
AA0001   99203; S9088        1350; 1350
ABB0001  99203; S9088        4123; 4123
ADA000   99203; S9088        530; 530
ADM000   99202; S9088;J2308  4516; 4516
ABD000   99203; S9088        3025; 3025

Desired Result should be:

ID    Rollcode1 Rollcode2 Rollcode3 Rack1   Rack2
AA0001  99203   S9088      Null     1350    1350
ABB0001 99203   S9088      Null     4123    4123
ADA000  99203   S9088      Null     530     530
ADM000  99202   S9088     J2308     4516    4516
ABD000  99203   S9088      Null     3025    3025

我已尝试过1列,即(滚动码)我们可以将其用于其他列

enter code here DECLARE @pivot varchar(8000) 
DECLARE @select varchar(8000) 
SELECT @pivot=coalesce(@pivot+',','')'[Rollcode'+cast`
(number+1 as varchar(10))+']'
FROM master..spt_values where type='p' and 
number<=(SELECT max(len(Rollcode)-len(replace
(Rollcode,';',''))) FROM tablename) 
SELECT @select=' select p.* 
from ( select  ID,substring(Rollcode, start+2, endPos-Start-2) as token, 
''Rollcode''+cast(row_number() over(partition by ID order by start) as 
varchar(10)) as n 
 from ( select ID, Rollcode, n as start
            , charindex('';'',Rollcode,n+2) endPos 
  from (select number as n from master..spt_values where type=''p'') num 
  cross join  
  ( 
  select  
  ID, '';'' + Rollcode +'';'' as Rollcode  
  from  tablename 
  ) m 
  where n < len(Rollcode)-1 
  and substring(Rollcode,n+1,1) = '';'') as Rollcode 
  ) pvt 
  Pivot ( max(token)for n in ('+@pivot+'))p'
  EXEC(@select)  

3 个答案:

答案 0 :(得分:1)

试试这个

declare @roll table (ID nvarchar(20),RollCode nvarchar(100),Rack Nvarchar(100))

insert into @roll
              select 'AA0001'   ,'99203; S9088'         ,'1350; 1350'
union all select 'ABB0001'  ,'99203; S9088'         ,'4123; 4123'
union all select 'ADA000'   ,'99203; S9088'         ,'530; 530'
union all select 'ADM000'   ,'99202; S9088;J2308'   ,'4516; 4516'
union all select 'ABD000'   ,'99203; S9088'         ,'3025; 3025'


 select ID, 
    PARSENAME(replace(RollCode,';','.'),1) as 'RollCode3', 
    PARSENAME(replace(RollCode,';','.'),2) as 'RollCode2',
    PARSENAME(replace(RollCode,';','.'),3) as 'RollCode1',
    PARSENAME(replace(Rack,';','.'),1) as 'Rack2',
    PARSENAME(replace(Rack,';','.'),2) as 'Rack1'

from @roll

答案 1 :(得分:1)

首先,您需要一种方法来分割用冒号分隔的值。有各种分割函数(一些用纯T-SQL编写,一些用.net编写)。

我正在使用CLR函数来解决您的问题。它的名称为[dbo].[fn_Utils_RegexSplitWithOrder],并按正则表达式模式拆分值。这是.net代码:

[SqlFunction(FillRowMethodName = "FillRowForSplitWithOrder")]
public static IEnumerable SplitWithOrder(SqlString sqlInput, SqlString sqlPattern)
{
    string[] substrings;
    List<Tuple<SqlInt64, SqlString>> values = new List<Tuple<SqlInt64, SqlString>>(); ;

    if (sqlInput.IsNull || sqlPattern.IsNull)
    {
        substrings = new string[0];
    }
    else
    {
        substrings = Regex.Split(sqlInput.Value, sqlPattern.Value);
    }

    for (int index = 0; index < substrings.Length; index++)
    {
        values.Add(new Tuple<SqlInt64, SqlString>(new SqlInt64(index), new SqlString(substrings[index])));
    }

    return values;
}

您可以查看此answer以了解如何部署CLR个功能。如果需要,可以实现纯T-SQL函数进行拆分。

现在,我们有一个分割数据的功能(我们需要存储它):

DECLARE @DataSource TABLE
(
    [ID] VARCHAR(12)
   ,[RollCode] VARCHAR(MAX)
   ,[Rack] VARCHAR(MAX)
);

INSERT INTO @DataSource ([ID], [RollCode], [Rack])
VALUES ('AA0001', '99203; S9088', '1350; 1350')
      ,('ABB0001', '99203; S9088', '4123; 4123')
      ,('ADA000', '99203; S9088', '530; 530')
      ,('ADM000', '99202; S9088;J2308', '4516; 4516')
      ,('ABD000', '99203; S9088', '3025; 3025');

IF OBJECT_ID('tempdb..#TempDataSource') IS NOT NULL
BEGIN
    DROP TABLE #TempDataSource;
END;

CREATE TABLE #TempDataSource
(
    [ID] VARCHAR(12)
   ,[Type] VARCHAR(8)
   ,[Index] INT
   ,[Value] VARCHAR(12)
);

INSERT INTO #TempDataSource ([ID], [Type], [Index], [Value])
SELECT DS.[ID]
      ,'Rollcode'
      ,RS.[index]
      ,LTRIM(RTRIM(RS.[value]))
FROM @DataSource DS
CROSS APPLY [dbo].[fn_Utils_RegexSplitWithOrder] (DS.[RollCode], ';') RS
UNION ALL
SELECT DS.[ID]
      ,'Rack'
      ,RS.[index]
      ,LTRIM(RTRIM(RS.[value]))
FROM @DataSource DS
CROSS APPLY [dbo].[fn_Utils_RegexSplitWithOrder] (DS.[Rack], ';') RS

在表中你有:

enter image description here

我们现在已准备好动态PIVOT。请注意,我按照您的意愿排列列,但您可以更改计算[ColumnOrder]的方式:

DECLARE @DynamicTSQLStatement NVARCHAR(MAX);
DECLARE @DynamicPIVOTColumns NVARCHAR(MAX);

SELECT @DynamicPIVOTColumns = STUFF
(
    (
        SELECT ',[' + [ColumnName] + ']'
        FROM
        (
            SELECT DISTINCT CONCAT([Type], [Index])
                  ,[Index] + IIF([Type] = 'RACK', 100000, 0)
            FROM #TempDataSource
        ) DS ([ColumnName], [ColumnOrder])
        ORDER BY [ColumnOrder]
        FOR XML PATH('')
    )
   ,1
   ,1
   ,''
);

SET @DynamicTSQLStatement =
'
SELECT *
FROM
(
    SELECT [ID]
          ,CONCAT([Type], [Index])
          ,[Value]
    FROM #TempDataSource
) DS ([ID], [ColumnName], [Value])
PIVOT
(
    MAX([Value]) FOR [ColumnName] IN (' + @DynamicPIVOTColumns + ')
) PVT
';

EXEC  sp_executesql @DynamicTSQLStatement;

最后一部分给你:

enter image description here

看起来很复杂,但事实并非如此。这是一项简单的动态PIVOT任务。只需实现分割值的功能,你就可以了。

答案 2 :(得分:0)

提示试试这个,

SELECT LEFT(RoleCode,CHARINDEX(';',RoleCode)-1) AS RoleCode1,
       RIGHT(RoleCode,CHARINDEX(';',RoleCode)) AS RoleCode2
FROM tableName