web-dev-qa-db-fra.com

Recherche d'une colonne int sur la base d'une valeur de chaîne

J'ai une vue View_Booking dans SQL Server 2014:

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

En c #, j'ai utilisé une grille et jeté bkStatus dans une chaîne comme:

<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

Maintenant, je recherche dans View en utilisant cette requête:

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

Mais je ne sais pas comment rechercher bkStatus qui est passé comme chaîne à partir de c # alors qu'il s'agit d'un int en sql?

22
5377037

Quelques recommandations

La requête que vous avez fournie doit être optimisée:

  1. Tout d'abord, l'utilisation de CAST(bkID AS NVARCHAR(MAX)) affectera les performances de la requête, car elle n'utilisera aucun index, la conversion en NVARCHAR(MAX) diminuera également les performances.

  2. bkStatus est une colonne numérique, vous devez donc utiliser l'opérateur = et comparer avec les valeurs numériques (0 or 1 or ...), les valeurs de texte fournies sont également définies dans la balise asp pas dans la base de données, ils sont donc utilisés au niveau de l'application et non au niveau des données.

  3. si vous utilisez CAST(bkID AS NVARCHAR(MAX)) pour rechercher la colonne bkid qui contient un chiffre spécifique (ex: recherchez 1 -> résultat 1, 10, 11, ...), puis essayez de diffuser à une taille spécifique (ex: CAST(bkID as NVARCHAR(10))

  4. Il est recommandé d'utiliser des requêtes paramétrées pour de meilleures performances et pour empêcher les attaques par injection SQL . regardez @ réponse malchanceuse

  5. Vous pouvez utiliser un objet dictionnaire pour stocker les valeurs d'ID liées aux mots clés

Exemple

Remarque: L'utilisation de CAST et Like n'utilisera aucun index, cet exemple est basé sur vos besoins (j'ai essayé de combiner les recommandations que j'ai fournies avec d'autres recommandations)

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();
     }
}

De plus, si BkID est une colonne entière, il vaut mieux l'utiliser

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

Références et liens utiles

12
Hadi

Vous avez donc besoin d'une boîte de recherche dans laquelle l'utilisateur peut rechercher en utilisant bkID, bkSlot ou bkStatus, si le texte de recherche est Booked ou Pending nous devons ajouter le filtre pour bkStatus qui sera un champ entier dans la base de données. droite? Peu de choses que je dois mentionner ici sont les tilisation de using ainsi que le paramétrage des requêtes pour une manière d'exécution plus intelligente et plus sûre. Je voudrais donc suggérer de construire et d'exécuter la requête comme suit:

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();
     }
}

Veuillez noter ce qui suit:

  • Si vous souhaitez inclure le filtre bkStatus pour book, Pend etc., alors vous devez modifier la condition en conséquence en utilisant .Contains() ou .StartsWith() à la place pour .ToLower()
  • statusCode est initialisé avec -1 pour éviter le filtre basé sur bkStatus pour toutes les autres valeurs
7
sujith karivelil

Vous pouvez utiliser la fonction declare pour créer une table temporaire qui a une liste de bkStatus.

Il vous sera plus facile de créer une requête en utilisant bkstatus comme clé étrangère. Après cela, vous n'avez plus besoin d'utiliser la fonction cast ou like. Ce sera un peu inefficace.

Vous pouvez essayer ce code ci-dessous:

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

puis en utilisant cette requête:

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

Juste une autre option utilisant CHOOSE () pour décoder le bkStatus et TRY_CONVERT () pour tester bkID.

Exemple

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

retourne

bkID    bkSlot  bkStatus
6       Dinner  0
4
John Cappelletti

Si keyword sera le nom d'état et non l'ID d'état, je créerais une table BookingStatus, y aurais des colonnes bkStatus et bkStatusTitle et les joindrais à la View_Booking. Vous pourriez alors facilement faire LIKE sur bkStatusTitle.

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

Si keyword sera une représentation sous forme de chaîne de bkStatus, je verrais simplement si les valeurs sont les mêmes.

En remarque, c'est une mauvaise idée de construire vos requêtes SQL concaténant les entrées utilisateur comme '%' + keyword + '%'. Ceci est ouvert aux attaques par injection SQL. Il est préférable d'utiliser des paramètres SQL pour transmettre les entrées utilisateur aux requêtes SQL. En utilisant '%' + @keyword + '%' dans le bit SQL et en C # quelque chose comme l'exemple ci-dessous serait beaucoup plus sûr.

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

Les requêtes paramétrées vous offrent également un avantage du même texte de requête pour plusieurs requêtes, ce qui permet à SQL Server de mettre en cache les plans d'exécution SQL et de les réutiliser, offrant des performances légèrement meilleures.

3
Justinas Marozas

Votre bkStatus est un entier. Dans la vue, vous traduisez la valeur entière en une chaîne significative pour l'utilisateur. Jusque-là, tout va bien. Maintenant, pour que l'utilisateur puisse rechercher, il vous suffit d'inverser vos traductions de chaînes en entier et de rechercher des entiers.

Garder les choses simples

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

Cependant, pour éviter les bogues fastidieux et les mises à niveau indolores du code à plusieurs endroits (disons parce qu'ils ont décidé d'y ajouter un autre statut), je recommanderais une table de traduction ici. Quelque chose comme HashMap (tableau associatif).

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

Maintenant, en supposant que votre paramètre se trouve dans une variable appelée searchStatus

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

Maintenant, fournissez comme paramètre bkStatus dans la partie where la valeur searchStatusKey et vous avez terminé.

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

Je vais me concentrer sur cette partie de votre question (est-ce la question elle-même?):

Mais je ne sais pas comment rechercher bkStatus qui est passé en tant que chaîne à partir de c # alors qu'il s'agit d'un int en sql?

Une façon de gérer cela dans [~ # ~] sql [~ # ~] est à l'aide de CASE clause. Dans votre cas spécifique, vous pourriez (ne signifie pas devrait) faire quelque chose comme:

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' 

Mais je suggère l'utilisation de paramètres comme indiqué dans la réponse @ un-lucky . Il y a beaucoup plus que nous pourrions discuter en termes de meilleures pratiques ici, donc je vous suggère de jeter un œil aux articles suivants:

  1. Tables de recherche: Vous avez déclaré que bkStatus est de type INT soy Je suppose que vous pourriez avoir plus d'options que Réservé ou En attente, par exemple: Réservé ou Annulé. Dans ce cas, votre code réel peut devenir de plus en plus désordonné avec chaque option que vous ajoutez.
  2. Meilleures pratiques pour l'utilisation d'ADO.NET: Vous n'avez pas spécifié comment accédez-vous à la base de données depuis votre frontal. Même si cet article existe depuis des années, la plupart de son contenu est toujours à jour. Je suppose que cela peut être utile.
  3. Building Better Entity Framework: Dans le cas où vous utilisez Entity Framework pour accéder à votre base de données.

J'espère que ça aide.

2

Créez un enum pour BookingStatus et créez une fonction qui accepte la chaîne et renvoie la valeur enum. Voir le code ci-dessous.

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

Maintenant, la fonction est comme ci-dessous,

    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);
    }

Maintenant, dans le paramètre de la requête SQL, appelez cette fonction avec le paramètre comme "Booked" ou "Pending" et il renverra enum BookingStatus.Booked. Vous pouvez facilement en extraire la valeur int.

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

Il semble que vous essayiez de rechercher librement parmi plusieurs colonnes. C'est un problème assez courant, et la vraie solution peut être trouvée sur www.Sommarskog.se sur les conditions de recherche dynamique.

Votre solution semble vulnérable à l'injection SQL. Puis-je vous suggérer d'implémenter quelque chose de similaire à la procédure stockée search_orders_3?

2