Select statement processing before Where clause in view
-
12-11-2019 - |
Question
I have a statement that selects the substring of a charindex as follows:
SELECT SUBSTRING(StringField, 5, CHARINDEX('ABC', StringField) - 5)...
WHERE
CHARINDEX('ABC', StringField) > 5
When I run the above statement in a select query, the results are returned just fine. When I run the above statement in a schema bound indexed view, I get this error:
Invalid length parameter passed to the LEFT or SUBSTRING function
To solve the problem I will write a function to get the Max of the CharIndex and 0 as to remove the possibilities of a negative value. But does anyone know why the where clause would not be filtering out the select statement?
Solution
Because order of operations behind the scenes in a query isn't guaranteed.
I'm guessing if you check the execution plan you will see it's doing both checks in parallel - this because neither operation can use an index!
SQL needs to load every row of that field into memory anyways, so it's processing it for both criteria at the same time.
You can try WITH (MAXDOP(1))
as a query hint to see if that keeps the issue from appearing, or you could do a subselect to force execution order:
SELECT SUBSTRING(StringField, 5, CHARINDEX('ABC', StringField) - 5)...
FROM (
SELECT Stringfield
FROM Table
WHERE CHARINDEX('ABC', StringField) > 5
) as [X]
I had a similar issue once when I was checking to see if a field was numeric using PATINDEX
and one of the columns in my view was converting this to an int
- I got conversion errors because the engine was converting every row since my filters were not SARGable.
OTHER TIPS
This is an ugly workaround but you could do something like this perhaps:
DECLARE @x TABLE(StringField VARCHAR(32));
INSERT @x SELECT 'ABC'
UNION ALL SELECT 'A'
UNION ALL SELECT 'AAAAAAAABC';
SELECT SUBSTRING(StringField, 5, CHARINDEX('ABC', StringField) - 5)
FROM @x
WHERE CHARINDEX('ABC', StringField) > 5;
SELECT SUBSTRING(StringField,
CASE WHEN CHARINDEX('ABC', StringField) > 5 THEN 5 ELSE 1 END,
CHARINDEX('ABC', StringField) -
CASE WHEN CHARINDEX('ABC', StringField) > 5 THEN 5 ELSE 0 END)
FROM @x
WHERE CHARINDEX('ABC', StringField) > 5;
Both yield:
---
AAA
But I suspect the latter will be allowed in your view. It's ugly but unfortunately unless you dump the filtered data to a #temp table first (or try to see if MAXDOP
reliably removes the issue) you're not going to have much control over order of processing.
Another idea is to try putting a computed column in the table (but I'm not sure that will help if you are trying to create an indexed view - there may still be complications). Or to use a filtered index with that expression, instead of an indexed view. There could be several "solutions" if we know what the indexed view is meant to accomplish and what version of SQL Server you are using.