Spliting a column into multiple columns in SQL Server - sql

I am working with a column called FullName which stores a lengthy value.
The format is something like
'Microsoft.SQL.Server.20XX.DBFile:ABC.edf.com;XXXX_XXX_XXX;master;1;1'
'SQLVersion.DBFile:Hostname;InstanceName;Dummy;Dummy;Dummy'
What I want is to split FullName into SQLVersion, Hostname and InstanceName.
I have searched a sort of threads about splitting values which separate by a dot or a colon, which is slightly different with my case.

You can use the following trick with cross apply. I think the code is self explanatory and doesn't need elaboration:
create table t(v varchar(200))
insert into t values
('Microsoft.SQL.Server.20XX.DBFile:ABC.edf.com;XXXX_XXX_XXX;master;1;1'),
('SQLVersion.DBFile:Hostname;InstanceName;Dummy;Dummy;Dummy')
select substring(v, 1, c1.i1 - 1) as SqlVersion,
substring(v, c1.i1 + 1, c2.i2 - c1.i1 - 1) as HostName,
substring(v, c2.i2 + 1, c3.i3 - c2.i2 - 1) as InstanceName
from t
cross apply(select charindex(':', t.v) as i1 ) c1
cross apply(select charindex(';', t.v, c1.i1 + 1) as i2) c2
cross apply(select charindex(';', t.v, c2.i2 + 1) as i3) c3
In case it is not clear. In first cross apply I am selecting index of symbol :. In second cross apply I am selecting index of symbol ; that is after the index of first cross apply. In third the index of symbol ; that is after the index of second cross apply. In main select I just use those indeces to grab the needed portions of string.
Fiddle here http://sqlfiddle.com/#!3/d79b45/16

I agree with the comment above that separate columns would be preferred, if possible. However, I believe this parsing logic is doing what you want:
declare
#test nvarchar(200)
, #versionStart int
, #versionLength int
, #hostStart int
, #hostLength int
, #instanceStart int
, #instanceLength int
SET #test = 'Microsoft.SQL.Server.20XX.DBFile:ABC.edf.com;XXXX_XXX_XXX;master;1;1'
SET #versionStart = PATINDEX('%Microsoft.SQL.Server.%', #test) + 21
SET #versionLength = CHARINDEX('.', #test, #versionStart) - #versionStart
SET #hostStart = PATINDEX('%.DBFile:%', #test) + 8
SET #hostLength = CHARINDEX(';', #test, #hostStart) - #hostStart
SET #instanceStart = CHARINDEX(';', #test, #hostStart + #hostLength) + 1
SET #instanceLength = CHARINDEX(';', #test, #instanceStart) - #instanceStart
select
SUBSTRING(#test, #versionStart, 4) AS Version
, SUBSTRING(#test, #hostStart, #hostLength) AS HostName
, SUBSTRING(#test, #instanceStart, #instanceLength) AS InstanceName

Related

How do I combine a substring and trim right in SQL

I am trying to extract the data between two underscore characters. In some situations, the 2nd underscore may not exist.
MyFld
P_36840
U_216137
C_203134_H
C_203134_W
I tried this:
substring(i.[MyFld],
CHARINDEX ('_',i.[MyFld])+1,len(i.[MyFld])
-CHARINDEX ('_',i.[MyFld])
) [DerivedPrimaryKey]
And I get this:
DerivedPrimaryKey
36840
216137
203134_H
203134_W
https://dbfiddle.uk/uPKC6oX4
I want to remove the second underscore and data that follows it. I'm trying to combine it with a trim right, but I'm unsure where to start.
How can I do this?
We can start by simplifying what you have so far. I will also add enough to make this a complete query, so we can see it in context for later steps:
SELECT
right(i.MyFld, len(i.MyFld) - charindex('_', i.MyFld)) [DerivedPrimaryKey]
FROM I
With this much done, we can now use it as the source for removing the trailing portion of the field:
SELECT
reverse(substring(reverse(step1)
, charindex('_', reverse(step1))+1
, len(step1)
)) [DerivedPrimaryKey]
FROM (
SELECT right(i.MyFld, len(i.MyFld) - charindex('_', i.MyFld)) [step1]
FROM I
) T
Notice the layer of nesting. You can, of course, remove the nesting, but it means replicating the entire inner expression every time you see step1 (good thing I took the time to simplify it):
SELECT
reverse(substring(reverse(right(i.MyFld, len(i.MyFld) - charindex('_', i.MyFld)))
, charindex('_', reverse(right(i.MyFld, len(i.MyFld) - charindex('_', i.MyFld))))+1
, len(right(i.MyFld, len(i.MyFld) - charindex('_', i.MyFld)))
))
FROM I
And now back to just the expression:
reverse(substring(reverse(right(i.MyFld, len(i.MyFld) - charindex('_', i.MyFld)))
, charindex('_', reverse(right(i.MyFld, len(i.MyFld) - charindex('_', i.MyFld))))+1
, len(right(i.MyFld, len(i.MyFld) - charindex('_', i.MyFld)))
))
See it work here:
https://dbfiddle.uk/nFO4Vwhm
There is also this alternate expression that saves one function call:
left( right(i.MyFld,len(i.MyFld)-charindex('_',i.MyFld)),
coalesce(
nullif(
charindex('_',
right(i.MyFld,len(i.MyFld)-charindex('_',i.MyFld))
) -1, -1,
),
len( right(i.MyFld,len(i.MyFld)-charindex('_',i.MyFld)) )
)
)
Just a two more options. One using parsename() provided your data does not have more than 4 segments. The second using a JSON array
Example
Declare #YourTable Table ([MyFld] varchar(50)) Insert Into #YourTable Values
('P_36840')
,('U_216137')
,('C_203134_H')
,('C_203134_W')
Select *
,UsingParseName = reverse(parsename(reverse(replace(MyFld,'_','.')),2))
,UsingJSONValue = json_value('["'+replace(MyFld,'_','","')+'"]','$[1]')
From #You
Results
MyFld UsingParseName UsingJSONValue
P_36840 36840 36840
U_216137 216137 216137
C_203134_H 203134 203134
C_203134_W 203134 203134
We can do this:
Declare #testData Table ([MyFld] varchar(50));
Insert Into #testData (MyFld)
Values ('P_36840')
, ('U_216137')
, ('C_203134_H')
, ('C_203134_W');
Select *
, second_element = substring(v.MyFld, p1.pos, p2.pos - p1.pos - 1)
From #testData As td
Cross Apply (Values (concat(td.MyFld, '__'))) As v(MyFld) -- Make sure we have at least 2 delimiters
Cross Apply (Values (charindex('_', v.MyFld, 1) + 1)) As p1(pos) -- First Position
Cross Apply (Values (charindex('_', v.MyFld, p1.pos) + 1)) As p2(pos) -- Second Position
If you actually have a fixed number of characters in the first element, then it could be simplified to:
Select *
, second_element = substring(v.MyFld, 3, charindex('_', v.MyFld, 4) - 3)
From #testData td
Cross Apply (Values (concat(td.MyFld, '_'))) As v(MyFld)
Often I try to fake out SQL if an expected character isn't always present and I don't need the resulting value:
SELECT SUBSTRING(field_Calculated, 1, CHARINDEX('_', field_Calculated) - 1)
FROM (SELECT SUBSTRING(MyFld, CHARINDEX('_', MyFld) + 1, LEN(MyFld)) + '_' As field_Calculated
FROM MyTable) T
I think this is clear, but I really like the ParseName solution #JohnCappalletti suggests.
If it's only ever one numeric value you can use string_split:
SELECT * FROM MyTable
CROSS APPLY string_split(MyFld, '_')
WHERE ISNUMERIC(value) = 1
Either way you have to be careful of the data before deciding the best approach.
your data
Declare #Table Table ([MyFld] varchar(100))
Insert Into #Table
([MyFld] ) Values
('P_36840')
,('U_216137')
,('C_203134_H')
,('C_203134_W')
use SubString,Left and PatIndex
select
Left(
SubString(
[MyFld],
PatIndex('%[0-9.-]%', [MyFld]),
8000
),
PatIndex(
'%[^0-9.-]%',
SubString(
[MyFld],
PatIndex('%[0-9.-]%', [MyFld]),
8000
) + 'X'
)-1
) as DerivedPrimaryKey
from
#Table

SQL - Split Concatenated String into Columns after ;

I really need help with SQL. I have a database where you can find values like: abc;defg;hi and this is in just 1 column. So I wanna create 2 more colums which inserts the splited values.
For example:
Before:
Value01: abc;defg;hi
After:
Value01: abc,
Value02: defg,
Value03: hi
--Another Example would be this:--
Before:
Value01: abcd;efg;
After:
Value01: abcd,
Value02: efg,
Value03: null
So always 3 new values were created. I hope you understand my question!
Greetings
You can use string_split():
select nullif(s.value, '')
from string_split(#value, ';') s
Use WITH
WITH CTE
AS
(
SELECT [xml_val] = CAST('<t>' + REPLACE(SomeValue,';','</t><t>') + '</t>' AS XML)
FROM #yourTable
)
SELECT [SomeValue] = col.value('.','VARCHAR(100)')
FROM CTE
CROSS APPLY [xml_val].nodes('/t') CA(col)
Here is a solution that should be fairly performant:
--==== Sample Data
Declare #testData Table (Value01 varchar(255));
Insert Into #testData (Value01)
Values ('abc;defg;hi'), ('abcd;efg;');
--==== Solution using sample data from above
Select *
, Value01 = substring(td.Value01, 1, p1.pos - 2)
, Value02 = substring(td.Value01, p1.pos, p2.pos - p1.pos - 1)
, Value03 = substring(td.Value01, p2.pos, p3.pos - p2.pos - 1)
From #testData td
Cross Apply (Values (concat(td.Value01, ';;;'))) As v(Value01) --Make sure we have delimiters
Cross Apply (Values (charindex(';', v.Value01, 1) + 1)) As p1(pos)
Cross Apply (Values (charindex(';', v.Value01, p1.pos) + 1)) As p2(pos)
Cross Apply (Values (charindex(';', v.Value01, p2.pos) + 1)) As p3(pos) --End of string/element
You could also create a function that parses out the elements and then just call the function using cross apply.

How to split string with delimiter and get the first value

I have the following query where I have a table which is storing from information with a delimiter
SQL version - Microsoft SQL Azure (RTM) - 12.0.2000.8
DECLARE #commanTable TABLE
(
CommaId NVARCHAR(MAX),
Name NVARCHAR(500)
)
DECLARE #commanTable1 TABLE
(
CommaId INT,
Name NVARCHAR(500)
)
INSERT INTO #commanTable VALUES ('2324205.3933251.7336404', 'Test1'),
('2324206.3933252.7336405', 'Test2')
INSERT INTO #commanTable1 (CommaId, Name)
SELECT value, Name
FROM #commanTable CROSS APPLY STRING_SPLIT(CommaId,'.');
SELECT * FROM #commanTable1
Getting the following results
Where I need the results to be
If you only interested in the first value, you don't need to use STRING_SPLIT(). You can simply use charindex() to find the first delimiter and use left() to extract it
SELECT *, left(CommaId, charindex('.', CommaId) - 1)
FROM #commanTable
Edit : if you always wanted the nth value, you can do a cascade charindex(). Provided that the n is not too huge. Else use the function that I provided in the comment.
SELECT *,
item1 = left(CommaId, p1.p - 1),
item2 = substring(CommaId, p1.p + 1, p2.p - p1.p - 1)
FROM #commanTable t
cross apply
(
select p = charindex('.', CommaId)
) p1
cross apply
(
select p = charindex('.', CommaId, p1.p + 1)
) p2

Increasing a number in a string

There are some objects encoded as key:value strings and stored in a table, I'd like to increase sequence number of all objects, which is one field in the object.
For example:
ID Value
--------------------------
504 s:0;d:n;e:test;
506 s:1;d:y;e:branch;
507 s:2;d:y;e:;
I'd like to change them to:
ID Value
--------------------------
504 s:1;d:n;e:test;
506 s:2;d:y;e:branch;
507 s:3;d:y;e:;
Is there a simple way to do this?
Is there a simple way to do this?
No not really.
You can find the positions of s: and d: and then use that to extract the number inbetween, increase it by one and stuff it back into where it belongs.
declare #T table
(
ID int,
Value varchar(50)
);
insert into #T values
(504, 's:0;d:n;e:test;'),
(506, 's:1;d:y;e:branch;'),
(507, 's:2;d:y;e:;');
select T.ID,
stuff(T.Value, P.S, P.D - P.S - 1, S.Value) as NewValue
from #T as T
cross apply (values(charindex('s:', T.Value) + 2,
charindex('d:', T.Value))) as P(S, D)
cross apply (values(substring(T.Value, P.S, P.D - P.S - 1) + 1)) as S(Value)
A version where you find the ; after s: instead of d: as suggested by Eric in a comment.
select T.ID,
stuff(T.Value, S.Pos, SEnd.Pos - S.Pos, V.NewValue) as NewValue
from #T as T
cross apply (values(charindex('s:', T.Value) + 2)) as S(Pos)
cross apply (values(charindex(';', T.Value, S.Pos))) as SEnd(Pos)
cross apply (values(substring(T.Value, S.Pos, SEnd.Pos - S.Pos) + 1)) as V(NewValue)
DECLARE #val nvarchar(200)
SET #val = 's:1;d:y;e:branch;'
SELECT 's:' + CONVERT(nvarchar(100), CONVERT(INT, SUBSTRING(#val, charindex(':', #val) + 1, charindex(';', #val) - charindex(':', #val) -1)) + 1) + SUBSTRING(#val, charindex(':', #val),1000)
You can use what's in the SELECT's query in an UPDATE statement to change the table values
Using the split string functions from here:Split strings the right way – or the next best way
declare #string varchar(max)
set #string='504 s:0;d:n;e:test;'
;with cte as(select * from
[dbo].[SplitStrings_Numbers]
(#string,':'))
select b.item+1 from cte c
cross apply
(select * from [dbo].[SplitStrings_Numbers](c.item,';')) b
where isnumeric(b.item)=1
This accounts for empty or non-integer values; it will ignore them in the event they can't be incremented by one.
-- Build Test Data
IF OBJECT_ID('tempdb..#test') IS NOT NULL DROP TABLE #test
CREATE TABLE #test (ID INT, Value VARCHAR(100))
INSERT #test
VALUES
(504,'s:0;d:n;e:test;'),
(506,'s:1;d:y;e:branch;'),
(507,'s:2;d:y;e:;'),
(508,'s:;d:y;e:;'),
(509,'s:xyz;d:y;e:;');
-- Update S: values
WITH sVals AS
(
SELECT ID, Value, TRY_PARSE(SUBSTRING(Value,CHARINDEX('s:',Value)+2,CHARINDEX(';',Value,CHARINDEX('s:',Value))-(CHARINDEX('s:',Value)+2)) AS INT) AS sVal
FROM #test AS t
)
UPDATE s
SET Value = IIF(sVal IS NOT NULL, STUFF(Value,CHARINDEX('s:',Value)+2,CHARINDEX(';',Value,CHARINDEX('s:',Value))-(CHARINDEX('s:',Value)+2),sVal+1), Value)
FROM sVals AS s
-- Check the results
SELECT *
FROM #test
You can as the below:
DECLARE #val VARCHAR(100) = 's:12;d:n;e:test;'
SELECT REPLACE(#val, ':' + SUBSTRING(#val, 3, PATINDEX('%;d:%', #val) - 3) + ';', ':' + CAST(SUBSTRING(#val, 3, PATINDEX('%;d:%', #val) - 3)+ 1 AS VARCHAR(MAX)) + ';')
Result: s:13;d:n;e:test;

Semicolon seperated value to other column in sql server

i have a table with a column have value seperated by semi colon.
the concern is value in the column are not fixed. it starts from 1 and end upto 80 semicolon sepaeration.
i am trying to put each individual value to seperate column
SQL SERVER 2008 code
DECLARE #Table TABLE(
Val VARCHAR(50)
)
INSERT INTO #Table (Val) SELECT '2Xcalcium; kidney' union all SELECT '3XMagnessium; liver' union all SELECT '2-ECG;3XSODIUM;DIALYSIS'
SELECT *,
CAST(LEFT(Val,CHARINDEX(';',Val)-1) AS VARCHAR) FirstValue,
CAST(RIGHT(Val,LEN(Val) - CHARINDEX(';',Val)) AS VARCHAR) SecondValue
FROM #Table
I tried the above code but this is limited to 2 semicolon only. please share your expertise.
Try it like this:
DECLARE #Table TABLE(
Val VARCHAR(50)
)
INSERT INTO #Table (Val) SELECT '2Xcalcium; kidney' union all SELECT '3XMagnessium; liver' union all SELECT '2-ECG;3XSODIUM;DIALYSIS';
;WITH Splitted AS
(
SELECT *
,CAST('<x>' + REPLACE(Val,';','</x><x>') + '</x>' AS XML) ValuesAsXML
FROM #Table
)
SELECT *
,ValuesAsXML.value('x[1]','varchar(max)') AS FirstCol
,ValuesAsXML.value('x[2]','varchar(max)') AS SecondCol
,ValuesAsXML.value('x[3]','varchar(max)') AS ThirdCol
,ValuesAsXML.value('x[4]','varchar(max)') AS FourthCol
,ValuesAsXML.value('x[5]','varchar(max)') AS FifthCol
FROM Splitted
The result
Val FirstCol SecondCol ThirdCol FourthCol FifthCol
2Xcalcium; kidney 2Xcalcium kidney NULL NULL NULL
3XMagnessium; liver 3XMagnessium liver NULL NULL NULL
2-ECG;3XSODIUM;DIALYSIS 2-ECG 3XSODIUM DIALYSIS NULL NULL
Most of the link provided extract the element into rows.
If you prefer to use your existing logic and extract the individual element into separate column, you can use multiple cascaded CROSS APPLY.
SELECT t.Val,
v1.V as V1,
v2.V as V2,
v3.V as V3
FROM #Table t
cross apply
(
select V = LEFT(t.Val, CHARINDEX(';', t.Val + ';') - 1),
Val = STUFF(t.Val, 1, CHARINDEX(';', t.Val + ';'), '')
) v1
cross apply
(
select V = LEFT(v1.Val, CHARINDEX(';', v1.Val + ';') - 1),
Val = STUFF(v1.Val, 1, CHARINDEX(';', v1.Val + ';'), '')
) v2
cross apply
(
select V = LEFT(v2.Val, CHARINDEX(';', v2.Val + ';') - 1),
Val = STUFF(v2.Val, 1, CHARINDEX(';', v2.Val + ';'), '')
) v3
From your question ,it seems that you have data in below format..This can be done easily with numbers table..
declare #string varchar(max)
set #string='s,t,a,c,k'
select substring(','+#string+',',n+1,charindex(',',','+#string+',',n+1)-n-1)
from
numbers
where n<=len(#string)
and substring(','+#string+',',n,1)=','
Output:
s
t
a
c
k
Few more Gems:
https://dba.stackexchange.com/questions/11506/why-are-numbers-tables-invaluable
http://sqlperformance.com/2012/07/t-sql-queries/split-strings