I trying to split the csv to individual columns
SAMPLE DATA
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
EXPECTED RESULT :
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
I tried like this. It is working but very slow when data is large. Is there any better alternative.
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)
Note: The question is NOT to convert CSV
to Individual Rows
. I am trying to convert CSV
to indivdual Columns
Basically am trying to calculate RUNNING Multiplication in Value
column
Dynamically solve this problem, use DSQL to add more columns in the result accordingly.
--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
You can roll your own string splitting function as detailed in great depth here by Jeff Moden.
For posterity purposes, the final code is:
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
Once you have this function, you can pull out the relevant data using a pivot
table:
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
With a little help from a CROSS APPLY and an UDF to split values
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
Returns
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
The 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
Just a select that transforms the field to an XML so that the numbers can be extracted from it.
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;
If just the total of the multiplication is needed, then a subquery can be avoided.
Which could speed it up.
The query below uses XQuery to do the multiplication.
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
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.