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
вы можете свернуть свою собственную функцию разделения строк, как подробно описано в большой глубине здесь Джефф моден.
для целей потомства, окончательный код:
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