attempting a replace function on sql but with wildcards - sql

this is my first time posting so hopefully I get all the information needed across correctly.
I'm attempting to do an update replace statement on a row where there is a set of characters but a wildcard statement won't work, below is a select statement example
SELECT replace(column, '[aA][bB][cC]-', '')
FROM table
where column like '%[aA][bB][cC]-%'
an example row would be "abc-123 aBc-123 abC-abc Def-123", but I need it to return "123 123 abc"
this is a basic example, but I'm trying to get rid of a set of 3 characters and a "-" character anywhere in a string. the abc could change to def but the "-" character will always come after.
I've done some googling and can't find an appropriate solution as most examples will only remove one example of abc- where I need to get rid of all versions. I'm running version 12.0 of sql server (SQL Server 2014) so I think some functions I wouldn't be able to use.
I think the closest example I could find was Using Wildcard For Range of Characters In T-SQL but I can't use the translate function.
Edit: below is an example of a created table
CREATE TABLE String_Removal_Example(
someValue VARCHAR(100)
)
INSERT INTO String_Removal_Example (someValue) VALUES ('abc-123')
INSERT INTO String_Removal_Example (someValue) VALUES ('abc-123 ABC-123')
INSERT INTO String_Removal_Example (someValue) VALUES ('abc-123 ABc-123 123-ABC DEF-123')
select statement brings back
someValue
abc-123
abc-123 ABC-123
abc-123 ABc-123 123-ABC DEF-123
Edit2: If this isn't possible to do in this manner, a possible alternative would be to remove 3 characters and the - character. I've tried the below
select Substring (someValue, Charindex( '-', someValue ) + 1, Len(someValue)) From String_Removal_Example
but this returns the below, which is only affecting the first instance of nnn-.
123
123 ABC-123
123 ABc-123 123-ABC DEF-123
Edit3: The string I need to replace is nnn- for clarification. I was trying for the [aA][bB][cC]- format in case I needed to change it. It will always be 3 characters followed by a "-" Character

You can create a function like this:
(I named it ThreeLetters, but what's in a name...)
CREATE FUNCTION [dbo].[ThreeLetters]
(
#p0 VARCHAR(MAX)
)
RETURNS VARCHAR(MAX)
AS
BEGIN
DECLARE #p1 VARCHAR(20) = '%[A-Z][A-Z][A-Z]-%';
DECLARE #Result VARCHAR(MAX) = #p0;
DECLARE #pos INT = PATINDEX(#p1,#Result COLLATE SQL_Latin1_General_CP1_CS_AS);
DECLARE #pos2 INT ;
DECLARE #i INT =0;
WHILE #pos>0 and #i<10
BEGIN
SET #pos2 = CHARINDEX('-',#Result,#pos)-#pos;
SELECT #Result = SUBSTRING(#Result,1,#pos-1)+SUBSTRING(#Result, #pos+#pos2+1, len(#Result));
SET #pos = PATINDEX(#p1,#Result COLLATE SQL_Latin1_General_CP1_CS_AS);
SET #i = #i + 1;
END
RETURN #Result
END
output with your sample data:
someValue
(No column name)
abc-123
123
abc-123 ABC-123
123 123
abc-123 ABc-123 123-ABC DEF-123
123 123 123-ABC 123
see DBFIDDLE
P.S. The check for #i was introduced while debugging. It might not be needed, but was tooo lazy to remove it.
P.P.S I am removing ThreeLetters followed by a -, that is why 123-ABC remains unchanged.

Here's an XML trick that'll also work in Sql Server 2014
(In Sql Server 2016 and beyond, the string_split function would be better.)
declare #String_Removal_Example table (
someValue varchar(100) not null
);
insert into #String_Removal_Example (someValue) VALUES
('abc-123')
, ('abc-123 ABC-123')
, ('abc-123 ABC-123 123-Abc DEF-123');
select ca.value as someValue
from #String_Removal_Example t
cross apply
(
select rtrim((
select node.n.value('.','varchar(30)')+' '
from
(
select cast('<x><a>' + replace(replace(t.someValue,
' ', '</n><a>'),
'-', '</a><n>') +
'</n></x>' as xml) as x
) q
cross apply x.nodes ('/x/n') AS node(n)
for xml path('')
)) as value
) ca
where lower(t.someValue) like '%[a-z0-9]-[a-z0-9]%';
GO
| someValue |
| :-------------- |
| 123 |
| 123 123 |
| 123 123 Abc 123 |
db<>fiddle here

Please try the following solution. It is a variation of #LukStorms solution.
It is based on XML and XQuery. Their data model is based on ordered sequences.
XML allows us to tokenize the input string.
It is looking for 'nnn-' strings and filters them out.
For example, 2nd row is converted to XML like this:
<root>
<r>abc-</r>
<r>123</r>
<r>ABC-</r>
<r>123</r>
</root>
SQL
-- DDL and sample data population, start
declare #String_Removal_Example table (
someValue varchar(100) not null
);
insert into #String_Removal_Example (someValue) VALUES
('abc-123')
, ('abc-123 ABC-123')
, ('abc-123 ABC-123 123-Abc DEF-123');
-- DDL and sample data population, end
SELECT someValue AS [before], [after]
FROM #String_Removal_Example
CROSS APPLY (SELECT TRY_CAST('<root><r><![CDATA[' +
REPLACE(REPLACE(someValue, '-', '- '), SPACE(1), ']]></r><r><![CDATA[') +
']]></r></root>' AS XML)
.query('data(/root/r[not(contains(text()[1],"-"))])')
.value('.','VARCHAR(255)')) AS t1(after);
Output
+---------------------------------+-----------------+
| before | after |
+---------------------------------+-----------------+
| abc-123 | 123 |
| abc-123 ABC-123 | 123 123 |
| abc-123 ABC-123 123-Abc DEF-123 | 123 123 Abc 123 |
+---------------------------------+-----------------+

This is basically dividing up the string where there are spaces, then looking for "words" that have a '-' in the 4th position, removing the prefix and then stitching it back together:
SELECT s.someValue,
STUFF((SELECT ' ' + SUBSTRING(value, 5, LEN(value) - 4)
FROM STRING_SPLIT(someValue, ' ')
WHERE CHARINDEX('-', value) = 4
FOR XML PATH('')),1,1,'') a
FROM String_Removal_Example s

As there is no regex replace in SQL Server, I think the only way you can reliably use a pattern replace is through an iterative process. One such way is with a recursive CTE:
WITH Data AS
( SELECT SomeValue
FROM (VALUES
('abc-123'),
('abc-123 ABC-123'),
('abc-123 aBc-123 abC-abc Def-123'),
('abc-123 ABCD-123'),
('abc-123 A$C-123')
) x (SomeValue)
), RegexReplace AS
( SELECT SomeValue, NewValue = CONVERT(VARCHAR(100), Data.SomeValue), Level = 1
FROM Data
UNION ALL
SELECT SomeValue, CONVERT(VARCHAR(100), STUFF(NewValue, PATINDEX('% [A-z][A-z][A-z]-%', CONCAT(' ', NewValue)), 4, '')), Level + 1
FROM RegexReplace AS d
WHERE PATINDEX('% [A-z][A-z][A-z]-%', CONCAT(' ', NewValue)) > 0
), RankedData AS
( SELECT *, RowNumber = ROW_NUMBER() OVER(PARTITION BY rr.SomeValue ORDER BY rr.Level DESC)
FROM RegexReplace AS rr
)
SELECT rd.SomeValue, rd.NewValue
FROM RankedData AS rd
WHERE rd.RowNumber = 1;
Which gives:
SomeValue
New Value
abc-123
123
abc-123 ABC-123
123 123
abc-123 aBc-123 abC-abc Def-123
123 123 abc 123
abc-123 A$C-123
123 A$C-123
abc-123 ABCD-123
123 ABCD-123
This basically uses PATINDEX() to find the first instance of the pattern, then STUFF() to remove the 4 characters after the index found. This process then repeats until the PATINDEX() fails to find a match.
Probably also of note is that when doing the PATINDEX() I am adding a space to the start of the string, and including a space at the start of the pattern. This ensures that it is limited to only 3 characters followed by a hyphen, if it is more than 3 (e.g. ABCD-123) the entire string is left intact rather than leaving A123 as shown in the last row above. Adding the space to the start of the string being searched ensures that the pattern is still found even if it is at the start of the string.

SELECT replaceAll(column, '[aA][bB][cC]-', '')
FROM table
where column like '%[aA][bB][cC]-%'

Related

SQL REPLACE with Multiple [0-9]

I have a string that I want to replace a group of numbers.
The string contains groupings of numbers (and a few letters). 'A12 456 1 65 7944'
I want to replace the group of 3 numbers with 'xxx', and the group of 4 numbers with 'zzzz'
I thought something like REPLACE(#str, '%[0-9][0-9][0-9]%', 'xxx') would work, but it doesn't. I can't even get '%[0-9]%' to replace anything.
If REPLACE is not suitable, how can I replace groups of numbers?
Please try the following solution based on XML and XQuery.
Notable points:
We are tokenizing input string as XML in the CROSS APPLY clause.
XQuery's FLWOR expression is checking for numeric integer values with
a particular length, and substitutes then with a replacement string.
XQuery .value() method outputs back a final result.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, tokens VARCHAR(MAX));
INSERT INTO #tbl (tokens) VALUES
('A12 456 1 65 7944');
-- DDL and sample data population, end
DECLARE #separator CHAR(1) = SPACE(1);
SELECT t.*
, c.query('
for $x in /root/r/text()
return if (xs:int($x) instance of xs:int and string-length($x)=3) then "xxx"
else if (xs:int($x) instance of xs:int and string-length($x)=4) then "zzzz"
else data($x)
').value('.', 'VARCHAR(MAX)') AS Result
FROM #tbl AS t
CROSS APPLY (SELECT TRY_CAST('<root><r><![CDATA[' +
REPLACE(tokens, #separator, ']]></r><r><![CDATA[') +
']]></r></root>' AS XML)) AS t1(c);
Output
+----+-------------------+-------------------+
| ID | tokens | Result |
+----+-------------------+-------------------+
| 1 | A12 456 1 65 7944 | A12 xxx 1 65 zzzz |
+----+-------------------+-------------------+

Query to update strings using string_split function

I am trying to update column in table where data is in below format:
Id | ColA
----------
1 Peter,John:Ryan,Jack:Evans,Chris
2 Peter,John:Ryan,Jack
3 Hank,Tom
4
5 Cruise,Tom
I need to split the string by ':' and remove ',' and need to reverse the name and again append the same data separated by: and finally data should be as shown
Id | ColA
----------
1 John Peter:Jack Ryan:Chris Evans
2 John Peter:Jack Ryan
3 Tom Hank
4
5 Tom Cruise
Please let me know how can we achieve this
I tried to use Replace and Substring but how can we do it if we have data some are separated by two colon and some are separated by single colon.
Is there any way to identify and achieve the data in the above formatted one.
Here is a solution for SQL Server 2008 onwards.
It is based on XML and XQuery.
Using XQuery's FLWOR expression allows to tokenize odd vs. even XML elements. The rest is just a couple of the REPLACE() function calls to compose the desired output.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, tokens VARCHAR(1024));
INSERT INTO #tbl (tokens) VALUES
('Peter,John:Ryan,Jack:Evans,Chris'),
('Peter,John:Ryan,Jack'),
('Hank,Tom'),
(''),
('Cruise,Tom');
-- DDL and sample data population, end
DECLARE #separator CHAR(1) = ':'
, #comma CHAR(1) = ',';
SELECT ID, tokens
, REPLACE(REPLACE(c.query('
for $x in /root/r[position() mod 2 eq 0]
let $pos := count(root/r[. << $x])
return concat($x, sql:variable("#comma"), (/root/r[$pos])[1])
').value('text()[1]', 'VARCHAR(8000)')
, SPACE(1), #separator), #comma, SPACE(1)) AS result
FROM #tbl
CROSS APPLY (SELECT CAST('<root><r><![CDATA[' +
REPLACE(REPLACE(tokens,#comma,#separator), #separator, ']]></r><r><![CDATA[') +
']]></r></root>' AS XML)) AS t1(c)
ORDER BY ID;
Output
+----+----------------------------------+----------------------------------+
| ID | tokens | result |
+----+----------------------------------+----------------------------------+
| 1 | Peter,John:Ryan,Jack:Evans,Chris | John Peter:Jack Ryan:Chris Evans |
| 2 | Peter,John:Ryan,Jack | John Peter:Jack Ryan |
| 3 | Hank,Tom | Tom Hank |
| 4 | | NULL |
| 5 | Cruise,Tom | Tom Cruise |
+----+----------------------------------+----------------------------------+
SQL #2 (don't try it, it won't work)
Unfortunately, SQL Server doesn't fully support even XQuery 1.0 standard. XQuery 3.1 is the latest standard. XQuery 1.0 functions fn:substring-after() and fn:substring-before() are badly missing.
In a dream world a solution would be much simpler, along the following:
SELECT *
, c.query('
for $x in /root/r
return concat(fn:substring-after($x, ","), ",", fn:substring-before($x, ","))
')
FROM #tbl
CROSS APPLY (SELECT TRY_CAST('<root><r><![CDATA[' +
REPLACE(tokens, #separator, ']]></r><r><![CDATA[') +
']]></r></root>' AS XML)) AS t1(c);
Please up-vote the following suggestion to improve SQL Server:
SQL Server vNext (post 2019) and NoSQL functionality
It became one of the most popular requests for SQL Server.
The current voting tally is 590 and counting.
Something like this should work:
CREATE TABLE YourTableNameHere (
Id int NULL
,ColA varchar(1000) NULL
);
INSERT INTO YourTableNameHere (Id,ColA) VALUES
(1, 'Peter,John:Ryan,Jack:Evans,Chris')
,(2, 'Peter,John:Ryan,Jack')
,(3, 'Hank,Tom')
,(4, '')
,(5, 'Cruise,Tom');
SELECT
tbl.Id
,STUFF((SELECT
CONCAT(':'
,RIGHT(REPLACE(ss.value, ',', ' '), LEN(REPLACE(ss.value, ',', ' ')) - CHARINDEX(' ', REPLACE(ss.value, ',', ' '), 1)) /*first name*/
,' '
,CASE WHEN CHARINDEX(',', ss.value, 1) > 1 THEN LEFT(REPLACE(ss.value, ',', ' '), CHARINDEX(' ', REPLACE(ss.value, ',', ' '), 1) - 1) /*last name*/ ELSE '' END)
FROM
YourTableNameHere AS tbl_inner
CROSS APPLY string_split(tbl_inner.ColA, ':') AS ss
WHERE
tbl_inner.Id = tbl.Id
FOR XML PATH('')), 1, 1, '') AS ColA
FROM
YourTableNameHere AS tbl;
This uses the string_split function within a FOR XML clause to split the values in ColA by the : character, then replace the , with a space, parse to the left and right of the space, then recombine the parsed values delimited by a : character.
One thing to note here, per Microsoft the output of string_split is not guaranteed to be in the same order as the input:
Note
The order of the output may vary as the order is not guaranteed to match the order of the substrings in the input string.
So in order to guarantee the output of this function is going to concatenate the names back in the same order that they existed in the input column you would either need to implement your own function to split the string or come up with some criteria for combining them in a certain order. For example, you could recombine them in alphabetical order by adding ORDER BY ss.value to the inner query for ColA in the final result set. In my testing using your input the final values were ordered the same as the input column, but it is worth noting that that behaviour is not guaranteed and in order to guarantee it then you need to do more work.

How to compare two comma-separated value in a column, and return only matching values in SQL

I have a column with comma-separated strings, I need to compare it with another comma-separated column and return only matching values.
For example
column 1 = John, james, steve
column 2 = john, smith, will, james
I need a result like John,james since it is available in both column 1 and column 2. Is this possible in SQL?
As I'm using SQL Server 2012, I'm not able to use the String_split function
You can try the following solution.
It is using XQuery and its FLWOR expression.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, col1 VARCHAR(MAX), col2 VARCHAR(MAX));
INSERT INTO #tbl (col1, col2) VALUES
('John, james, steve', 'john, smith, will, james'),
('Mary, Lisa', 'Mary');
-- DDL and sample data population, end
DECLARE #separator CHAR(2) = ', ';
SELECT *
, REPLACE(TRY_CAST('<root>' +
'<source><r><![CDATA[' + REPLACE(col1, #separator, ']]></r><r><![CDATA[') +
']]></r></source>' +
'<target><r><![CDATA[' + REPLACE(col2, #separator, ']]></r><r><![CDATA[') +
']]></r></target>' +
'</root>' AS XML)
.query('
for $x in /root/source/r/text(),
$y in /root/target/r/text()
where lower-case($y) eq lower-case($x)
return data($x)
').value('.', 'NVARCHAR(MAX)'), SPACE(1), ',') AS result
FROM #tbl;
Output
+----+--------------------+--------------------------+------------+
| ID | col1 | col2 | result |
+----+--------------------+--------------------------+------------+
| 1 | John, james, steve | john, smith, will, james | John,james |
| 2 | Mary, Lisa | Mary | Mary |
+----+--------------------+--------------------------+------------+
This can certainly be done using STRING_SPLIT():
DECLARE #a VARCHAR(MAX) = 'john,james,steve'
DECLARE #b VARCHAR(MAX) = 'john,smith,will,james'
SELECT a.value
FROM string_split(#a, ',') a
JOIN string_split(#b, ',') b
ON a.value = b.value
But I think the broader question is if you should be doing this in SQL at all. SQL is much better and more efficient at set-based operations, and this approach is very iterative going row by row. I would consider restructing your data so it's easier to do this or read it into memory and use C# to do this type of manipulation.

Using STRING_SPLIT in SQL for delimited values

#InStr = '0|ABC|3033.9|3032.4444|0|0|0^1|DEF|3033.2577|3033.053|3032.0808|0|0^2|JHI|3032.8376|3033.2596|3033.2259|3033.322|0^3|XYZ|3032.8376|3032.8376|3032.8376|3032.8376|0'
I have the string above in a variable #InStr and I want to use STRING_SPLIT to inserts values into a table.
As you can see its a double split.
SELECT Value FROM STRING_SPLIT(#InStr,'^')
Produces:
0|ABC|3033.9|3032.4444|0|0|0
1|DEF|3033.2577|3033.053|3032.0808|0|0
2|JHI|3032.8376|3033.2596|3033.2259|3033.322|0
3|XYZ|3032.8376|3032.8376|3032.8376|3032.8376|0
Which is good, now I need to take each row and insert into a table.
I'm not sure how to combine the 2 splits to do the insert. The table has 7 columns which it would populate.
Any help appreciated.
First of all: You should avoid STRING_SPLIT() in almost any case. It does not guarantee to return the items in the expected sort order. This might work in all your tests and break in production with silly hardly to find errors.
There are various answers already, the best one should be the table type parameter. But (if you cannot follow this route), I'd like to suggest two type-safe approaches:
DECLARE #InStr NVARCHAR(MAX) = '0|ABC|3033.9|3032.4444|0|0|0^1|DEF|3033.2577|3033.053|3032.0808|0|0^2|JHI|3032.8376|3033.2596|3033.2259|3033.322|0^3|XYZ|3032.8376|3032.8376|3032.8376|3032.8376|0';
--xml approach (working for almost any version)
--We do the double split in one single action and return a nested XML with <x> and <y> elements
--We can fetch the values type-safe from their 1-based position:
SELECT x.value('y[1]','int') AS [First]
,x.value('y[2]','varchar(100)') AS [Second]
,x.value('y[3]','decimal(28,8)') AS Third
,x.value('y[4]','decimal(28,8)') AS Fourth
,x.value('y[5]','decimal(28,8)') AS Fifth
,x.value('y[6]','decimal(28,8)') AS Sixth
,x.value('y[7]','decimal(28,8)') AS Seventh
FROM (VALUES(CAST('<x><y>' + REPLACE(REPLACE(#Instr,'|','</y><y>'),'^','</y></x><x><y>') + '</y></x>' AS XML)))v(Casted)
CROSS APPLY Casted.nodes('/x') b(x);
--json approach (needs v2016+)
--faster than XML
--We transform your string to a JSON-array with one item per row and use another OPENJSON to retrieve the array's items.
--The WITH-clause brings in implicit pivoting to retrieve the items type-safe as columns:
SELECT b.*
FROM OPENJSON(CONCAT('[["',REPLACE(#Instr,'^','"],["'),'"]]')) a
CROSS APPLY OPENJSON(CONCAT('[',REPLACE(a.[value],'|','","'),']'))
WITH([First] INT '$[0]'
,[Second] VARCHAR(100) '$[1]'
,[Third] DECIMAL(28,8) '$[2]'
,[Fourth] DECIMAL(28,8) '$[3]'
,[Fifth] DECIMAL(28,8) '$[4]'
,[Sixth] DECIMAL(28,8) '$[5]'
,[Seventh] DECIMAL(28,8) '$[6]') b;
Both approaches return the same result:
+-------+--------+---------------+---------------+---------------+---------------+------------+
| First | Second | Third | Fourth | Fifth | Sixth | Seventh |
+-------+--------+---------------+---------------+---------------+---------------+------------+
| 0 | ABC | 3033.90000000 | 3032.44440000 | 0.00000000 | 0.00000000 | 0.00000000 |
+-------+--------+---------------+---------------+---------------+---------------+------------+
| 1 | DEF | 3033.25770000 | 3033.05300000 | 3032.08080000 | 0.00000000 | 0.00000000 |
+-------+--------+---------------+---------------+---------------+---------------+------------+
| 2 | JHI | 3032.83760000 | 3033.25960000 | 3033.22590000 | 3033.32200000 | 0.00000000 |
+-------+--------+---------------+---------------+---------------+---------------+------------+
| 3 | XYZ | 3032.83760000 | 3032.83760000 | 3032.83760000 | 3032.83760000 | 0.00000000 |
+-------+--------+---------------+---------------+---------------+---------------+------------+
Instead of passing a string from .NET like 'a|b|c^d|e|f' and then having to parse it, leave it in its original structure (DataTable?) and create a table type in SQL Server. Then you can pass in your structure instead of this cobbled-together string.
In SQL Server:
CREATE TYPE dbo.MyTableType AS TABLE
(
ColumnA int,
ColumnB nvarchar(32),
...
);
GO
CREATE PROCEDURE dbo.ShowArray
#DataTable dbo.MyTableType
AS
BEGIN
SET NOCOUNT ON;
SELECT ColumnA, ColumnB, ...
FROM #DataTable;
END
In C# (untested and incomplete):
DataTable dt = new DataTable();
dt.Columns.Add("ColumnA", typeof(Int32));
dt.Columns.Add("ColumnB", typeof(String));
...
DataRow dr = dt.NewRow();
dr[0] = 1;
dr[1] = "foo";
...
dt.Rows.Add(dr);
...
SqlCommand cmd = new SqlCommand("dbo.ShowArray", connectionObject);
cmd.CommandType = CommandType.StoredProcedure;
SqlParameter tvp1 = c2.Parameters.AddWithValue("#DataTable", dt);
tvp1.SqlDbType = SqlDbType.Structured;
...
More on this shift away from splitting strings here and, actually, in this answer as well:
https://stackoverflow.com/a/11105413/61305
You can use a recursive CTE:
declare #instr varchar(max) = '0|ABC|3033.9|3032.4444|0|0|0^1|DEF|3033.2577|3033.053|3032.0808|0|0^2|JHI|3032.8376|3033.2596|3033.2259|3033.322|0^3|XYZ|3032.8376|3032.8376|3032.8376|3032.8376|0'
;
with cte as (
select row_number() over (order by (select null)) as id, convert(varchar(max), null) as el, Value + '|' as rest, 0 as lev
from string_split(#InStr, '^')
union all
select id, left(rest, charindex('|', rest) - 1),
stuff(rest, 1, charindex('|', rest), ''),
lev + 1
from cte
where rest <> ''
)
select max(case when lev = 1 then el end),
max(case when lev = 2 then el end),
max(case when lev = 3 then el end),
max(case when lev = 4 then el end),
max(case when lev = 5 then el end),
max(case when lev = 6 then el end),
max(case when lev = 7 then el end)
from cte
group by id;
Here is a db<>fiddle.
Unfortunately, you can't safely use string_split() because it does not provide the offset for the values returned.
For a subsequent splitting of pipe-separated substrings you can utilise openjson(), as demonstrated in the example below:
declare #InStr varchar(max) = '0|ABC|3033.9|3032.4444|0|0|0^1|DEF|3033.2577|3033.053|3032.0808|0|0^2|JHI|3032.8376|3033.2596|3033.2259|3033.322|0^3|XYZ|3032.8376|3032.8376|3032.8376|3032.8376|0';
select p.*
from (
select ss.value as [RowId], oj.[key] as [ColumnId], oj.value as [ColumnValue]
from string_split(#InStr,'^') ss
cross apply openjson('["' + replace(ss.value, '|', '","') + '"]', '$') oj
) q
pivot (
min(q.ColumnValue)
for q.[ColumnId] in ([0], [1], [2], [3], [4], [5], [6])
) p;
There are many caveats with this approach, however. The most prominent are:
You need SQL Server 2016 or later, and the database compatibility level needs to be 130 or above;
If your data is of any size worth mentioning (1Mb+), this code might work unacceptably slow. String manipulation is not the strong point of SQL Server.
Personally, I would recommend parsing this string outside of SQL. If it's a flat file you are importing, SSIS dataflow will be much easier to develop and faster to work. If it's an application, then redesign it to pass either a suitable table type, or XML / JSON blob at the very least.
I am generating INSERT statement and then executing it. First I am splitting the string and then I am generating INSERT statement.
Note:
I am assuming that second column will be three letter code.
I am
assuming that sort order of rows doesn't matter
declare #instr varchar(max) = '0|ABC|3033.9|3032.4444|0|0|0^1|DEF|3033.2577|3033.053|3032.0808|0|0^2|JHI|3032.8376|3033.2596|3033.2259|3033.322|0^3|XYZ|3032.8376|3032.8376|3032.8376|3032.8376|0'
;
declare #insertStmt VARCHAR(max) ='INSERT INTO TABLEName VALUES '+ CHAR(13) + CHAR(10);
SELECT #insertStmt += CONCAT('(',replace(stuff(stuff(value,3,0,''''),7,0,''''),'|',','),'),')
from STRING_SPLIT(#instr,'^')
SELECT #insertStmt = STUFF(#insertStmt,len(#insertStmt),1,'')
select #insertStmt
EXEC(#insertStmt)
INSERT INTO TABLEName VALUES
(0,'ABC',3033.9,3032.4444,0,0,0),(1,'DEF',3033.2577,3033.053,3032.0808,0,0),(2,'JHI',3032.8376,3033.2596,3033.2259,3033.322,0),(3,'XYZ',3032.8376,3032.8376,3032.8376,3032.8376,0)

Get all characters to the left of final special character + 2 characters after the final special character SQL Server

I have a varchar column with version information in the following format 1.x.y.z where x,y,z can be of any length.I need to limit the length of z to only two if it is more than 2.
For example:1.345.23.5dfgdfg should be 1.345.23.5d and 1.345.23.5 should be 1.345.23.5
I have the following sql that gives me the required version but is there a simpler way to achieve this?
declare #s varchar(50)
set #s = '1.345.23.5dfgdfg'
select left(#s,len(#s) - (charindex('.',reverse(#s))-1)) + substring(right(#s,charindex('.',reverse(#s))-1),0,3)
condensing some of the logic:
select left(#s,len(#s)+(3-charindex('.',reverse(#s))))
testing with different values for #s:
create table t (s varchar(50))
insert into t values
('1.345.23.5dfgdfg')
,('1.345.23.5d')
,('1.345.23.3')
select s, left(s,len(s)+(3-charindex('.',reverse(s))))
from t
rextester demo: http://rextester.com/USIW73651
returns:
+------------------+------------------+
| s | (No column name) |
+------------------+------------------+
| 1.345.23.5dfgdfg | 1.345.23.5d |
| 1.345.23.5d | 1.345.23.5d |
| 1.345.23.5 | 1.345.23.5 |
+------------------+------------------+
If you know that the string always has this format, I would be inclined to use:
select (case when charindex('.', reverse(#s)) < 3 then #s
else left(#s, len(#s) - charindex('.', reverse(#s)) + 3)
end)
This is pretty similar to your statement, so you may not think it is simpler.
Use Parsename() to divide and add back?
Demo
declare #s varchar(50)
set #s = '1.345.23.5dfgdfg'
Select PARSENAME ( #s , 4 ) +'.'+
PARSENAME ( #s , 3 ) +'.'+
PARSENAME ( #s , 2 ) +'.'+
left(PARSENAME ( #s , 1 ),2)
Usually used in referece to IP address or more specifically as it's intended use: Fully qualified objects
1 = Object name
2 = Schema name
3 = Database name
4 = Server name