web-dev-qa-db-fra.com

WHERE IN (tableau d'identifiants)

J'ai WebService qui reçoit un tableau d'ints .. Je voudrais faire la déclaration select comme suit mais continuer à avoir des erreurs Dois-je changer le tableau en chaîne?

[WebMethod]
public MiniEvent[] getAdminEvents(int buildingID, DateTime startDate)
{    
    command.CommandText = @"SELECT id,
                            startDateTime, endDateTime From
                            tb_bookings WHERE buildingID IN
                            (@buildingIDs) AND startDateTime <=
                            @fromDate";

    SqlParameter buildID = new SqlParameter("@buildingIDs", buildingIDs);
}
26
user17510

Vous ne pouvez pas (malheureusement) faire ça. Un paramètre Sql ne peut être qu'une seule valeur, vous devez donc faire:

WHERE buildingID IN (@buildingID1, @buildingID2, @buildingID3...)

Ce qui, bien sûr, vous oblige à connaître le nombre d'identifiants de bâtiment ou à construire dynamiquement la requête.

Pour contourner le problème *, j'ai effectué les opérations suivantes:

WHERE buildingID IN (@buildingID)

command.CommandText = command.CommandText.Replace(
  "@buildingID", 
  string.Join(buildingIDs.Select(b => b.ToString()), ",")
);

qui remplacera le texte de la déclaration par les chiffres, pour aboutir à quelque chose comme:

WHERE buildingID IN (1,2,3,4)
  • Notez que cela se rapproche d'une vulnérabilité d'injection SQL, mais comme c'est un tableau int, c'est sûr. Les chaînes arbitraires sont non safe, mais il n'y a aucun moyen d'incorporer des instructions Sql dans un entier (ou date/heure, booléen, etc.).
31
Mark Brackett

Vous allez d'abord avoir besoin d'une fonction et d'un sproc. La fonction divisera vos données et retournera une table:

CREATE function IntegerCommaSplit(@ListofIds nvarchar(1000))
returns @rtn table (IntegerValue int)
AS
begin
While (Charindex(',',@ListofIds)>0)
Begin
    Insert Into @Rtn 
    Select ltrim(rtrim(Substring(@ListofIds,1,Charindex(',',@ListofIds)-1)))
    Set @ListofIds = Substring(@ListofIds,Charindex(',',@ListofIds)+len(','),len(@ListofIds))
end
Insert Into @Rtn 
    Select  ltrim(rtrim(@ListofIds))
return 
end

Ensuite, vous avez besoin d'un sproc pour l'utiliser:

create procedure GetAdminEvents 
    @buildingids nvarchar(1000),
    @startdate datetime
as
SELECT id,startDateTime, endDateTime From
            tb_bookings t INNER JOIN 
dbo.IntegerCommaSplit(@buildingids) i
on i.IntegerValue = t.id
 WHERE startDateTime <= @fromDate

Enfin, votre code:

[WebMethod]
        public MiniEvent[] getAdminEvents(int[] buildingIDs, DateTime startDate)
        command.CommandText = @"exec GetAdminEvents";
 SqlParameter buildID= new SqlParameter("@buildingIDs", buildingIDs);

Cela va bien au-delà de ce que votre question a demandé, mais cela fera tout ce dont vous avez besoin.

Remarque: si vous transmettez quelque chose qui n'est pas un int, la fonction de base de données entière échouera. Je laisse la gestion des erreurs pour cela comme un exercice pour l'utilisateur final.

8
Josef

NOTE: Je ne suis généralement pas pour utiliser des requêtes non paramétrées. DANS CETTE INSTANCE, cependant, étant donné que nous avons affaire à un tableau d’entiers, vous pourriez faire une telle chose et ce serait plus efficace. Cependant, étant donné que tout le monde semble vouloir déclasser la réponse parce que cela ne répond pas à leurs critères de conseil valide, je vais soumettre une autre réponse qui exécute horriblement mais qui fonctionnera probablement dans LINK2SQL.

En supposant, comme le stipule votre question, que vous disposiez d'un tableau d'ints, vous pouvez utiliser le code suivant pour renvoyer une chaîne contenant une liste délimitée par des virgules que SQL accepterait:

private string SQLArrayToInString(Array a)
{
 StringBuilder sb = new StringBuilder();
 for (int i = 0; i < a.GetUpperBound(0); i++)
  sb.AppendFormat("{0},", a.GetValue(i));
 string retVal = sb.ToString();
 return retVal.Substring(0, retVal.Length - 1);
}

Ensuite, je vous conseillerais de ne pas essayer de paramétrer la commande étant donné qu’il s’agit d’un tableau d’ints et d’utiliser simplement:

command.CommandText = @"SELECT id,
            startDateTime, endDateTime From
            tb_bookings WHERE buildingID IN
            (" + SQLArrayToInString(buildingIDs) + ") AND startDateTime <=
            @fromDate";
6
Josef

Une méthode XML ultra-rapide qui ne nécessite aucun code non sécurisé ni fonction définie par l'utilisateur:

Vous pouvez utiliser une procédure stockée et transmettre la liste des ID de bâtiment séparés par des virgules: 

Declare @XMLList xml
SET @XMLList=cast('<i>'+replace(@buildingIDs,',','</i><i>')+'</i>' as xml)
SELECT x.i.value('.','varchar(5)') from @XMLList.nodes('i') x(i))

Tout le mérite revient au gourou Blog de Brad Schulz

3
Nishant

Visitez Procédure stockée T-SQL qui accepte plusieurs valeurs Id pour des idées sur la façon de procéder.

1
jop

J'utilise cette approche et travaille pour moi.

Ma variable act = ma liste d'ID à la chaîne.

act = "1, 2, 3, 4"

 command = new SqlCommand("SELECT x FROM y WHERE x.id IN (@actions)", conn);    
 command.Parameters.AddWithValue("@actions", act);
 command.CommandText = command.CommandText.Replace("@actions", act);
1
Gonçalo Dinis

[WebMethod]

public MiniEvent [] getAdminEvents (int buildingIDDateDate Date)

...

SqlParameter buildID = new SqlParameter ("@ buildingIDs", iD de bâtiment)

Peut-être que je suis trop détaillé, mais cette méthode accepte un seul int, pas un tableau d'ints. Si vous prévoyez de passer dans un tableau, vous devrez mettre à jour la définition de votre méthode pour créer un tableau int. Une fois que vous obtenez ce tableau, vous devrez le convertir en chaîne si vous envisagez de l’utiliser dans une requête SQL.

0
Chris Porter

Voici une solution Linq que j'ai imaginée. Tous les éléments de la liste seront automatiquement insérés en tant que paramètres @ item0, @ item1, @ item2, @ item3, etc.

[WebMethod]
public MiniEvent[] getAdminEvents(Int32[] buildingIDs, DateTime startDate)
{
    // Gets a list with numbers from 0 to the max index in buildingIDs,
    // then transforms it into a list of strings using those numbers.
    String idParamString = String.Join(", ", (Enumerable.Range(0, buildingIDs.Length).Select(i => "@item" + i)).ToArray());
    command.CommandText = @"SELECT id,
                        startDateTime, endDateTime From
                        tb_bookings WHERE buildingID IN
                        (" + idParamString + @") AND startDateTime <=
                        @fromDate";
    // Reproduce the same parameters in idParamString 
    for (Int32 i = 0; i < buildingIDs.Length; i++)
            command.Parameters.Add(new SqlParameter ("@item" + i, buildingIDs[i]));
    command.Parameters.Add(new SqlParameter("@fromDate", startDate);
    // the rest of your code...
}
0
Nyerguds

Vous pouvez utiliser ceci. Exécutez dans SQLServer pour créer une fonction sur votre base de données (une seule fois):

IF EXISTS(
    SELECT *
    FROM sysobjects
    WHERE name = 'FN_RETORNA_ID_FROM_VARCHAR_TO_TABLE_INT')
BEGIN
    DROP FUNCTION FN_RETORNA_ID_FROM_VARCHAR_TO_TABLE_INT
END
GO

CREATE FUNCTION [dbo].FN_RETORNA_ID_FROM_VARCHAR_TO_TABLE_INT (@IDList VARCHAR(8000))
RETURNS
    @IDListTable TABLE (ID INT)
AS
BEGIN

    DECLARE
        --@IDList VARCHAR(100),
        @LastCommaPosition INT,
        @NextCommaPosition INT,
        @EndOfStringPosition INT,
        @StartOfStringPosition INT,
        @LengthOfString INT,
        @IDString VARCHAR(100),
        @IDValue INT

    --SET @IDList = '11,12,113'

    SET @LastCommaPosition = 0
    SET @NextCommaPosition = -1

    IF LTRIM(RTRIM(@IDList)) <> ''
    BEGIN

        WHILE(@NextCommaPosition <> 0)
        BEGIN

            SET @NextCommaPosition = CHARINDEX(',',@IDList,@LastCommaPosition + 1)

            IF @NextCommaPosition = 0
                SET @EndOfStringPosition = LEN(@IDList)
            ELSE
                SET @EndOfStringPosition = @NextCommaPosition - 1

            SET @StartOfStringPosition  = @LastCommaPosition + 1
            SET @LengthOfString = (@EndOfStringPosition + 1) - @StartOfStringPosition

            SET @IDString =  SUBSTRING(@IDList,@StartOfStringPosition,@LengthOfString)                  

            IF @IDString <> ''
                INSERT @IDListTable VALUES(@IDString)

            SET @LastCommaPosition = @NextCommaPosition

        END --WHILE(@NextCommaPosition <> 0)

    END --IF LTRIM(RTRIM(@IDList)) <> ''

    RETURN

ErrorBlock:

    RETURN

END --FUNCTION

Après avoir créé la fonction, vous devez l'appeler dans votre code:

command.CommandText = @"SELECT id,
                        startDateTime, endDateTime From
                        tb_bookings WHERE buildingID IN
                        (SELECT ID FROM FN_RETORNA_ID_FROM_VARCHAR_TO_TABLE_INT(@buildingIDs))) AND startDateTime <=
                        @fromDate";

command.Parameters.Add(new SqlParameter(){
                           DbType = DbType.String,
                           ParameterName = "@buildingIDs",
                           Value = "1,2,3,4,5" //Enter the parameters here separated with commas
                       });

Cette fonction récupère les virgules internes dans "tableau" et crée une table avec cette valeur sous la forme int, appelée ID. Lorsque cette fonction est sur votre base de données, vous pouvez l'utiliser dans n'importe quel projet.


Merci à Microsoft MSDN.

Igo S Ventura

Microsoft MVA

Sistema Ari de Sá

[email protected]

P.S .: Je viens du Brésil. Excuse mon anglais ... XD

0
Igo Soares