T-SQL Case statement utilizing substring and isnumeric - sql

I have a T-SQL stored proc that supplies a good amount of data to a grid on a .NET page....so much so that I put choices at the top of the page for "0-9" and each letter in the alphabet so that when the user clicks the letter I want to filter my results based on results that begin with that first letter. Let's say we're using product names. So if the user clicks on "A" I only want my stored proc to return results where SUBSTRING(ProductName, 1, 1) = "A".
Where I'm getting hung up is on product names that begin with a number. In that case I want to fetch all ProductName values where ISNUMERIC(SUBSTRING(ProductName, 1, 1)) = 1. I'm using an input parameter called #FL. #FL will either be a zero (we have few products that begin with numerics, so I lump them all together this way).
Of course there's also the alternative of WHERE SUBSTRING(ProductName, 1, 1) IN ('0', '1', '2'.....) but even then, I've never been able to devise a CASE statement that will do an = on one evaluation and an IN statement for the other.
Here's what I have in my proc for the CASE part of my WHERE clause. It doesn't work, but it may be valuable if only from a pseudocode standpoint.
Thanks in advance for any ideas you may have.
AND CASE #FL
WHEN "0" THEN
CASE WHEN #FL = "0" THEN
isnumeric(substring(dbo.AssnCtrl.Name, 1, 1)) = 1
ELSE
SUBSTRING(dbo.AssnCtrl.Name, 1, 1) = #FL
END
END
*** I know that this use of the CASE statement is "non-standard", but I found it online and thought it had some promise. But attempts to use a single CASE statement yielded the same result (an error near '=').

Why not just use Like operator ?
Where dbo.AssnCtrl.Name Like #FL + '%'
When they select the Any Number option, pass in #FL as '[0-9]'
(I assume you have an index on this name column ?)

To steal a bit from Charles:
AND Substring(dbo.AssnCtrl.Name, 1, 1) Like
CASE WHEN #FL = '0' THEN '[0-9]'
ELSE #FL
END

Have you tried
AND (
isnumeric(substring(dbo.AssnCtrl.Name, 1, 1)) = 1
or
SUBSTRING(dbo.AssnCtrl.Name, 1, 1) = #FL )
this works for me:
select * from casefile where
isnumeric(substring(pmin,1,1)) = 1
or
substring(pmin,1,1) = 'a'

Related

How to ignore specific string value when using pattern and patindex function in SQL Server Query?

I have this query here.
WITH Cte_Reverse
AS (
SELECT CASE PATINDEX('%[^0-9.- ]%', REVERSE(EmailName))
WHEN 0
THEN REVERSE(EmailName)
ELSE left(REVERSE(EmailName), PATINDEX('%[^0-9.- ]%', REVERSE(EmailName)) - 1)
END AS Platform_Campaign_ID,
EmailName
FROM [Arrakis].[xtemp].[Stage_SendJobs_Marketing]
)
SELECT REVERSE(Platform_Campaign_ID) AS Platform_Campaign_ID, EmailName
FROM Cte_Reverse
WHERE REVERSE(Platform_Campaign_ID) <> '2020'
AND REVERSE(Platform_Campaign_ID) <> ''
AND LEN(REVERSE(Platform_Campaign_ID)) = 4;
It is working for the most part, below is a screenshot of the result set.
The query I posted above extracts the 4 numbers to the right out of the initial value that is set for the column I am extracting out of. But I am unable to figure out how I can also have the query ignore cases when the right most value is -v2, -v1, etc. essentially anything with -v and whatever number version it is.
If you want four digits, then one method is:
select substring(emailname, patindex('%[0-9][0-9][0-9][0-9]%', emailname), 4)

View causing Invalid length parameter passed to the LEFT or SUBSTRING function error when providing where clause

I'm having an odd thing occurring in one of my views. Initially, I have a view that does the following:
SELECT id, CAST((CASE WHEN
LEN(line) = 1
THEN ISNULL(LTRIM(RTRIM(line)), '-1')
ELSE
ISNULL(LTRIM(RTRIM(SUBSTRING(line, 1, (CHARINDEX(CHAR(9),
line) - 1)))), '-1')
END) AS varchar(MAX)) AS ObjMarker
FROM dbo.tblM2016_RAW_Current_Import_File
WHERE ((CASE WHEN
LEN(line) = 1
THEN ISNULL(LTRIM(RTRIM(line)), '')
ELSE
LTRIM(RTRIM(SUBSTRING([line], 1, CHARINDEX(CHAR(9), [line]))))
END) <> CHAR(9))
AND
((CASE WHEN
LEN(line) = 1
THEN ISNULL(LTRIM(RTRIM(line)), '')
ELSE
LTRIM(RTRIM(SUBSTRING([line], 1, CHARINDEX(CHAR(9), [line]))))
END) NOT LIKE '%*%')
AND
((CASE WHEN
LEN(line) = 1
THEN ISNULL(LTRIM(RTRIM(line)), '')
ELSE LTRIM(RTRIM(SUBSTRING([line], 1, CHARINDEX(CHAR(9),
[line])))) END) <> '')
And it works fine. However, I have another view which uses the results of the above view, shown below:
SELECT curr.id
,curr.ObjMarker
,Nxt.id AS NxtID
,Nxt.ObjMarker AS NxtObjMarker
,Nxt.id - curr.id - 2 AS OFFSET
,curr.id + 1 AS StrtRec
,Nxt.id - 1 AS EndRec
FROM dbo.vwM2016_RAW_Import_File_Object_Line_Markers AS curr
LEFT OUTER JOIN
dbo.vwM2016_RAW_Import_File_Object_Line_Markers AS Nxt ON
Nxt.id =
(SELECT MIN(id) AS Expr1
FROM dbo.vwM2016_RAW_Import_File_Object_Line_Markers AS source
WHERE (id > curr.id))
WHERE curr.ObjMarker <> '0'
And apparently, if I leave the WHERE curr.ObjMarker <> '0' in the second query, it gives the error
Msg 537, Level 16, State 3, Line 1
Invalid length parameter passed to the LEFT or SUBSTRING function.
But if I remove WHERE curr.ObjMarker <> '0' it returns the result set without error.
Could this be a problem with the query optimizer not doing operations in order? I've checked the rows where 0 occurs for any special characters in an editor and I couldn't find any hidden whitespace characters or anything.
There's no guarantee of the order the criteria in your select statements will be evaluated, and SQL Server does not short circuit. Predicate pushdown can also happen for any criteria SQL Server estimates to be useful -- so you can't assume that a certain criteria will always be evaluated before something else.
The problem is with this expression:
ISNULL(LTRIM(RTRIM(SUBSTRING(line, 1, (CHARINDEX(CHAR(9),
line) - 1)))), '-1')
If [line] does not contain a Char(9), the CharIndex field returns 0. This turns the substring expression into SUBSTRING(line,1,-1), which is invalid because the length cannot be a negative number.
When not used in the WHERE clause, the expression is not evaluated until after the other filters are applied and the result set is reduced. At least one of the filters in the view eliminate the rows without tabs, so the expression never operates on those rows.
However, when the expression is used in the WHERE clause, it is combined with the view filters and evaluated in the order that SQL Server determines is best for performance. Unfortunately, some of the rows without tabs are still part of the result set when this is evaluated, causing the result to fail.
A possible fix, add an explicit test in your case statement (in the first view where you define objMarker) to address rows that do not contain tabs.
WHEN CHARINDEX(CHAR(9), line) = 0 THEN '-1'

Is this simple UPDATE SQL an error waiting to happen? How to rewrite it?

I need to examine ACCT_NUMS values om TABLE_1. If the ACCT_NUM is prefixed by "GF0", then I need to disregard the "GF0" prefix and take the rightmost 7 characters of the remaining string. If this resulting value is not found in account_x_master or CW_CLIENT_STAGE, then, the record is to be flagged as an error.
The following seems to do the trick, but I have a concern...
UPDATE
table_1
SET
Error_Ind = 'GW001'
WHERE
LEFT(ACCT_NUM, 3) = 'GF0'
AND RIGHT(SUBSTRING(ACCT_NUM, 4, LEN(ACCT_NUM) - 3), 7) NOT IN
(
SELECT
acct_num
FROM
account_x_master
)
AND RIGHT(SUBSTRING(ACCT_NUM, 4, LEN(ACCT_NUM) - 3), 7) NOT IN
(
SELECT
CW_CLIENT_STAGE.AGS_NUM
FROM
dbo.CW_CLIENT_STAGE
)
My concern is that SQL Server may attempt to perform a SUBSTRING operation
SUBSTRING(ACCT_NUM, 4, LEN(ACCT_NUM) - 3)
that results in a computed negative value and causing the SQL to fail. Of course, this wouldn't fail is the SUBSTRING operation were only applied to those records that we at least 3 characters long, which would always be the case if the
LEFT(ACCT_NUM, 3) = 'GF0'
were applied first. If possible, I'd like to avoid adding new columns to the table. Bonus points for simplicity and less overhead :-)
How can I rewrite this UPDATE SQL to protect against this?
As other people said, your concern is valid.
I'd make two changes to your query.
1) To avoid having negative value in the SUBSTRING parameter we can rewrite it using STUFF:
SUBSTRING(ACCT_NUM, 4, LEN(ACCT_NUM) - 3)
is equivalent to:
STUFF(ACCT_NUM, 1, 3, '')
Instead of extracting a tail of a string we replace first three characters with empty string. If the string is shorter than 3 characters, result is empty string.
By the way, if your ACCT_NUM may end with space(s), they will be trimmed by the SUBSTRING version, because LEN doesn't count trailing spaces.
2) Instead of
LEFT(ACCT_NUM, 3) = 'GF0'
use:
ACCT_NUM LIKE 'GF0%'
If you have an index on ACCT_NUM and only relatively small number of rows start with GF0, then index will be used. If you use a function, such as LEFT, index can't be used.
So, the final query becomes:
UPDATE
table_1
SET
Error_Ind = 'GW001'
WHERE
ACCT_NUM LIKE 'GF0%'
AND RIGHT(STUFF(ACCT_NUM, 1, 3, ''), 7) NOT IN
(
SELECT
acct_num
FROM
account_x_master
)
AND RIGHT(STUFF(ACCT_NUM, 1, 3, ''), 7) NOT IN
(
SELECT
CW_CLIENT_STAGE.AGS_NUM
FROM
dbo.CW_CLIENT_STAGE
)
You have a very valid concern, because SQL Server will rearrange the order of evaluation of expressions in the WHERE.
The only way to guarantee the order of operations in a SQL statement is to use case. I don't think there is a way to catch failing calls to substring() . . . there is no try_substring() analogous to try_convert().
So:
WHERE
LEFT(ACCT_NUM, 3) = 'GF0' AND
(CASE WHEN LEN(ACCT_NUM) > 3 THEN RIGHT(SUBSTRING(ACCT_NUM, 4, LEN(ACCT_NUM) - 3), 7) END) NOT IN (SELECT acct_num
FROM account_x_master
) AND
(CASE WHEN LEN(ACCT_NUM) > 3 THEN RIGHT(SUBSTRING(ACCT_NUM, 4, LEN(ACCT_NUM) - 3), 7) END) NOT IN (SELECT CW_CLIENT_STAGE.AGS_NUM
FROM dbo.CW_CLIENT_STAGE
)
This is uglier. And, there may be ways around it, say by using LIKE with wildcards rather than string manipulation. But, the case will guarantee that the SUBSTRING() is only run on strings long enough so no error is generated.
Please try the below query.
Since there is no short circuit and or in SQL WHERE clause, only way to achieve is via CASE syntax.
I noticed that you had two NOT IN comparisons in different parts of WHERE which I combined into one.
Note that CASE condition is >=3 and not >3, as RIGHT('',x) is allowed.
Also note the proper use of CASE with NOT IN
UPDATE table_1
SET
Error_Ind = 'GW001'
select * from table_1
WHERE
LEFT(ACCT_NUM, 3) = 'GF0'
AND CASE
WHEN LEN(ACCT_NUM)>=3
THEN RIGHT(SUBSTRING(ACCT_NUM, 4, LEN(ACCT_NUM) - 3), 7)
ELSE NULL END NOT IN
(
SELECT acct_num as num
FROM account_x_master
UNION
SELECT CW_CLIENT_STAGE.AGS_NUM as num
FROM dbo.CW_CLIENT_STAGE
)

checking for invalid values sql

I have a stored procedure where I have a condition to check whether a Rating Code is 1,2 or 3 in the where clause. Something like this:
WHERE
CONVERT(INT, LEFT(RatingCode, 1)) IN (1,2,3) AND
At times when there are bad values in RatingCode column the above line throws an error. Hence I came up with the below solution:
WHERE
CASE WHEN ISNUMERIC(LEFT(RatingCode, 1)) = 1
THEN CASE WHEN CONVERT(INT, LEFT(RatingCode, 1)) IN (1,2,3)
THEN 1
ELSE 0
END
ELSE 0
END = 1 AND
Here if there is an invalid value(non numeric) in RatingCode column then I want to ignore that record. Is my above solution a good one? Or is there any better solution?
In that specific case, you could also just use
WHERE
LEFT(RatingCode, 1) IN ('1','2','3') AND
Besides that, also string comparisons are allowed in tsql.
WHERE
LEFT(RatingCode, 1) BETWEEN '1' AND '3' AND
This does not throw an error for non-numeric letters.

sql with <> and substring function

The output of query has to return records where company is not equal to 'CABS' OR substring of company until empty space (eg CABS NUTS).The company name can the CABS, COBS, CABST , CABS NUTS , CAB
SELECT *
FROM records
WHERE UPPER(SUBSTR(company, 0, (INSTR(company,' ')-1))) <> 'CABS'
OR COMPANY <> 'CABS'
But the above query is returing CABS NUTS along with COBS , CAB.
I tried using "LIKE CABS" it looks fine but if the company name is "CAB" it will not return "CABS" and CABS NUTS because of like. So LIKE is completely ruled out.
Can anyone please suggest me.
So you want all records where the first 4 characters of the Company field are not "CABS". Okay.
WHERE left(company, 4) != 'CABS'
SELECT
*
FROM
Records
WHERE
LEFT(Company, 4) <> 'CABS'
AND Company <> 'CABS'
Note: Basic TSQL String Comparison is case-insensitive
Can quite work out which ones you do want returns, but have you considered LIKE 'CABS %'
select * from records where company NOT IN (SELECT company
FROM records
WHERE UPPER(SUBSTR(company, 0, (INSTR(company,' ')-1))) = 'CABS'
OR COMPANY = 'CABS')
I think this will fetch the desired records from the records table
RECORDS:
COMPANY
=====================
CAB
CABST
COBS
First, I think you should use AND instead of OR in your compound condition.
Second, you could simplify the condition this way:
WHERE UPPER(SUBSTR(company, 0, (INSTR(company || ' ',' ') - 1))) <> 'CABS'
That is, the company <> 'CABS' part is not needed in this case.
The problem you are getting comes about because the result of the SUBSTR is null if there is not a space. And thanks to three value logic, the result of some_var <> NULL is NULL, rather than TRUE as you might expect.
And example of this is shown by the query below:
with mytab as (
select 1 as myval from dual union all
select 2 as myval from dual union all
select null as myval from dual
)
select *
from mytab
where myval = 1
union all
select *
from mytab
where myval <> 1
This example will only return two rows rather than three rows that you might expect.
There are several ways to rewrite the condition to make it ignore the null result from the substr function. These are listed below. However, as mentioned by one of the other respondents, the two conditions need to be joined using the AND operator rather than OR.
Firstly, you could explicitly check that the column has a space in it using the set of conditions below:
(INSTR(company,' ') = 0 or
UPPER(SUBSTR(company, 0, (INSTR(company,' ')-1))) <> 'CABS') and
COMPANY <> 'CABS'
Another option would be to use the LNNVL function. This is a function that I only recently found out about. It return TRUE from a condition when the result of the condition provided as the input is FALSE or NULL.
lnnvl(UPPER(SUBSTR(company, 0, (INSTR(company,' ')-1))) = 'CABS') and
COMPANY <> 'CABS'
And another option (which would probably be my preferred option) is to use the REGEXP_LIKE function. This is simple, to the point and easy to read.
WHERE not regexp_like(company, '^CABS( |$)')