views:

126

answers:

4

I am working on a search query (with an asp.net 3.5 front end) which seems quite simple, but is quite complex. The complete query is:

set ANSI_NULLS ON
set QUOTED_IDENTIFIER ON
go

ALTER PROCEDURE [dbo].[usp_Item_Search]
    @Item_Num varchar(30) = NULL
    ,@Search_Type int = NULL
    ,@Vendor_Num varchar(10) = NULL
    ,@Search_User_ID int = 0
    ,@StartDate smalldatetime = NULL
    ,@EndDate smalldatetime = NULL
AS
DECLARE @SQLstr as nvarchar(4000)

Set @SQLstr = 'SELECT RecID, Vendor_Num, Vendor_Name, InvoiceNum, Item_Num, 
(SELECT CONVERT(VARCHAR(11), RecDate, 106) AS [DD MON YYYY]) As RecDate, NeedsUpdate, RecAddUserID FROM [tbl_ItemLog] where 1=1 '

IF (@Item_Num IS NOT NULL and LTRIM(@Item_Num) <> '')
    Begin
        If @Search_Type = 0
            BEGIN
                Set @SQLstr = @SQLstr +  'AND Item_Num LIKE ''' + @Item_Num + '%'''
            END
        If @Search_Type = 1
            BEGIN
                Set @SQLstr = @SQLstr + 'AND Item_Num LIKE ''%' + @Item_Num + '%'''
            END
        If @Search_Type = 2
            BEGIN
                Set @SQLstr = @SQLstr + 'AND Item_Num LIKE ''%' + @Item_Num + ''''
            END
    End

IF (@Vendor_Num IS NOT NULL and LTRIM(@Vendor_Num) <> '')
    Begin
        Set @SQLstr = @SQLstr + ' AND Vendor_Num = ''' + @Vendor_Num + ''''
    End

IF (@Search_User_ID IS NOT NULL and @Search_User_ID > 0)
    Begin
        Set @SQLstr = @SQLstr + ' AND RecAddUserID = ' + convert(nvarchar(20),@Search_User_ID)
    End

Set @SQLstr = @SQLstr + ' AND (RecDate BETWEEN ''' + convert(nvarchar(10),@StartDate,106) + ''' AND ''' + convert(nvarchar(10),@EndDate,106) + ''')'

PRINT (@SQLstr)
--Execute (@SQLstr)

When I pass all empty parameter values, I get an error:

"Failed to convert parameter value from a String to a Int32."

The asp.net code that is calling the stored proc is:

        //Display search results in GridView;
        SqlConnection con = new SqlConnection(strConn);
        //string sqlItemSearch = "usp_Item_Search";
        SqlCommand cmdItemSearch = new SqlCommand(sqlItemSearch, con);
        cmdItemSearch.CommandType = CommandType.StoredProcedure;

        cmdItemSearch.Parameters.Add(new SqlParameter("@Item_Num", SqlDbType.VarChar, 30));
        cmdItemSearch.Parameters["@Item_Num"].Value = txtItemNumber.Text.Trim();

        cmdItemSearch.Parameters.Add(new SqlParameter("@Search_Type", SqlDbType.Int));
        cmdItemSearch.Parameters["@Search_Type"].Value = ddlSearchType.SelectedItem.Value;

        cmdItemSearch.Parameters.Add(new SqlParameter("@Vendor_Num", SqlDbType.VarChar, 10));
        cmdItemSearch.Parameters["@Vendor_Num"].Value = txtVendorNumber.Text.Trim();

        cmdItemSearch.Parameters.Add(new SqlParameter("@Search_User_ID", SqlDbType.Int));
        cmdItemSearch.Parameters["@Search_User_ID"].Value = ddlSeachUser.SelectedItem.Value;

        if (!string.IsNullOrEmpty(txtStartDate.Text))
        {
            cmdItemSearch.Parameters.Add(new SqlParameter("@StartDate", SqlDbType.DateTime));
            cmdItemSearch.Parameters["@StartDate"].Value = Convert.ToDateTime(txtStartDate.Text.Trim());
        }
        else
        {
            cmdItemSearch.Parameters.Add(new SqlParameter("@StartDate", SqlDbType.DateTime));
            cmdItemSearch.Parameters["@StartDate"].Value = Convert.ToDateTime("01/01/1996");
        }

        if (!string.IsNullOrEmpty(txtEndDate.Text))
        {
            cmdItemSearch.Parameters.Add(new SqlParameter("@EndDate", SqlDbType.DateTime));
            cmdItemSearch.Parameters["@EndDate"].Value = Convert.ToDateTime(txtEndDate.Text.Trim());
        }
        else
        {
            cmdItemSearch.Parameters.Add(new SqlParameter("@EndDate", SqlDbType.DateTime));
            cmdItemSearch.Parameters["@EndDate"].Value = Convert.ToDateTime(DateTime.Now);
        }
        con.Open();

        SqlDataAdapter ada = new SqlDataAdapter(cmdItemSearch);
        DataSet ds = new DataSet();
        ada.Fill(ds);

            gvSearchDetailResults.DataSource = ds;
            gvSearchDetailResults.DataBind();
            pnlSearchResults.Visible = true;

How can I resolve this?

A: 
IF (Search_User_ID IS NOT NULL) 

needs an @ symbol infront of the variable

You say you are passing empty string in for all variables but one is an int, it can't take an empty string that is not int data. Can't believe I didn;t notice that the first time.

HLGEM
Good catch. Done :-)
DotNetRookie
Which int variable are you talking about?
DotNetRookie
@Search_User_IDYou need to test it with no values by sending in NUll not empty string ' '.
HLGEM
A: 

Why don't you use single parameterized query like this:

select 
   recdid,
   Vendor_Num,
   Vendor_Name,
   InvoiceNum,
   Item_Num, 
   CONVERT(VARCHAR(11), RecDate, 106) as RecDate,
   NeedsUpdate, 
   RecAddUserID 
FROM 
  [tbl_ItemLog] as t
where
   (((Item_num like @Item_Num + '%' and @Search_Type = 0) OR
    (Item_num like '%' + @Item_Num + '%' and @Search_Type =  1) OR
    (Item_num like '%' + @Item_Num + '%' and @Search_Type = 2))  
        OR
    @Item_Num IS NULL) AND
   (Vendor_Num = @Vendor_Num OR @Vendor_Num IS NULL) AND
   (RecAddUserId = @Search_User_Id OR @Search_User_Id IS NULL) AND
   (RecDate BETWEEN @StartDate AND @EndDate)
STO
Just a note this is cleaner but actually slower (performance wise).
JonH
I need to study your response a bit before I plug it into my app. Thanks for the suggestion.
DotNetRookie
Comparing to the performance of dynamic SQL, this is a step *backwards*. Queries like this will ensure a hard parse almost every time, unless the parameters are exactly identical. And there's also a risk of parameter sniffing...
OMG Ponies
+3  A: 

You're not quite building the string correctly as far as I can tell. If no @Item_Num is passed in, you'll end up with no WHERE key word... you'll just have "FROM [tblItem_Log] AND..."

I would make all of the criteria appends be "AND ..." and as your initial statement use:

FROM [tbl_Item_Log] WHERE (1=1)

Since you have code to return the generated string, why not put that into SSMS and try to run it?

I also just noticed that if you don't pass in date values that you will end up executing a NULL string, because your final concatenation will end up causing a NULL. These are the kinds of things that you need to pay very close attention to if you're going to be using dynamic SQL to build queries.

Once I corrected that I was able to run the stored procedure without any errors (at least to generate what looks like a valid SQL statement). That leads me to believe that it may be a problem with data types in the underlying table. Can you provide the definition for that?

One last note: Personally, I would use

CONVERT(VARCHAR(11), RecDate, 106) AS RecDate

instead of the seemingly unnecessary subquery that you have.

Yet another edit: You may want to remove the code that checks LTRIM(@Search_User_ID) <> ''. It's a pointless bit of code and perhaps a setting particular to your server/connection is causing it to fail because of the type mismatch.

Tom H.
I need a vacation. I did not catch the where clause issue. Would 1=1 mess up the query as far as number of records returned?
DotNetRookie
@DotNetRookie: `WHERE 1 = 1` will get optimized out, and is a good means of specifying a WHERE clause that is easy to append to for situations like dynamic SQL.
OMG Ponies
Thats good. But I am still getting that error msg. "Failed to convert parameter value from a String to a Int32."
DotNetRookie
@DotNetRookie - please post your calling syntax. I.e. what do you *mean* when you say you pass all "empty" parameter values?
GalacticCowboy
Pass empty params meaning the user does not enter a value for Item_Num, Search_Type, Vendor_Num, Search_User_ID etc
DotNetRookie
Tom, the StartDate and EndDate are user provided values. If the user does not provide them, the default startdate is 01/01/1996 and default EndDate is today. So I am doing that check on the asp side (much easier than to do it on the s/proc). See asp.net calling code in my original post.
DotNetRookie
You should probably do the check in the stored procedure as well. It's a single line of code really and the stored procedure should react appropriately to any combination of possible parameters.
Tom H.
regarding LTRIM(@Search_User_ID) <> '' please see this posting:http://stackoverflow.com/questions/3142161/writing-a-dreaded-sql-search-query
DotNetRookie
Yes, @Vendor_Num is a string, which could be empty or "blank". @Search_User_ID is an INT. It's either NULL or it's a number. It can't be an empty string.
Tom H.
ahh... got it..
DotNetRookie
ok, so I changed the @Search_User_ID default value to 0 and then I am checking if @Search_User_ID > 0. I am still getting that error message "Failed to convert parameter value from a String to a Int32."
DotNetRookie
A: 

You really have several different stored procedures here. Why not just write them separately? Everything that's controlled by switch statements could be easily in procedural code. Same for the LTRIM calls.

You could call them all from a single SP with switch statements; but I think it's generally better to not merge them in the first place. The SP queries will optimize more easily, and the code will be simpified. There's not much you gain by consolidating them.

Not sure of your business rules, but you could simplify this outside SQL with

switch(search_type) {    
case 1:  
    do_query_type_1(args);  
    break;  
case 2:  
    do_query_type_2(args);  
    break;  
case 3:  
    do_query_type_3(args);  
    break;  
default:  
    whatever ...  
}

Also it looks like you have separate logic for cases where the item number is provided or not. Same for other fields. Each of your use cases looks like it resolves to a pretty simple query.

le dorfier
I really dont get the "three different stored procedures".
DotNetRookie
Rewritten to "several".
le dorfier
I don't understand how this is "several" different stored procs. Can you be a bit specific please?
DotNetRookie
Also, you're using the deadly "string with leading wildcard" search key, which will guarantee index scans or worse whenever it comes into play.
le dorfier
Sure. Have a stored procedure for each search type. Andif(vendornum) {} else if(search_user_id) {} else {}
le dorfier