IN-Klausel und Platzhalter

104

Ich versuche, die folgende SQL-Abfrage in Android durchzufü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 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?

Nick
quelle

Antworten:

188

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). Anschließend können die Platzhalter wie gewohnt verwendet werden.

Stellen Sie sich eine Funktion vor, String makePlaceholders(int len)die lendurch Kommas getrennte Fragezeichen zurückgibt. 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 einfach sicher, dass Sie genau so viele Werte wie Orte übergeben. Die standardmäßige maximale Anzahl von Hostparametern in SQLite beträgt 999 - zumindest in einem normalen Build, bei Android nicht sicher :)

Viel Spaß beim Codieren.


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

quelle
8
Ja, dies ist die (einzige) Möglichkeit, parametrisierte IN () - Abfragen in SQLite und so ziemlich jeder anderen SQL-Datenbank zu verwenden.
Larry Lustig
Mit dieser Methode habe ich den von mir verwendeten ContentProvider erweitert und in der query () -Methode eine Logik hinzugefügt, um das Vorhandensein zu testen: "IN?" und wenn gefunden, zählt das Auftreten von "?" In der ursprünglichen Auswahl wird im Vergleich zur Länge der übergebenen Argumente ein "?,?, ...?" für den Unterschied und ersetzt das Original "IN?" mit der generierten Fragezeichen-Sammlung. Dies macht die Logik fast global verfügbar und für meine Zwecke scheint sie gut zu funktionieren. Ich musste eine spezielle Bereitstellung hinzufügen, um leere IN-Listen zu filtern. In diesen Fällen wurde "IN?" wird vorerst durch "1" ersetzt.
SandWyrm
3
Das Dumme daran ist natürlich, dass Sie die Daten genauso gut direkt codieren können, wenn Sie einen eigenen String mit N Fragezeichen erstellen. Vorausgesetzt, es ist saniert.
Christopher
16
Dieses Ganze makePlaceholderskönnte durch ersetzt werden TextUtils.join(",", Collections.nCopies(len, "?")). Weniger ausführlich.
Konrad Morawski
1
Caused by: android.database.sqlite.SQLiteException: near ",": syntax error (code 1): , while compiling: SELECT url FROM tasks WHERE url=?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?
Iman Marashi
9

Kurzes Beispiel, basierend auf der Antwort von 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;
}
Yuliia Ashomok
quelle
5

Leider gibt es keine Möglichkeit, dies zu tun (offensichtlich 'name1', 'name2'ist es kein einzelner Wert und kann daher nicht in einer vorbereiteten Aussage verwendet werden).

Sie müssen also Ihre Sicht verringern (z. B. indem Sie sehr spezifische, nicht wiederverwendbare Abfragen erstellen WHERE name IN (?, ?, ?)) oder keine gespeicherten Prozeduren verwenden und versuchen, SQL-Injektionen mit einigen anderen Techniken zu verhindern ...

florian h
quelle
4
Mit ein wenig Arbeit können Sie tatsächlich eine parametrisierte IN-Abfrage erstellen. Siehe die Antwort von pst unten. Die resultierende Abfrage ist parametrisiert und injektionssicher.
Larry Lustig
@ LarryLustig auf welche Antwort beziehen Sie sich mit "pst's" Antwort? Wurde es gelöscht? Dies scheint das einzige Vorkommen von PST hier zu sein ...
Stefan Haustein
1
@StefanHaustein " pst's answer " (Benutzer entfernt).
user4157124
5

Wie in der akzeptierten Antwort vorgeschlagen, jedoch ohne Verwendung einer benutzerdefinierten Funktion zum Generieren eines durch Kommas getrennten '?'. 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);
Kalpesh Gohel
quelle
1

Sie können TextUtils.join(",", parameters)SQLite-Bindungsparameter nutzen, wobei parameterseine Liste mit "?"Platzhaltern und die Ergebniszeichenfolge so etwas wie ist "?,?,..,?".

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()])
);
epool
quelle
Was ist, wenn einer der "Parameter" null sein muss?
Android-Entwickler
0

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;
}
Stan
quelle
0

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

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

Verwenden von 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);
Vahe Gharibyan
quelle
0

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())
Aleksey Kornienko
quelle
0

Ich benutze die StreamAPI dafür:

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