将文本从列标准化为表格的最有效方法是什么?

时间:2018-04-27 13:24:38

标签: sql sql-server tsql split

在T-SQL中,我有一个列中包含一些文本,其格式如下:

[Key1:Value1:Value2:Value3:Value4:Value5]
[Key2:Value1:Value2:Value3:Value4:Value5]
[Key3:Value1:Value2:Value3:Value4:Value5]

其中可以有任意数量的括号集,但通常在3到6之间。我正在寻找一种方法来快速将它们格式化为临时表或表变量,以便我可以报告数据。例如,我希望表格格式为:

|Key|Column 1|Column 2|Column 3|Column 4|Column 5|  
|Key 1|Value 1|Value 2|Value 3|Value 4|Value 5|  
|Key 2|Value 1|Value 2|Value 3|Value 4|Value 5|  
|Key 3|Value 1|Value 2|Value 3|Value 4|Value 5|  

我知道这是推动SQL的极限,应该通过修改应用程序来处理,但我希望我现在可以用T-SQL做一些聪明的事情。

2 个答案:

答案 0 :(得分:7)

如果您有最大列数,则在CROSS APPLY中使用一点XML。

如果不知道,你必须去DYNAMIC。

示例

Declare @YourTable Table ([ID] varchar(50),[SomeCol] varchar(50))
Insert Into @YourTable Values 
 (1,'[Key1:Value1:Value2:Value3:Value4:Value5]')
,(2,'[Key2:Value1:Value2:Value3:Value4:Value5]')
,(3,'[Key3:Value1:Value2:Value3:Value4:Value5]')

Select A.ID
      ,B.*
 From  @YourTable A
 Cross Apply (
                Select Pos1 = ltrim(rtrim(xDim.value('/x[1]','varchar(max)')))
                      ,Pos2 = ltrim(rtrim(xDim.value('/x[2]','varchar(max)')))
                      ,Pos3 = ltrim(rtrim(xDim.value('/x[3]','varchar(max)')))
                      ,Pos4 = ltrim(rtrim(xDim.value('/x[4]','varchar(max)')))
                      ,Pos5 = ltrim(rtrim(xDim.value('/x[5]','varchar(max)')))
                      ,Pos6 = ltrim(rtrim(xDim.value('/x[6]','varchar(max)')))
                      ,Pos7 = ltrim(rtrim(xDim.value('/x[7]','varchar(max)')))
                      ,Pos8 = ltrim(rtrim(xDim.value('/x[8]','varchar(max)')))
                      ,Pos9 = ltrim(rtrim(xDim.value('/x[9]','varchar(max)')))
                From  (Select Cast('<x>' + replace(replace(replace(SomeCol,'[',''),']',''),':','</x><x>')+'</x>' as xml) as xDim) as A 
             ) B

<强>返回

ID  Pos1    Pos2    Pos3    Pos4    Pos5    Pos6    Pos7    Pos8    Pos9
1   Key1    Value1  Value2  Value3  Value4  Value5  NULL    NULL    NULL
2   Key2    Value1  Value2  Value3  Value4  Value5  NULL    NULL    NULL
3   Key3    Value1  Value2  Value3  Value4  Value5  NULL    NULL    NULL
  

修改

我应该补充一点,ltrim(rtrim(...))是可选的,varchar(max)是我的示范默认值。

  

编辑 - 一个用CRLF分隔的字符串

Declare @S varchar(max)='
[Key1:Value1:Value2:Value3:Value4:Value5]
[Key2:Value1:Value2:Value3:Value4:Value5]
[Key3:Value1:Value2:Value3:Value4:Value5]
'

Select B.*
 From  (
        Select RetSeq = Row_Number() over (Order By (Select null))
              ,RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
        From  (Select x = Cast('<x>' + replace(@S,char(13)+char(10),'</x><x>')+'</x>' as xml).query('.')) as A 
        Cross Apply x.nodes('x') AS B(i)
       ) A
 Cross Apply (
               Select  Pos1 = ltrim(rtrim(xDim.value('/x[1]','varchar(max)')))
                      ,Pos2 = ltrim(rtrim(xDim.value('/x[2]','varchar(max)')))
                      ,Pos3 = ltrim(rtrim(xDim.value('/x[3]','varchar(max)')))
                      ,Pos4 = ltrim(rtrim(xDim.value('/x[4]','varchar(max)')))
                      ,Pos5 = ltrim(rtrim(xDim.value('/x[5]','varchar(max)')))
                      ,Pos6 = ltrim(rtrim(xDim.value('/x[6]','varchar(max)')))
                      ,Pos7 = ltrim(rtrim(xDim.value('/x[7]','varchar(max)')))
                      ,Pos8 = ltrim(rtrim(xDim.value('/x[8]','varchar(max)')))
                      ,Pos9 = ltrim(rtrim(xDim.value('/x[9]','varchar(max)')))
                From  (Select Cast('<x>' + replace(replace(replace(RetVal,'[',''),']',''),':','</x><x>')+'</x>' as xml) as xDim) as A 
       ) B
 Where A.RetVal is not null

答案 1 :(得分:1)

当您知道最大列数时,分割字符串的最快方法是使用Cascading CROSS APPLY技术。让我们说你知道他们的字符串中不会超过10个项目。你可以这样做:

DECLARE @string varchar(1000) = '[Key1:Value1:Value2:Value3:Value4:Value5]'

SELECT 
  [key] = SUBSTRING(t.string,1,d1.d-1),
  col1  = SUBSTRING(t.string,d1.d+1,d2.d-d1.d-1),
  col2  = SUBSTRING(t.string,d2.d+1,d3.d-d2.d-1),
  col3  = SUBSTRING(t.string,d3.d+1,d4.d-d3.d-1),
  col4  = SUBSTRING(t.string,d4.d+1,d5.d-d4.d-1),
  col5  = SUBSTRING(t.string,d5.d+1,d6.d-d5.d-1),
  col6  = SUBSTRING(t.string,d6.d+1,d7.d-d5.d-1),
  col7  = SUBSTRING(t.string,d7.d+1,d8.d-d5.d-1),
  col8  = SUBSTRING(t.string,d8.d+1,d9.d-d5.d-1),
  col9  = SUBSTRING(t.string,d9.d+1,d10.d-d5.d-1)
FROM (VALUES (REPLACE(REPLACE(@string,']',':'),'[',''))) t(string)
CROSS APPLY (VALUES (CHARINDEX(':',t.string)))                   d1(d)
CROSS APPLY (VALUES (NULLIF(CHARINDEX(':',t.string,d1.d+1),0)))  d2(d)
CROSS APPLY (VALUES (NULLIF(CHARINDEX(':',t.string,d2.d+1),0)))  d3(d)
CROSS APPLY (VALUES (NULLIF(CHARINDEX(':',t.string,d3.d+1),0)))  d4(d)
CROSS APPLY (VALUES (NULLIF(CHARINDEX(':',t.string,d4.d+1),0)))  d5(d)
CROSS APPLY (VALUES (NULLIF(CHARINDEX(':',t.string,d5.d+1),0)))  d6(d)
CROSS APPLY (VALUES (NULLIF(CHARINDEX(':',t.string,d6.d+1),0)))  d7(d)
CROSS APPLY (VALUES (NULLIF(CHARINDEX(':',t.string,d7.d+1),0)))  d8(d)
CROSS APPLY (VALUES (NULLIF(CHARINDEX(':',t.string,d8.d+1),0)))  d9(d)
CROSS APPLY (VALUES (NULLIF(CHARINDEX(':',t.string,d9.d+1),0)))  d10(d);

对于存储在行中的字符串的表使用此技术将是这样的:

DECLARE @table TABLE (someid int identity, somestring varchar(1000));
INSERT @table(somestring) VALUES 
('[Key1:Value1:Value2:Value3:Value4:Value5]'),
('[Key2:Value1:Value2:Value3:Value4:Value5]'),
('[Key3:Value1:Value2:Value3:Value4:Value5]'),
('[Key4:Value1:Value2:Value3:Value4:Value5:Value6:Value7:Value8]'),
('[Key5:Value1:Value2:Value3:Value4:Value5:Value6:Value7:Value8:Value9:Value10]');

SELECT * 
FROM @table s
CROSS APPLY
(
  SELECT 
    [key]  = SUBSTRING(t.string,1,d1.d-1),
    dCount = LEN(t.string)-LEN(REPLACE(t.string,':','')),
    col1   = SUBSTRING(t.string,d1.d+1,d2.d-d1.d-1),
    col2   = SUBSTRING(t.string,d2.d+1,d3.d-d2.d-1),
    col3   = SUBSTRING(t.string,d3.d+1,d4.d-d3.d-1),
    col4   = SUBSTRING(t.string,d4.d+1,d5.d-d4.d-1),
    col5   = SUBSTRING(t.string,d5.d+1,d6.d-d5.d-1),
    col6   = SUBSTRING(t.string,d6.d+1,d7.d-d6.d-1),
    col7   = SUBSTRING(t.string,d7.d+1,d8.d-d7.d-1),
    col8   = SUBSTRING(t.string,d8.d+1,d9.d-d8.d-1),
    col9   = SUBSTRING(t.string,d9.d+1,d10.d-d9.d-1)
  FROM (VALUES (REPLACE(REPLACE(s.somestring,']',':'),'[',''))) t(string)
  CROSS APPLY (VALUES (CHARINDEX(':',t.string)))                   d1(d)
  CROSS APPLY (VALUES (NULLIF(CHARINDEX(':',t.string,d1.d+1),0)))  d2(d)
  CROSS APPLY (VALUES (NULLIF(CHARINDEX(':',t.string,d2.d+1),0)))  d3(d)
  CROSS APPLY (VALUES (NULLIF(CHARINDEX(':',t.string,d3.d+1),0)))  d4(d)
  CROSS APPLY (VALUES (NULLIF(CHARINDEX(':',t.string,d4.d+1),0)))  d5(d)
  CROSS APPLY (VALUES (NULLIF(CHARINDEX(':',t.string,d5.d+1),0)))  d6(d)
  CROSS APPLY (VALUES (NULLIF(CHARINDEX(':',t.string,d6.d+1),0)))  d7(d)
  CROSS APPLY (VALUES (NULLIF(CHARINDEX(':',t.string,d7.d+1),0)))  d8(d)
  CROSS APPLY (VALUES (NULLIF(CHARINDEX(':',t.string,d8.d+1),0)))  d9(d)
  CROSS APPLY (VALUES (NULLIF(CHARINDEX(':',t.string,d9.d+1),0)))  d10(d)
) split
WHERE LEN(s.somestring)-LEN(REPLACE(s.somestring,':','')) < 10

如果您不知道可能的最大项目数,可以采用此逻辑并将其包装在一些动态SQL中,以创建正确数量的CROSS APPLY。我没有时间把这个逻辑放在一起但是,为了获得最大数量的可能分隔符你可以做这样的事情:

DECLARE @maxDelimiters tinyint = 
  (SELECT MAX(LEN(s.somestring)-LEN(REPLACE(s.somestring,':',''))) FROM @table s);

或者,如果您想使用John的技术,您还可以使用动态SQL创建具有&#34; pos&#34;的确切数字的查询。需要的价值。