How to work with reverse and split in a select in SQL Server 2008? - sql

I found the below query very useful for an application I'm working on. However, I wanna replace the values for a select in a table.
WITH data AS
(
select Value, REVERSE(Value) AS ReverseValue from (values
('texttext/21/812/21a'), ('texttext/6/163/38a'), ('texttext/53/7a/a2'), ('text/t/e/xt/53/7a/a2')
)t(Value)
), split AS
(
select
Value, ReverseValue,
reverse(substring(ReverseValue, 1, P1.Pos - 1)) AS Forth,
reverse(substring(ReverseValue, P1.Pos + 1, P2.Pos - P1.Pos - 1)) AS Third,
reverse(substring(ReverseValue, P2.Pos + 1, P3.Pos - P2.Pos - 1)) AS Second,
reverse(substring(ReverseValue, p3.Pos + 1, len(ReverseValue))) AS First
from data
cross apply (select (charindex('/', ReverseValue))) as P1(Pos)
cross apply (select (charindex('/', ReverseValue, P1.Pos+1))) as P2(Pos)
cross apply (select (charindex('/', ReverseValue, P2.Pos+1))) as P3(Pos)
)
select Value, First + '-' + Forth + Third + Second AS NewValue from split
So instead of (values ('texttext/21/812/21a') (...) i want something like (select myfield from myutable). Any ideas of how to do that? Thanks.

Just replace the values in the first CTE:
WITH data AS (
select myfield, REVERSE(myfield) AS ReverseValue
from mytable
),
. . .

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

Substring in nested query

I have a nvarchar column in table that stores data as '{CRLF}' separated values. Those values can be in the format 'namespace#name$value' but also in other formats.
I need to extract separated namespace, name and value and skip data in other formats. I can do it with a query:
select
SUBSTRING([value], 1, CHARINDEX('#',[value]) - 1) as paramNamespace,
SUBSTRING([value], CHARINDEX('#',[value]) + 1, CHARINDEX('$',[value]) - CHARINDEX('#',[value]) - 1) as paramName,
SUBSTRING([value], CHARINDEX('$',[value]) + 1, LEN([value])) as paramValue
FROM STRING_SPLIT(REPLACE((select data from test where Id = 1), '{CRLF}', CHAR(7)), CHAR(7))
WHERE [value] LIKE '%#%$%'
However, when I try also to filter in by paramName with this query:
select * from (select
SUBSTRING([value], 1, CHARINDEX('#',[value]) - 1) as paramNamespace,
SUBSTRING([value], CHARINDEX('#',[value]) + 1, CHARINDEX('$',[value]) - CHARINDEX('#',[value]) - 1) as paramName,
SUBSTRING([value], CHARINDEX('$',[value]) + 1, LEN([value])) as paramValue
FROM STRING_SPLIT(REPLACE((select data from test where Id = 1), '{CRLF}', CHAR(7)), CHAR(7))
WHERE [value] LIKE '%#%$%') a
where paramName IN ('name', 'name3')
it returns
Invalid length parameter passed to the LEFT or SUBSTRING function.
It looks like "WHERE [value] LIKE '%#%$%'" weren't executed first and it tries to substring value in other format.
How should I write this query to filter it by paramName?
Schema:
CREATE TABLE test (Id INT, data nvarchar(max));
INSERT INTO test VALUES (1, N'namespace#name$value{CRLF}somedatainotherformat{CRLF}namespace2#name2$value2{CRLF}namespace3#name3$value3{CRLF}');
Fiddle: http://sqlfiddle.com/#!18/e5d71/1
Perhaps PARSENAME to split the data into columns is what you are after:
CREATE TABLE test (Id int,
data nvarchar(MAX));
INSERT INTO test
VALUES (1, N'namespace#name$value{CRLF}somedatainotherformat{CRLF}namespace2#name2$value2{CRLF}namespace3#name3$value3{CRLF}');
GO
SELECT *
FROM dbo.test t
CROSS APPLY STRING_SPLIT(REPLACE(t.data,'{CRLF}','|'),'|') SS
CROSS APPLY (VALUES(PARSENAME(TRANSLATE(SS.[value],'$#','..'),3),PARSENAME(TRANSLATE(SS.[value],'$#','..'),2),PARSENAME(TRANSLATE(SS.[value],'$#','..'),1)))V(paramNamespace, paramName, paramValue)
WHERE V.paramName IN ('name', 'name3')
GO
DROP TABLE test;
Here is one option:
Select paramNamespace = substring(v.data, 1, p1.pos - 2)
, paramName = substring(v.data, p1.pos, p2.pos - p1.pos - 1)
, paramValue = substring(v.data, p2.pos, p3.pos - p2.pos - 1)
From test As t
Cross Apply string_split(replace(t.data, '{CRLF}', '|'), '|') As s
Cross Apply (Values (concat(s.value, '#$'))) As v(data)
Cross Apply (Values (charindex('#', v.data, 1) + 1)) As p1(pos)
Cross Apply (Values (charindex('$', v.data, p1.pos) + 1)) As p2(pos)
Cross Apply (Values (charindex('#', v.data, p2.pos) + 1)) As p3(pos) --end of set
Where p2.pos - p1.pos > 1;
We concat the delimiters to the end - and then filter out any rows where the difference between the delimiters is greater than 1. So any rows that have only '#$' or rows that start with '#$' or rows that have some other value that does not include either of the delimiters.
If you have data that can have either delimiter - but not the other, you need to filter those out some other way.
I combined Larnu's query with my and created a query that separates only the data in the expected format and then filters it:
SELECT paramName, paramValue
FROM STRING_SPLIT(REPLACE((select data from test where Id = 1),'{CRLF}',CHAR(7)),CHAR(7)) SS
CROSS APPLY (VALUES(SUBSTRING([value], 1, CHARINDEX('#',[value]) - 1),
SUBSTRING([value], CHARINDEX('#',[value]) + 1, CHARINDEX('$',[value]) - CHARINDEX('#',[value]) - 1),
SUBSTRING([value], CHARINDEX('$',[value]) + 1, LEN([value]))))V(paramNamespace, paramName, paramValue)
WHERE ss.value LIKE '%#%$%' and paramName IN ('name', 'name3')
But I still don't understand why the subquery from my question produces an error.

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 solve this string algorithm?

So here is the challenge:
I need to update the strings of a specific column in my table following the pattern as the examples below:
Example 1:
From: texttext/21/812/21a
To: texttext-21a81221
Example 2:
From: texttext/6/163/38a
To: texttext-38a1636
"texttext" lenght may vary and may contain slashes (/) as well.
Also, the block 2 and block 3 of numbers can’t have 2 digits.
So,
texttext/53/7a/a2
must turn into:
texttext-0a207a53
I'm using SQL Server 2008.
I appreciate your efforts to help me.
Thanks!
Not sure what you mean of block 2 and block 3 of numbers can’t have 2 digits
Anyway below should give you idea
WITH data AS
(
select Value, REVERSE(Value) AS ReverseValue from (values
('texttext/21/812/21a'), ('texttext/6/163/38a'), ('texttext/53/7a/a2'), ('text/t/e/xt/53/7a/a2')
)t(Value)
), split AS
(
select
Value, ReverseValue,
reverse(substring(ReverseValue, 1, P1.Pos - 1)) AS Forth,
reverse(substring(ReverseValue, P1.Pos + 1, P2.Pos - P1.Pos - 1)) AS Third,
reverse(substring(ReverseValue, P2.Pos + 1, P3.Pos - P2.Pos - 1)) AS Second,
reverse(substring(ReverseValue, p3.Pos + 1, len(ReverseValue))) AS First
from data
cross apply (select (charindex('/', ReverseValue))) as P1(Pos)
cross apply (select (charindex('/', ReverseValue, P1.Pos+1))) as P2(Pos)
cross apply (select (charindex('/', ReverseValue, P2.Pos+1))) as P3(Pos)
)
select Value, First + '-' + Forth + Third + Second AS NewValue from split

Extract string between after second / and before -

I have a field that holds an account code. I've managed to extract the first 2 parts OK but I'm struggling with the last 2.
The field data is as follows:
812330/50110/0-0
812330/50110/BDG001-0
812330/50110/0-X001
I need to get the string between the second "/" and the "-" and after the "-" .Both fields have variable lengths, so I would be looking to output 0 and 0 on the first record, BDG001 and 0 on the second record and 0 and X001 on the third record.
Any help much appreciated, thanks.
You can use CHARINDEX and LEFT/RIGHT:
CREATE TABLE #tab(col VARCHAR(1000));
INSERT INTO #tab VALUES ('812330/50110/0-0'),('812330/50110/BDG001-0'),
('812330/50110/0-X001');
WITH cte AS
(
SELECT
col,
r = RIGHT(col, CHARINDEX('/', REVERSE(col))-1)
FROM #tab
)
SELECT col,
r,
sub1 = LEFT(r, CHARINDEX('-', r)-1),
sub2 = RIGHT(r, LEN(r) - CHARINDEX('-', r))
FROM cte;
LiveDemo
EDIT:
or even simpler:
SELECT
col
,sub1 = SUBSTRING(col,
LEN(col) - CHARINDEX('/', REVERSE(col)) + 2,
CHARINDEX('/', REVERSE(col)) -CHARINDEX('-', REVERSE(col))-1)
,sub2 = RIGHT(col, CHARINDEX('-', REVERSE(col))-1)
FROM #tab;
LiveDemo2
EDIT 2:
Using PARSENAME SQL SERVER 2012+ (if your data does not contain .):
SELECT
col,
sub1 = PARSENAME(REPLACE(REPLACE(col, '/', '.'), '-', '.'), 2),
sub2 = PARSENAME(REPLACE(REPLACE(col, '/', '.'), '-', '.'), 1)
FROM #tab;
LiveDemo3
...Or you can do this, so you only go from left side to right, so you don't need to count from the end in case you have more '/' or '-' signs:
SELECT
SUBSTRING(columnName, CHARINDEX('/' , columnName, CHARINDEX('/' , columnName) + 1) + 1,
CHARINDEX('-', columnName) - CHARINDEX('/' , columnName, CHARINDEX('/' , columnName) + 1) - 1) AS FirstPart,
SUBSTRING(columnName, CHARINDEX('-' , columnName) + 1, LEN(columnName)) AS LastPart
FROM table_name
One method way is to download a split() function off the web and use it. However, the values end up in separate rows, not separate columns. An alternative is a series of nested subqueries, CTEs, or outer applies:
select t.*, p1.part1, p12.part2, p12.part3
from table t outer apply
(select t.*,
left(t.field, charindex('/', t.field)) as part1,
substring(t.field, charindex('/', t.field) + 1) as rest1
) p1 outer apply
(select left(p1.rest1, charindex('/', p1.rest1) as part2,
substring(p1.rest1, charindex('/', p1.rest1) + 1, len(p1.rest1)) as part3
) p12
where t.field like '%/%/%';
The where clause guarantees that the field value is in the right format. Otherwise, you need to start sprinkling the code with case statements to handle misformated data.