Link to home
Start Free TrialLog in
Avatar of Jason Livengood
Jason LivengoodFlag for United States of America

asked on

Problem with IN Statement in SQL Server

I currently have a a query where I pass in some delimited values as a varchar. When I go to test against this list in my Where clause SQL Server freaks out and tells me I need to have integers . Not sure what to do.

@states varchar(max)
in C# I pass this in as - @states='AL,CT,FL'

Then in my query it looks like this :

Select *
From dhpDrivers
Where  dhpDriver.State in(@states)


error I get is :
Conversion failed when converting the varchar value 'AL,CT,FL' to data type int

The dhpDriver.State  field is not an integer field so I'm a bit confused.Any idea what I need to do with my states list to make this work ? Any advice would be much appreciated.

Jason
Avatar of Paul MacDonald
Paul MacDonald
Flag of United States of America image

Is this a stored procedure?  Is the passed parameter defined as an Int?
ASKER CERTIFIED SOLUTION
Avatar of Kent Olsen
Kent Olsen
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of Jason Livengood

ASKER

dhpDriver.State is a char(2). This is source field I need to compare against.
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Here is the:
-- code to insert values from @states to @statesTable


-- insert the first state into the table
INSERT INTO @statesTable
SELECT LEFT(@states, CHARINDEX(',', @states) - 1)

-- dynamically create a table of seq #s from 1 to 9999 to help parse input parameter
;WITH
cteDigits AS (
    SELECT 0 AS digit UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL
    SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9
),
cteTally AS (
    SELECT [1000s].digit * 1000 + [100s].digit * 100 + [10s].digit * 10 + [1s].digit AS num
    FROM cteDigits [1s]
    CROSS JOIN cteDigits [10s]
    CROSS JOIN cteDigits [100s]
    CROSS JOIN cteDigits [1000s]    
)
INSERT INTO @statesTable
SELECT
    SUBSTRING(@states, num + 1, CHARINDEX(',', @states + ',', num + 1) - num - 1) AS state
FROM cteTally
WHERE
    num BETWEEN 2 AND LEN(@states) AND
    SUBSTRING(@states, num, 1) = ','
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Not necessarily for performance it isn't.

That could have awful performance, depending on the number of rows and number of values entered, and if a state index exists on the table.
ok! if we have a look-up table for states we can use this format :) and still use index and performance would not be awful :)

Select d.*
From dhpDrivers d inner join dphStates s on s.state=d.state and ','+@states+',' like '%,'+s.State+',%'
If you could create a table that's guaranteed to hold all the state values (*), you could do this:


SELECT ...
FROM dbo.dhpDrivers d
INNER JOIN (
    SELECT state
    FROM dbo.dphStates
    WHERE
        ',' + @states + ',' LIKE '%,' + state + ',%'
) AS s ON
    s.state = d.state
...


(*) Sometimes "state" includes things like "DC", Canadian provinces, etc., so it's more than just a fixed list of (exactly) 50 values.

I still see that as a disadvantage over just dynamically creating a much more limited table from only the state values that are entered in the parameter for that run.


However, I'd probably change to a true temporary table, #states, rather than @statesTable, so that stats are available for SQL, so the optimizer will "know" it's a very small table.
ScottPletcher, is your last post any different then my post @ 37744667 :)
Yes.  I reduced the number of rows as much as possible BEFORE joining to the main table.

Again, I still think that is an inferior approach to creating a temp table of the actual values passed in, since very few values are likely to be passed in for a typical run.
So I created a User Defined function called CSVStringToTable to pump out my @states into a single column table.

So when @states = 'AL,OH,PA'
and I call the function like so : CSVStringToTable(@states)

I get a single column table (column name is ItemList) with 3 records. One record per state.

so in my stored proc I create a temp table like so :
SELECT * INTO #TEMP_StateList34 FROM CSVStringToTable(@states)

this lets lets me do my in function from my where clause:
WHERE dhpDriver.State in(SELECT Itemlist from #TEMP_StateList34)

AS Kdo mentioned earlier the problem is that you really need something that sql server sees as an list and not a string. Thank you for all the help.
Looks good.

A primary key on the temp table should help SQL perform (slightly) better.  The more states there are in the table, the more they key should help.