Clause IN et espaces réservés

104

J'essaie de faire la requête SQL suivante dans Android:

    String names = "'name1', 'name2";   // in the code this is dynamically generated

    String query = "SELECT * FROM table WHERE name IN (?)";
    Cursor cursor = mDb.rawQuery(query, new String[]{names});

Cependant, Android ne remplace pas le point d'interrogation par les valeurs correctes. Je pourrais faire ce qui suit, cependant, cela ne protège pas contre l'injection SQL:

    String query = "SELECT * FROM table WHERE name IN (" + names + ")";
    Cursor cursor = mDb.rawQuery(query, null);

Comment contourner ce problème et pouvoir utiliser la clause IN?

pseudo
la source

Réponses:

188

Une chaîne du formulaire "?, ?, ..., ?"peut être une chaîne créée dynamiquement et placée en toute sécurité dans la requête SQL d'origine (car il s'agit d'un formulaire restreint qui ne contient pas de données externes), puis les espaces réservés peuvent être utilisés normalement.

Considérons une fonction String makePlaceholders(int len)qui renvoie lendes points d'interrogation séparés par des virgules, puis:

String[] names = { "name1", "name2" }; // do whatever is needed first
String query = "SELECT * FROM table"
    + " WHERE name IN (" + makePlaceholders(names.length) + ")";
Cursor cursor = mDb.rawQuery(query, names);

Assurez-vous simplement de transmettre exactement autant de valeurs que de lieux. La limite maximale par défaut des paramètres d'hôte dans SQLite est de 999 - au moins dans une version normale, pas sûr d'Android :)

Bon codage.


Voici une implémentation:

String makePlaceholders(int len) {
    if (len < 1) {
        // It will lead to an invalid query anyway ..
        throw new RuntimeException("No placeholders");
    } else {
        StringBuilder sb = new StringBuilder(len * 2 - 1);
        sb.append("?");
        for (int i = 1; i < len; i++) {
            sb.append(",?");
        }
        return sb.toString();
    }
}

la source
8
Oui, c'est la (seule) façon d'utiliser des requêtes IN () paramétrées dans SQLite et à peu près dans n'importe quelle autre base de données SQL.
Larry Lustig le
En utilisant cette méthode, j'ai augmenté le ContentProvider que j'ai utilisé et dans la méthode query () j'ai ajouté une logique pour tester la présence: "IN?" et s'il est trouvé, est-ce qu'un décompte de l'occurrence de "?" dans la sélection d'origine, par rapport à la longueur des arguments passés, assemble un "?,?, ...?" pour la différence et remplace l'original "IN?" avec la collection de points d'interrogation générée. Cela rend la logique disponible presque globale et pour mes utilisations, elle semble bien fonctionner. J'ai dû ajouter un provisionnement spécial pour filtrer les listes IN vides, dans ces cas, le "IN?" est remplacé par "1" pour le moment.
SandWyrm
3
La chose idiote à ce sujet est, bien sûr, que si vous allez créer votre propre chaîne avec N points d'interrogation, vous pouvez tout aussi bien encoder directement les données. En supposant qu'il soit désinfecté.
Christopher
16
Cet ensemble makePlaceholderspourrait être remplacé par TextUtils.join(",", Collections.nCopies(len, "?")). Moins verbeux.
Konrad Morawski
1
Caused by: android.database.sqlite.SQLiteException: near ",": syntax error (code 1): , while compiling: SELECT url FROM tasks WHERE url=?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?
Iman Marashi
9

Exemple court, basé sur la réponse de l'utilisateur166390:

public Cursor selectRowsByCodes(String[] codes) {
    try {
        SQLiteDatabase db = getReadableDatabase();
        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();

        String[] sqlSelect = {COLUMN_NAME_ID, COLUMN_NAME_CODE, COLUMN_NAME_NAME, COLUMN_NAME_PURPOSE, COLUMN_NAME_STATUS};
        String sqlTables = "Enumbers";

        qb.setTables(sqlTables);

        Cursor c = qb.query(db, sqlSelect, COLUMN_NAME_CODE+" IN (" +
                        TextUtils.join(",", Collections.nCopies(codes.length, "?")) +
                        ")", codes,
                null, null, null); 
        c.moveToFirst();
        return c;
    } catch (Exception e) {
        Log.e(this.getClass().getCanonicalName(), e.getMessage() + e.getStackTrace().toString());
    }
    return null;
}
Yuliia Ashomok
la source
5

Malheureusement, il n'y a aucun moyen de le faire (il ne 'name1', 'name2's'agit évidemment pas d'une valeur unique et ne peut donc pas être utilisée dans une instruction préparée).

Vous devrez donc baisser vos vues (par exemple en créant des requêtes très spécifiques, non réutilisables comme WHERE name IN (?, ?, ?)) ou ne pas utiliser de procédures stockées et essayer d'empêcher les injections SQL avec d'autres techniques ...

florian h
la source
4
Vous pouvez en fait, avec un peu de travail, créer une requête IN paramétrée. Voir la réponse de pst ci-dessous. La requête résultante est paramétrée et sécurisée pour l'injection.
Larry Lustig le
@LarryLustig à quelle réponse parlez-vous avec la réponse "pst"? A-t-il été supprimé? Cela semble être la seule occurrence de pst ici ...
Stefan Haustein
1
@StefanHaustein " réponse de pst " (utilisateur supprimé).
user4157124
5

Comme suggéré dans la réponse acceptée, mais sans utiliser la fonction personnalisée pour générer un «?» Séparé par des virgules. Veuillez vérifier le code ci-dessous.

String[] names = { "name1", "name2" }; // do whatever is needed first
String query = "SELECT * FROM table"
    + " WHERE name IN (" + TextUtils.join(",", Collections.nCopies(names.length, "?"))  + ")";
Cursor cursor = mDb.rawQuery(query, names);
Kalpesh Gohel
la source
1

Vous pouvez utiliser TextUtils.join(",", parameters)pour tirer parti des paramètres de liaison sqlite, où se parameterstrouve une liste avec des "?"espaces réservés et la chaîne de résultat est quelque chose comme "?,?,..,?".

Voici un petit exemple:

Set<Integer> positionsSet = membersListCursorAdapter.getCurrentCheckedPosition();
List<String> ids = new ArrayList<>();
List<String> parameters = new ArrayList<>();
for (Integer position : positionsSet) {
    ids.add(String.valueOf(membersListCursorAdapter.getItemId(position)));
    parameters.add("?");
}
getActivity().getContentResolver().delete(
    SharedUserTable.CONTENT_URI,
    SharedUserTable._ID + " in (" + TextUtils.join(",", parameters) + ")",
    ids.toArray(new String[ids.size()])
);
epool
la source
Que faire si l'un des "paramètres" doit être nul?
développeur android
0

En fait, vous pouvez utiliser la méthode native de requête d'Android au lieu de rawQuery:

public int updateContactsByServerIds(ArrayList<Integer> serverIds, final long groupId) {
    final int serverIdsCount = serverIds.size()-1; // 0 for one and only id, -1 if empty list
    final StringBuilder ids = new StringBuilder("");
    if (serverIdsCount>0) // ambiguous "if" but -1 leads to endless cycle
        for (int i = 0; i < serverIdsCount; i++)
            ids.append(String.valueOf(serverIds.get(i))).append(",");
    // add last (or one and only) id without comma
    ids.append(String.valueOf(serverIds.get(serverIdsCount))); //-1 throws exception
    // remove last comma
    Log.i(this,"whereIdsList: "+ids);
    final String whereClause = Tables.Contacts.USER_ID + " IN ("+ids+")";

    final ContentValues args = new ContentValues();
    args.put(Tables.Contacts.GROUP_ID, groupId);

    int numberOfRowsAffected = 0;
    SQLiteDatabase db = dbAdapter.getWritableDatabase());
        try {
            numberOfRowsAffected = db.update(Tables.Contacts.TABLE_NAME, args, whereClause, null);
        } catch (Exception e) {
            e.printStackTrace();
        }
        dbAdapter.closeWritableDB();


    Log.d(TAG, "updateContactsByServerIds() numberOfRowsAffected: " + numberOfRowsAffected);

    return numberOfRowsAffected;
}
Stan
la source
0

Ce n'est pas valide

String subQuery = "SELECT _id FROM tnl_partofspeech where part_of_speech = 'noun'";
Cursor cursor = SQLDataBase.rawQuery(
                "SELECT * FROM table_main where part_of_speech_id IN (" +
                        "?" +
                        ")",
                new String[]{subQuery}););

Ceci est valide

String subQuery = "SELECT _id FROM tbl_partofspeech where part_of_speech = 'noun'";
Cursor cursor = SQLDataBase.rawQuery(
                "SELECT * FROM table_main where part_of_speech_id IN (" +
                        subQuery +
                        ")",
                null);

Utilisation de ContentResolver

String subQuery = "SELECT _id FROM tbl_partofspeech where part_of_speech = 'noun' ";

final String[] selectionArgs = new String[]{"1","2"};
final String selection = "_id IN ( ?,? )) AND part_of_speech_id IN (( " + subQuery + ") ";
SQLiteDatabase SQLDataBase = DataBaseManage.getReadableDatabase(this);

SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
queryBuilder.setTables("tableName");

Cursor cursor =  queryBuilder.query(SQLDataBase, null, selection, selectionArgs, null,
        null, null);
Vahe Gharibyan
la source
0

Dans Kotlin, vous pouvez utiliser joinToString

val query = "SELECT * FROM table WHERE name IN (${names.joinToString(separator = ",") { "?" }})"
val cursor = mDb.rawQuery(query, names.toTypedArray())
Aleksey Kornienko
la source
0

J'utilise l' StreamAPI pour cela:

final String[] args = Stream.of("some","data","for","args").toArray(String[]::new);
final String placeholders = Stream.generate(() -> "?").limit(args.length).collect(Collectors.joining(","));
final String selection = String.format("SELECT * FROM table WHERE name IN(%s)", placeholders);

db.rawQuery(selection, args);
PPartisan
la source