First and foremost, read the following articles, they discuss splitting strings (and reasons not to do it), and compare performance of methods to do it if you can't avoid it.
The upshot of these three articles is (in case the links ever become dead):
- Avoid delimited lists as strings where possible, if you need to store a list, a table is much better alternative.
- If you have to do it, CLR is the most scaleable (and accurate method).
- If you can be certain there are no special XML characters to split, then converting the delimited string to XML then using XQuery to get the individual items works well.
- Otherwise, building a tally table using cross joining is the best of the rest.
The most versatile method is the last, since not everyone can use CLR, and guarantee no special XML characters, so the split method for that is:
CREATE FUNCTION [dbo].[Split]
(
@List NVARCHAR(MAX),
@Delimiter NVARCHAR(255)
)
RETURNS TABLE
WITH SCHEMABINDING AS
RETURN
( WITH N1 AS (SELECT N FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1), (1)) n (N)),
N2(N) AS (SELECT 1 FROM N1 a CROSS JOIN N1 b),
N3(N) AS (SELECT 1 FROM N2 a CROSS JOIN N2 b),
N4(N) AS (SELECT 1 FROM N3 a CROSS JOIN N3 b),
cteTally(N) AS
( SELECT 0 UNION ALL
SELECT TOP (DATALENGTH(ISNULL(@List,1))) ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM n4
),
cteStart(N1) AS
( SELECT t.N+1
FROM cteTally t
WHERE (SUBSTRING(@List,t.N,1) = @Delimiter OR t.N = 0)
)
SELECT Item = SUBSTRING(@List, s.N1, ISNULL(NULLIF(CHARINDEX(@Delimiter,@List,s.N1),0)-s.N1,8000)),
Position = s.N1,
ItemNumber = ROW_NUMBER() OVER(ORDER BY s.N1)
FROM cteStart s
);
Now that you have your Split function, you can split your first string:
DECLARE @ActivityID AS VARCHAR(MAX) = 'BKR394859607,MTP293840284,SPN489620586';
SELECT Item,
NewID = LEFT(Item, 3)
FROM dbo.Split(@ActivityID, ',');
Which gives you:
Item NewID
-----------------------------
BKR394859607 BKR
MTP293840284 MTP
SPN489620586 SPN
Then you can concatenate this back up using FOR XML PATH()
:
DECLARE @ActivityID AS VARCHAR(MAX) = 'BKR394859607,MTP293840284,SPN489620586';
SELECT STUFF(( SELECT ',' + LEFT(Item, 3)
FROM dbo.Split(@ActivityID, ',')
FOR XML PATH(''), TYPE).value('.', 'VARCHAR(MAX)'), 1, 1, '');
For more on how this works see this answer.
The optimal solution would probably be to have a user defined table type to store string lists:
CREATE TYPE dbo.StringList AS TABLE (Value VARCHAR(MAX));
Then rather than building a delimited string, build up a table:
DECLARE @Activity dbo.StringList;
INSERT @Activity (Value)
VALUES ('BKR394859607'), ('MTP293840284'), ('SPN489620586');
Then you avoid a painful split, and can manipulate each individual record much mor easily.
If you really did need to get a new delimited string, then you can use the same logic as above:
DECLARE @Activity dbo.StringList;
INSERT @Activity (Value)
VALUES ('BKR394859607'), ('MTP293840284'), ('SPN489620586');
SELECT STUFF(( SELECT ',' + LEFT(Value, 3)
FROM @Activity
FOR XML PATH(''), TYPE).value('.', 'VARCHAR(MAX)'), 1, 1, '');