views:

155

answers:

5

I've got a SQL query that joins a pricing table to a table containing user-provided answers. My query is used to get the price based on the entered quantity. Below is my SQL statement:

SELECT JobQuestion.Value, Price.Min, Price.Max, Price.Amount FROM Price
    INNER JOIN JobQuestion 
     ON Price.QuestionFK=JobQuestion.QuestionFK
     AND JobQuestion.JobFK=1
WHERE Price.Min <= JobQuestion.Value 
    AND Price.Max >= JobQuestion.Value

The problem is SQL Server is running the where clause before the JOIN and it is throwing the error:

Conversion failed when converting the varchar value 'TEST' to data type int.

because it is doing the min and max comparisons before the join ('TEST' is a valid user entered value in the JobQuestion table, but should not be returned when JobQuestion is joined to Price). I believe SQL Server is choosing to run the WHERE because for some reason the parser thinks that would be a more efficient query. If i Just run

SELECT JobQuestion.Value, Price.Min, Price.Max, Price.Amount FROM Price
    INNER JOIN JobQuestion 
     ON Price.QuestionFK=JobQuestion.QuestionFK
     AND JobQuestion.JobFK=1

I get these results back:

500 1    500  272.00
500 501  1000 442.00
500 1001 2000 782.00

So, adding the WHERE should filter out the last two and just return the first record. How do I force SQL to run the JOIN first or use another technique to filter out just the records I need?

+2  A: 

You shouldn't be comparing strings to ints. If you have any influence at all over your table design, then split the two different uses of the JobQuestion.Value column into two different columns.

Christian Hayter
I do have control over the table design, but the JobQuestion table is designed to be kind a dumb table that holds values for dynamically created questions. In the future I'll take that into consideration, it would just add a few more layers of complexity when I have to ask for Question answers because I would have to look at a varchar and an int field.
Austin
Could you clarify why you are trying to compare a question to a price? It doesn't make sense to me.
Christian Hayter
I'm comparing the answer of a question to the Min and Max columns of the Price table to determine which Price level to choose. So, if they user selects a quantity of 500 (which gets stored in the JobQuestion table), I look up in the Price table for the Price record that has has a Min of <= 500 and a Max of >= 500 and that gives me my Price level.
Austin
There are hacks you could try, e.g. a CTE, or a table-valued UDF. Ultimately though it's down to your table design.
Christian Hayter
A: 

In case you have no influence over your table design - Could you try to filter out those records with numeric values using ISNUMERIC()? I would guess adding this to your where clause could help.

wilth
I tried ISNUMERIC, but someone could enter a numeric value that overflows an int and it would throw an error
Austin
@Austin: What is "it" in your comment? IsNumeric would assume it's a different datatype and still return true.
Austin Salonen
Sorry, "it" referred to SQL Server. When I tried using ISNUMERIC, SQL Server filtered out all the non-numeric values, but then when it compared Price.Min <= JobQuestion.Value for value '333333333333333' it gave the error "The conversion of the varchar value '333333333333333' overflowed an int column"
Austin
ok, I didn't think that you were using huge numbers. But in this case, you can still use is_numeric to filter out strings and then cast your integer columns to decimals. Of course this is not ideal, but it might be much faster than obstructing the optimizer using hints.
wilth
+4  A: 

Try "re-phrasing" the query as follows:

SELECT *
FROM   (
          SELECT JobQuestion.Value, 
                 Price.Min, 
                 Price.Max, 
                 Price.Amount 
          FROM   Price
          INNER 
          JOIN   JobQuestion 
                 ON Price.QuestionFK = JobQuestion.QuestionFK
                 AND JobQuestion.JobFK = 1
       ) SQ
WHERE  SQ.Min <= SQ.Value 
AND    SQ.Max >= SQ.Value

As per the answer from Christian Hayter, if you have the choice, change the table design =)

Rob
Is a cast of JobQuestion.Value to an int (some numeric) necessary here?
Austin Salonen
I hadn't tried that, but interestingly enough it produces the same error. Again, SQL Server Optimizer must be turning that into the same execution path as the original
Austin
If it's being that "awkward" about it then you could consider trying to transform the inner section (SQ) into a view, or creating a stored procedure, storing the result of "SQ" in a temporary table and then executing the WHERE clause over that..
Rob
+1  A: 

First, this is very likely sign of poor design.
If you cannot change schema, then maybe you could force this behavior using hints. Quote:

Hints are options or strategies specified for enforcement by the SQL Server query processor on SELECT, INSERT, UPDATE, or DELETE statements. The hints override any execution plan the query optimizer might select for a query.

And some more:

Caution:
Because the SQL Server query optimizer typically selects the best execution plan for a query, we recommend that < join_hint>, < query_hint>, and < table_hint> be used only as a last resort by experienced developers and database administrators.

zendar
I don't love the idea of using SQL hints, but it seems to be the only way (using the current schema) to process the query without an error. I tried using the "OPTION (FORCE ORDER)" hint and that works!
Austin
A: 

You can likely remove the where... and just add those as predicates to your join. Since it's a inner join this should work

SELECT JobQuestion.Value, Price.Min, Price.Max, Price.Amount 
FROM Price    
INNER JOIN JobQuestion
ON Price.QuestionFK=JobQuestion.QuestionFK       
AND JobQuestion.JobFK=1
AND Price.Min <= JobQuestion.Value
AND Price.Max >= JobQuestion.Value
Brian Rudolph
Results in the same thing because the SQL Server Optimizer is running those in the same order.
Austin
hmm the `and`s are not tested in order? doesn't seem very likely.
dotjoe
I agree, but that is what is being returned.
Austin
Maybe try moving the fk=1 to the where clause before the price checks. This has to work otherwise it would defy all logic and my head will explode.
dotjoe
No, still didn't work. I think the thing is that the SQL Server Optimizer can do whatever it wants to create the most efficient query, which in my case blows up the Query when it does the comparison
Austin
try nesting the "and"s and (JobQuestion.JobFK = 1 AND (price.min<=jq.value and price.max >=jq.value))
Brian Rudolph