web-dev-qa-db-fra.com

Clause IN et espaces réservés

J'essaie de faire la requête SQL suivante sous 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 puis-je contourner ce problème et pouvoir utiliser la clause IN?

97
Nick

Une chaîne de la forme "?, ?, ..., ?" 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'une forme restreinte ne contenant pas de données externes). Les espaces réservés peuvent ensuite être utilisés normalement.

Considérons une fonction String makePlaceholders(int len) qui renvoie len 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 de l'hôte dans SQLite est de 999 - au moins dans une construction normale, pas sûr de savoir à propos d'Android :)

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

Petit exemple, basé sur la réponse de user166390:

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;
}
8
Yuliia Ashomok

Comme suggéré dans la réponse acceptée mais sans utiliser la fonction personnalisée pour générer des '?' Séparés par des virgules. S'il vous plaît 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);
5
Kalpesh Gohel

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

Vous devrez donc réduire votre champ de vision (par exemple en créant des requêtes très spécifiques et non réutilisables comme WHERE name IN (?, ?, ?)) ou en n’utilisant pas de procédures stockées et essayer d’empêcher les injections SQL avec certaines autres techniques ... 

4
florian h

Vous pouvez utiliser TextUtils.join(",", parameters) pour tirer parti des paramètres de liaison sqlite, où parameters est 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()])
);
1
epool

J'utilise l'API Stream 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);
0
PPartisan

Dans Kotlin, vous pouvez utiliser joinToString

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

En fait, vous pouvez utiliser le mode de requête natif 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;
}
0
Stan

Ceci 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);
0
Vahe Gharibyan