Reputation: 939
Take the following table as an instance:
CREATE TABLE TBL_Names(Name VARCHAR(32))
INSERT INTO TBL_Names
VALUES ('Ken'),('1965'),('Karen'),('2541')
Executing following query throws an exception:
SELECT [name]
FROM dbo.tblNames AS tn
WHERE [name] IN ( SELECT [name]
FROM dbo.tblNames
WHERE ISNUMERIC([name]) = 1 )
AND [name] = 2541
Msg 245, Level 16, State 1, Line 1 Conversion failed when converting the varchar value 'Ken' to data type int.
While the following query executes without error:
SELECT [name]
FROM dbo.tblNames AS tn
WHERE ISNUMERIC([name]) = 1
AND [name] = 2541
I know that this is because of SQL Server Query Optimizer's decision. but I am wondering if there is any way to make sql server evaluate clauses in a certain order. this way, in the first query,the first clause filters out those Names
that are not numeric so that the second clause will not fail at converting to a number.
Update: As you may noticed, the above query is just an instance to exemplify the problem. I know the risks of that implicit conversion and appreciate those who tried to warn me of that. However my main question is how to change Optimizer's behavior of evaluating clauses in a certain order.
Upvotes: 4
Views: 204
Reputation: 14189
There is no "direct" way of telling the engine to perform operations in order. SQL isn't an imperative language where you have complete control of how to do things, you simply tell what you need and the server decides how to do it itself.
For this particular case, as long as you have [name] = 2541
, you are risking a potential conversion failure since you are comparing a VARCHAR
column against an INT
. Even if you use a subquery/CTE there is still room for the optimizer to evaluate this expression first and try to convert all varchar values to int (thus failing).
You can evade this with workarounds:
Correctly comparing matching data types:
[name] = '2541'
Casting [name]
to INT
beforehand and only whenever possible and on a different statement, do the comparison.
DECLARE @tblNamesInt TABLE (nameInt INT)
INSERT INTO @tblNamesInt (
nameInt)
SELECT
[nameInt] = CONVERT(INT, [name])
FROM
dbo.tblNames
WHERE
TRY_CAST([name] AS INT) IS NOT NULL -- TRY_CAST better than ISNUMERIC for INT
SELECT
*
FROM
@tblNamesInt AS T
WHERE
T.nameInt = 2351 -- data types match
Even an index hint won't force the optimizer to use an index (that's why it's called a hint), so we have little control on how it gets stuff done.
There are a few mechanics that we know are evaluated in order and we can use to our advantage, such as the HAVING
expressions will always be computed after grouping values, and the grouping always after WHERE
conditions. So we can "safely" do the following grouping:
DECLARE @Table TABLE (IntsAsVarchar VARCHAR(100))
INSERT INTO @Table (IntsAsVarchar)
VALUES
('1'),
('2'),
('20'),
('25'),
('30'),
('A') -- Not an INT!
SELECT
CASE WHEN T.IntsAsVarchar < 15 THEN 15 ELSE 30 END,
COUNT(*)
FROM
@Table AS T
WHERE
TRY_CAST(T.IntsAsVarchar AS INT) IS NOT NULL -- Will filter out non-INT values first
GROUP BY
CASE WHEN T.IntsAsVarchar < 15 THEN 15 ELSE 30 END
But you should always avoid writing code that implies implicit conversions (like T.IntsAsVarchar < 15
).
Upvotes: 4
Reputation: 95564
Honestly, I wouldn't apply the implicit cast to your column [name]
, it'll make the query non-SARGable. Instead, convert the value of your input (or pass it as a string)
SELECT [name]
FROM dbo.TBL_Names tn
WHERE [name] = CONVERT(varchar(32),2541);
If you "must", however, wrap [name]
(and suffer performance degradation) then use TRY_CONVERT
:
SELECT [name]
FROM dbo.TBL_Names tn
WHERE TRY_CONVERT(int,[name]) = 2541;
Upvotes: 0
Reputation: 5643
You can try this
SELECT [name]
FROM dbo.TBL_Names AS tn
WHERE [name] IN ( SELECT [name]
FROM dbo.TBL_Names
WHERE ISNUMERIC([name]) = 1 )
AND [name] = '2541'
You need to just [name] = 2541
to [name] = '2541'
. You are missing '
(single quote) with name in where condition.
You can find the live demo Here.
Upvotes: 0
Reputation: 164089
What about:
SELECT *
FROM dbo.tblNames AS tn
WHERE [name] = convert(varchar, 2541)
Why do you need ISNUMERIC([name]) = 1)
since you only care about the value '2541'
?
Upvotes: 2
Reputation: 5893
Try like this
SELECT [name]
FROM #TBL_Names AS tn
WHERE [name] IN ( SELECT [name]
FROM #TBL_Names
WHERE ISNUMERIC([name]) = 1 )
AND [name] = '2541'
2)
AND [name] = convert(varchar,2541 )
Since You are storing name as varchar(32) varchar will accept integer datatype values also called precedence value
Upvotes: 1