I am trying to reverse string values in a column in SQL Server.
What I am trying to achieve is, when the value of the column is child/parent, I want to change it to parent/child when child contains '112'
For example I want to change columns with value Reports/112-Major to 112-Major/Reports
To start with, I tried to use STRING_SPLIT and append them like String_Split(ColumnName,'/')[1] + String_Split(ColumnName,'/')[0] if String_Split(ColumnName,'/')[1] like '%112%'
Most of the examples that I see online have something like
SELECT Value FROM STRING_SPLIT('Lorem/ipsum/dolor/sit/amet.', '/');
But I want to split and then merge based on condition and then update the column
Something like,
update tblTableName
set siteUrl = String_Split(ColumnName,'/')[1] + String_Split(ColumnName,'/')[0]
where `String_Split(ColumnName,'/')[1] like '%112%'
Is there a way to do this in SQL Server?
You can use this expression:
stuff(col, 1, charindex('/', col), '') + '/' + left(col, charindex('/', col) - 1)
Another option just for fun.
Gordon's solution would be my first choice and is certainly more performant (+1), but the following illustrates a simple technique which can be used to split a string into columns and not rows.
Sample
Declare #YourTable table (ID int,YourCol varchar(100))
Insert Into #YourTable values
(1,'Reports/112-Major'),
(2,'Reports/Something Else')
Update #YourTable Set YourCol = Pos2+'/'+Pos1
From #YourTable A
Cross Apply (
Select Pos1 = ltrim(rtrim(xDim.value('/x[1]','varchar(max)')))
,Pos2 = ltrim(rtrim(xDim.value('/x[2]','varchar(max)')))
From (Select Cast('<x>' + replace((Select replace(A.YourCol,'/','§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml) as xDim) as B1
) B
Where Pos2 Like '%112%'
Updated Results
ID YourCol
1 112-Major/Reports
2 Reports/Something Else
Related
Heads!
In my database, I have a column that contains the following data (examples):
H-01-01-02-01
BLE-01-03-01
H-02-05-1.1-03
The task is to get the second to last element of the array if you would split that using the "-" character. The strings are of different length.
So this would be the result using the above mentioned data:
02
03
1.1
Basically I'm searching for an equivalent of the following ruby-statement for use in a Select-Statement in SQL-Server:
"BLE-01-03-01".split("-")[-2]
Is this possible in any way in SQL Server? After spending some time searching for a solution, I only found ones that work for the last or first element.
Thanks very much for any clues or solutions!
PS: Version of SQL Server is Microsoft SQL Server 2012
As an alternative you can try this:.
--A mockup table with some test data to simulate your issue
DECLARE #mockupTable TABLE (ID INT IDENTITY, YourColumn VARCHAR(50));
INSERT INTO #mockupTable VALUES
('H-01-01-02-01')
,('BLE-01-03-01')
,('H-02-05-1.1-03');
--The query
SELECT CastedToXml.value('/x[sql:column("CountOfFragments")-1][1]','nvarchar(10)') AS TheWantedFragment
FROM #mockupTable t
CROSS APPLY(SELECT CAST('<x>' + REPLACE(t.YourColumn,'-','</x><x>') + '</x>' AS XML))A(CastedToXml)
CROSS APPLY(SELECT CastedToXml.value('count(/x)','int')) B(CountOfFragments);
The idea in short:
The first APPLY will transform the string to a XML like this
<x>H</x>
<x>01</x>
<x>01</x>
<x>02</x>
<x>01</x>
The second APPLY will xquery into this XML to get the count of fragments. As APPLY will add this as a column to the result set, we can use the value using sql:column() to get the wanted fragment by its position.
As I wrote in my comment - using charindex with reverse.
First, create and populate sample table (Please save us this step in your future questions):
DECLARE #T AS TABLE
(
Col Varchar(100)
);
INSERT INTO #T (Col) VALUES
('H-01-01-02-01'),
('BLE-01-03-01'),
('H-02-05-1.1-03');
The query:
SELECT Col,
LEFT(RIGHT(Col, AlmostLastDelimiter-1), AlmostLastDelimiter - LastDelimiter - 1) As SecondToLast
FROM #T
CROSS APPLY (SELECT CharIndex('-', Reverse(Col)) As LastDelimiter) As A
CROSS APPLY (SELECT CharIndex('-', Reverse(Col), LastDelimiter+1) As AlmostLastDelimiter) As B
Results:
Col SecondToLast
H-01-01-02-01 02
BLE-01-03-01 03
H-02-05-1.1-03 1.1
Similar to Zohar's solution, but using CTEs instead of CROSS APPLY to prevent redundancy. I personally find this easier to follow, as you can see what happens in each step. Doesn't make it a better solution though ;)
DECLARE #strings TABLE (data VARCHAR(50));
INSERT INTO #strings VALUES ('H-01-01-02-01') , ('BLE-01-03-01'), ('H-02-05-1.1-03');
WITH rev AS (
SELECT
data,
REVERSE(data) AS reversed
FROM
#strings),
first_hyphen AS (
SELECT
data,
reversed,
CHARINDEX('-', reversed) + 1 AS first_pos
FROM
rev),
second_hyphen AS (
SELECT
data,
reversed,
first_pos,
CHARINDEX('-', reversed, first_pos) AS second_pos
FROM
first_hyphen)
SELECT
data,
REVERSE(SUBSTRING(reversed, first_pos, second_pos - first_pos)) AS result
FROM
second_hyphen;
Results:
data result
H-01-01-02-01 02
BLE-01-03-01 03
H-02-05-1.1-03 1.1
Try this
declare #input NVARCHAR(100)
declare #dlmt NVARCHAR(3);
declare #pos INT = 2
SET #input=REVERSE(N'H-02-05-1.1-03');
SET #dlmt=N'-';
SELECT
CAST(N'<x>'
+ REPLACE(
(SELECT REPLACE(#input,#dlmt,'#DLMT#') AS [*] FOR XML PATH(''))
,N'#DLMT#',N'</x><x>'
) + N'</x>' AS XML).value('/x[sql:variable("#pos")][1]','nvarchar(max)');
I'm trying to create a stored procedure for updating a table in a batch. I want to take parameters in as a nvarchar and call string_split on them.
#ParamList1 NVARCHAR(max) = '1,2,3,4,5'
#ParamList2 NVARCHAR(max) = 'a,b,c,d,e'
I want to get a temporary table like
Param1 Param2
1 a
2 b
3 c
...
How would I do this?
Unfortunately, string_split() does not guarantee ordering or provide a position argument (Microsoft are you listening?).
So, the safest method is a recursive CTE (or perhaps another approach using XML):
with cte as (
select convert(nvarchar(max), NULL) as x1, convert(nvarchar(max), NULL) as x2, #paramlist1 as rest1, #paramlist2 as rest2, 1 as lev
union all
select convert(nvarchar(max), left(rest1, charindex(',', rest1 + ',') - 1)),
convert(nvarchar(max), left(rest2, charindex(',', rest2 + ',') - 1)),
stuff(rest1, 1, charindex(',', rest1 + ','), ''),
stuff(rest2, 1, charindex(',', rest2 + ','), ''),
lev + 1
from cte
where rest1 <> '' and rest2 <> ''
)
select *
from cte
where x1 is not null;
Here is a db<>fiddle.
You've got an answer already, which is working fine, but this should be faster and easier:
You did not specify your SQL-Server's version, but - talking about STRING_SPLIT() - I assume it's at least v2016. If this is correct, you can use OPENJSON. Your list of numbers needs nothing more than brackets to be a JSON-array ([1,2,3]), while an array of words/letters can be transformed with some easy string operations (["a","b","c"]).
Following the docs, OPENJSON returns the elements position in [key], while the element itself is returned in [value]. You can simply JOIN these sets:
DECLARE #ParamList1 NVARCHAR(max) = '1,2,3,4,5';
DECLARE #ParamList2 NVARCHAR(max) = 'a,b,c,d,e';
SELECT p1.[key] AS FragmentNr
,p1.[value] AS P1
,p2.[value] AS P2
FROM OPENJSON(CONCAT('[',#ParamList1 + ']')) p1
INNER JOIN OPENJSON(CONCAT('["',REPLACE(#ParamList2,',','","'),'"]')) p2 ON p1.[key]=p2.[key] ;
In this answer you will find some details (UPDATE section 1 and 2).
I have a view where I would like to create a column that isolates a string between 3 of the same character ("-"). So for example, I want AC-RBQ/4110-WS-L1 to become RBQ/4110.
So far I have tried this and it gets me very close.
SELECT SUBSTRING(locnum,CHARINDEX('-',locnum)+1,(((LEN(locnum))-CHARINDEX('-', REVERSE(locnum)))-CHARINDEX('-',locnum))) AS Result
Result:
RBQ/4110-WS
RBQ/4110-CS
I just need to remove that last "-WS" or "-CS"
Any thoughts or ideas would be greatly appreciated!
If the pattern is consistent, perhaps parsename() would be a good fit here
Example
Declare #S varchar(100) = 'AC-RBQ/4110-WS-L1'
Select parsename(replace(#S,'-','.'),3)
Returns
RBQ/4110
EDIT -
Just in case your data is more variable, you can use XML to extract the SECOND value
Example
Declare #YourTable table (locnum varchar(100))
Insert Into #YourTable values
('AC-RBQ/4110-WS-L1')
Select NewValue = convert(xml,'<x>'+replace(locnum,'-','</x><x>')+'</x>').value('/x[2]','varchar(100)')
From #YourTable
Returns
NewValue
RBQ/4110
It would seem simpler to split the value and then extract the 2nd item from your data. If your data always has 4 elements (and you always need the second), you could use PARSENAME:
SELECT PN.P
FROM (VALUES('AC-RBQ/4110-WS-L1'))V(S)
CROSS APPLY (VALUES(PARSENAME(REPLACE(V.S,'-','.'),3)))PN(P); --Part 3 as PARSENAME works right to left
Otherwise you could use a splitter like delimitedsplit8k_lead:
SELECT DS.Item
FROM (VALUES('AC-RBQ/4110-WS-L1'))V(S)
CROSS APPLY dbo.DelimitedSplit8K_lead(V.S,'-') DS
WHERE DS.ItemNumber = 2;
Please Try below Query. If your pattern is same per your question then below will work.
select left(SUBSTRING('AC-RBQ/4110-WS-L1', charindex('-','AC-RBQ/4110-WS-L1') + 1, LEN('AC-RBQ/4110-WS-L1')),8)
WITH TEMP AS
(
SELECT 'AC-RBQ/4110-WS-L1' AS COL
)
SELECT * ,
CHARINDEX('-', [COL]) AS FR,
CHARINDEX('-', [COL],CHARINDEX('-', [COL])+1) AS FR2,
SUBSTRING([COL], CHARINDEX('-', [COL]) +1 ,CHARINDEX('-', [COL],CHARINDEX('-
', [COL])+1) - (CHARINDEX('-', [COL]) +1) ) AS RESULT
FROM TEMP
You've done most of the work and you need to get the left part of the result up to -:
SELECT
LEFT(SUBSTRING(locnum, CHARINDEX('-', locnum) + 1,
(((LEN(locnum)) - CHARINDEX('-', REVERSE(locnum))) - CHARINDEX('-', locnum))),
CHARINDEX('-', SUBSTRING(locnum, CHARINDEX('-', locnum) + 1,
(((LEN(locnum)) - CHARINDEX('-', REVERSE(locnum))) - CHARINDEX('-', locnum)))) - 1) AS Result
I have a question about SQL Server: I have a database column with a pattern which is like this:
up to 10 digits
then a comma
up to 10 digits
then a semicolon
e.g.
100000161, 100000031; 100000243, 100000021;
100000161, 100000031; 100000243, 100000021;
and I want to extract within the pattern the first digits (up to 10) (1.) and then a semicolon (4.)
(or, in other words, remove everything from the semicolon to the next semicolon)
100000161; 100000243; 100000161; 100000243;
Can you please advice me how to establish this in SQL Server? Im not very familiar with regex and therefore have no clue how to fix this.
Thanks,
Alex
Try this
Declare #Sql Table (SqlCol nvarchar(max))
INSERT INTO #Sql
SELECT'100000161,100000031;100000243,100000021;100000161,100000031;100000243,100000021;'
;WITH cte
AS (SELECT Row_number()
OVER(
ORDER BY (SELECT NULL)) AS Rno,
split.a.value('.', 'VARCHAR(1000)') AS Data
FROM (SELECT Cast('<S>'
+ Replace( Replace(sqlcol, ';', ','), ',',
'</S><S>')
+ '</S>'AS XML) AS Data
FROM #Sql)AS A
CROSS apply data.nodes('/S') AS Split(a))
SELECT Stuff((SELECT '; ' + data
FROM cte
WHERE rno%2 <> 0
AND data <> ''
FOR xml path ('')), 1, 2, '') AS ExpectedData
ExpectedData
-------------
100000161; 100000243; 100000161; 100000243
I believe this will get you what you are after as long as that pattern truly holds. If not it's fairly easy to ensure it does conform to that pattern and then apply this
Select Substring(TargetCol, 1, 10) + ';' From TargetTable
You can take advantage of SQL Server's XML support to convert the input string into an XML value and query it with XQuery and XPath expressions.
For example, the following query will replace each ; with </b><a> and each , to </a><b> to turn each string into <a>100000161</a><a>100000243</a><a />. After that, you can select individual <a> nodes with /a[1], /a[2] :
declare #table table (it nvarchar(200))
insert into #table values
('100000161, 100000031; 100000243, 100000021;'),
('100000161, 100000031; 100000243, 100000021;')
select
xCol.value('/a[1]','nvarchar(200)'),
xCol.value('/a[2]','nvarchar(200)')
from (
select convert(xml, '<a>'
+ replace(replace(replace(it,';','</b><a>'),',','</a><b>'),' ','')
+ '</a>')
.query('a') as xCol
from #table) as tmp
-------------------------
A1 A2
100000161 100000243
100000161 100000243
value extracts a single value from an XML field. nodes returns a table of nodes that match the XPath expression. The following query will return all "keys" :
select
a.value('.','nvarchar(200)')
from (
select convert(xml, '<a>'
+ replace(replace(replace(it,';','</b><a>'),',','</a><b>'),' ','')
+ '</a>')
.query('a') as xCol
from #table) as tmp
cross apply xCol.nodes('a') as y(a)
where a.value('.','nvarchar(200)')<>''
------------
100000161
100000243
100000161
100000243
With 200K rows of data though, I'd seriously consider transforming the data when loading it and storing it in indivisual, indexable columns, or add a separate, related table. Applying string manipulation functions on a column means that the server can't use any covering indexes to speed up queries.
If that's not possible (why?) I'd consider at least adding a separate XML-typed column that would contain the same data in XML form, to allow the creation of an XML index.
How to get the string between 5th and 6th comma, without using a function, because I am selecting other columns as well, so using a function will not help me
This is the string value
"RBC,Dev,PROM0006581,T85230,P0263240,**Dev**,CHG0048754_DYN_DIF,Code changes as part of DYN -Tiered/Scaled & Bonus Interest for DIF Products
"
can someone please help me
You can do following:
DECLARE #Values VARCHAR(MAX) = 'RBC,Dev,PROM0006581,T85230,P0263240,**Dev**,CHG0048754_DYN_DIF,Code changes as part of DYN -Tiered/Scaled & Bonus Interest for DIF Products'
IF OBJECT_ID('TEMPDB..#Values') IS NOT NULL DROP TABLE #Values;
CREATE TABLE #Values (ID INT IDENTITY(1,1),Item VARCHAR(100));
DECLARE #Insert VARCHAR(MAX) = 'INSERT INTO #Values VALUES ('''+REPLACE(#Values,',','''),(''')+''');';
EXEC (#Insert);
SELECT * FROM #Values
Resulting:
And
SELECT Item FROM #Values WHERE ID=5
Resulting
This should get you what you're looking for...
IF OBJECT_ID('tempdb..#TestData', 'U') IS NOT NULL
DROP TABLE #testData;
CREATE TABLE #TestData (
String VARCHAR(1000) NOT NULL
);
INSERT #TestData (String) VALUES ('RBC,Dev,PROM0006581,T85230,P0263240,**Dev**,CHG0048754_DYN_DIF,Code changes as part of DYN -Tiered/Scaled & Bonus Interest for DIF Product');
SELECT *,
RequestedValue = SUBSTRING(td.String, c4.Comma + 1, ISNULL(c5.Comma - c4.Comma - 1, 1000))
FROM
#TestData td
CROSS APPLY ( VALUES (NULLIF(CHARINDEX(',', td.String, 1), 0)) ) c1 (Comma)
CROSS APPLY ( VALUES (NULLIF(CHARINDEX(',', td.String, c1.Comma + 1), 0)) ) c2 (Comma)
CROSS APPLY ( VALUES (NULLIF(CHARINDEX(',', td.String, c2.Comma + 1), 0)) ) c3 (Comma)
CROSS APPLY ( VALUES (NULLIF(CHARINDEX(',', td.String, c3.Comma + 1), 0)) ) c4 (Comma)
CROSS APPLY ( VALUES (NULLIF(CHARINDEX(',', td.String, c4.Comma + 1), 0)) ) c5 (Comma);
HTH, Jason
Here is a simple in-line and XML Safe approach (not just &'s).
Grabbing the 5th value. Not 100% sure if this needs to be adjusted up or down
Sample Data
Declare #YourTable table (ID int,SomeColumn varchar(max))
Insert Into #YourTable values
(1,'RBC,Dev,PROM0006581,T85230,P0263240,**Dev**,CHG0048754_DYN_DIF,Code changes as part of DYN -Tiered/Scaled & Bonus Interest for DIF Products')
The Query
Select ID
,SomeValue = Cast('<x>' + replace((Select replace(SomeColumn,',','|||') as [*] For XML Path('')),'|||','</x><x>')+'</x>' as xml).query('.').value('/x[5]','varchar(max)')
From #YourTable
Returns
ID SomeValue
1 P0263240
There is one solution that involve replacing comma , with xml tags and thereby converting column into an XML datatype.
Below query will give you 6th item (i.e between 5th and 6th comma as you want)
select *
, Column2 = convert(XML,'<s>' + REPLACE(REPLACE(MyColumn,'&','&'),',','</s><s>') + '</s>').value('/s[6]','varchar(200)')
FROM [dbo].[Table1]
SQL Fiddle Demo
But you have to escape any reserved XML character in your data by replacing it with entity references otherwise XML cast will fail. like & is replaced with & in the above query
XML reserved character on Technet
Updated Query
As #John mentioned in his answer FOR XML Path is a neat and elegant way to escape special XML characters.So my updated query would be.
SELECT *
, Column2 = convert(XML,'<s>' + REPLACE((SELECT ISNULL(MyColumn,'') FOR XML Path('')),',','</s><s>') + '</s>').value('/s[6]','varchar(200)')
FROM [dbo].[Table1]
Write a table-valued function that accepts the string and a delimiter character as parameters and returns the parsed elements of the string and the element's position as a two-column table. You can then JOIN to an invocation of that function, which SQL will treat just like a table, so if you want the text between the 5th and 6th comma then you'd put "and position = 6" in the JOIN clause.