IN пункт и заполнители

82

Я пытаюсь выполнить следующий SQL-запрос в 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});

Однако Android не заменяет знак вопроса правильными значениями. Я мог бы сделать следующее, однако это не защищает от SQL-инъекции:

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

Как я могу обойти эту проблему и иметь возможность использовать предложение IN?

Теги:

8 ответов

181
Лучший ответ

Строка формы "?, ?, ..., ?" может быть динамически созданной строкой и безопасно помещаться в исходный SQL-запрос (поскольку это ограниченная форма, которая не содержит внешних данных), а затем заполнители могут использоваться как обычно.

Рассмотрим функцию String makePlaceholders(int len), которая возвращает len вопросительные знаки, разделенные запятыми, а затем:

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

Просто убедитесь, что переданы столько же значений, сколько и мест. Максимальное ограничение по умолчанию параметров хоста в SQLite составляет 999 - по крайней мере, при нормальной сборке, не уверен в Android:)

Счастливое кодирование.


Вот одна реализация:

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();
    }
}
  • 7
    Да, это единственный способ использовать параметризованные запросы IN () в SQLite и практически в любой другой базе данных SQL.
  • 0
    Используя этот метод, я дополнил используемый мной ContentProvider, а в методе query () добавила логику для проверки наличия: «IN?» и если найдено, подсчитывает ли вхождение "?" в исходной выборке, по сравнению с длиной переданных аргументов, собирается "?,?, ...?" за разницу и заменяет оригинал "IN?" с созданной коллекцией вопросительного знака. Это делает логику доступной почти глобальной, и для моего использования она, кажется, работает хорошо. Мне пришлось добавить некоторые специальные условия для фильтрации пустых списков IN, в этих случаях «IN?» заменено на "1" на данный момент.
Показать ещё 3 комментария
8

Короткий пример, основанный на ответе пользователя166390:

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

К сожалению, нет никакого способа сделать это (очевидно, 'name1', 'name2' не является единственным значением и поэтому не может использоваться в подготовленном заявлении).

Таким образом, вам придется опустить свои взгляды (например, создав очень конкретные, а не повторяющиеся запросы, такие как WHERE name IN (?, ?, ?)) или не используя хранимые процедуры, и попытайтесь предотвратить инъекции SQL с помощью некоторых других методов...

  • 4
    Вы на самом деле можете, немного поработав, построить параметризованный запрос IN. Смотрите ответ PST ниже. Полученный запрос параметризован и безопасен для инъекций.
3

Как предлагается в принятом ответе, но без использования пользовательской функции для генерации разделенных запятыми "?". Пожалуйста, проверьте код ниже.

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

Вы можете использовать TextUtils.join(",", parameters), чтобы воспользоваться параметрами привязки sqlite, где parameters - это список с "?" заполнителями, а строка результата - как "?,?,..,?".

Вот небольшой пример:

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()])
);
  • 0
    Что если один из «параметров» должен быть нулевым?
0

Это не допустимо

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

Это допустимо

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

Использование 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

На самом деле вы можете использовать собственный запрос для android вместо 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;
}
-3

У меня была та же проблема, и я считаю, что принятый ответ действительно сложный. Я предпочитаю следующее:

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

String query = "SELECT * FROM table WHERE name IN (" + names + ")";
Cursor cursor = mDb.rawQuery(query, null);
  • 0
    Создание строки имен может привести к некоторой ошибке Sql, используя параметры, чтобы ее избежать.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню