sql server разделяет разделенные запятыми значения на столбцы

Я пытаюсь разделить csv на отдельные столбцы

ПРИМЕРЫ ДАННЫХ

PAR_COLUMN  PERIOD  VALUE       mul_query
----------  ------  ---------   ---------
1           601     10.134542   10.134542
1           602     20.234234   10.134542*20.234234
1           603     30.675643   10.134542*20.234234*30.675643
1           604     40.234234   10.134542*20.234234*30.675643*40.234234
2           601     10.345072   10.345072
2           602     20.345072   10.345072*20.345072
2           603     30.345072   10.345072*20.345072*30.345072
2           604     40.345072   10.345072*20.345072*30.345072*40.345072

ОЖИДАЕМЫЙ РЕЗУЛЬТАТ :

PAR_COLUMN  period   value     (No column name)    (No column name)    (No column name)    (No column name)
----------  ------  ---------  ----------------    ----------------    ----------------     ---------------
1           601     10.134542  10.134542            1                   1                   1
1           602     20.234234  10.134542            20.234234           1                   1
1           603     30.675643  10.134542            20.234234           30.675643           1
1           604     40.234234  10.134542            20.234234           30.675643           40.234234
2           601     10.345072  10.345072            1                   1                   1
2           602     20.345072  10.345072            20.345072           1                   1
2           603     30.345072  10.345072            20.345072           30.345072           1
2           604     40.345072  10.345072            20.345072           30.345072           40.345072

Я пытался так. Он работает, но очень медленно, когда данные большие. Есть ли лучшая альтернатива.

declare @sql varchar(max) = ''
set @sql =
';WITH Split_Names 
AS
(
    SELECT PAR_COLUMN,
    mul_query,period,
    CONVERT(XML,''<Names><name>''  
    + REPLACE(mul_query,''*'', ''</name><name>'') + ''</name></Names>'') AS xmlname
      FROM #finals
)
SELECT PAR_COLUMN,
    period,
'


declare @start int =1 ,@count int 
set @count = (select (max(period) - min(period))+1 from #finals)
while @start <= @count
begin 
set @sql +=concat( 'isnull(xmlname.value(''/Names[1]/name[',@start,']'',''float''),1) , ')
 set @start+=1
end
set @sql =left(@sql,len(@sql)-1)
set @sql+= ' FROM Split_Names'

exec( @sql)

Примечание: вопрос не преобразование CSV к индивидуальному Rows. Я пытаюсь преобразовать CSV к indivdual Columns в основном пытаюсь вычислить работает умножение на Value колонки

4 ответов


динамически решите эту проблему, используйте DSQL, чтобы добавить больше столбцов в результат соответственно.

--create split function
CREATE FUNCTION [dbo].[SO_Split]
(
    @List nvarchar(2000),
    @SplitOn nvarchar(5)
) 
RETURNS @RtnValue table
(

    Id int identity(1,1),
    Value nvarchar(100)
)
AS 
BEGIN
While (Charindex(@SplitOn,@List)>0)
Begin
Insert Into @RtnValue (value)
Select
    Value = ltrim(rtrim(Substring(@List,1,Charindex(@SplitOn,@List)-1)))
    Set @List =Substring(@List,Charindex(@SplitOn,@List)+len(@SplitOn),len(@List))
End
    Insert Into @RtnValue (Value)
    Select Value = ltrim(rtrim(@List))
    Return
END

--below is the dynamic solution for this problem
declare @sql nvarchar(3000) = 'select *'
declare @cnt int = 1
declare @rowNum int = (select max(a) from (select(select max(id) as id_max from dbo.so_split(mul_query,'*')) as a from #test) as b)

while(@cnt <= @rowNum)
begin
    set @sql = @sql + N', ISNULL((select value from dbo.so_split(mul_query,''*'') where id = '+cast(@cnt as nvarchar(5))+N'),''1'')'
    set @cnt = @cnt + 1
end

set @sql = @sql + N' from #test'

exec sp_executesql @sql

результат прилагается ниже. enter image description here


вы можете свернуть свою собственную функцию разделения строк, как подробно описано в большой глубине здесь Джефф моден.

для целей потомства, окончательный код:

CREATE FUNCTION [dbo].[DelimitedSplit8K]
--===== Define I/O parameters
        (@pString VARCHAR(8000), @pDelimiter CHAR(1))
--WARNING!!! DO NOT USE MAX DATA-TYPES HERE!  IT WILL KILL PERFORMANCE!
RETURNS TABLE WITH SCHEMABINDING AS
 RETURN
--===== "Inline" CTE Driven "Tally Table" produces values from 1 up to 10,000...
     -- enough to cover VARCHAR(8000)
  WITH E1(N) AS (
                 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
                 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
                 SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
                ),                          --10E+1 or 10 rows
       E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
       E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
 cteTally(N) AS (--==== This provides the "base" CTE and limits the number of rows right up front
                     -- for both a performance gain and prevention of accidental "overruns"
                 SELECT TOP (ISNULL(DATALENGTH(@pString),0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
                ),
cteStart(N1) AS (--==== This returns N+1 (starting position of each "element" just once for each delimiter)
                 SELECT 1 UNION ALL
                 SELECT t.N+1 FROM cteTally t WHERE SUBSTRING(@pString,t.N,1) = @pDelimiter
                ),
cteLen(N1,L1) AS(--==== Return start and length (for use in substring)
                 SELECT s.N1,
                        ISNULL(NULLIF(CHARINDEX(@pDelimiter,@pString,s.N1),0)-s.N1,8000)
                   FROM cteStart s
                )
--===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found.
 SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY l.N1),
        Item       = SUBSTRING(@pString, l.N1, l.L1)
   FROM cteLen l

как только у вас есть эта функция, вы можете вытащить необходимые данные с помощью pivot стол:

select PAR_COLUMN
        ,PERIOD
        ,VALUE
        ,mul_query
        ,[1]
        ,[2]
        ,[3]
        ,[4]
from(select f.PAR_COLUMN
            ,f.PERIOD
            ,f.VALUE
            ,f.mul_query
            ,s.ItemNumber
            ,s.Item
    from @finals f
        cross apply dbo.DelimitedSplit8K(f.mul_query,'*') s
    ) as d
pivot
(
max(Item)
for ItemNumber in([1],[2],[3],[4])
) as pvt

С небольшой помощью креста применить и UDF для разделения значений

Declare @YouTable table (PAR_COLUMN int,PERIOD int,VALUE decimal(18,6), mul_query varchar(250))
Insert Into @YouTable values
(1,601,10.134542,'10.134542'),
(1,602,20.234234,'10.134542*20.234234'),
(1,603,30.675643,'10.134542*20.234234*30.675643'),
(1,604,40.234234,'10.134542*20.234234*30.675643*40.234234'),
(2,601,10.345072,'10.345072'),
(2,602,20.345072,'10.345072*20.345072'),
(2,603,30.345072,'10.345072*20.345072*30.345072'),
(2,604,40.345072,'10.345072*20.345072*30.345072*40.345072')

Select A.PAR_COLUMN
      ,A.PERIOD
      ,A.VALUE
      ,Pos1=IsNull(B.Pos1,1)
      ,Pos2=IsNull(B.Pos2,1)
      ,Pos3=IsNull(B.Pos3,1)
      ,Pos4=IsNull(B.Pos4,1)
      ,Pos5=IsNull(B.Pos5,1)
      ,Pos6=IsNull(B.Pos6,1)
 From  @YouTable A
 Cross Apply (Select * from [dbo].[udf-Str-Parse-Row](A.mul_query,'*')) B

возвращает

PAR_COLUMN  PERIOD  VALUE       Pos1        Pos2      Pos3      Pos4    Pos5    Pos6
1           601     10.134542   10.134542   1         1         1   1   1
1           602     20.234234   10.134542   20.234234 1         1   1   1
1           603     30.675643   10.134542   20.234234 30.675643 1   1   1
1           604     40.234234   10.134542   20.234234 30.675643 40.234234   1   1
2           601     10.345072   10.345072   1   1   1 1 1
2           602     20.345072   10.345072   20.345072 1 1   1   1
2           603     30.345072   10.345072   20.345072 30.345072 1   1   1
2           604     40.345072   10.345072   20.345072 30.345072 40.345072   1   1

UDF

CREATE FUNCTION [dbo].[udf-Str-Parse-Row] (@String varchar(max),@Delimeter varchar(10))
--Usage: Select * from [dbo].[udf-Str-Parse-Row]('Dog,Cat,House,Car',',')
--       Select * from [dbo].[udf-Str-Parse-Row]('John Cappelletti',' ')
--       Select * from [dbo].[udf-Str-Parse-Row]('id26,id46|id658,id967','|')

Returns Table 

As

Return (
    SELECT Pos1 = xDim.value('/x[1]','varchar(250)')
          ,Pos2 = xDim.value('/x[2]','varchar(250)')
          ,Pos3 = xDim.value('/x[3]','varchar(250)')
          ,Pos4 = xDim.value('/x[4]','varchar(250)')
          ,Pos5 = xDim.value('/x[5]','varchar(250)')
          ,Pos6 = xDim.value('/x[6]','varchar(250)')
          ,Pos7 = xDim.value('/x[7]','varchar(250)')
          ,Pos8 = xDim.value('/x[8]','varchar(250)')
          ,Pos9 = xDim.value('/x[9]','varchar(250)')
    FROM (Select Cast('<x>' + Replace(@String,@Delimeter,'</x><x>')+'</x>' as XML) as xDim) A

просто выберите, который преобразует поле в XML, чтобы из него можно было извлечь числа.

select PAR_COLUMN, PERIOD, VALUE
 ,x.value('/x[1]','float') as mul1
 ,x.value('/x[2]','float') as mul2
 ,x.value('/x[3]','float') as mul3
 ,x.value('/x[4]','float') as mul4
 --,(x.value('/x[1]','float') * x.value('/x[2]','float') * x.value('/x[3]','float') * x.value('/x[4]','float')) as mul_total
from (
  select PAR_COLUMN, PERIOD, VALUE, 
  cast('<x>'+replace(mul_query,'*','</x><x>')+'</x><x>1</x><x>1</x><x>1</x>' as xml) as x 
  from YourTable t
) q;

Если требуется только общая сумма умножения, то можно избежать подзапроса.
Что может ускорить процесс.
В приведенном ниже запросе для умножения используется XQuery.

select PAR_COLUMN, PERIOD, VALUE, 
cast('<x>'+replace(mul_query,'*','</x><x>')+'</x><x>1</x><x>1</x><x>1</x>' as xml).value('x[1]*x[2]*x[3]*x[4]','float') as mul_result
from YourTable t