В предложении и заполнителях
Я пытаюсь сделать следующий 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?
10 ответов
строка вида "?, ?, ..., ?"
может быть динамически созданной строкой и безопасно помещаться в исходный 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();
}
}
короткий пример, основанный на ответе 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;
}
как предлагают в принятом ответе, но без использования пользовательской функции для создания разделенных запятыми '?'. Пожалуйста, проверьте код ниже.
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);
к сожалению, нет никакого способа сделать это (очевидно 'name1', 'name2'
не является единственным значением и поэтому не может использоваться в подготовленном операторе).
поэтому вам придется снизить прицел (например, путем создания очень конкретных, а не многоразовых запросов, таких как WHERE name IN (?, ?, ?)
) или не использовать хранимые процедуры и попытаться предотвратить SQL-инъекции с помощью некоторых других методов...
можно использовать 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()])
);
на самом деле вы можете использовать собственный способ запроса 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;
}
Это недопустимо
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);
в Котлине вы можете использовать joinToString
val query = "SELECT * FROM table WHERE name IN (${names.joinToString(separator = ",") { "?" }})"
val cursor = mDb.rawQuery(query, names.toTypedArray())
Я использую 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);
я столкнулся с той же проблемой и я считаю, что принятый ответ действительно сложно. Я предпочитаю следующее:
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);