Ich versuche, die folgende SQL-Abfrage in Android auszuführen:
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 ersetzt das Fragezeichen jedoch nicht durch die richtigen Werte. Ich könnte Folgendes tun, dies schützt jedoch nicht vor der SQL-Injection:
String query = "SELECT * FROM table WHERE name IN (" + names + ")";
Cursor cursor = mDb.rawQuery(query, null);
Wie kann ich dieses Problem umgehen und die IN-Klausel verwenden?
Eine Zeichenfolge des Formulars "?, ?, ..., ?"
kann eine dynamisch erstellte Zeichenfolge sein und sicher in die ursprüngliche SQL-Abfrage eingefügt werden (da es sich um ein eingeschränktes Formular handelt, das keine externen Daten enthält) und die Platzhalter dann normal verwendet werden können.
Betrachten Sie eine Funktion String makePlaceholders(int len)
, die len
-Fragezeichen mit Kommas getrennt zurückgibt, und dann:
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);
Stellen Sie sicher, dass Sie genau so viele Werte wie Orte übergeben. Das Standardlimit Limit der Host-Parameter in SQLite beträgt 999 - zumindest in einem normalen Build, nicht sicher über Android :)
Glückliche Kodierung.
Hier ist eine Implementierung:
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();
}
}
Kurzes Beispiel, basierend auf Antwort von Benutzer166390:
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;
}
Wie in akzeptierter Antwort, aber ohne Verwendung einer benutzerdefinierten Funktion zum Generieren durch Kommas getrenntes "?". Bitte überprüfen Sie den Code unten.
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);
Leider gibt es keine Möglichkeit, dies zu tun (offensichtlich ist 'name1', 'name2'
kein einzelner Wert und kann daher nicht in einer vorbereiteten Anweisung verwendet werden).
Sie müssen also Ihre Sichtweise verringern (z. B. indem Sie sehr spezifische, nicht wiederverwendbare Abfragen wie WHERE name IN (?, ?, ?)
erstellen) oder gespeicherte Prozeduren nicht verwenden und versuchen, SQL-Injektionen mit anderen Techniken zu verhindern ...
Sie können TextUtils.join(",", parameters)
verwenden, um die sqlite-Bindungsparameter zu nutzen, wobei parameters
eine Liste mit "?"
-Platzhaltern ist und die Ergebniszeichenfolge etwa "?,?,..,?"
lautet.
Hier ist ein kleines Beispiel:
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()])
);
Ich verwende dazu die 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);
In Kotlin können Sie joinToString verwenden.
val query = "SELECT * FROM table WHERE name IN (${names.joinToString(separator = ",") { "?" }})"
val cursor = mDb.rawQuery(query, names.toTypedArray())
Tatsächlich könnten Sie anstelle von rawQuery die native Abfragemethode von Android verwenden:
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;
}
Das ist nicht gültig
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}););
Das ist gültig
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 verwenden
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);