SQL - Selecting portion of a string - sql

If I have a simple table where the data is such that the rows contains strings like:
/abc/123/gyh/tgf/345/6yh/5er
In SQL, how can I select out the data between the 5th and 6th slash? Every row I have is simply data inside front-slashes, and I will only want to select all of the characters between slash 5 and 6.

CLR functions are more efficient in handling strings than T-SQL. Here is some info to get you started on writing a CLR user defined function.
http://msdn.microsoft.com/en-us/library/ms189876.aspx
http://www.mssqltips.com/tip.asp?tip=1344
I think you should create the function that has 3 parameters:
the value you are searching
the delimiter (in your case: /)
The instance you are looking for (in your case: 5)
Then you split on the delimiter (into an array). Then return the 5th item in the array (index 4)
Here is a t-sql solution, but I really believe that a CLR solution would be better.
DECLARE #RRR varchar(500)
SELECT #RRR = '/abc/123/gyh/tgf/345/6yh/5er'
DECLARE
#index INT,
#INSTANCES INT
SELECT
#index = 1,
#INSTANCES = 5
WHILE (#INSTANCES > 1) BEGIN
SELECT #index = CHARINDEX('/', #RRR, #index + 1)
SET #INSTANCES = #INSTANCES - 1
END
SELECT SUBSTRING(#RRR, #index + 1, CHARINDEX('/', #RRR, #index + 1) - #index - 1)

SELECT SUBSTRING(myfield,
/* 5-th slash */
CHARINDEX('/', myfield,
CHARINDEX('/', myfield,
CHARINDEX('/', myfield,
CHARINDEX('/', myfield,
CHARINDEX('/', myfield) + 1) + 1) + 1) + 1)
+ 1,
/* 6-th slash */
CHARINDEX('/', myfield,
CHARINDEX('/', myfield,
CHARINDEX('/', myfield,
CHARINDEX('/', myfield,
CHARINDEX('/', myfield,
CHARINDEX('/', myfield) + 1) + 1) + 1) + 1) + 1)
-
/* 5-th slash again */
CHARINDEX('/', myfield,
CHARINDEX('/', myfield,
CHARINDEX('/', myfield,
CHARINDEX('/', myfield,
CHARINDEX('/', myfield) + 1) + 1) + 1) + 1)
- 1)
FROM myTable
WHERE ...
This will work, but it's far from elegant. If possible, select the complete field and filter out the required value on the client side (using a more powerful programming language than T-SQL). As you can see, T-SQL was not designed to do this kind of stuff.
(Edit: I know the following does not apply to your situation but I'll keep it as a word of advise for others who read this:)
In fact, relational databases are not designed to work with string-separated lists of values at all, so an even better solution would be to split that field into separate fields in your table (or into a subtable, if the number of entries varies).

Maybe... SELECT FROM `table` WHERE `field` LIKE '%/345/%'

Related

T-SQL substring between two slashes to extract data

I am trying extract part of a string in T-SQL for a project I am working on.
Examples:
/Clients/AAA/Something/Something
/Clients/BBBB/Something/Something
I am specifically trying to extract the AAA or the BBB which are not a consistent number of characters.
Try the following using CHARINDEX and SUBSTRING.
drop table #a
create table #a (d varchar(100))
insert into #a (d)
values ('/Clients/AAA/Something/Something/')
,('/Clients/bbbbb/Something/Something/')
select d as [OriginalData]
,charindex('/', d, charindex('/', d, 0)+1) as [SecondSlash]
,charindex('/', d, charindex('/', d, charindex('/', d, 0)+1)+1) as [ThirdSlash]
,SUBSTRING(d -- Value
, charindex('/', d, charindex('/', d, 0)+1)+1 -- Startpoint (SecondSlash) + 1
, charindex('/', d, charindex('/', d, charindex('/', d, 0)+1)+1) - charindex('/', d, charindex('/', d, 0)+1)-1) as [Extract]
-- Endpoint (ThirdSlash - SecondSlash - 1)
from #a
It's a bit messy and will only return the text between the second and third slash, but it should be fairly quick.
I find that apply is convenient for expressing this type of logic:
with t as (
select *
from (values ('/Clients/AAA/Something/Something/'), ('/Clients/bbbbb/Something/Something/')) t(str)
)
select *, left(str2, charindex('/', str2) - 1)
from t cross apply
(values (stuff(str, 1, patindex('%_/%', str) + 1, ''))) v(str2);
Note that this looks for the pattern _/ to find the second slash in the string.
select Data
-- Find second slash
, charindex('/', Data, 2)
-- Find third slash
, charindex('/', Data, charindex('/', Data, 2)+1)
-- Find string between the second and third slash
, substring(data, charindex('/', Data, 2) + 1, charindex('/', Data, charindex('/', Data, 2)+1) - charindex('/', Data, 2) - 1)
from (
select '/Clients/AAA/Something/Something' Data
union all select '/Clients/BBBB/Something/Something'
) x

Get substring between second and fourth slash

I have a string that looks like this:
Y:\Data\apples\oranges\Scott\notes
I need a column that looks like this:
apples\oranges
This is what I have so far and it does not work:
SELECT SUBSTRING(
[Group],
CHARINDEX('\', [Group]) + 1,
LEN([Group]) - CHARINDEX('\', [Group]) - CHARINDEX('\', REVERSE([Group]))
) from datamap.finaltest
The strings will not always have a finite amount of slashes. For example you could have:
Y:\Data\Apples\bananas
Y:\Apples\Pears\oranges\peanuts
The data will always have:
drive letter + '\' + '1st level folder' + '\' + 'Second level folder'
It may have more than two levels though.
I have searched the forum but can't find anything specific.
Thanks
A blatant approach by converting your input into XML and taking the values by node and re-concatenating the nodes you want in output
;WITH MyTempData
AS
(
SELECT Convert(xml,'<n>'+Replace('Y:\Data\Apples','\','</n><n>')+'</n>') XMLString
)
SELECT COALESCE(XMLString.value('(/n[3])', 'varchar(20)'),'') + '\' +
COALESCE(XMLString.value('(/n[4])', 'varchar(20)'),'') MyFinalOutput
FROM MyTempData
Probably not the best way, but this will get you there.
DECLARE #string varchar(255) = 'Y:\data\apples\oranges\Scott\notes'
SELECT LEFT(RIGHT(#string,LEN(#string)-CHARINDEX('\', #string, CHARINDEX('\', #string,1) + 1)),CHARINDEX('\', RIGHT(#string,LEN(#string)-CHARINDEX('\', #string, CHARINDEX('\', #string,1) + 1)), CHARINDEX('\',RIGHT(#string,LEN(#string)-CHARINDEX('\', #string, CHARINDEX('\', #string,1) + 1)),1)+1)-1)
Here is a way using recursive CHARINDEX
declare #var varchar(4000) = 'Y:\Data\apples\oranges\Scott\notes'
declare #firstSlash int = (select CHARINDEX('\',#var,CHARINDEX('\',#var) + 1))
declare #fourthSlash int = (select CHARINDEX('\',#var,CHARINDEX('\',#var,CHARINDEX('\',#var,CHARINDEX('\',#var) + 1)+1)+1))
select SUBSTRING(#var,#firstSlash + 1,#fourthSlash - #firstSlash - 1)
Or, for your data table...
select SUBSTRING([Group],CHARINDEX('\',[Group],CHARINDEX('\',[Group]) + 1) + 1,CHARINDEX('\',[Group],CHARINDEX('\',[Group],CHARINDEX('\',[Group],CHARINDEX('\',[Group]) + 1)+1)+1) - CHARINDEX('\',[Group],CHARINDEX('\',[Group]) + 1) - 1)
If this is something you need to do often, or is prone to changing, it may be beneficial to implement a function which will make your code more readable/maintainable:
SELECT SUBSTRING(#t, dbo.CHARINDEX2('\', #t, 2) + 1, dbo.CHARINDEX2('\', #t, 3));
Using this 'find nth occurence' function:
http://www.sqlservercentral.com/scripts/Miscellaneous/30497/

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.

How to get expression from string

Here is the string :'(a+b)+(x/y)*1000'
from that string i want to get '(x/y)' meaning i want the part that contains the division to check later if denominator <> 0 to avoid division by zero.
The string formula can vary but divisions are always between parenthesis.
How can i achieve that in sql ?
Bits that it appears you already have (based on a comment you made)...
Pos of the '/' = CHARINDEX('/', yourString)
Pos of the ')' = CHARINDEX(')', yourString, CHARINDEX('/', yourString) + 1)
The position of the ( is a little different, as you need to search backwards. So you need to reverse the string. And so you also need to change the starting position.
CHARINDEX('(', REVERSE(yourString), LEN(yourString) - CHARINDEX('/', yourString) + 2)
Which give the position from the right hand side. LEN(yourString) - position + 1 give the position from the left hand side.
Add that all together and you get a very long formula...
SUBSTRING(
yourString,
LEN(yourString)
- CHARINDEX('(', REVERSE(yourString), LEN(yourString) - CHARINDEX('/', yourString) + 2)
+ 1,
CHARINDEX(')', yourString, CHARINDEX('/', yourString) + 1)
- LEN(yourString)
+ CHARINDEX('(', REVERSE(yourString), LEN(yourString) - CHARINDEX('/', yourString) + 2)
- 1
)
Remove everything up to the second ( using stuff and get the characters to the next ) using left.
declare #S varchar(20)
set #S = '(1+2)+(3/4)*1000'
select left(S2.S, charindex(')', S2.S)-1)
from (select stuff(#S, 1, charindex('(', #S), '')) as S1(S)
cross apply (select stuff(S1.S, 1, charindex('(', S1.S), '')) as S2(S)

Ternary operator in SQL? "invalid length parameter passed to the LEFT or SUBSTRING function"

Sorry for this misleading subject, i didn't know how to word better.
Because i'm mainly a software-developer, the ternary operator comes to my mind with my following problem.
I need to find the most robust way to link two tables via nullable foreign-key(modModel and tabSparePart). The only similarity between both is the model's name and the sparepart's description(the tabSparePart is an external table from customer that is imported automatically, so it's not my responsibility and i cannot change the data).
Consider the following sparepart-names:
W200I_E/Swap
EXCHANGEUNIT P1i / SILVERBLACK/ CYRILLIC
The modelnames that i want to find are P1i and W200I_E.
So there is only one strong rule that i can ensure in the where-clause:
there must be a separator / and the relevant part is the first one.
Here is the sample data:
Create table #temp(Partname varchar(100))
INSERT INTO #temp
SELECT 'EXCHANGEUNIT P1i / SILVERBLACK/ CYRILLIC' UNION ALL SELECT 'W200I_E/Swap unit/Black'
I would have been finished with following query:
SELECT RTRIM(LEFT(Partname, CHARINDEX('/', Partname) - 1)) AS UNIT
FROM #temp
WHERE CHARINDEX('/', Partname) > 0
... what returns:
EXCHANGEUNIT P1i
W200I_E
But i need P1i. So i need a way to handle also the case that the first part is separated by whitespaces. In that case i need to select the last word, but only if it is separated at all.
I'm getting a "invalid length parameter passed to the LEFT or SUBSTRING function"-error with following query:
SELECT REVERSE( LEFT( REVERSE(RTRIM(LEFT(Partname, CHARINDEX('/', Partname) - 1)))
, CHARINDEX(' ', REVERSE(RTRIM(LEFT(Partname, CHARINDEX('/', Partname) - 1))))-1 ))
AS Unit
FROM #temp
WHERE CHARINDEX('/', Partname) > 0
This would work without the second record that has no whitespace. If i would also ensure that the first part contains a whitespace, i would discard valid records.
To cut a long story short, I need to find a way to combine both ways according to the existence of separators.
PS: This has arisen from: Get the last word of a part of a varchar (LEFT/RIGHT)
If anybody is interested, this is the complete (working) stored-procedure. I'm sure i've never used such a strange JOIN:
CREATE PROC [dbo].[UpdateModelSparePart](#updateCount int output)
with execute as Owner
AS
BEGIN
BEGIN TRANSACTION
UPDATE modModel SET fiSparePart=ModelPart.idSparePart
FROM modModel INNER JOIN
(
SELECT m.idModel
,m.ModelName
,sp.idSparePart
,sp.Price
,Row_Number()Over(Partition By idModel ORDER BY Price DESC)as ModelPrice
FROM modModel AS m INNER JOIN tabSparePart AS sp
ON m.ModelName = CASE
WHEN CHARINDEX(' ', REVERSE(RTRIM(LEFT(sp.SparePartDescription, CHARINDEX('/', sp.SparePartDescription) - 1)))) > 0 THEN
REVERSE( LEFT( REVERSE(RTRIM(LEFT(sp.SparePartDescription, CHARINDEX('/', sp.SparePartDescription) - 1)))
,CHARINDEX(' ', REVERSE(RTRIM(LEFT(sp.SparePartDescription, CHARINDEX('/', sp.SparePartDescription) - 1))))-1 ))
ELSE
RTRIM(LEFT(sp.SparePartDescription, CHARINDEX('/', sp.SparePartDescription) - 1))
END
WHERE (CHARINDEX('/', sp.SparePartDescription) > 0)
GROUP BY idModel,ModelName,idSparePart,Price
)As ModelPart
ON ModelPart.idModel=modModel.idModel
Where ModelPrice=1
SET #updateCount = ##ROWCOUNT;
COMMIT TRANSACTION
END
A more concise version.
SELECT REVERSE(SUBSTRING(Rev, 0, CHARINDEX(' ', Rev))) AS Unit
FROM #temp
CROSS APPLY (
SELECT REVERSE(RTRIM(LEFT(Partname, CHARINDEX('/', Partname) - 1))) + ' '
) T(Rev)
WHERE CHARINDEX('/', Partname) > 0
I was able to solve the problem:
SELECT 'Unit' =
CASE
WHEN CHARINDEX(' ', REVERSE(RTRIM(LEFT(Partname, CHARINDEX('/', Partname) - 1)))) > 0 THEN
REVERSE( LEFT( REVERSE(RTRIM(LEFT(Partname, CHARINDEX('/', Partname) - 1)))
,CHARINDEX(' ', REVERSE(RTRIM(LEFT(Partname, CHARINDEX('/', Partname) - 1))))-1 ))
ELSE
RTRIM(LEFT(Partname, CHARINDEX('/', Partname) - 1))
END
FROM #temp
WHERE CHARINDEX('/', Partname) > 0
Ugly but working fine.