MS Sql order by numeric parts of string - sql

I need to order my query by numeric parts.
I have a lot of rows looks like:
'ABCD.1234.567'
'ABCD.1234-2345'
'ABCD.1234.1213.1'
So, I want firs order by first numeric part, then by second and then by therd.
Like this:
'ABCD.1234.567'
'ABCD.1234.1213.1'
'ABCD.1234-2345'
How can I do this?
UPD: I have tried to use PATINDEX function in order by, but can figure out how to do that for all numbers.
order by s.product, (case when Patindex('% ,.,-%', s.product)=0 then 0
else Cast(SUBSTRING(s.product, Patindex('%[0-9]%', s.product), len(s.product)) as int) end)

You could use a string split function
DECLARE #SampleData AS TABLE
(
Name varchar(100)
)
INSERT INTO #SampleData
VALUES
('ABCD.1234.567'),
('ABCD.1234-2345'),
('ABCD.1234.1213.1')
Your query would be
SELECT *
FROM #SampleData sd
OUTER APPLY
(
SELECT CAST(t.Value AS int) AS Part1
FROM [dbo].[SplitString](replace(sd.Name,'-', '.'),'.') t
WHERE t.Pos = 2
) p1
OUTER APPLY
(
SELECT CAST(t.Value AS int) AS Part2
FROM [dbo].[SplitString](replace(sd.Name,'-', '.'),'.') t
WHERE t.Pos = 3
) p2
OUTER APPLY
(
SELECT CAST(t.Value AS int) AS Part3
FROM [dbo].[SplitString](replace(sd.Name,'-', '.'),'.') t
WHERE t.Pos = 4
) p3
ORDER BY p1.[Part1], p2.[Part2], p3.Part3
And string split function
CREATE FUNCTION [dbo].[SplitString] (#Text varchar(max),#Delimiter varchar(10))
Returns Table
As
Return (
Select Pos = Row_Number() over (Order By (Select null))
,Value = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>'+ Replace(#Text,#Delimiter,'</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
);
Result:
Name Part1 Part2 Part3
---------------------------------------
ABCD.1234.567 1234 567 NULL
ABCD.1234.1213.1 1234 1213 1
ABCD.1234-2345 1234 2345 NULL

For 4 groups or less, PARSENAME makes a great string split function
DECLARE #foo TABLE (foo varchar(100));
INSERT #foo
VALUES
('ABCD.1234.567'),
('ABCD.1234-2345'),
('ABCD.1234.1213.1')
SELECT
Y.foo
FROM
(
SELECT
Element1 = REVERSE(PARSENAME(X.ProcessedFoo, 1)),
Element2 = REVERSE(PARSENAME(X.ProcessedFoo, 2)),
Element3 = REVERSE(PARSENAME(X.ProcessedFoo, 3)),
Element4 = REVERSE(PARSENAME(X.ProcessedFoo, 4)),
X.foo
FROM
(
SELECT
foo,
ProcessedFoo = REVERSE(REPLACE(foo, '-', '.'))
FROM #foo
) X
) Y
ORDER BY
Y.Element1,
CAST(Y.Element2 AS int),
CAST(Y.Element3 AS int),
CAST(Y.Element4 AS int);

Related

Extract string between two characters in a string

I have a set of strings that has datetime values and I would like to extract them. I am not sure if this is even possible using T-SQL.
CREATE TABLE #Temp (
BLOB_NM VARCHAR(100)
);
INSERT INTO #Temp
SELECT 'products_country_20200528102030.txt'
UNION ALL
SELECT 'products_territory_20190528102030.txt'
UNION ALL
SELECT 'products_country_2020-05-20_20200528102030.txt'
;
Expected Results:
20200528102030
20190528102030
20200528102030
For this dataset, string functions should do it:
select blob_nm, substring(blob_nm, len(blob_nm) - 17, 14) res from #temp
The idea is to count backwards from the end of the string, and capture the 14 characters that preced the extension (represented by the last 4 characters of the string).
Demo on DB Fiddle:
blob_nm | res
:--------------------------------------------- | :-------------
products_country_20200528102030.txt | 20200528102030
products_territory_20190528102030.txt | 20190528102030
products_country_2020-05-20_20200528102030.txt | 20200528102030
If interested in a helper function... I created this TVF because I was tiered of extracting portions of strings (left, right, charindex, reverse, substing, etc)
Example
Select *
From #Temp A
Cross Apply [dbo].[tvf-Str-Extract](Blob_NM,'_','.') B
Returns
BLOB_NM RetSeq RetVal
products_country_20200528102030.txt 1 20200528102030
products_territory_20190528102030.txt 1 20190528102030
products_country_2020-05-20_20200528102030.txt 1 20200528102030
The Function if Interested
CREATE FUNCTION [dbo].[tvf-Str-Extract] (#String varchar(max),#Delim1 varchar(100),#Delim2 varchar(100))
Returns Table
As
Return (
Select RetSeq = row_number() over (order by RetSeq)
,RetVal = left(RetVal,charindex(#Delim2,RetVal)-1)
From (
Select RetSeq = row_number() over (order by 1/0)
,RetVal = ltrim(rtrim(B.i.value('(./text())[1]', 'varchar(max)')))
From ( values (convert(xml,'<x>' + replace((Select replace(#String,#Delim1,'§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>').query('.'))) as A(XMLData)
Cross Apply XMLData.nodes('x') AS B(i)
) C1
Where charindex(#Delim2,RetVal)>1
)
i suppose :
Files extension are not always 3 of characters length
Your Date/Time format are always on 14 characters
Try this :
select
CONVERT(DATETIME, STUFF(STUFF(STUFF(left(right(BLOB_NM, charindex('_', reverse(BLOB_NM) + '_') - 1), 14),13,0,':'),11,0,':'),9,0,' ')) as Result
from #Temp

MS SQL Server Get value between commas

I have a column in Table1 with string in it separated by commma:
Id Val
1 ,4
2 ,3,1,0
3 NULL
4 ,5,2
Is there a simple way to split and get any value from that column,
for example
SELECT Value(1) FROM Table1 should get
Id Val
1 4
2 3
3 NULL
4 5
SELECT Value(2) FROM Table1 should get
Id Val
1 NULL
2 1
3 NULL
4 2
Thank you!
Storing comma separated values in a column is always a pain, consider changing your table structure
To get this done, create a split string function. Here is one of the best possible approach to split the string to individual rows. Referred from http://www.sqlservercentral.com/articles/Tally+Table/72993/
CREATE FUNCTION [dbo].[DelimitedSplit8K]
(#pString VARCHAR(8000), #pDelimiter CHAR(1))
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
--===== "Inline" CTE Driven "Tally Table" produces values from 0 up to 10,000...
-- enough to cover NVARCHAR(4000)
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
to call the function
SELECT *
FROM yourtable
CROSS apply (SELECT CASE WHEN LEFT(val, 1) = ',' THEN Stuff(val, 1, 1, '') ELSE val END) cs (cleanedval)
CROSS apply [dbo].[Delimitedsplit8k](cs.cleanedval, ',')
WHERE ItemNumber = 1
SELECT *
FROM yourtable
CROSS apply (SELECT CASE WHEN LEFT(val, 1) = ',' THEN Stuff(val, 1, 1, '') ELSE val END) cs (cleanedval)
CROSS apply [dbo].[Delimitedsplit8k](cs.cleanedval, ',')
WHERE ItemNumber = 2
Another option using a Parse/Split Function and an OUTER APPLY
Example
Declare #YourTable Table ([Id] int,[Val] varchar(50))
Insert Into #YourTable Values
(1,',4')
,(2,',3,1,0')
,(3,NULL)
,(4,',5,2')
Select A.ID
,Val = B.RetVal
From #YourTable A
Outer Apply (
Select * From [dbo].[tvf-Str-Parse](A.Val,',')
Where RetSeq = 2
) B
Returns
ID Val
1 4
2 3
3 NULL
4 5
The UDF if Interested
CREATE FUNCTION [dbo].[tvf-Str-Parse] (#String varchar(max),#Delimiter varchar(10))
Returns Table
As
Return (
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(#String,#Delimiter,'§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
);
Here is an example of using a CTE combined with converting the CSV to XML:
DECLARE #Test TABLE (
CsvData VARCHAR(10)
);
INSERT INTO #Test (CsvData)
VALUES
('1,2,3'),
(',4,5,7'),
(NULL),
(',3,');
WITH XmlData AS (
SELECT CONVERT(XML, '<val>' + REPLACE(CsvData, ',', '</val><val>') + '</val>') [CsvXml]
FROM #Test
)
SELECT xd.CsvXml.value('val[2]', 'VARCHAR(10)')
FROM XmlData xd;
This would output:
2
4
NULL
3
The column to display is controlled by the XPath query. In this case, val[2].
The main advantage here is that no user-defined functions are required.
Try This Logic Using recursive CTE
DECLARE #Pos INT = 2
DECLARE #T TABLE
(
Id INT,
Val VARCHAR(50)
)
INSERT INTO #T
VALUES(1,',4'),(2,',3,1,0'),(3,NULL),(4,',5,2')
;WITH CTE
AS
(
SELECT
Id,
SeqNo = 0,
MyStr = SUBSTRING(Val,CHARINDEX(',',Val)+1,LEN(Val)),
Num = REPLACE(SUBSTRING(Val,1,CHARINDEX(',',Val)),',','')
FROM #T
UNION ALL
SELECT
Id,
SeqNo = SeqNo+1,
MyStr = CASE WHEN CHARINDEX(',',MyStr)>0
THEN SUBSTRING(MyStr,CHARINDEX(',',MyStr)+1,LEN(MyStr))
ELSE NULL END,
Num = CASE WHEN CHARINDEX(',',MyStr)>0
THEN REPLACE(SUBSTRING(MyStr,1,CHARINDEX(',',MyStr)),',','')
ELSE MyStr END
FROM CTE
WHERE ISNULL(REPLACE(MyStr,',',''),'')<>''
)
SELECT
T.Id,
CTE.Num
FROM #T t
LEFT JOIN CTE
ON T.Id = cte.Id
AND SeqNo = #Pos
My Output for the above
Test Data
Declare #t TABLE (Id INT , Val VARCHAR(100))
INSERT INTO #t VALUES
(1 , '4'),
(2 , '3,1,0'),
(3 , NULL),
(4 , '5,2')
Function Definition
CREATE FUNCTION [dbo].[fn_xml_Splitter]
(
#delimited nvarchar(max)
, #delimiter nvarchar(1)
, #Position INT = NULL
)
RETURNS TABLE
AS
RETURN
(
SELECT Item
FROM (
SELECT Split.a.value('.', 'VARCHAR(100)') Item
, ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) ItemNumber
FROM
(SELECT Cast ('<X>' + Replace(#delimited, #delimiter, '</X><X>')
+ '</X>' AS XML) AS Data
) AS t CROSS APPLY Data.nodes ('/X') AS Split(a)
)x
WHERE x.ItemNumber = #Position OR #Position IS NULL
);
GO
Function Call
Now you can call this function in two different ways.
1 . to get return an Item on a specific position, specify the position in the 3rd parameter of the function:
SELECT *
FROM #t t
CROSS APPLY [dbo].[fn_xml_Splitter](t.Val , ',', 1)
2 . to get return all items, specify the key word DEFUALT in the 3rd parameter of the function:
SELECT *
FROM #t t
CROSS APPLY [dbo].[fn_xml_Splitter](t.Val , ',', DEFAULT)

how to Split And Count cell at Sql Server

i have a table like this
Food |
---------------|
nasi |
nasi goreng |
nasi uduk |
nasi,ikan,lalap|
nasi |
i want result count nasi = 3, nasi goreng = 1 nasi uduk = 1
how to Split And Count cell at Sql Server ?
You could use a string split function and CROSS APPLY
DECLARE #SampleData AS TABLE
(
Food varchar(100)
)
INSERT INTO #SampleData
VALUES
('nasi'),
('nasi goreng'),
('nasi uduk'),
('nasi,ikan,lalap'),
('nasi')
SELECT ca.Value AS Food,
count(*) AS Count
FROM #SampleData sd
CROSS APPLY
(
Select * from [dbo].[SplitString](sd.Food,',')
) ca
GROUP BY ca.Value
String split function
CREATE FUNCTION [dbo].[SplitString] (#Text varchar(max),#Delimiter varchar(10))
Returns Table
As
Return (
Select Pos = Row_Number() over (Order By (Select null))
,Value = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>'+ Replace(#Text,#Delimiter,'</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
);
Returns
Food Count
---------------------
ikan 1
lalap 1
nasi 3
nasi goreng 1
nasi uduk 1
Hope it helps...
Select food_Name,COUNT(*) from (
SELECT
Tbl.Col.value('./text()[1]','varchar(50)')as food_Name
FROM
(Select cast('<a>'+ replace((SELECT food As [*] FOR XML PATH('')), ',', '</a><a>') + '</a>' as xml)as t
from Your_Table) tl
Cross apply
tl.t.nodes('/a') AS Tbl(Col))t2
group by food_Name

Split SQL server string that has Decimal points into multiple Columns

I have the following table
Col
=========================
1270.8/847.2/254.16/106.9
And I would like to be split into columns like so:
Col1 Col2 Col3 Col4
============================================
1270.8 847.2 254.16 106.9
I have the code below, but it doesn't take the decimal into consideration.
Declare #Sample Table
(MachineName varchar(max))
Insert into #Sample
values ('1270.8/847.2/254.16');
SELECT
Reverse(ParseName(Replace(Reverse(MachineName), '/', ''), 1)) As [M1]
, Reverse(ParseName(Replace(Reverse(MachineName), '/', ''), 2)) As [M2]
, Reverse(ParseName(Replace(Reverse(MachineName), '/', ''), 3)) As [M3]
FROM #Sample
In SQL Server 2016+ you can use string_split().
In SQL Server pre-2016, using a CSV Splitter table valued function by Jeff Moden and conditional aggregation:
declare #Sample Table (id int not null identity(1,1), MachineName varchar(max));
insert into #Sample values ('1270.8/847.2/254.16'),('1270.8/847.2/254.16/106.9');
select
t.id
, m1 = max(case when s.ItemNumber = 1 then s.Item end)
, m2 = max(case when s.ItemNumber = 2 then s.Item end)
, m3 = max(case when s.ItemNumber = 3 then s.Item end)
, m4 = max(case when s.ItemNumber = 4 then s.Item end)
from #Sample t
cross apply dbo.delimitedsplit8K(MachineName,'/') s
group by id
rextester demo: http://rextester.com/WJVLB77682
returns:
+----+--------+-------+--------+-------+
| id | m1 | m2 | m3 | m4 |
+----+--------+-------+--------+-------+
| 1 | 1270.8 | 847.2 | 254.16 | NULL |
| 2 | 1270.8 | 847.2 | 254.16 | 106.9 |
+----+--------+-------+--------+-------+
splitting strings reference:
Tally OH! An Improved SQL 8K “CSV Splitter” Function - Jeff Moden
Splitting Strings : A Follow-Up - Aaron Bertrand
Split strings the right way – or the next best way - Aaron Bertrand
string_split() in SQL Server 2016 : Follow-Up #1 - Aaron Bertrand
Everyone should have a good split/parse function as illustrated by SQLZim (+1), but another option could be as follow:
Declare #YourTable table (ID int,Col varchar(max))
Insert Into #YourTable values
(1,'1270.8/847.2/254.16/106.9')
Select A.ID
,B.*
From #YourTable A
Cross Apply (
Select Col1 = xDim.value('/x[1]','float')
,Col2 = xDim.value('/x[2]','float')
,Col3 = xDim.value('/x[3]','float')
,Col4 = xDim.value('/x[4]','float')
From (Select Cast('<x>' + replace((Select replace(A.Col,'/','§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml) as xDim) as A
) B
Returns
ID Col1 Col2 Col3 Col4
1 1270.8 847.2 254.16 106.9
EDIT - If 2012+, and just to be super-duper safe
Select A.ID
,B.*
From #YourTable A
Cross Apply (
Select Col1 = try_convert(float,xDim.value('/x[1]','varchar(100)'))
,Col2 = try_convert(float,xDim.value('/x[2]','varchar(100)'))
,Col3 = try_convert(float,xDim.value('/x[3]','varchar(100)'))
,Col4 = try_convert(float,xDim.value('/x[4]','varchar(100)'))
From (Select Cast('<x>' + replace((Select replace(A.Col,'/','§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml) as xDim) as A
) B
If you are using SQL Server 2016, you can use String_Split()
;with cte as (
select RowN = row_number() over(order by (SELECT NULL)), * from string_split('1270.8/847.2/254.16/106.9','/')
) select * from cte
pivot (max(value) for RowN in ([1],[2],[3],[4])) p
If you are using less than SQL Server 2016 version then you might require to use custom split functions... Many ways to write custom split function one easier way is to write using xml
CREATE Function dbo.udf_split( #str varchar(max), #delimiter as varchar(5) )
RETURNS #retTable Table
( RowN int,
value varchar(max)
)
AS
BEGIN
DECLARE #xml as xml
SET #xml = cast(('<X>'+replace(#str,#delimiter ,'</X><X>')+'</X>') as xml)
INSERT INTO #retTable
SELECT RowN = Row_Number() over (order by (SELECT NULL)), N.value('.', 'varchar(MAX)') as value FROM #xml.nodes('X') as T(N)
RETURN
END
--Your query
;with cte as (
select * from udf_split('1270.8/847.2/254.16/106.9','/')
) select * from cte
pivot (max(value) for RowN in ([1],[2],[3],[4])) p
But mine is similar to John's solution... Just now only looking at that
If you are using in value in a table then you can use cross apply as below
create table #t (v varchar(50), i int)
insert into #t (v, i) values ('1270.8/847.2/254.16/106.9',1)
,('847.222/254.33/106.44',2)
select * from #t t cross apply string_split(t.v, '/')
create table #t (v varchar(50), i int)
insert into #t (v, i) values ('1270.8/847.2/254.16/106.9',1)
,('847.222/254.33/106.44',2)
--Just to get all the values
select * from #t t cross apply string_split(t.v, '/')
--Inorder to get into same row -pivoting the data
select * from (
select * from #t t cross apply (select RowN=Row_Number() over (Order by (SELECT NULL)), value from string_split(t.v, '/') ) d) src
pivot (max(value) for src.RowN in([1],[2],[3],[4])) p

Split string and divide value

I got a table Test with columns A and B.
The A column contains different values in one entry, e.g. 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, e.g. 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.