Unexpected order running OR operator on SQL Server 2019 - sql-server

I'm having a problem when migrating from SQL Server 2008 R2 to SQL Server 2019.
My code
DECLARE #str NVARCHAR(50) = 'all',
#int TINYINT = 1
DECLARE #tmp TABLE (val nvarchar(MAX))
INSERT INTO #tmp VALUES('123')
INSERT INTO #tmp VALUES('all')
SELECT val
FROM #tmp
WHERE #str = 'ALL' OR #int = val
When using SQL Server 2008 R2, it's fine. The output as expected like below
val
123
all
However, when I migrate to SQL Server 2019, the error like below occurs. Besides, it just happens unusually in 2019.
Msg 245 Level 16 State 1 Line 8 Conversion failed when converting the
nvarchar value 'all' to data type int.
As you can see, the second condition OR #int = val happened unexpectedly.
I was wondering if it fails due to any breaking changes related to the order of OR operator or case sensitive ALL vs all in the next SQL Server 2008 R2 version.
Updated
Sorry for my reproduce code make you guys confuse. This is my original code

You should do two of these three things:
(
Either use DECLARE #int nvarchar(max) = 1
OR
Use WHERE val = CONVERT(nvarchar(max), #int)
)
AND
Change to using STRING_SPLIT. That looping function is among the least efficient methods you could ever use to split strings, even before native solutions existed. See https://sqlblog.org/split
This db<>fiddle fiddle demonstrates.
And this one shows why WHERE #str = 'ALL' OR (#str <> 'ALL' AND #int = val) is not a solution. These patterns you're choosing only work if #str is always 'all', because they all break when it's anything else. So why have the OR at all?
You keep insisting that SQL Server should obey left to right evaluation, but we keep telling you that is simply not the case.
Here is an article by Bart Duncan at Microsoft, who worked on SQL Server, that you should absolutely read in full before posting any more comments or editing your question further. The critical point, though, is:
You cannot depend on expression evaluation order for things like WHERE <expr1> OR <expr2>since the optimizer might choose a plan that evaluates the second predicate before the first one.

Related

Insert statement fails on SQL Server 2005 but work well on 2008 R2 or later version

I have the following SQL insert statement inside the loop which iterate through a cursor:
SELECT #q_sql = 'INSERT INTO SYS_PARAMETER(parameter_uno, parameter_key, description, parameter_value, comments, created_date, created_user_id, created_user_name, last_modified_user, last_modified_date, module_uno, data_type)
VALUES ('+#sysparam_uno + ','''+#q_parameter_key+''','''','''+b.pairvalue+''','''',
getdate(),''setup'',''setup'',''setup'',getDate(),'''+#q_module_uno+''','''')'
from UTIL_pairkeys a
INNER JOIN UTIL_pairvalues b on a.pairkeyuno = b.pairkeyuno
and b.languno = 1
where a.pairkey=#q_parameter_key
EXEC sp_executesql #q_sql
Due to value coming to b.pairvalue parameter having a single quote, insert statement fails on SQL Server 2005, but work well on SQL Server 2008R2 and later versions. Any knows reason for this? I know that insert statement fails once parameter value has single quote in between varchar columns. But this something strange here.
Sample insert statement as follows;
INSERT INTO SYS_PARAMETER(parameter_uno,parameter_key,description,parameter_value,comments,created_date,created_user_id,created_user_name,last_modified_user,last_modified_date,module_uno,data_type)
values (269,'application.fs.company','','St John's Foods','',getdate(),'setup','setup','setup',getDate(),'1','')
If it is a problem of single quote only than you can replace it by two single quotes like this:
replace( b.pairvalue ,'''','''''')
I order to escape ', you need to replace it with '':
SELECT #q_sql = '
INSERT INTO SYS_PARAMETER(parameter_uno,parameter_key,description,parameter_value,comments,created_date,created_user_id,created_user_name,last_modified_user,last_modified_date,module_uno,data_type)
values ('+#sysparam_uno + ','''+#q_parameter_key+''','''','''+REPLACE(b.pairvalue, '''', '''''')+''','''',getdate(),''setup'',''setup'',''setup'',getDate(),'''+#q_module_uno+''','''')'
from UTIL_pairkeys a
INNER JOIN UTIL_pairvalues b on a.pairkeyuno = b.pairkeyuno
and b.languno = 1
where a.pairkey=#q_parameter_key
EXEC sp_executesql #q_sql
However, it's best if you could use parametrisation instead:
DECLARE #pair_value VARCHAR(100)
SELECT #pair_value = b.pair_value
from UTIL_pairkeys a
INNER JOIN UTIL_pairvalues b on a.pairkeyuno = b.pairkeyuno
and b.languno = 1
where a.pairkey=#q_parameter_k
SELECT #q_sql = '
INSERT INTO SYS_PARAMETER(parameter_uno,parameter_key,description,parameter_value,comments,created_date,created_user_id,created_user_name,last_modified_user,last_modified_date,module_uno,data_type)
VALUES( #parameter_uno_param,
#parameter_key_param,
'''',
#parameter_value_param,
'''',
getdate(),
''setup'',
''setup'',
getdate(),
''setup'',
#module_uno_param,
'''')'
EXEC sp_executesql
#q_sql,
N' #parameter_uno_param VARCHAR(100),
#parameter_key_param VARCHAR(100),
#parameter_value_param VARCHAR(100),
#module_uno_param VARCHAR(100)
',
#sysparam_uno,
#q_parameter_key,
#pair_value,
#q_module_uno
This assumes your select will only find one pair_value. If more, you need to consider looping through those.
Above code segment call by java program and since SQL Server 2005 thrown an exception to the calling java program it cause to break the java program itself. However, for SQL Server 2008, it look like database engine it self handle the exception without throwing exception to the calling application. To prove that I added try catch block to the error prone code segment and ran the java program against the SQL Server 2005. So, calling application didn't break. Did same against the SQL Server 2008 R2 and result was same.

sql server, big procedure performance

I have a stored procedure with 1k lines and 16 not nested if clauses. Each if works over a set of 4 variables, like:
if #a is null and #b is null and #c is null and #d is null
if #a is null and #b is null and #c is null and #d is not null
and so on (therefore 4^2=16). I wrote the procedure this way:
First, because its more readable than nested ifs (theres a lot of work happening over this procedure).
Second, because each if block gets a very simple peace of code (a select over a primary key, or a select union over 3 or 4 primary keys).
Third, the presence of variable #a means a union all, the presense of variable #b means a join and a distance calculation, the presence of #c means a join and the presence of #d means another join. Like:
Now I have been wondering about perfomance, and Im not sure how to write the procedure to get the best perfomance. Any tips?
you need to read Dynamic Search Conditions in T-SQL by Erland Sommarskog there are many techniques to support queries that use numerous optional filter parameters. The trick is picking the proper strategy, and doing a thousand IFs isn't the best.
If you have the proper SQL Server 2008 version (SQL 2008 SP1 CU5 (10.0.2746) and later), you can use this little trick to actually use an index: add OPTION (RECOMPILE) onto your query, see Erland's article, and SQL Server will resolve the OR from within (#OptionalParameter IS NULL OR YourColumn= #OptionalParameter ) before the query plan is created based on the runtime values of the local variables, and an index can be used.
This will work for any SQL Server version (return proper results), but only include the OPTION(RECOMPILE) if you are on SQL 2008 SP1 CU5 (10.0.2746) and later. The OPTION(RECOMPILE) will recompile your query, only the verison listed will recompile it based on the current run time values of the local variables, which will give you the best performance. If not on that version of SQL Server 2008, just leave that line off. Just remember that OR can kill index usage, but on the proper SQL Server 2008 version that OPTION(RECOMPILE) will use the index.
--sample procedure that uses optional search parameters
CREATE PROCEDURE YourProcedure
#FirstName varchar(25) = null,
#LastName varchar(25) = null,
#Title varchar(25) = null
AS
BEGIN
SELECT ID, FirstName, LastName, Title
FROM tblUsers
WHERE
(#FirstName IS NULL OR (FirstName = #FirstName))
AND (#LastNameIS NULL OR (LastName= #LastName))
AND (#TitleIS NULL OR (Title= #Title))
OPTION (RECOMPILE) ---<<<<use if on for SQL 2008 SP1 CU5 (10.0.2746) and later
END

Is there a way to prevent SQL Server silently truncating data in local variables and stored procedure parameters?

I recently encountered an issue while porting an app to SQL Server. It turned out that this issue was caused by a stored procedure parameter being declared too short for the data being passed to it: the parameter was declared as VARCHAR(100) but in one case was being passed more than 100 characters of data. What surprised me was that SQL Server didn't report any errors or warnings -- it just silently truncated the data to 100 characters.
The following SQLCMD session demonstrates this:
1> create procedure WhereHasMyDataGone (#data varchar(5)) as
2> begin
3> print 'Your data is ''' + #data + '''.';
4> end;
5> go
1> exec WhereHasMyDataGone '123456789';
2> go
Your data is '12345'.
Local variables also exhibit the same behaviour:
1> declare #s varchar(5) = '123456789';
2> print #s;
3> go
12345
Is there an option I can enable to have SQL Server report errors (or at least warnings) in such situations? Or should I just declare all local variables and stored procedure parameters as VARCHAR(MAX) or NVARCHAR(MAX)?
SQL Server has no such option. You will either have to manually check the length of strings in your stored procedure and somehow handle the longer strings or use the nvarchar(max) option. If disk space isn't an issue then the nvarchar(max) option is certainly the easiest and quickest solution.
You don't have to use nvarchar(max) just use nvarchar(length+1) [e.g. if your column length is 50 then you would set the parameter to be nvarchar(51)]. See the answer from DavidHyogo - SQL Server silently truncates varchar's in stored procedures.
I don't know of a way to make the server do it, but I've been using the SQL Server Projects feature of Visual Studio Team System Developer Edition. It includes code analysis which caught a truncation problem of mine: using an int parameter to insert into a smallint column.
Though awkward, you can, however, dynamically check for parameter length before a call, e.g.
CREATE FUNCTION MyFunction(#MyParameter varchar(10))
RETURNS int
AS
BEGIN
RETURN LEN(#MyParameter)
END
GO
DECLARE #MyValue varchar(15) = '123456789012345'
DECLARE #ParameterMaxLength int
SELECT #ParameterMaxLength = CHARACTER_MAXIMUM_LENGTH
FROM INFORMATION_SCHEMA.PARAMETERS
WHERE SPECIFIC_SCHEMA = 'dbo' AND
SPECIFIC_name = 'MyFunction' AND
PARAMETER_NAME = '#MyParameter'
IF #ParameterMaxLength <> -1 AND
LEN(#MyValue) > #ParameterMaxLength
PRINT 'It''s too looooooooooooooooooong'
I omitted the called function's database name in the query and in the reference to INFORMATION_SCHEMA.PARAMETERS to ensure that my sample would run without edits.
I don't necessarily advocate this, but I wanted to point out that the information may be available to detect imminent truncation dynamically, if in some critical situation this is needed.
You can use LEFT in SQL and specified the length that you want to insert.
for example.
CREATE TABLE Table1
(
test varchar(10)
)
insert into Table1 values (LEFT('abcdefghijklmnopqrstuvwxyz',10))
This will insert only
abcdefghij on table

Use of '' + in SQL Server 2005 Stored Procedure to build SQL string

I'm building a stored procedure which is rather stretching my experience. With the help of people who responded to this thread [Nested if statements in SQL Server stored procedure SELECT statement I think I'm most of the way there :)
In short, the SP takes a series of paramterised inputs to dynamically build an SQL statement that creates a temporary table of id values ordered in a specific way. The remainder of the SP, which returns the data according to the requested page from the id values in this temporary table is all sorted.
Reconsider the use of dynamic SQL - you should really know what you are doing if you go that route.
What is the problem you are trying to solve? I am sure people here will be able to find a better solution than the dynamic SQL you are proposing to use.
Take a look at CONVERT() and CAST() for the integers.
To concatenate integer values into the dynamic SQL statement you need to convert to a varchar e.g:
....WHERE
OT.site_id = ' + CAST(#siteid AS VARCHAR)
If the SQL statement is always going to be less than 4000 chars, I'd at least consider using sp_executesql to use parameterised SQL.
e.g.
DECLARE #SQL NVARCHAR(4000)
DECLARE #siteid INTEGER
SET #siteid = 1
SET #SQL = 'SELECT * FROM MyTable WHERE site_id = #siteid'
EXECUTE sp_executesql #SQL, N'#siteid INTEGER', #siteid
All in all, what you're doing is not likely to be very performant/scalable/maintainable and you don't really gain much from having it as a sproc. Plus you need to be very very careful to validate the input as you could open up yourself to SQL injection (hence my point about using sp_executesql with parameterised SQL).
You need to cast the int param to be a char/varchar so that you can add it to the existing string. The fact that you aren't surrounding it with quotes in the final sql means it will be interpreted as a number.

SQL Server Bug?

I am writing a function to process some CSV data. This is what I have written so far...
CREATE FUNCTION dbo.FGetCommaSeperatedValues(#csv as text)
RETURNS #tblIds TABLE(id int,csvlength int)
AS
BEGIN
DECLARE #csvlength AS int
--SET #csvlength = datalength(#csv);
SET #csvlength = 7685
DECLARE #currentIndex AS int
SET #currentIndex = 0
WHILE #currentIndex < #csvlength
BEGIN
--INSERT INTO #tblIds SELECT #currentIndex,#csvlength
INSERT INTO #tblIds (id,csvlength) values (#currentIndex,#csvlength)
SET #currentIndex = #currentIndex+1
END
RETURN
END
My issue is that when I execute the function, using the following command...
SELECT * FROM FGetCommaSeperatedValues('')
The table returned does not show the results that I expect.
Everything is fine until around row 3624 or so (as expected, id column increments by 1)
Then the values increment erratically.
My colleague tested this code in SQL Server 2008 and everything works fine. This appears to be isolated to SQL server 2000.
Does anyone know of this bug, and/or it's resolution?
In order to gaurantee sort order in SQL Server you must use the ORDER BY clause.
ORDER BY CLAUSE

Resources