Какой самый эффективный способ нормализовать текст из столбца в таблицу? - PullRequest
0 голосов
/ 27 апреля 2018

В 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 голосов
/ 27 апреля 2018

Самый быстрый способ разбить строку, если известно максимальное количество столбцов, - это использовать метод 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);

В качестве альтернативы, если вы хотите использовать технику Джона, вы также можете использовать Dynamic SQL для создания его запроса с точным количеством требуемых значений «pos».

0 голосов
/ 27 апреля 2018

Если у вас есть максимальное количество столбцов, немного XML внутри CROSS APPLY.

Если неизвестно, вам нужно идти ДИНАМИЧНО.

Пример

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

EDIT

Я должен добавить, что ltrim(rtrim(...)) является необязательным, а varchar(max) - мой демонстративный стандарт.

EDIT - одна строка, разделенная 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
...