将列动态拆分/定界为多列,使用sql server

时间:2018-07-05 15:09:38

标签: sql-server split sql-server-2012

我需要拆分一个表,其中ID字段重复多次。分号将用作分隔符。这是一个动态数据集,其中“内置”列是动态的。 Builder具有丰富的动态经验,因此我们无法使用固定位置定界符。这是示例数据。

---- 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,Builded,Property和type作为垂直列标题和数据的最终结果。

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

我已经仔细检查过,但是找不到以我需要的方式分割数据的任何东西。请有人帮助我。我真的很坚持。

我已成功添加“ |”作为分隔符,以指定新行从Sean提议的位置开始。恐怕我所拥有的数据有限,我可能无法做更多的事情。这是新的示例数据。

---- 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 :(得分:1)

正如我在评论中所说,这种数据结构令人恐惧。这将需要多次字符串拆分。我正在使用Jeff Moden的分割器,这是我要使用的分割器,因为它返回大多数其他分割器(包括MS的string_split函数)的字符串的顺序位置。你可以在这里找到它。 http://www.sqlservercentral.com/articles/Tally+Table/72993/如果您不喜欢这样,可以在2017年使用string_split,或者在这里找到其他几个出色的分离器。 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

答案 1 :(得分:1)

借助CROSS APPLY(或两个)和少量XML

示例

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

返回

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

答案 2 :(得分:0)

您可以通过查询将其水平拆分,然后UNPIVOT进行拆分以获取所需的垂直结果。