Пункт IN та заповнювачі


104

Я намагаюся зробити наступний 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?

Відповіді:


188

Рядок форми "?, ?, ..., ?"може представляти собою динамічно створений рядок і безпечно вводити його в оригінальний запит 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();
    }
}

8
Так, це (єдиний) спосіб використання параметризованих запитів IN () в SQLite і майже в будь-якій іншій базі даних SQL.
Ларрі Люстіг

Використовуючи цей метод, я доповнив використовуваний нами ContentProvider і в методі query () додав логіку для перевірки наявності: "IN?" і якщо його знайдено, чи вважає кількість виникнення "?" в оригінальному виділенні порівняно з тривалістю аргументів, зібраних ",?, ... ..." для різниці і замінює початковий "IN?" із створеною колекцією запитальних знаків. Це робить логіку доступною майже глобальною, і для мого використання, здається, вона працює добре. Мені довелося додати якесь спеціальне забезпечення для фільтрування порожніх списків IN, у тих випадках "IN?" наразі замінено на "1".
SandWyrm

3
Дурне в цьому полягає, звичайно, що якщо ви збираєтеся створити власну рядок з N знаками запитання, ви можете просто просто закодувати дані. Якщо припустити, що це дезінфіковано.
Крістофер

16
Це ціле makePlaceholdersможна було замінити на TextUtils.join(",", Collections.nCopies(len, "?")). Менш багатослівний.
Конрад Моравський

1
Caused by: android.database.sqlite.SQLiteException: near ",": syntax error (code 1): , while compiling: SELECT url FROM tasks WHERE url=?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?
Іман Мараші

9

Короткий приклад, заснований на відповіді користувача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;
}

5

На жаль, немає способу зробити це (очевидно, 'name1', 'name2'це не є єдиним значенням, і тому його не можна використовувати в підготовленому твердженні).

Тож вам доведеться зменшити приціл (наприклад, створивши дуже специфічні запити, що не використовуються повторно WHERE name IN (?, ?, ?)), або не використовуючи збережені процедури та спробувати запобігти ін'єкції SQL за допомогою інших методів ...


4
Ви насправді можете, трохи попрацювавши, створити параметризований запит IN. Дивіться відповідь pst нижче. Отриманий запит параметризований та безпечний для ін'єкцій.
Ларрі Люстіг

@LarryLustig, яку відповідь ви посилаєтесь на відповідь "pst"? Видалено? Здається, це єдине виникнення тут…
Стефан

1
@StefanHaustein "відповідь pst " (користувача видалено).
user4157124

5

Як пропонується у прийнятій відповіді, але без використання спеціальної функції для створення розділених комами "?". Перевірте код нижче.

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

Насправді ви можете використовувати рідний спосіб запиту 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;
}

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

Я використовую Streamдля цього API:

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);
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.