Написание страшного поискового запроса SQL (2-й этап) - PullRequest
2 голосов
/ 29 июня 2010

Я работаю над поисковым запросом (с интерфейсом asp.net 3.5), который кажется довольно простым, но довольно сложным.Полный запрос:

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)

Когда я передаю все пустые значения параметров, я получаю сообщение об ошибке:

«Не удалось преобразовать значение параметра из строки в Int32».

Код asp.net, который вызывает хранимый процесс:

        //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;

Как мне решить эту проблему?

Ответы [ 4 ]

3 голосов
/ 29 июня 2010

Насколько я могу судить, вы не совсем правильно строите строку.Если @Item_Num не передается, у вас не останется ключевого слова WHERE ... у вас просто будет "FROM [tblItem_Log] AND ..."

Я бы добавил все критериибыть "И ..." и в качестве вашего начального выражения использовать:

FROM [tbl_Item_Log] WHERE (1=1)

Поскольку у вас есть код для возврата сгенерированной строки, почему бы не поместить его в SSMS и попытаться запустить его?

Я также только что заметил, что если вы не передадите значения даты, вы в конечном итоге будете выполнять NULL-строку, потому что ваша окончательная конкатенация приведет к NULL.Это те вещи, на которые вам нужно обратить пристальное внимание, если вы собираетесь использовать динамический SQL для построения запросов.

После того, как я исправил, я смог запустить хранимую процедуру без каких-либо ошибок.(по крайней мере, для генерации того, что выглядит как действительный оператор SQL).Это наводит меня на мысль, что это может быть проблема с типами данных в базовой таблице.Можете ли вы дать определение для этого?

Последнее замечание: лично я бы использовал

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

вместо ненужного, казалось бы, подзапроса.

Еще одно редактирование: Вы можете удалить код, проверяющий LTRIM (@Search_User_ID) <> ''.Это бессмысленный фрагмент кода, и, возможно, настройка, специфичная для вашего сервера / соединения, приводит к сбою из-за несоответствия типов.

1 голос
/ 29 июня 2010
IF (Search_User_ID IS NOT NULL) 

требуется символ @ перед переменной

Вы говорите, что передаете пустую строку для всех переменных, но одна - int, она не может принимать пустую строку, которая не является int data,Не могу поверить, что я не заметил этого в первый раз.

0 голосов
/ 29 июня 2010

У вас действительно есть несколько различных хранимых процедур здесь.Почему бы просто не написать их отдельно?Все, что контролируется операторами switch, может быть легко написано в процедурном коде.То же самое для вызовов LTRIM.

Вы можете вызывать их все с одного SP с операторами switch;но я думаю, что вообще лучше не объединять их в первую очередь.Запросы SP будут легче оптимизироваться, а код будет упрощен.Их объединение мало что дает.

Не уверен в своих бизнес-правилах, но вы можете упростить это вне SQL с помощью

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 ...  
}

Кроме того, похоже, у вас есть отдельная логика для случаев, когданомер товара предоставлен или нет.То же самое для других полей.Каждый из ваших вариантов использования выглядит так, как будто он разрешается довольно простым запросом.

0 голосов
/ 29 июня 2010

Почему бы вам не использовать один параметризованный запрос, подобный этому:

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)
...