SQL Error in Non Case condition of CASE WHEN clause - sql

I tried executing the following SQL statement.
SELECT CASE WHEN CHARINDEX('~','test.pdf') > 0
THEN SUBSTRING('test.pdf',CHARINDEX('~', 'test.pdf'), -10)
ELSE NULL
END
This resulted in an error 'Invalid length parameter passed to the substring function.'. However, this was not expected because it is not going to execute anyway.
This query is a simplified version of my requirement. Actually we are computing the value length for the substring. The real scenario is also given below :
SELECT CASE
WHEN CHARINDEX('~', 'test.pdf') > 0 THEN SUBSTRING('test.pdf', CHARINDEX('~', 'test.pdf') + 1, CHARINDEX('~', 'test.pdf', (CHARINDEX('~', 'test.pdf', 1)) + 1) - CHARINDEX('~', 'test.pdf') - 1)
ELSE NULL
END;
In the example its hardcoded as 'test.pdf' but in real scenario it would be values like '111111~22222~33333~4444.pdf' from Table column. Also, I'm not sure this file name should always follow this format. Hence, a validation is required.
Actually, the computation for length is quite expensive, and don't want to use it twice in this query.

You have passed -10 as a constant to substring(). This function does not allow negative values for the third argument:
length
Is a positive integer or bigint expression that specifies how many characters of the expression will be returned. If length is negative, an error is generated and the statement is terminated. If the sum of start and length is greater than the number of characters in expression, the whole value expression beginning at start is returned.
SQL Server catches this problem during the compile phase. This has nothing to do with CASE expression evaluation, but with parsing the expressions.

Related

Divide by zero error encountered, but should be excluded by CASE

I have the following code and I am getting an error:
Divide by zero error encountered.
SELECT
CASE WHEN SUM([monthly_qty]) = 0 THEN 999
ELSE ROUND(SUM([monthly_buy] * ([monthly_markup]+100)/100),2) * SUM([monthly_qty] / [monthly_qty]) END as [monthly_total]
FROM [xxxxx].[dbo].[quote_items] WHERE docid='10152'
The field that is causing the error is the second [monthly_qty], just before the END of the CASE statement.
SUM([monthly_qty] / **[monthly_qty]**)
The value of monthly_qty is zero, so the error makes sense, but I am confused as this field is inside the CASE statement, so the expected result is 999
Any help greatly appreciated.
I can't understand reason of some parts of your code like this:
SUM([monthly_qty] / [monthly_qty])
The result is error when [monthly_qty] = Zero, And 1 for non zero.
Anyway, You can set a default value When "monthly_qty" is zero:
IIF([monthly_qty]= 0,'YOUR_DEFAULT_VALUE', [monthly_qty] / [monthly_qty])
Then:
SUM(IIF([monthly_qty]= 0,'YOUR_DEFAULT_VALUE', [monthly_qty] / [monthly_qty]))
Have a read at MS Docs, there is an example of your case under Remarks.
The CASE expression evaluates its conditions sequentially and stops
with the first condition whose condition is satisfied. In some
situations, an expression is evaluated before a CASE expression
receives the results of the expression as its input. Errors in
evaluating these expressions are possible. Aggregate expressions that
appear in WHEN arguments to a CASE expression are evaluated first,
then provided to the CASE expression. For example, the following query
produces a divide by zero error when producing the value of the MAX
aggregate. This occurs prior to evaluating the CASE expression.
The reason is simple. This code:
SUM([monthly_qty]) = 0
Does not prevent a divide-by-zero in this code:
SUM([monthly_qty] / [monthly_qty])
The expressions are different.
The simplest solution (and essentially the "standard" approach) is to use NULLIF():
SUM([monthly_qty] / NULLIF([monthly_qty], 0))
However, I wonder if you really intend:
SUM([monthly_qty]) / NULLIF(SUM([monthly_qty]), 0)

isnull(#variable, 1) but for 0 instead of null

I have an equation that multiplies loads of variables together, if one of those variables is 0 then I don't want it included in the equation by substituting it for 1 which won't affect the result.
A case when - then, statement for each variable validating if they're greater than 0is a bit clunky.
Is there a similar function like IsNull where if the variable is 0 then it returns an alternate value?
--edit #Backs answer is right but apparently after sql 2012 iif was taken out, when i try to write the statement there is a syntax error at the '=' sign. Is there a replacement for iif after sql-2012?
IIF(#variable = 0, 1, #variable)

Error with substring seperating data by char(10)+char(13) line breaks

What I am attempting to do is separate my data by the line breaks into separate fields: Attn, Addr1Field, Addr2Field. I have found the location of both of the line breaks but the difference isn't the same for every row of data so I'm using the expression as the third option in my Substring() function. I'm getting the error Invalid length parameter passed to the LEFT or SUBSTRING function. In the attached image I hardcoded 34-20 to get my desired results but each row has the possibility to be different so I need to be able to use the expression.
Select
case
when LEFT(CONVERT(VARCHAR(MAX), soship.fmstreet),5)='ATTN:'
Then SUBSTRING(CONVERT(VARCHAR(MAX), soship.fmstreet), 7,CHARINDEX(CHAR(13)+CHAR(10),CONVERT(VARCHAR(MAX), soship.fmstreet))-1)
Else ' '
End as AttnField,
case
when LEFT(CONVERT(VARCHAR(MAX), soship.fmstreet),5)='ATTN:'
Then SUBSTRING(CONVERT(VARCHAR(MAX), soship.fmstreet),CHARINDEX(CHAR(13)+CHAR(10),CONVERT(VARCHAR(MAX), soship.fmstreet))+2,CHARINDEX(CHAR(13)+CHAR(10),CONVERT(VARCHAR(MAX), soship.fmstreet),CHARINDEX(CHAR(13)+CHAR(10),CONVERT(VARCHAR(MAX), soship.fmstreet))+1) -
CHARINDEX(CHAR(13)+CHAR(10),CONVERT(VARCHAR(MAX), soship.fmstreet)))
Else ''
End as Addr1Field
My charindex was returning 0 so I wrote a Where clause to solve it.
Where
(CHARINDEX(CHAR(13)+CHAR(10),CONVERT(VARCHAR(MAX), soship.fmstreet),CHARINDEX(CHAR(13)+CHAR(10),CONVERT(VARCHAR(MAX), soship.fmstreet))+1) -
CHARINDEX(CHAR(13)+CHAR(10),CONVERT(VARCHAR(MAX), soship.fmstreet)) > 0)

Splitting and Analyzing data

I have a field that sometimes contains a string like the following: 2/23/2013 12:25:55~45
I need to split the string at the ~ and identify if what's left of the ~ is a valid date time value and what's right of the ~ is a valid integer. Basically what i want to return is a True/False whether those conditions are correct.
Keep in mind that the field could contain nulls, could contain any other type of data, and it could contain multiple tildes. In all cases I need to return false. The only time I need to return true is when the field contains a date/time value, a single tilde, and a whole number.
In SQL Server, you could do:
select (case when col like '%~%'
then (case when isdate(left(col, charindex('~', col) - 1)) = 1 and
isnumeric(substring(col, charindex('~', col)+1, 1000)) = 1 and col not like '%~%.%' and col not like '%~%e%'
then 1
else 0
end)
else 0
end) as IsFunkyFormat, substring(col, charindex('~', col)+1, 1000), left(col, charindex('~', col) - 1)
The nested case is to prevent errors when the separator is not found. The not like expressions are intended to rule out number formats that are not integers.
This question is trickier than it looks because it would be easy to get wrong, or to write it in such a way that it works now, for your given set of data, but fails to work for other sets of data.
If this is the data you are storing in your database, I strongly encourage you to learn about database normalization. One tenet of normalization is that you only store one value in a column. In this case, you are storing a datetime and an integer value in the same column. It would be far better to store the data in multiple columns.
That being said, I know that there are times when you are given some raw data that you need to import in to your database. Often times, we cannot control the raw data that we are given, so we must make do with SQL gymnastics. In this particular case, there are several different types of back-flips that will be useful.
Determine the number of ~ characters in your string.
splitting the data on the tilde.
Making sure one of the values is a datetime
Making sure the other value is an integer.
Only 1 of the 4 items is built in to SQL Server. There is a function names IsDate that takes a string parameter and returns a bit indicating whether the date represented by the string can be converted to a date.
To determine the number of ~ in your string, the trick is to determine the length of the string with the tilde's, and the length of the string without the tilde's. We can determine which rows contain a single tilde by doing this:
When Len(Data) = Len(Replace(Data, '~', '')) + 1
The other tricky problem to solve is to determine if a string represents a whole number. There are multiple ways of doing this, but my favorite method is to concatenate hard coded values to your data and then test for numeric. For example, the IsNumeric function will return true for the string 1e4 because the e represents scientific notation and 1e4 could be interpreted as 1000. So if you do this:
IsNumeric(Data + 'e0')
This will return false for scientific notation because the data would be something like 1e4, which is concatenated to 'e0' to get '1e4e0' which is not numeric. Similarly, we can concatenate .0 to the string to check for fractional numbers. If your data is 45.2 (which is numeric) and you concatenate .0 to it, you get '45.2.0' which is not numeric. You can also add '-' to the test to check for positive numbers. '-20' is numeric, but '-' + '-20' (which is '--20') is not numeric.
Select YourColumnHere,
Len(Replace(YourColumnHere, '~', '')) + 1,
Case When Len(YourColumnHere) = Len(Replace(YourColumnHere, '~', '')) + 1
Then
Case When IsDate(Left(YourColumnHere, CharIndex('~', YourColumnHere)-1)) = 1
Then
Case When Right(YourColumnHere, Len(YourColumnHere)-CharIndex('~', YourColumnHere)) > ''
Then IsNumeric('-' + Right(YourColumnHere, Len(YourColumnHere)-CharIndex('~', YourColumnHere)) + '.0e0')
Else 0
End
Else 0
End
Else 0
End
From YourTableNameHere

Why IsNull(LTrim(RTrim(Lower(null))), -1) is *?

Today I was testing something at work place and came across this one
Case 1:
Declare #a nvarchar(20)
Set #a = null
Select IsNull(LTrim(RTrim(Lower(#a))), -1)
Case 2:
Select IsNull(LTrim(RTrim(Lower(null))), -1)
The result in case 1 is -1 but * in case 2
I was expecting same results in both cases. Any reason?
Without the declaration of data type, null in this case is declared as varchar(1). You can observe this by selecting the results into a #temp table:
Select IsNull(LTrim(RTrim(Lower(null))), -1) as x INTO #x;
EXEC tempdb..sp_help '#x';
Among the results you'll see:
Column_name Type Length
----------- ------- ------
x varchar 1
Since -1 can't fit in a varchar(1), you are getting * as output. This is similar to:
SELECT CONVERT(VARCHAR(1), -1);
If you want to collapse to a string, then I suggest enclosing the integer in single quotes so there is no confusion caused by integer <-> string conversions that aren't intended:
SELECT CONVERT(VARCHAR(1), '-1'); -- yields "-"
SELECT CONVERT(VARCHAR(30), '-1'); -- yields "-1"
I would not make any assumptions about how SQL Server will handle a "value" explicitly provided as null, especially when complex expressions make it difficult to predict which evaluation rules might trump data type precedence.
In SQL Server, there are "typed NULLs" and "untyped NULLs".
In the first case, the NULL is typed—it is aware that NULL is a varchar(20) and so as your functions wrap the inner value, that data type is propagated throughout the expression.
In the second case, the NULL is untyped, so it has to infer the NULL's type from the surrounding expressions. The IsNull function evaluates the data type of the first operand and applies that to the whole expression, and thus the NULL defaults to varchar(1):
PRINT sql_variant_property(IsNull(LTrim(NULL), -1), 'BaseType'); -- varchar
PRINT sql_variant_property(IsNull(LTrim(NULL), -1), 'MaxLength'); -- 1
Another complication is that IsNull does not do type promotion in the same way that Coalesce does (though Coalesce has its own problems due to not being a function—it is expanded to a CASE expression, sometimes causing unexpected side-effects due to repeat expression evaluation). Look:
SELECT Coalesce(LTrim(NULL), -1);
This results in -1 with data type int!
Check out Sql Server Data Type Precedence and you'll see that int is much higher than varchar, so the whole expression becomes int.
The naked NULL is being passed to LOWER(), which expects a character. This is being defaulted to one character wide. The value "-1" doesn't fit in this field, so it is returning "*".
You can get the same effect with:
select isnull(CAST(NULL as varchar(1)), -1)
The following code also causes the problem:
declare #val varchar;
set #val = -1
select #val
Note that COALESCE() does not cause this problem.
I'm pretty sure this is fully documented behavior.