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

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.

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 a URL to the 5th slash

Could you help me sub-string a list of different URLs?
I can only understand how to sub-string a part of it, but could not manage to make it as requested - which is I need the URL to be full to the 5th slash (bold section), but some of the URLs don't have the 5th slash.
Example URLs:
'http://db-hit-internet/bags/personnel/default.axxxx'
'http://db-hit-internet/store/books/preview/default.axxxx'
'http://db-git-internet/friends/default.aspx?lang=LTT'
Expected output:
'http://db-hit-internet/bags/personnel'
'http://db-hit-internet/store/books/preview'
'http://db-git-internet/friends/default.aspx?lang=LTT'
I have query:
SELECT ('CS' + cast([id] as char (4))) AS name, [SysName], [Link], COUNT(*) AS Viewed
FROM main AS A
INNER JOIN
(
SELECT [LogDate], [LogPage] COLLATE Latin1_General_CI_AS PageName
FROM web
UNION
SELECT [LogDate], [LogPage] PageName
FROM web2
)
ON A.Link= PageName
WHERE A.[Link] is not null
GROUP BY A.id, A.[SysName], A.[Link]
And I need the web and web2 union to have the URLs sub-stringed to the 5th slash. The problem is that there should be a CASE statement to check if the 5th slash exists and then according to that SUBSTRING and CHARINDEX should be included somewhere.
I tried:
LEFT([LogPage], CHARINDEX('/', [LogPage], CHARINDEX('/', [LogPage], CHARINDEX('/', [LogPage], CHARINDEX('//', [LogPage])+2)+1)+1))
But it only works with the URLs that have the 5th slash.
You can also use CROSS APPLY.
SELECT URL,SUBSTRING(URL,1,CASE WHEN (TS.LOC!=0 AND FRS.LOC!=0 AND FVS.LOC!=0) THEN FVS.LOC
ELSE LEN(URL) END) SUBURL
FROM TEST
CROSS APPLY (VALUES(CHARINDEX('/',URL))) FS(LOC)
CROSS APPLY (VALUES(CHARINDEX('/',URL,FS.LOC+1))) SS(LOC)
CROSS APPLY (VALUES(CHARINDEX('/',URL,SS.LOC+1))) TS(LOC)
CROSS APPLY (VALUES(CHARINDEX('/',URL,TS.LOC+1))) FRS(LOC)
CROSS APPLY (VALUES(CHARINDEX('/',URL,FRS.LOC+1))) FVS(LOC)
Check Demo Here
Note : Please change the CASE conditions accordingly (Assumption: 2 slashes will always be there). This will give sub string till 5th slash.
One option is a JSON-based approach, which transforms the data into a valid JSON and parses this JSON with OPENJSON():
Table:
CREATE TABLE Data (url varchar(100))
INSERT INTO Data (url)
VALUES
('http://db-hit-internet/bags/personnel/default.axxxx'),
('http://db-hit-internet/store/books/preview/default.axxxx'),
('http://db-git-internet/friends/default.aspx?lang=LTT'),
('http://db-git-internet.net')
Statement:
SELECT CONCAT(j.part1, j.part2, j.part3, j.part4, j.part5) AS url
FROM Data d
CROSS APPLY OPENJSON(CONCAT('[["', REPLACE(STRING_ESCAPE(d.url, 'json'), '/', '/","'), '"]]')) WITH (
part1 varchar(100) '$[0]',
part2 varchar(100) '$[1]',
part3 varchar(100) '$[2]',
part4 varchar(100) '$[3]',
part5 varchar(100) '$[4]'
) j
Result:
url url
http://db-hit-internet/bags/personnel/default.axxxx http://db-hit-internet/bags/personnel/
http://db-hit-internet/store/books/preview/default.axxxx http://db-hit-internet/store/books/
http://db-git-internet/friends/default.aspx?lang=LTT http://db-git-internet/friends/default.aspx?lang=LTT
http://db-git-internet.net http://db-git-internet.net
If you want a string-based approach, the following statement is a possible solution:
SELECT d.url, LEFT(d.url, v5.pos)
FROM Data d
CROSS APPLY (SELECT CASE WHEN CHARINDEX('/', d.url, 1) = 0 THEN LEN(d.url) ELSE CHARINDEX('/', d.url, 1) END) v1 (pos)
CROSS APPLY (SELECT CASE WHEN CHARINDEX('/', d.url, v1.pos + 1) = 0 THEN LEN(d.url) ELSE CHARINDEX('/', d.url, v1.pos + 1) END) v2 (pos)
CROSS APPLY (SELECT CASE WHEN CHARINDEX('/', d.url, v2.pos + 1) = 0 THEN LEN(d.url) ELSE CHARINDEX('/', d.url, v2.pos + 1) END) v3 (pos)
CROSS APPLY (SELECT CASE WHEN CHARINDEX('/', d.url, v3.pos + 1) = 0 THEN LEN(d.url) ELSE CHARINDEX('/', d.url, v3.pos + 1) END) v4 (pos)
CROSS APPLY (SELECT CASE WHEN CHARINDEX('/', d.url, v4.pos + 1) = 0 THEN LEN(d.url) ELSE CHARINDEX('/', d.url, v4.pos + 1) END) v5 (pos)

How to get the middle word using Substring via Charindex of Second Position?

Basically what I am trying to do is that I want to get the middle word, using the second occurrence of the same character (on this case, dash "-").
This is the sample input:
declare #word nvarchar(max)
set #word = 'Technical Materials - Conversion - Team Dashboard'
There are three parts on this sentence, and they are divided by '-' dash line.
The first part is 'Technical Materials' which I am able to get using:
SELECT LTRIM(RTRIM(SUBSTRING(#word, 0, CHARINDEX('-', #word, 0))))
The last set was 'Team Dashboard' which I am able to get using:
SELECT CASE WHEN LEN(#word) - LEN(REPLACE(#word, '-', '')) = 1
THEN NULL
ELSE
RIGHT(#word,CHARINDEX('-', REVERSE(#word))-1)
END
The problem was, I am having a hard time getting the middle words which is 'Conversion' in this example.
If the format is fixed, you can use PARSENAME to achieve your expectation:
DECLARE #Word AS NVARCHAR(MAX) = 'Technical Materials - Conversion - Team Dashboard'
SELECT PARSENAME(REPLACE(#Word, '-', '.'), 2)
if you want to trim the extra spaces, then:
SELECT LTRIM(RTRIM(PARSENAME(REPLACE(#Word, '-', '.'), 2)))
Try this query:
SELECT
SUBSTRING(#word,
CHARINDEX('-', #word) + 2,
CHARINDEX('-', #word, CHARINDEX('-', #word) + 1) -
CHARINDEX('-', #word) - 3)
FROM yourTable
The general strategy here is to use SUBSTRING(), which requires the starting and ending positions of the middle string in question. We can use CHARINDEX to find both the first and second dash in the string. From this, we can compute the positions of the middle substring we want.
Demo here:
Rextester
This will find the text between the first 2 occurrences of '-'
DECLARE #word nvarchar(max)
SET #word = 'Technical Materials - Conversion - Team Dashboard'
SELECT SUBSTRING(x, 0, charindex('-', x))
FROM (values(stuff(#word, 1, charindex('-', #word), ''))) x(x)
This will find the middle element. In case of an even number of elements it will pick the first of the 2 middle elements
DECLARE #word nvarchar(max)
SET #word = 'Technical Materials - Conversion - Team Dashboard'
;WITH CTE(txt, rn, cnt) as
(
SELECT
t.c.value('.', 'VARCHAR(2000)'),
row_number() over (order by (select 1)), count(*) over()
FROM (
SELECT x = CAST('<t>' +
REPLACE(#word, ' - ', '</t><t>') + '</t>' AS XML)
) a
CROSS APPLY x.nodes('/t') t(c)
)
SELECT txt
FROM CTE
WHERE (cnt+1) / 2 = rn

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)