web-dev-qa-db-ja.com

文字列値に基づくint列の検索

ビューがありますView_Booking SQL Server 2014の場合:

bkID    bkSlot    bkStatus
----    ------    --------
2       Lunch     1
4       Lunch     1
6       Dinner    0
7       Lunch     1

C#では、グリッドビューを使用してbkStatusを次のような文字列にキャストしました。

<asp:Label ID="lblStatus" Text='<%# (Eval("bkStatus")+"" == "1") ? "Booked" : "Pending" %>'
    ... ></asp:Label>

bkID    bkSlot    bkStatus
----    ------    --------
2       Lunch     Booked
4       Lunch     Booked
6       Dinner    Pending
7       Lunch     Booked

今、私はこのクエリを使用してビューを検索しています:

SELECT * FROM View_Booking 
WHERE CAST(bkID AS NVARCHAR(MAX)) LIKE '%" + keyword + "%' 
OR bkSlot LIKE '%"+keyword+"%' 
OR bkStatus LIKE << ? >>

しかし、SQLでintであるときに、c#からstringとして渡されるbkStatusを検索する方法がわかりませんか?

22
5377037

いくつかの推奨事項

指定したクエリは最適化する必要があります:

  1. まず、CAST(bkID AS NVARCHAR(MAX))を使用するとクエリのパフォーマンスに影響します。インデックスを使用しないため、NVARCHAR(MAX)にキャストするとパフォーマンスが低下します。

  2. bkStatusは数値列であるため、=演算子を使用して数値(0 or 1 or ...)と比較する必要があります。また、提供されるテキスト値は、データベースではなくaspタグで定義されているため、データレベルではなく、アプリケーションレベル。

  3. 特定の数字を含むbkid列を検索するためにCAST(bkID AS NVARCHAR(MAX))を使用している場合(例:1の検索->結果11011 ,. ..)、特定のサイズにキャストしてみます(例:CAST(bkID as NVARCHAR(10)

  4. パフォーマンスを向上させ、Sqlインジェクション攻撃を防ぐために、パラメーター化されたクエリを使用することをお勧めします。 @ un-lucky answerを見てください

  5. 辞書オブジェクトを使用して、キーワードに関連するID値を保存できます

注:CASTやLikeを使用してもインデックスは使用されません。この例は要件に基づいています(私が提供した推奨事項を他の推奨事項と組み合わせようとしました)

var dicStatus = new Dictionary<int, string> { 
    { 0, "Pending" }, 
    { 1, "Booked"  },
    { 2, "Cancelled" }
    // ...
};

string querySql = " SELECT * FROM View_Booking" +
                  " WHERE CAST(bkID AS NVARCHAR(10)) LIKE @bkID" + 
                  " OR bkSlot LIKE @bkSlot" +
                  " OR bkStatus = @status";
using (SqlConnection dbConn = new SqlConnection(connectionString))
{
    dbConn.Open();
    using (SqlCommand sqlCommand = new SqlCommand(querySql, dbConn))
    {
        sqlCommand.Parameters.Add("@bkID", SqlDbType.VarChar).value ="%" + keyword + "%";
        sqlCommand.Parameters.Add("@bkSlot", SqlDbType.VarChar).value ="%" + keyword + "%";
        sqlCommand.Parameters.Add("@status", SqlDbType.Int).value = dicStatus.FirstOrDefault(x => x.Value == keyword).Key;
        sqlCommand.ExecuteNonQuery();
     }
}

また、BkIDが整数列の場合は、使用することをお勧めします

sqlCommand.Parameters.Add("@bkID", SqlDbType.Int).value = (Int)keyword ;

リファレンスと役立つリンク

12
Hadi

したがって、ユーザーがbkIDbkSlotまたはbkStatusを使用して検索できる検索ボックスが必要です。検索テキストがBookedまたはPendingデータベースの整数フィールドになるbkStatusのフィルターを追加する必要があります。正しい?ここで言及しなければならないことは、usingの使用法と、よりスマートで安全な実行方法のためのクエリのパラメーター化です。したがって、次のようなクエリを作成して実行することをお勧めします。

_int statusCode = -1;
if(keyword.ToLower() == "booked")
   statusCode = 1;
else if(keyword.ToLower() == "pending")
   statusCode = 0;
string querySql = " SELECT * FROM View_Booking" +
                  " WHERE CAST(bkID AS NVARCHAR(MAX)) LIKE @bkID" + 
                  " OR bkSlot LIKE @bkSlot" +
                  " OR bkStatus = @status";
using (SqlConnection dbConn = new SqlConnection("connectionString here"))
{
    dbConn.Open();
    using (SqlCommand sqlCommand = new SqlCommand(querySql, dbConn))
    {
        sqlCommand.Parameters.Add("@bkID", SqlDbType.VarChar).value ="%" + keyword + "%";
        sqlCommand.Parameters.Add("@bkSlot", SqlDbType.VarChar).value ="%" + keyword + "%";
        sqlCommand.Parameters.Add("@status", SqlDbType.int).value = statusCode;
        sqlCommand.ExecuteNonQuery();
     }
}
_

次の点に注意してください:

  • bookPendなどのbkStatusフィルターを含める場合は、.Contains()または.StartsWith()を使用して、それに応じて条件を変更する必要があります。代わりに.ToLower()
  • statusCodeは_-1_で初期化され、他のすべての値に対するbkStatusベースのフィルターを回避します
7

declare関数を使用して、bkStatusのリストを持つ一時テーブルを作成できます。

bkstatusを外部キーとして使用すると、クエリを簡単に作成できます。その後は、cast関数やlike関数を使用する必要がなくなります。それは少し非効率的です。

以下のこのコードを試すことができます:

declare @bkstatus table (number int primary key , bkstatus varchar(10) )
insert into @bkstatus (number , bkstatus)
values ( 0 , 'Pending'), (1 , 'Booked')

そして、このクエリを使用します:

SELECT * FROM View_Booking v
INNER JOIN @bkstatus b on v.bkstatus = b.number
WHERE b.bkstatus =  @keyword  

CHOOSE()を使用してbkStatusをデコードし、TRY_CONVERT()を使用してbkIDをテストする別のオプション。

Declare @KeyWord varchar(50) = 'Pending';

Select * 
 From  View_Booking 
 Where bkID = try_convert(int,@KeyWord)
    or bkSlot like '%'+@KeyWord+'%'
    or choose(bkStatus+1,'Pending','Booked')=@KeyWord

返品

bkID    bkSlot  bkStatus
6       Dinner  0
4

keywordがステータス名であり、ステータスIDではない場合、BookingStatusテーブルを作成し、そこにbkStatus列とbkStatusTitle列を作成して、_View_Booking_。その後、bkStatusTitleでLIKEを簡単に実行できます。

_SELECT * FROM View_Booking 
WHERE CAST(bkID AS NVARCHAR(16)) LIKE '%' + @keyword + '%' 
OR bkSlot LIKE '%' + @keyword + '%' 
OR bkStatusTitle LIKE '%' + @keyword + '%'
_

keywordbkStatusの文字列表現になる場合、値が同じかどうかを確認します。

補足として、_'%' + keyword + '%'_のようにユーザー入力を連結してSQLクエリを作成することはお勧めできません。これはSQLインジェクション攻撃にさらされています。 SQLパラメータを使用して、ユーザー入力をSQLクエリに渡すことをお勧めします。 SQLビットとC#で_'%' + @keyword + '%'_を使用すると、以下の例のようなものがはるかに安全になります。

_sqlCommand.Parameters.Add("@keyword", SqlDbType.VarChar, 1000);
sqlCommand.Parameters["@keyword"].Value = searchText;
_

パラメータ化されたクエリには、複数の要求に対して同じクエリテキストを使用できるという利点もあります。これにより、SQL ServerはSQL実行プランをキャッシュして再利用できるため、パフォーマンスがわずかに向上します。

3

BkStatusは整数です。ビューでは、整数値をユーザーにとって意味のある文字列に変換します。それまではすべて問題ありません。これで、ユーザーが検索する必要があるのは、文字列から整数への変換を逆にして整数を検索することだけです。

物事をシンプルに保つ

searchStatusKey = yourVariableHodingTheString == "Booked" ? 1 : 0;

ただし、複数の場所でのコードの面倒なバグや無痛のアップグレードを回避するために(たとえば、そこに別のステータスを追加することにしたため)、ここに変換テーブルをお勧めします。 HashMap(連想配列)のようなもの。

var statusMap = new Dictionary<int, string> { 
    { 0, "Pending" }, 
    { 1, "Booked" },
    /* can always add more pairs in the future as needed */
};

ここで、パラメータがsearchStatusという変数にあると仮定します

searchStatusKey = statusMap.FirstOrDefault(x => x.Value == searchStatus).Key;

searchStatusKey値のwhere部分にbkStatusパラメータを指定すると、完了です。

select * from View_Booking where bkStatus = << ? >>
3
ptheofan

私はあなたの質問のこの部分にのみ焦点を当てます(それ自体が質問ですか?):

しかし、それがSQLのintであるときにc#から文字列として渡されるbkStatusを検索する方法がわかりませんか?

[〜#〜] sql [〜#〜]でこれに対処する1つの方法は、 [〜#〜 ] case [〜#〜] 句。あなたの特定のケースでは、次のようなことをすることができます(すべきではない):

SELECT * FROM View_Booking 
WHERE CAST(bkID AS NVARCHAR(MAX)) LIKE '%" + keyword + "%' 
OR bkSlot LIKE '%"+keyword+"%' 
OR bkStatus = CASE '%"+keyword+"%' WHEN 'Booked' THEN CAST(1 AS INT) WHEN 'Pending' THEN CAST(0 AS INT) WHEN ... THEN ... ELSE ... END' 

しかし、@ un-lucky'sの回答に示されているように、パラメータの使用をお勧めします。ここでは、ベストプラクティスに関して説明できることはまだまだたくさんあるため、次の記事をご覧になることをお勧めします。

  1. ルックアップテーブル:bkStatusのタイプはINTであると述べましたが、Bookedよりも多くのオプションがあると思いますまたは保留、例:予約済みまたはキャンセル。その場合、実際のコードは、追加するオプションごとにますます乱雑になります
  2. ADO.NETを使用するためのベストプラクティス:方法を指定していませんフロントエンドからデータベースにアクセスしますか?この記事は何年も前から存在していますが、その内容のほとんどはまだ最新のものです。これは役立つと思います。
  3. Building Better Entity Framework: Entity Frameworkを使用してデータベースにアクセスしている場合。

それが役に立てば幸い。

enumBookingStatusを作成し、文字列を受け入れて列挙値を返す関数を作成します。以下のコードを参照してください。

public enum BookingStatus {
    [Description("Pending")]
    Pending = 0,
    [Description("Booked")]
    Booked = 1
}

これで、関数は次のようになります。

    public static T GetValueFromDescription<T>(string p_description)
    {
        var type = typeof(T);
        if (!type.IsEnum) throw new InvalidOperationException();
        foreach (var field in type.GetFields())
        {
            var attribute = Attribute.GetCustomAttribute(field,
                typeof(DescriptionAttribute)) as DescriptionAttribute;
            if (attribute != null)
            {
                if (attribute.Description == p_description)
                    return (T)field.GetValue(null);
            }
            else
            {
                if (field.Name == p_description)
                    return (T)field.GetValue(null);
            }
        }
        throw new ArgumentException("Not found.", "description");
        // or return default(T);
    }

SQLクエリのパラメーターで、この関数をパラメーターとして"Booked"または"Pending"として呼び出すと、列挙型BookingStatus.Bookedが返されます。そこからint値を簡単に抽出できます。

(int)BookingStatus.Booked // will give 1
2
DhavalR

複数の列を自由に検索しようとしているように見えます。これは非常に一般的な問題であり、実際のソリューションは、動的検索条件の www.Sommarskog.se にあります。

ソリューションは、SQLインジェクションに対して脆弱であるかのように見えます。ストアドプロシージャsearch_orders_3に似たものを実装することを提案できますか?