How can I separate dynamic data (different lengths) with a substring? - sql

I need some assistance on separating data based on dynamic ranges with a start and end point.
For example, a string that looks like this:
N=Chris,,Lane,,M,,
N=Alen,,Smith,,E,,
N= is static and always the same. I can start a substring after the equals. The name on the other hand is dynamic and always changing in length, BUT there is a terminator with the commas. How do I select each segment of this column with SQL where Chris, Lane and M can be displayed in separate columns for First, Last and Middle name?

Adapting this example
SELECT p.id, REPLACE(p.[1], 'N=', '') Col1, p.[2] Col2, p.[3] Col3, p.[4] Col4, p.[5] Col5, p.[6] Col6, p.[7] Col7
FROM (
SELECT id, substring(d, start + 2, endPos - Start - 2) token, row_number() OVER (
PARTITION BY id ORDER BY start
) n
FROM (
SELECT id, d, n start, charindex(',', d, n + 2) endPos
FROM num
CROSS JOIN (
SELECT id, ',' + d + ',' d
FROM #m
) m
WHERE n < len(d) - 1
AND substring(d, n + 1, 1) = ','
) d
) pvt
Pivot(max(token) FOR n IN ([1], [2], [3], [4], [5], [6], [7])) p
demo

It's not pretty, but you can do parsing like this with charindex and locate:
select
substring(text, 3, charindex(',', text) - 3) firstName,
substring(text, charindex(',', text, charindex(',', text) + 1) + 1, charindex(',', text, charindex(',', text, charindex(',', text) + 1) + 1) - charindex(',', text, charindex(',', text) + 1) - 1) lastName,
substring(text, charindex(',', text, charindex(',', text, charindex(',', text, charindex(',', text) + 1) + 1) + 1) + 1, charindex(',', text, charindex(',', text, charindex(',', text, charindex(',', text, charindex(',', text) + 1) + 1) + 1) + 1) - charindex(',', text, charindex(',', text, charindex(',', text, charindex(',', text) + 1) + 1) + 1) - 1) middleName,
from
t
SQLFiddle here

Sounds like a job for.... split function!!! fn_split()
http://technet.microsoft.com/en-us/library/aa496058(v=sql.80).aspx
CREATE FUNCTION dbo.fn_Split(#text varchar(8000), #delimiter varchar(20) = ' ')
RETURNS #Strings TABLE
(
position int IDENTITY PRIMARY KEY,
value varchar(8000)
)
AS
BEGIN
DECLARE #index int
SET #index = -1
WHILE (LEN(#text) > 0)
BEGIN
SET #index = CHARINDEX(#delimiter , #text)
IF (#index = 0) AND (LEN(#text) > 0)
BEGIN
INSERT INTO #Strings VALUES (#text)
BREAK
END
IF (#index >= 1)
BEGIN
INSERT INTO #Strings VALUES (LEFT(#text, #index - 1))
SET #text = RIGHT(#text, (LEN(#text) - #index))
END
ELSE
SET #text = RIGHT(#text, (LEN(#text) - #index))
END
RETURN
END
GO
SELECT * FROM dbo.fn_Split('N=Chris,,,,,Lane,,M,,',',')
or you can pivot to your liking....
SELECT [1] AS FirstName,[2],[3] AS LastName,[4],[5] AS MiddleInitial,[6]
FROM (
SELECT POSITION, VALUE
FROM dbo.fn_Split('N=Chris,,Lane,,M,,',',')
) p
PIVOT (MAX(VALUE) FOR POSITION IN ([1],[2],[3],[4],[5],[6])) AS pvt
Didn't want to do all the work but the downvote upset me... I don't know why it feel so personal when I'm downvoted.... I need to work on that. =(

Related

Extract email address from string using tsql

I'm trying to extract email addresses from an existing comments field and put it into its own column. The string may be something like this "this is an example comment with an email address of someemail#domain.org" or just literally the email itself "someemail#domain.org".
I figure the best thing to do would be to find the index of the '#' symbol and search in both directions until either the end of the string was hit or there was a space. Can anyone help me out with this implementation?
I know wewesthemenace already answered the question, but his/her solution seems over complicated. Why concatenate the left and right sides of the email address together? I'd rather just find the beginning and the end of the email address and then use substring to return the email address like so:
My Table
DECLARE #Table TABLE (comment NVARCHAR(50));
INSERT INTO #Table
VALUES ('blah MyEmailAddress#domain.org'), --At the end
('blah MyEmailAddress#domain.org blah blah'), --In the middle
('MyEmailAddress#domain.org blah'), --At the beginning
('no email');
Actual Query:
SELECT comment,
CASE
WHEN CHARINDEX('#',comment) = 0 THEN NULL
ELSE SUBSTRING(comment,beginningOfEmail,endOfEmail-beginningOfEmail)
END email
FROM #Table
CROSS APPLY (SELECT CHARINDEX(' ',comment + ' ',CHARINDEX('#',comment))) AS A(endOfEmail)
CROSS APPLY (SELECT DATALENGTH(comment)/2 - CHARINDEX(' ',REVERSE(' ' + comment),CHARINDEX('#',REVERSE(' ' + comment))) + 2) AS B(beginningOfEmail)
Results:
comment email
-------------------------------------------------- --------------------------------------------------
blah MyEmailAddress#domain.org MyEmailAddress#domain.org
blah MyEmailAddress#domain.org blah blah MyEmailAddress#domain.org
MyEmailAddress#domain.org blah MyEmailAddress#domain.org
no email NULL
You can search for '#' in the string. Then you get the string at the LEFT and RIGHT side of '#'. You then want to REVERSE the LEFT side and get first occurrence of ' ' then get the SUBSTRING from there. Then REVERSE it to get the original form. Same principle apply to the RIGHT side without doing REVERSE.
Example string: 'some text someemail#domain.org some text'
LEFT = 'some text someemail'
RIGHT = '#domain.org some text'
Reverse LEFT = 'liameemos txet emos'
SUBSTRING up to the first space = 'liameemos'
REVERSE(4) = someemail
SUBSTRING (2) up to the first space = '#domain.org'
Combine 5 and 6 = 'someemail#domain.org'
Your query would be:
;WITH CteEmail(email) AS(
SELECT 'someemail#domain.org' UNION ALL
SELECT 'some text someemail#domain.org some text' UNION ALL
SELECT 'no email'
)
,CteStrings AS(
SELECT
[Left] = LEFT(email, CHARINDEX('#', email, 0) - 1),
Reverse_Left = REVERSE(LEFT(email, CHARINDEX('#', email, 0) - 1)),
[Right] = RIGHT(email, CHARINDEX('#', email, 0) + 1)
FROM CteEmail
WHERE email LIKE '%#%'
)
SELECT *,
REVERSE(
SUBSTRING(Reverse_Left, 0,
CASE
WHEN CHARINDEX(' ', Reverse_Left, 0) = 0 THEN LEN(Reverse_Left) + 1
ELSE CHARINDEX(' ', Reverse_Left, 0)
END
)
)
+
SUBSTRING([Right], 0,
CASE
WHEN CHARINDEX(' ', [Right], 0) = 0 THEN LEN([Right]) + 1
ELSE CHARINDEX(' ', [Right], 0)
END
)
FROM CteStrings
Sample Data:
email
----------------------------------------
someemail#domain.org
some text someemail#domain.org some text
no email
Result
---------------------
someemail#domain.org
someemail#domain.org
Stephan's answer is great when looking for a single email address in each row.
However, I was running into this error when trying to get multiple email addresses in each row:
Invalid length parameter passed to the LEFT or SUBSTRING function
I used this answer from DBA Stack Exchange to get all of the positions of # inside the string. It entails a table-valued function that returns the number of positions equal to the number a certain pattern inside the string. I also had to modify the CROSS APPLY functions to handle multiple email addresses as well.
My Table:
DECLARE #Table TABLE (comment VARCHAR(500));
INSERT INTO #Table (comment)
VALUES ('blah blah My.EmailAddress#domain.org more blah someemailaddress#domain.com even more blah asdf#gmail.com'),
('blah hello.world#domain.org more'),
('no email')
Table-valued Function:
CREATE FUNCTION dbo.fnFindPatternLocation
(
#string NVARCHAR(MAX),
#term NVARCHAR(255)
)
RETURNS TABLE
AS
RETURN
(
SELECT pos = Number - LEN(#term)
FROM (SELECT Number, Item = LTRIM(RTRIM(SUBSTRING(#string, Number,
CHARINDEX(#term, #string + #term, Number) - Number)))
FROM (SELECT ROW_NUMBER() OVER (ORDER BY [object_id])
FROM sys.all_objects) AS n(Number)
WHERE Number > 1 AND Number <= CONVERT(INT, LEN(#string))
AND SUBSTRING(#term + #string, Number, LEN(#term)) = #term
) AS y);
GO
Query:
SELECT comment, pos, SUBSTRING(comment,beginningOfEmail,endOfEmail-beginningOfEmail) AS email
FROM #Table
CROSS APPLY (SELECT pos FROM dbo.fnFindPatternLocation(comment, '#')) AS A(pos)
CROSS APPLY (SELECT CHARINDEX(' ',comment + ' ', pos)) AS B(endOfEmail)
CROSS APPLY (SELECT pos - CHARINDEX(' ', REVERSE(SUBSTRING(comment, 1, pos))) + 2) AS C(beginningOfEmail)
Results:
comment
---------------------------------------------------------------------------------------------------------
blah blah My.EmailAddress#domain.org more blah someemailaddress#domain.com even more blah asdf#gmail.com
blah blah My.EmailAddress#domain.org more blah someemailaddress#domain.com even more blah asdf#gmail.com
blah blah My.EmailAddress#domain.org more blah someemailaddress#domain.com even more blah asdf#gmail.com
blah hello.world#domain.org more
pos email
--- ------------------------------
26 My.EmailAddress#domain.org
64 someemailaddress#domain.com
95 asdf#gmail.com
17 hello.world#domain.org
DECLARE #t TABLE (row_id INT, email VARCHAR(100))
INSERT #t (row_id, email)
VALUES (1, 'drgkls<ivan#gvi.ru>, info#gvi.com, # dgh507-16-65#'),
(2, 'hjshfkjshfj#kjs.kjsehf herwfjewr#kjsd.com adjfhja#.com u3483dhj#hhb#.dfj'),
(3, 'kjsdghfjs4254.23detygh#jhjdfg.dgb лдоврывплдоо isgfsi# klsdfksdl#,dd.com')
DECLARE #pat VARCHAR(100) = '%[^a-z0-9#._ ]%';
WITH f AS (
SELECT row_id,
CAST(' ' + email + ' ' AS VARCHAR(102)) email,
SUBSTRING(email, PATINDEX(#pat, email), 1) bad,
PATINDEX(#pat, email) pat
FROM #t
UNION ALL
SELECT row_id,
CAST(REPLACE(email, bad, ' ') AS VARCHAR(102)),
SUBSTRING(REPLACE(email, bad, ' '), PATINDEX(#pat, REPLACE(email, bad, ' ')), 1) bad,
PATINDEX(#pat, REPLACE(email, bad, ' '))
FROM f
WHERE PATINDEX(#pat, email) > 0
),
s AS
(
SELECT row_id,
email, PATINDEX('%#%', email) pos
FROM f
WHERE pat = 0
AND PATINDEX('%#%', email) > 0
UNION ALL
SELECT row_id,
SUBSTRING(email, pos + 1, 102),
PATINDEX('%#%', SUBSTRING(email, pos + 1, 102))
FROM s
WHERE PATINDEX('%#%', SUBSTRING(email, pos + 1, 102)) > 0
)
SELECT row_id, o1 + pp
FROM s
CROSS APPLY (SELECT REVERSE(LEFT(email, pos -1)) s1) x
CROSS APPLY (SELECT CHARINDEX(' ', s1) i1) y
CROSS APPLY (SELECT REVERSE(LEFT(s1, i1 -1)) o1 WHERE i1 > 0) z
CROSS APPLY (SELECT CHARINDEX(' ', email, pos) i2) e
CROSS APPLY (SELECT SUBSTRING(email, pos, i2 -pos) pp WHERE i2 > pos + 1) q
WHERE LEN(o1) > 1
AND CHARINDEX('.', pp) > 0
AND PATINDEX('%#%#%', pp) = 0
AND PATINDEX('%#.%', pp) = 0
AND PATINDEX('%.', pp) = 0
This one line would also work (a bit long line though lol):
--declare #a varchar(100)
--set #a = 'a asfd saasd asdfgh#asd.com wqe z zx cxzc '
select substring(substring(#a,0,charindex('#',#a)),len(substring(#a,0,charindex('#',#a)))-charindex(' ',reverse(substring(#a,0,charindex('#',#a))))+2,len(substring(#a,0,charindex('#',#a)))) + substring(substring(#a,charindex('#',#a),len(#a)),0,charindex(' ',substring(#a,charindex('#',#a),len(#a))))
For strings that contain new line characters I modified Felix's answer using PATINDEX to search for the first control character rather than white space.
I also had to modify the Right field to subtract the correct amount of text.
WITH CteEmail(email) AS(
SELECT 'example string with new lines
Email: some.example#email.address.com
(first email address - should be returned)
Email: another#test.co.uk
(other email addresses should be ignored
more example text' UNION ALL
SELECT 'Email: some.example#email.address.com' UNION ALL
SELECT 'someemail#domain.org' UNION ALL
SELECT 'some text someemail#domain.org some text' UNION ALL
SELECT 'no email'
)
,CteStrings AS(
SELECT
[Left] = LEFT(email, CHARINDEX('#', email, 0) - 1),
Reverse_Left = REVERSE(LEFT(email, CHARINDEX('#', email, 0) - 1)),
[Right] = RIGHT(email, LEN(email) - CHARINDEX('#', email, 0) + 1 )
FROM CteEmail
WHERE email LIKE '%#%'
)
SELECT *,
REVERSE(
SUBSTRING(Reverse_Left, 0,
CASE
WHEN PATINDEX('%[' + CHAR(10)+'- ]%', Reverse_Left) = 0 THEN LEN(Reverse_Left) + 1
ELSE PATINDEX('%[' + CHAR(0)+'- ]%', Reverse_Left)
END
)
)
+
SUBSTRING([Right], 0,
CASE
WHEN PATINDEX('%[' + CHAR(0)+'- ]%', [Right]) = 0 THEN LEN([Right]) + 1
ELSE PATINDEX('%[' + CHAR(0)+'- ]%', [Right])
END
)
FROM CteStrings
If you need it in a function then this works for me...
CREATE FUNCTION [dbo].[extractEmail]
(
#input nvarchar(500)
)
RETURNS nvarchar(100)
AS
BEGIN
DECLARE #atPosition int
DECLARE #firstRelevantSpace int
DECLARE #name nvarchar(100)
DECLARE #secondRelelvantSpace int
DECLARE #everythingAfterAt nvarchar(500)
DECLARE #domain nvarchar(100)
DECLARE #email nvarchar(100) = ''
IF CHARINDEX('#', #input,0) > 0
BEGIN
SET #input = ' ' + #input
SET #atPosition = CHARINDEX('#', #input, 0)
SET #firstRelevantSpace = CHARINDEX(' ',REVERSE(LEFT(#input, CHARINDEX('#', #input, 0) - 1)))
SET #name = REVERSE(LEFT(REVERSE(LEFT(#input, #atPosition - 1)),#firstRelevantSpace-1))
SET #everythingAfterAt = SUBSTRING(#input, #atPosition,len(#input)-#atPosition+1)
SET #secondRelelvantSpace = CHARINDEX(' ',#everythingAfterAt)
IF #secondRelelvantSpace = 0
SET #domain = #everythingAfterAt
ELSE
SET #domain = LEFT(#everythingAfterAt, #secondRelelvantSpace)
SET #email = #name + #domain
END
RETURN #email
END
Using Cymorg's Function: I ran into an issue where my data included CR/LF and it prevented the Function from working 100%. It was tough to figure out because, when using the function in a select statement, it would return occasionally incorrect results. If I copied the offending text from my query results and invoked the function using sql print with the text in quotes it would work fine. Inconceivable!
After much trial and error, I used sql replace to replace the CR/LF with spaces and huzza! I am an excellent guesser.
select extractEmail(replace(replace(MyColumn,CHAR(10),' '),CHAR(13),' ')) as AsYouWish from FacilityContacts

Hot to convert a variable with value '1,2,3' to a table (every number as a record)

Working on SQL (2005 and 2008)
the variable with value '1,2,3' would be call #cedis and this could to have N number for example
set #cedis='1' or set #cedis='1,2,3,4,5,6,7' or set #cedis='125,98,91'
so important, its this must to be a select only, a loop could not to be use, only a select!
this must to return a (result as ) table with values for example
set #cedis='1,2,3,4' this must to return a result
number 1 2 3 4
declare #cedis varchar(max)
set #cedis='1,58,123,8'
;with datos as
(
my select with is going to return me the table
)
select * from datos
result set is
number
1
58
123
8
If am not wrong this is what you need
DECLARE #cedis VARCHAR(500)='1,2,3,4'
SELECT Split.a.value('.', 'VARCHAR(100)') Numbers
FROM (SELECT Cast ('<M>' + Replace(#cedis, ',', '</M><M>') + '</M>' AS XML) AS Numbers) AS A
CROSS APPLY Numbers.nodes ('/M') AS Split(a)
Result:
Numbers
-------
1
2
3
4
A table valued function would do it.
CREATE FUNCTION [dbo].[fn_Split](#text VARCHAR(MAX), #delimiter VARCHAR(5) = ',')
RETURNS #Strings TABLE
(
position int IDENTITY PRIMARY KEY,
value VARCHAR(8000)
)
AS
BEGIN
DECLARE #index int
SET #index = -1
WHILE (LEN(#text) > 0)
BEGIN
SET #index = CHARINDEX(#delimiter , #text)
IF (#index = 0) AND (LEN(#text) > 0)
BEGIN
INSERT INTO #Strings VALUES (#text)
BREAK
END
IF (#index > 1)
BEGIN
INSERT INTO #Strings VALUES (LEFT(#text, #index - 1))
END
SET #text = RIGHT(#text, (LEN(#text) - (#index+LEN(#delimiter)-1)))
END
RETURN
END
You can call it as follows:
SELECT *
FROM dbo.fn_Split(#cedis,',')
Here is a more generic solution that breaks any given string into a table based on any given separator:
http://rextester.com/VSRDLS48817
Not an original idea, but I've found it very useful.
create function [dbo].[SplitString]
(
#str nvarchar(255),
#separator char(1)
)
returns table
AS
return (
with tokens(p, a, b) AS (
select
cast(1 as int),
cast(1 as int),
charindex(#separator, #str)
union all
select
p + 1,
b + 1,
charindex(#separator, #str, b + 1)
from tokens
where b > 0
)
select
p-1 ItemIndex,
substring(
#str,
a,
case when b > 0 then b-a ELSE LEN(#str) end)
AS Item
from tokens
);
This is another one approach to get required output result
DECLARE #cedis VARCHAR(MAX) ,
#delimeter VARCHAR(10)
SET #cedis = '1,58,123,8,14144,15,155231,15,3647,2347,45,76,68,2354,577,5'
SET #delimeter = ','
SET #cedis = #cedis + #delimeter;
WITH datos
AS ( SELECT n = 1
UNION ALL
SELECT n + 1
FROM datos
WHERE n <= LEN(#cedis)
),
cte
AS ( SELECT T.N ,
ROW_NUMBER() OVER ( ORDER BY T.N ) AS RN
FROM datos AS T
WHERE SUBSTRING(#cedis, T.N, LEN(#delimeter)) = #delimeter
AND LEN(#cedis) >= T.N
)
SELECT SUBSTRING(#cedis, COALESCE(R.N + LEN(#delimeter), 1),
L.N - COALESCE(R.N + LEN(#delimeter), 1)) AS part ,
L.RN AS ID
FROM cte AS L
LEFT JOIN cte AS R ON L.RN = R.RN + 1
OPTION ( MAXRECURSION 1000 )

T-SQL split string based on delimiter

I have some data that I would like to split based on a delimiter that may or may not exist.
Example data:
John/Smith
Jane/Doe
Steve
Bob/Johnson
I am using the following code to split this data into First and Last names:
SELECT SUBSTRING(myColumn, 1, CHARINDEX('/', myColumn)-1) AS FirstName,
SUBSTRING(myColumn, CHARINDEX('/', myColumn) + 1, 1000) AS LastName
FROM MyTable
The results I would like:
FirstName---LastName
John--------Smith
Jane--------Doe
Steve-------NULL
Bob---------Johnson
This code works just fine as long as all the rows have the anticipated delimiter, but errors out when a row does not:
"Invalid length parameter passed to the LEFT or SUBSTRING function."
How can I re-write this to work properly?
May be this will help you.
SELECT SUBSTRING(myColumn, 1, CASE CHARINDEX('/', myColumn)
WHEN 0
THEN LEN(myColumn)
ELSE CHARINDEX('/', myColumn) - 1
END) AS FirstName
,SUBSTRING(myColumn, CASE CHARINDEX('/', myColumn)
WHEN 0
THEN LEN(myColumn) + 1
ELSE CHARINDEX('/', myColumn) + 1
END, 1000) AS LastName
FROM MyTable
For those looking for answers for SQL Server 2016+. Use the built-in STRING_SPLIT function
Eg:
DECLARE #tags NVARCHAR(400) = 'clothing,road,,touring,bike'
SELECT value
FROM STRING_SPLIT(#tags, ',')
WHERE RTRIM(value) <> '';
Reference: https://msdn.microsoft.com/en-nz/library/mt684588.aspx
Try filtering out the rows that contain strings with the delimiter and work on those only like:
SELECT SUBSTRING(myColumn, 1, CHARINDEX('/', myColumn)-1) AS FirstName,
SUBSTRING(myColumn, CHARINDEX('/', myColumn) + 1, 1000) AS LastName
FROM MyTable
WHERE CHARINDEX('/', myColumn) > 0
Or
SELECT SUBSTRING(myColumn, 1, CHARINDEX('/', myColumn)-1) AS FirstName,
SUBSTRING(myColumn, CHARINDEX('/', myColumn) + 1, 1000) AS LastName
FROM MyTable
WHERE myColumn LIKE '%/%'
SELECT CASE
WHEN CHARINDEX('/', myColumn, 0) = 0
THEN myColumn
ELSE LEFT(myColumn, CHARINDEX('/', myColumn, 0)-1)
END AS FirstName
,CASE
WHEN CHARINDEX('/', myColumn, 0) = 0
THEN ''
ELSE RIGHT(myColumn, CHARINDEX('/', REVERSE(myColumn), 0)-1)
END AS LastName
FROM MyTable
ALTER FUNCTION [dbo].[split_string](
#delimited NVARCHAR(MAX),
#delimiter NVARCHAR(100)
) RETURNS #t TABLE (id INT IDENTITY(1,1), val NVARCHAR(MAX))
AS
BEGIN
DECLARE #xml XML
SET #xml = N'<t>' + REPLACE(#delimited,#delimiter,'</t><t>') + '</t>'
INSERT INTO #t(val)
SELECT r.value('.','varchar(MAX)') as item
FROM #xml.nodes('/t') as records(r)
RETURN
END
I just wanted to give an alternative way to split a string with multiple delimiters, in case you are using a SQL Server version under 2016.
The general idea is to split out all of the characters in the string, determine the position of the delimiters, then obtain substrings relative to the delimiters. Here is a sample:
-- Sample data
DECLARE #testTable TABLE (
TestString VARCHAR(50)
)
INSERT INTO #testTable VALUES
('Teststring,1,2,3')
,('Test')
DECLARE #delimiter VARCHAR(1) = ','
-- Generate numbers with which we can enumerate
;WITH Numbers AS (
SELECT 1 AS N
UNION ALL
SELECT N + 1
FROM Numbers
WHERE N < 255
),
-- Enumerate letters in the string and select only the delimiters
Letters AS (
SELECT n.N
, SUBSTRING(t.TestString, n.N, 1) AS Letter
, t.TestString
, ROW_NUMBER() OVER ( PARTITION BY t.TestString
ORDER BY n.N
) AS Delimiter_Number
FROM Numbers n
INNER JOIN #testTable t
ON n <= LEN(t.TestString)
WHERE SUBSTRING(t.TestString, n, 1) = #delimiter
UNION
-- Include 0th position to "delimit" the start of the string
SELECT 0
, NULL
, t.TestString
, 0
FROM #testTable t
)
-- Obtain substrings based on delimiter positions
SELECT t.TestString
, ds.Delimiter_Number + 1 AS Position
, SUBSTRING(t.TestString, ds.N + 1, ISNULL(de.N, LEN(t.TestString) + 1) - ds.N - 1) AS Delimited_Substring
FROM #testTable t
LEFT JOIN Letters ds
ON t.TestString = ds.TestString
LEFT JOIN Letters de
ON t.TestString = de.TestString
AND ds.Delimiter_Number + 1 = de.Delimiter_Number
OPTION (MAXRECURSION 0)
The examples above work fine when there is only one delimiter, but it doesn't scale well for multiple delimiters. Note that this will only work for SQL Server 2016 and above.
/*Some Sample Data*/
DECLARE #mytable TABLE ([id] VARCHAR(10), [name] VARCHAR(1000));
INSERT INTO #mytable
VALUES ('1','John/Smith'),('2','Jane/Doe'), ('3','Steve'), ('4','Bob/Johnson')
/*Split based on delimeter*/
SELECT P.id, [1] 'FirstName', [2] 'LastName', [3] 'Col3', [4] 'Col4'
FROM(
SELECT A.id, X1.VALUE, ROW_NUMBER() OVER (PARTITION BY A.id ORDER BY A.id) RN
FROM #mytable A
CROSS APPLY STRING_SPLIT(A.name, '/') X1
) A
PIVOT (MAX(A.[VALUE]) FOR A.RN IN ([1],[2],[3],[4],[5])) P
These all helped me get to this. I am still on 2012 but now have something quick that will allow me to split a string, even if string has varying numbers of delimiters, and grab the nth substring from that string. It's quick too. I know this post is old, but it took me forever to find something so hopefully this will help someone else.
CREATE FUNCTION [dbo].[SplitsByIndex]
(#separator VARCHAR(20) = ' ',
#string VARCHAR(MAX),
#position INT
)
RETURNS VARCHAR(MAX)
AS
BEGIN
DECLARE #results TABLE
(id INT IDENTITY(1, 1),
chrs VARCHAR(8000)
);
DECLARE #outResult VARCHAR(8000);
WITH X(N)
AS (SELECT 'Table1'
FROM(VALUES(0), (0), (0), (0), (0), (0), (0), (0), (0), (0), (0), (0), (0), (0), (0), (0)) T(C)),
Y(N)
AS (SELECT 'Table2'
FROM X A1,
X A2,
X A3,
X A4,
X A5,
X A6,
X A7,
X A8), -- Up to 16^8 = 4 billion
T(N)
AS (SELECT TOP (ISNULL(LEN(#string), 0)) ROW_NUMBER() OVER(
ORDER BY
(
SELECT NULL
)) - 1 N
FROM Y),
Delim(Pos)
AS (SELECT t.N
FROM T
WHERE(SUBSTRING(#string, t.N, LEN(#separator + 'x') - 1) LIKE #separator
OR t.N = 0)),
Separated(value)
AS (SELECT SUBSTRING(#string, d.Pos + LEN(#separator + 'x') - 1, LEAD(d.Pos, 1, 2147483647) OVER(
ORDER BY
(
SELECT NULL
))-d.Pos - LEN(#separator))
FROM Delim d
WHERE #string IS NOT NULL)
INSERT INTO #results(chrs)
SELECT s.value
FROM Separated s
WHERE s.value <> #separator;
SELECT #outResult =
(
SELECT chrs
FROM #results
WHERE id = #position
);
RETURN #outResult;
END;
This can be used like this:
SELECT [dbo].[SplitsByIndex](' ',fieldname,2)
from tablename
I would protect the substring operation by always appending a delimiter to the test strings. This makes the parsing much simpler. Your code may now rely on finding the right pattern, and not need to cope with special cases.
SELECT SUBSTRING(myColumn + '/', 1, CHARINDEX('/', myColumn)-1) AS FirstName,
SUBSTRING(myColumn + '/', CHARINDEX('/', myColumn) + 1, 1000) AS LastName
FROM MyTable
It eliminates edge cases and conditionals and cases.
Always add an extra delimiter at the end, then the challenge case is no problem.

How to split dash-separated values in SQL Server?

How do I get only the middle part of the data in my table? I tried the following code, but this only removes the right part... my output should only be middle part.
For instance when I select the data 1-021514-1 the output should be 021514 without the left and right dashes
select LEFT(ticketid, CHARINDEX('-', ticketid + '-') + 4)
from Table
My Data is:
|TicketID |
------------
|1-021514-1 |
|10-021514-1|
|2-021514-1 |
|4-021414-1 |
Please try:
SELECT
LEFT(st, CHARINDEX('-', st)-1) TicketID
from
(
SELECT
SUBSTRING(TicketID, CHARINDEX('-',TicketID)+1, 10000) st
FROM Table
)x
Try this
with t as (select TicketID as val)
select t.*,
LEFT(val, charindex('-', val) - 1),
SUBSTRING(val, charindex('-', val)+1, len(val) - CHARINDEX('-', reverse(val)) - charindex('-', val)),
REVERSE(LEFT(reverse(val), charindex('-', reverse(val)) - 1))
from t;
(Or)
Use below Function
CREATE FUNCTION dbo.SplitStrings_CTE(#List nvarchar(max), #Delimiter nvarchar(1))
RETURNS #returns TABLE(val nvarchar(max), [level] int, PRIMARY KEY CLUSTERED([level]))
AS
BEGIN
;WITH cte AS
(
SELECT SUBSTRING(#List, 0, CHARINDEX(#Delimiter, #List)) AS val,
CAST(STUFF (#List + #Delimiter, 1, CHARINDEX(#Delimiter, #List), '') AS nvarchar(max)) AS stval,
1 AS [level]
UNION ALL
SELECT SUBSTRING(stval, 0, CHARINDEX(#Delimiter, stval)),
CAST(STUFF (stval, 1, CHARINDEX(#Delimiter, stval), '') AS nvarchar(max)),
[level] + 1
FROM cte
WHERE stval != ''
)
INSERT #returns
SELECT REPLACE(val, ' ', '') AS val, [level]
FROM cte
RETURN
END
hi Ron try this,
declare #string varchar(25)='1-021514-1'
declare #val varchar(25)
SELECT #val= SUBSTRING(#string, CHARINDEX('-', #string)+1, ((CHARINDEX('-',#string,(charindex('-',#string)+1))-CHARINDEX('-', #string))-1))
select #val
Try this:
select right(left(ticketid, charindex('-', ticketid, charindex('-', ticketid, 0) + 1) - 1), len(left(ticketid, charindex('-', ticketid, charindex('-', ticketid, 0) + 1) - 1)) - charindex('-', left(ticketid, charindex('-', ticketid, charindex('-', ticketid, 0) + 1) - 1), 0)) from Table
Try this
SELECT STUFF(
STUFF(TicketID,1,CHARINDEX('-',TicketID,1),'')
,CHARINDEX('-',STUFF(TicketID,1,CHARINDEX('-',TicketID,1),''),1)
,LEN(TicketID)
,'')
from Table1

sql query to get word between characters

I have problem where I have a string and I need to get a specific part of it.
For example:
\Stack\Over\Programming\Users\
I need "Programming" from the above string.
DECLARE #TExt NVARCHAR(MAX)= '\Stack\Over\Programming\Users\'
DECLARE #Delimiter VARCHAR(1000)= '\' ;
WITH numbers
AS ( SELECT ROW_NUMBER() OVER ( ORDER BY o.object_id, o2.object_id ) Number
FROM sys.objects o
CROSS JOIN sys.objects o2
),
c AS ( SELECT Number CHARBegin ,
ROW_NUMBER() OVER ( ORDER BY number ) RN
FROM numbers
WHERE SUBSTRING(#text, Number, LEN(#Delimiter)) = #Delimiter
),
res
AS ( SELECT CHARBegin ,
CAST(LEFT(#text, charbegin) AS NVARCHAR(MAX)) Res ,
RN
FROM c
WHERE rn = 1
UNION ALL
SELECT c.CHARBegin ,
CAST(SUBSTRING(#text, res.CHARBegin+1,
c.CHARBegin - res.CHARBegin-1) AS NVARCHAR(MAX)) ,
c.RN
FROM c
JOIN res ON c.RN = res.RN + 1
)
SELECT *
FROM res
Result:
CHARBegin |Res |RN
1 | \ |1
7 |Stack |2
12 |Over |3
24 |Programming |4
30 |Users |5
In your case you need last statement
SELECT * FROM res WHERE Rn=4
If it is always the word between the 3th and 4th \, following would do the trick.
DECLARE #String VARCHAR(32)
SET #String = '\Stack\Over\Programming\Users\'
SELECT SUBSTRING(
#String
, CHARINDEX('\', #String, CHARINDEX('\', #String, CHARINDEX('\', #String, 1) + 1) + 1) + 1
, CHARINDEX('\', #String, CHARINDEX('\', #String, CHARINDEX('\', #String, CHARINDEX('\', #String, 1) + 1) + 1) + 1)
- CHARINDEX('\', #String, CHARINDEX('\', #String, CHARINDEX('\', #String, 1) + 1) + 1) - 1)
you will need to create a string split function in SQL. unfortunately there is not one built into MS SQL.
How do I split a string so I can access item x?