Invalid length parameter passed to the substring function case - sql-server

I have an issue with substring function while using it inside case statement.
select
case when -1<0 then 'ok' else SUBSTRING('abcd',1,-1) end
gives me an issue:
Invalid length parameter passed to the substring function.
Why is the case "looking" at the else condition since the first condition is met?
On the other hand query:
declare #a int;
set #a=-1
select
#a,
case when #a<0 then 'ok' else SUBSTRING('abcd',1,#a) end
presents the right answer 'ok' without any errors.

The problem is that the literal value of -1 is parsed by the compiler before run-time for the length parameter. The compiler knows that -1 is invalid, as length must have a positive value, so the error is flagged before the SQL is even run.
In the latter statement, the length passed is a variable. At compile time, the variable has an "unknown" value, as it's not SET till run time, thus the syntax is fine.
Simply put, the compiler knows that a length of -1 for SUBSTRINGis invalid, regardless of if that SQL will actually run, and so errors.
Unlike other functions, such as STUFF and REPLICATE, which state "If length is negative, a null string is returned.", SUBSTRING, LEFT, and RIGHT all state: "If integer_expression is negative, an error is returned." For a literal value, it appears that the compiler is checking these values, even if they will never be used, and then flagging the error.
This isn't limited to logic within a CASE either. For example, the using logical flow operators such as IF generates the same behaviour:
IF 1 = 0
SELECT LEFT('abc',-1)
As does the ISNULL function:
SELECT ISNULL('ok',RIGHT('abc',-1));
It only, however, occurs with literal values. If, for example, you were to use the values from a column, the behaviour is not seen:
IF 1 = 0
SELECT SUBSTRING('abc',1,n) FROM (VALUES(1),(-1)) V(n);
This does not return an error, even though everything in VALUES is a literal. That is because the value of n has not been evaluated.

You can try this as it needed positive end values as length can not be in negative but can be 0.
select
case when -1 < 0 then 'ok' else SUBSTRING('abcd',1, 1) end

Related

SQL Server CHOOSE() function behaving unexpectedly with RAND() function

I've encountered an interesting SQL server behaviour while trying to generate random values in T-sql using RAND and CHOOSE functions.
My goal was to try to return one of two given values using RAND() as rng. Pretty easy right?
For those of you who don't know it, CHOOSE function accepts in an index number(int) along with a collection of values and returns a value at specified index. Pretty straightforward.
At first attempt my SQL looked like this:
select choose(ceiling((rand()*2)) ,'a','b')
To my surprise, this expression returned one of three values: null, 'a' or 'b'. Since I didn't expect the null value i started digging. RAND() function returns a float in range from 0(included) to 1 (excluded). Since I'm multiplying it by 2, it should return values anywhere in range from 0(included) to 2 (excluded). Therefore after use of CEILING function final value should be one of: 0,1,2. After realising that i extended the value list by 'c' to check whether that'd be perhaps returned. I also checked the docs page of CEILING and learnt that:
Return values have the same type as numeric_expression.
I assumed the CEILINGfunction returned int, but in this case would mean that the value is implicitly cast to int before being used in CHOOSE, which sure enough is stated on the docs page:
If the provided index value has a numeric data type other than int,
then the value is implicitly converted to an integer.
Just in case I added an explicit cast. My SQL query looks like this now:
select choose(cast(ceiling((rand()*2)) as int) ,'a','b','c')
However, the result set didn't change. To check which values cause the problem I tried generating the value beforehand and selecting it alongside the CHOOSE result. It looked like this:
declare #int int = cast(ceiling((rand()*2)) as int)
select #int,choose( #int,'a','b','c')
Interestingly enough, now the result set changed to (1,a), (2,b) which was my original goal. After delving deeper in the CHOOSE docs page and some testing i learned that 'null' is returned in one of two cases:
Given index is a null
Given index is out of range
In this case that would mean that index value when generated inside the SELECT statement is either 0 or above 2/3 (I'm assuming that negative numbers are not possible here and CHOOSE function indexes from 1). As I've stated before 0 should be one of possibilities of:
ceiling((rand()*2))
,but for some reason it's never 0 (at least when i tried it 1 million+ times like this)
set nocount on
declare #test table(ceiling_rand int)
declare #counter int = 0
while #counter<1000000
begin
insert into #test
select ceiling((rand()*2))
set #counter=#counter+1
end
select distinct ceiling_rand from #test
Therefore I assume that the value generated in SELECT is greater than 2/3 or NULL. Why would it be like this only when generated in SELECT statement? Perhaps order of resolving CAST, CELING or RAND inside SELECT is different than it would seem? It's true I've only tried it a limited number of times, but at this point the chances of it being a statistical fluctuation are extremely small. Is it somehow a floating-point error? I truly am stumbled and looking forward to any explanation.
TL;DR: When generating a random number inside a SELECT statement result set of possible values is different then when it's generated before the SELECT statement.
Cheers,
NFSU
EDIT: Formatting
You can see what's going on if you look at the execution plan.
SET SHOWPLAN_TEXT ON
GO
SELECT (select choose(ceiling((rand()*2)) ,'a','b'))
Returns
|--Constant Scan(VALUES:((CASE WHEN CONVERT_IMPLICIT(int,ceiling(rand()*(2.0000000000000000e+000)),0)=(1) THEN 'a' ELSE CASE WHEN CONVERT_IMPLICIT(int,ceiling(rand()*(2.0000000000000000e+000)),0)=(2) THEN 'b' ELSE NULL END END)))
The CHOOSE is expanded out to
SELECT CASE
WHEN ceiling(( rand() * 2 )) = 1 THEN 'a'
ELSE
CASE
WHEN ceiling(( rand() * 2 )) = 2 THEN 'b'
ELSE NULL
END
END
and rand() is referenced twice. Each evaluation can return a different result.
You will get the same problem with the below rewrite being expanded out too
SELECT CASE ceiling(( rand() * 2 ))
WHEN 1 THEN 'a'
WHEN 2 THEN 'b'
END
Avoid CASE for this and any of its variants.
One method would be
SELECT JSON_VALUE ( '["a", "b"]' , CONCAT('$[', FLOOR(rand()*2) ,']') )

Opensips avp_db_query can't compare null value

I am using avp_db_query to retrieve my table row, sometimes one field value is null. But when I use if condition it does not follow and move on.
avp_db_query("select status from orders where id = 1", "$avp(status);")
Now if i write condition
if($avp(status)==1){
do success
} else {
do failure
exit();
}
Above condition does not work on failure status and continue, but when I put two if conditions and check if it is equal to one or equal to 0 then it works.
another issue is if this column has null value than nothing work, and it proceeds with giving the following warning.
WARNING:core:comp_scriptvar: invalid EQUAL operation: left is
VARIABLE_ELEMENT/STRING_VAL, right is NUMBER/NO_VAL
You can test for NULL SQL column values with:
if ($avp(status) == "<null>")
... which is equivalent to:
if ($(avp(status)[0]) == "<null>")
It was the only way we could get this to work, given that the $avp(status) = NULL; statement is meant to delete the top-most value in the AVP's stack.
If you are claiming that the else statement is not executed when it should be, please give a minimally viable example, along with the output of opensips -V, possibly opening up a new issue, separately.

What does two return statements inside an SQL function mean?

What code path is actually being returned from this a Scalar function that returns a decimal:
DECLARE #SafetyStockUnitsCalcNew decimal(14,2)
RETURN( SELECT STDEV(
.
.
-- Calculating code here.
)
-- Return the result of the function
RETURN #SafetyStockUnitsCalcNew
END
So what gets returned?
Is it the first return scaler statement or the second scaler one?
Also, once #SafetyStockUnitsCalcNew is declared it is never used until it is returned. Where is it set?
The code inside STDEV does not reference #SafetyStockUnitsCalcNew at all.
A function can have as many RETURN statements as you want.
E.g. Below there are three.
When the flow of control encounters a return statement then execution of the function stops and the value is returned.
CREATE FUNCTION dbo.Foo(#x int)
RETURNS INT
AS
BEGIN
IF #x = 1
RETURN 10;
ELSE
RETURN 18;
RETURN 1/0;
END
SQL Server does not do anything like the same level of analysis as happens in C# to detect that all code paths return a value and warn you of unreachable code. It just insists that the last statement must be a return.
In the above example the third RETURN statement can never be reached but nonetheless without it you would see an error "The last statement included within a function must be a return statement.".
In your example the second RETURN statement can never be reached and it is not required for the "last statement" rule either so it is useless. Perhaps originally the SELECT assigned the result to the variable and then the person coding it realised that they could just return the SELECT result directly.
I'd go on to a test server, create the function and comment out that second RETURN and see what happens. I'm 99% sure that it is never executing.

Why is '-' equal to 0 (zero) in SQL?

When you run the following query in SQL Management studio the result will be 1.
SELECT
CASE WHEN '-' = 0 THEN
1
ELSE
0
END
That scares me a bit, because I have to check for 0 value a numerous number of times and it seems it is vulnerable for being equal to value '-'.
You're looking at it the wrong way around.
'-' is a string, so it will get implicitly casted to an integer value when comparing it with an integer:
select cast('-' as int) -- outputs 0
To make sure that you are actually comparing a value to the string '0', make your comparison like this instead:
select case when '-' = '0' then 1 else 0 end
In general, you're asking for trouble when you're comparing values of different data types, since implicit conversions happen behind the scene - so avoid it at all costs.

Is SQL Server's double checking needed here?

IF #insertedValue IS NOT NULL AND #insertedValue > 0
This logic is in a trigger.
The value comes from a deleted or inserted row (doesn't matter).
2 questions :
Do I need to check both conditions? (I want all value > 0, value in db can be nullable)
Does SQL Server check the expression in the order I wrote it ?
1) Actually, no, since if the #insertedValue is NULL, the expression #insertedValue > 0 will evaulate to false. (Actually, as Martin Smith points out in his comment, it will evaluate to a special value "unknown", which when forced to a Boolean result on its own collapses to false - examples: unknown AND true = unknown which is forced to false, unknown OR true = true.) But you're relying on comparison behaviour with NULL values. A single step equivalent method, BTW, would be:
IF ISNULL(#insertedValue, 0) > 0
IMHO, you're better sticking with the explicit NULL check for clarity if nothing else.
2) Since the query will be optimised before execution, there is absolutely no guarantee of order of execution or short circuiting of the AND operator.
Combining the two - if the double check is truly unnecessary, then it will probably be optimised out before execution anyway, but your SQL code will be more maintainable in my view if you make this explicit.
You can use COALESCE => Returns the first nonnull expression among its arguments.
Now you can make the query more flexible, by increasing the column limits and again you need to check the Greater Then Zero condition. Important point to note down here is you have the option to check values in multiple columns.
declare #val int
set #val = COALESCE( null, 1, 10 )
if(#val>0)
select 'fine'
else
select 'not fine'

Resources