简体   繁体   中英

Split string and divide value

I got a table Test with columns A and B . The A column contains different values in one entry, eg abc;def;ghi , all separated by ; . And the B column contains numeric values, but only one.

What I want is to seperate the values from column A into multiple rows.

So:

abc;def;ghi;jkl

-->

abc
def
ghi
jkl

In column B is one value, eg 20 and I want that value split to the amount of rows,

So the final result shut be:

abc 5
def 5
ghi 5
jkl 5 

The issue is that the amount of values in column A must be variable.

First you need to create this function

REATE FUNCTION Split
(
  @delimited nvarchar(max),
  @delimiter nvarchar(100)
) RETURNS @t TABLE
(
-- Id column can be commented out, not required for sql splitting string
  id int identity(1,1), -- I use this column for numbering splitted parts
  val nvarchar(max),
  origVal nvarchar(max)
)
AS
BEGIN
  declare @xml xml
  set @xml = N'<root><r>' + replace(@delimited,@delimiter,'</r><r>') + '</r></root>'

  insert into @t(val,origval)
  select
    r.value('.','varchar(max)') as item, @delimited
  from @xml.nodes('//root/r') as records(r)

  RETURN
END
GO

then this query might help

Select x.Val, test.B / (len(test.A) - len(replace(Test.A, ';', '')) + 1)  from Test
inner join dbo.Split(Test.A,';') x on x.origVal = Test.A 

this part len(test.A) - len(replace(Test.A, ';', '')) will count the number of ; in string

Be aware this query might have some malfunctioning if there will be duplicate strings in A column, in this situation you need to pass the unique value (for example ID ) to split function and return it in the result table, then join it by this value (ie. x.origVal = Test.A => x.origID = Test.ID )

You can use some tricks with CTE, STUFF and windows functions

DECLARE @t TABLE
    (
      ID INT ,
      A NVARCHAR(MAX) ,
      B INT
    )

INSERT  INTO @t
VALUES  ( 1, 'a;b;c;d;', 20 ),
        ( 2, 'x;y;z;', 40 );


WITH    cte ( ID, B, D, A )
          AS ( SELECT   ID ,
                        B ,
                        LEFT(A, CHARINDEX(';', A + ';') - 1) ,
                        STUFF(A, 1, CHARINDEX(';', A + ';'), '')
               FROM     @t
               UNION ALL
               SELECT   ID ,
                        B ,
                        LEFT(A, CHARINDEX(';', A + ';') - 1) ,
                        STUFF(A, 1, CHARINDEX(';', A + ';'), '')
               FROM     cte
               WHERE    A > ''
             )
    SELECT  ID ,
            B ,
            D,
            CAST(B AS DECIMAL) / COUNT(*) OVER (PARTITION BY ID) AS Portion     
    FROM    cte            

Output:

ID  B   D   Portion
1   20  a   5.00000000000
1   20  b   5.00000000000
1   20  c   5.00000000000
1   20  d   5.00000000000
2   40  x   13.33333333333
2   40  y   13.33333333333
2   40  z   13.33333333333

this an example how you can achieve required result

DECLARE @table AS TABLE
    (
      ColumnA VARCHAR(100) ,
      ColumnB FLOAT
    )
INSERT  INTO @table
        ( ColumnA, ColumnB )
VALUES  ( 'abc;def;ghi;jkl', 20 ),
        ( 'asf;ret;gsd;jas', 30 ),
        ( 'dfa;aef;gffhi;fjfkl', 40 );
WITH    C AS ( SELECT   n = 1
               UNION ALL
               SELECT   n + 1
               FROM     C
               WHERE    n <= 100
             ),
        SetForSplit
          AS ( SELECT   T.ColumnA ,
                        T.ColumnB ,
                        C.n ,
                        ( CASE WHEN LEFT(SUBSTRING(T.ColumnA, n, 100), 1) = ';'
                               THEN SUBSTRING(T.ColumnA, n + 1, 100) + ';'
                               ELSE SUBSTRING(T.ColumnA, n, 100) + ';'
                          END ) AS SomeText
               FROM     @table AS T
                        JOIN C ON C.n <= LEN(T.ColumnA)
               WHERE    SUBSTRING(T.ColumnA, n, 1) = ';'
                        OR n = 1
             )
    SELECT  ROW_NUMBER() OVER ( PARTITION BY columnA ORDER BY LEFT(SomeText,
                                                              CHARINDEX(';',
                                                              SomeText) - 1) ) AS RowN,
            LEFT(SomeText, CHARINDEX(';', SomeText) - 1) AS ColA ,
            ColumnB / COUNT(*) OVER ( PARTITION BY ColumnA ) AS ColB
    FROM    SetForSplit
    ORDER BY ColumnA

在此处输入图片说明

This is full working exmaple:

 DECLARE @DataSource TABLE
 (
     [A] VARCHAR(MAX)
    ,[B] INT
 );

 INSERT INTO @DataSource ([A], [B])
 VALUES ('a;b;c;d', 20 ),
        ('x;y;z', 40 );

SELECT T.c.value('.', 'VARCHAR(100)')
      ,[B] / COUNT([B]) OVER (PARTITION BY [B])
FROM @DataSource
CROSS APPLY 
(
    SELECT CONVERT(XML, '<t>' + REPLACE([A], ';', '</t><t>') + '</t>')
) DS([Bxml])
CROSS APPLY [Bxml].nodes('/t') AS T(c)

and of couse you can ROUND the devision as you like.

在此处输入图片说明

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM