【问题标题】:What's the most efficient way to normalize text from column into a table?将列中的文本标准化为表格的最有效方法是什么?
【发布时间】:2018-10-08 08:37:04
【问题描述】:

在 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 做一些聪明的事情。

【问题讨论】:

  • 您的数据库使用什么? SQLServer 甲骨文,其他?它将决定您有哪些选择。
  • SQL Server - 我相信 2014 年。

标签: sql sql-server tsql split


【解决方案1】:

如果您有最大列数,则在 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

【讨论】:

  • 这几乎可以满足我的需求。输入是一个长字符串,所以我仍然需要将每组 [] 数据拆分为我可以在此处操作的一组。我认为这会让我到达那里 - 我会修改它并报告。
  • @ShawnHubbard 轻松修复,我不清楚那是一根长字符串。给我一点时间
【解决方案2】:

当您知道最大列数时,拆分字符串的最快方法是使用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 来创建他的查询,其中包含所需的确切数量的“pos”值。

【讨论】:

  • 你让我好奇了。稍后我会运行一些基准测试并让您知道。 +1
  • 使用包含 5,000 条记录的样本,每条记录运行 5 次。我的平均为 963 毫秒(953 分钟 983 最大)。您的平均时间为 7,635 毫秒(7,566 分钟,7,773 最大)
  • 谢谢约翰...您是否在显示执行计划的情况下运行它?对我来说,这让它慢下来。另一个问题可能是我如何在第一次应用中删除括号,有时派生值会使事情变慢一点。我将尝试一下并发布一些更新的代码。
  • @JohnCappelletti 请在某处发布比较代码和示例数据,并在 cmets 中留下链接。我现在也很好奇……
  • @ZoharPeled on dbfiddle.uk/… 与我的机器略有不同,但 1.5 秒与 5.7 秒的差异仍然很大
猜你喜欢
  • 2014-10-17
  • 1970-01-01
  • 1970-01-01
  • 2010-12-11
  • 1970-01-01
  • 2020-07-31
  • 1970-01-01
  • 2023-04-01
  • 1970-01-01
相关资源
最近更新 更多