Динамически разделить / разделить столбец на несколько столбцов Вертикально с помощью сервера SQL - PullRequest
0 голосов
/ 05 июля 2018

Мне нужно разделить таблицу, в которой поле идентификатора повторяется несколько раз. Точка с запятой будет служить разделителем. Это динамический набор данных, в котором «встроенный» столбец является динамическим. Строитель имеет многолетний опыт работы, поэтому мы не можем использовать ограничитель фиксированной позиции. Вот пример данных.

---- Sample data
    CREATE TABLE TEST1
    (
        ID varchar(20), 
        FIELD varchar(max)
    );

    INSERT INTO TEST1 (ID, FIELD)
    VALUES 
    ('ERIC','BUILT;1995/PROPERTY;House/TYPE;OZ C1/BUILT;2010/PROPERTY;Condo/TYPE;F4/BUILT;2010/PROPERTY;Condo/TYPE;F9/'),
    ('ERIC','BUILT;1996/PROPERTY;House/TYPE;OZ C1/'),
    ('ERIC','BUILT;1997/PROPERTY;House/TYPE;OZ C1/'),
    ('ERIC','BUILT;2011/PROPERTY;Condo/TYPE;F4/BUILT;2015/PROPERTY;House/TYPE;C5/');

Это конечный результат, к которому я стремлюсь: с идентификатором, встроенным, свойством и типом в виде заголовков столбцов и данных, представленных вертикально.

ID      BUILT   PROPERTY    TYPE
--------------------------------
ERIC    1995    House       OZ C1
ERIC    2010    Condo       F4
ERIC    2010    Condo       F9
ERIC    1996    House       OZ C1
ERIC    1997    House       OZ C1
ERIC    2011    Condo       F4
ERIC    2015    House       C5

Мне известен следующий способ разбиения на столбцы. Однако это не то, что мне нужно. Это разделит данные по горизонтали, и мне нужно это по вертикали.

WITH Split_Fields (ID, xmlfields)
AS
(
    SELECT ID ,
    CONVERT(XML,'<Fields><field>'  
    + REPLACE(name,'.', '</field><field>') + '</field></Fields>') AS xmlfields
      FROM TEST1
)

SELECT ID      
 ,xmlfields.value('/Fields[1]/field[1]';'varchar(100)') AS Field1    
 ,xmlfields.value('/Fields[1]/field[2]';'varchar(100)') AS Field2
,xmlfields.value('/Fields[1]/field[3]';'varchar(100)') AS Field3    
 ,xmlfields.value('/Fields[1]/field[4]';'varchar(100)') AS Field4
,xmlfields.value('/Fields[1]/field[5]';'varchar(100)') AS Field5
FROM Split_Fields

Я просмотрел SO, но не смог найти что-нибудь о разделении данных так, как мне нужно. Пожалуйста, кто-нибудь, помогите мне с этим. Я действительно застрял на этом.

Я успешно добавил '|' в качестве разделителей, чтобы указать, где начинается новая строка, как предложил Шон. Я боюсь, что из-за ограниченных данных у меня не получится сделать больше. Вот новый пример данных.

---- Sample data
    CREATE TABLE TEST1
    (
        ID varchar(20), 
        FIELD varchar(max)
    );

    INSERT INTO TEST1 (ID, FIELD)
    VALUES 
    ('ERIC','BUILT;1995/PROPERTY;House/TYPE;OZ C1|BUILT;2010/PROPERTY;Condo/TYPE;F4|BUILT;2010/PROPERTY;Condo/TYPE;F9|'),
    ('ERIC','BUILT;1996/PROPERTY;House/TYPE;OZ C1|'),
    ('ERIC','BUILT;1997/PROPERTY;House/TYPE;OZ C1|'),
    ('ERIC','BUILT;2011/PROPERTY;Condo/TYPE;F4|BUILT;2015/PROPERTY;House/TYPE;C5|');

Simran

Ответы [ 3 ]

0 голосов
/ 05 июля 2018

Как я уже сказал в комментариях, эта структура данных ужасна. Это потребует разделения строк несколько раз. Я использую сплиттер Джеффа Модена, который мне нужен для сплиттера, потому что он возвращает порядковую позицию строки, которая используется большинством других сплиттеров (включая функцию string_split от MS). Вы можете найти это здесь. http://www.sqlservercentral.com/articles/Tally+Table/72993/ Если вам не нравится этот, вы можете использовать string_split, если вы на 2017 или вы можете найти несколько других отличных сплиттеров здесь. https://sqlperformance.com/2012/07/t-sql-queries/split-strings

Одна вещь, которую я должен был сделать, это удалить конечный разделитель из ваших данных, потому что конечный разделитель означает, что вы получите пустую строку. Мне также нужно было использовать ROW_NUMBER для генерации уникального значения, поскольку каждая строка в ваших исходных данных указывает на целый набор информации. Это необходимо, чтобы у вас было что использовать для группировки при выполнении условного агрегирования. Я разбил это на пару цит, чтобы проиллюстрировать путь, используемый здесь. Вы заметите, что мне пришлось использовать разделитель строк один раз, чтобы изолировать каждую «строку» данных в строке с разделителями. Затем используйте его снова, чтобы отделить «столбцы» от «строк» ​​для каждой строки в вашей таблице.

Опять же, рефакторинг исходных данных в нормализованную структуру будет первым и лучшим выбором. Но вот некоторые sql, чтобы вернуть то, что вы заявили, что вы хотите.

with MyRows as
(
    select *
        , RowNum = ROW_NUMBER() over(order by ID) --Need a unique value to use for grouping later
    from test1 t
    cross apply dbo.DelimitedSplit8K(left(t.FIELD, len(t.FIELD) - 1), '|') s1
)
, MyValueGroups as
(
    select r.ID
        , r.Item as TestBedItem
        , r.RowNum
        , v.*
    from MyRows r
    cross apply dbo.DelimitedSplit8K(r.Item, '/') v
)

select ID
    , BUILT = max(case when ItemNumber = 1 then substring(Item, charindex(';', Item) + 1, len(Item)) end) 
    , PROPERTY = max(case when ItemNumber = 2 then substring(Item, charindex(';', Item) + 1, len(Item)) end) 
    , TYPE = max(case when ItemNumber = 3 then substring(Item, charindex(';', Item) + 1, len(Item)) end) 
from MyValueGroups
group by ID
    , RowNum

Вот что это возвращает. Кажется, чтобы соответствовать желаемому выводу правильно.

ID      BUILT   PROPERTY   TYPE
----    -----   --------   -----
ERIC    1995    House      OZ C1
ERIC    2010    Condo      F4
ERIC    2010    Condo      F9
ERIC    1996    House      OZ C1
ERIC    1997    House      OZ C1
ERIC    2011    Condo      F4
ERIC    2015    House      C5
0 голосов
/ 05 июля 2018

С помощью CROSS APPLY (или двух) и небольшого XML

* ** 1003 тысяча два * Пример
Select A.ID
      ,Built    = C.Pos1
      ,Property = C.Pos3
      ,PropType = C.Pos5
 From  Test1 A
 Cross Apply (
                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((Select replace(A.FIELD,'BUILT;','§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml).query('.')) as A 
                Cross Apply x.nodes('x') AS B(i)
                Where B.i.value('(./text())[1]', 'varchar(max)') is not null
             ) B
 Cross Apply (
                Select Pos1 = ltrim(rtrim(xDim.value('/x[1]','varchar(max)')))
                      ,Pos3 = ltrim(rtrim(xDim.value('/x[3]','varchar(max)')))
                      ,Pos5 = ltrim(rtrim(xDim.value('/x[5]','varchar(max)')))
                From  (Select Cast('<x>' + replace((Select replace(replace(B.RetVal,'/',';'),';','§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml) as xDim) as A 
             ) C

Returns

ID      Built   Property    PropType
ERIC    1995    House       OZ C1
ERIC    1996    House       OZ C1
ERIC    1997    House       OZ C1
ERIC    2010    Condo       F4
ERIC    2010    Condo       F9
ERIC    2011    Condo       F4
ERIC    2015    House       C5
0 голосов
/ 05 июля 2018

Вы можете разделить его по горизонтали с помощью своего запроса, а затем UNPIVOT , чтобы получить желаемые результаты по вертикали.

...