Cláusula IN y marcadores de posición

Estoy intentando hacer la siguiente consulta SQL dentro de 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}); 

Sin embargo, Android no reemplaza el signo de interrogación con los valores correctos. Podría hacer lo siguiente, sin embargo, esto no protege contra la inyección SQL:

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

¿Cómo puedo evitar este problema y poder usar la cláusula IN?

Una cadena de la forma "?, ?, ..., ?" puede ser una cadena creada dinámicamente y colocada de forma segura en la consulta SQL original (porque es una forma restringida que no contiene datos externos) y luego los marcadores de posición se pueden usar de forma normal.

Considere una función String makePlaceholders(int len) que devuelve signos de interrogación len separados por comas, luego:

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

Solo asegúrate de pasar exactamente tantos valores como lugares. El límite máximo predeterminado de parámetros de host en SQLite es 999, al menos en una comstackción normal, no estoy seguro acerca de Android 🙂

Feliz encoding.


Aquí hay una implementación:

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

Ejemplo corto, basado en la respuesta del usuario166390:

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

Como sugerir en la respuesta aceptada pero sin usar la función personalizada para generar ‘?’ Separados por comas. Por favor, consulte el código a continuación.

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

Lamentablemente, no hay forma de hacerlo (obviamente 'name1', 'name2' no es un valor único y, por lo tanto, no se puede usar en una statement preparada).

Por lo tanto, tendrá que bajar la mira (por ejemplo, creando consultas muy específicas, no reutilizables, como WHERE name IN (?, ?, ?) ) O no utilizando procedimientos almacenados e intentando evitar las inyecciones SQL con algunas otras técnicas …

Puede usar TextUtils.join(",", parameters) para aprovechar los parámetros de enlace sqlite, donde los parameters son una lista con "?" marcadores de posición y la cadena de resultados es algo así como "?,?,..,?" .

Aquí hay un pequeño ejemplo:

 Set positionsSet = membersListCursorAdapter.getCurrentCheckedPosition(); List ids = new ArrayList<>(); List 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()]) ); 

De hecho, podrías usar la forma nativa de consultar de android en lugar de rawQuery:

 public int updateContactsByServerIds(ArrayList 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; } 

Esto no es Válido

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

Esto es Válido

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

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

En Kotlin puedes usar joinToString

 val query = "SELECT * FROM table WHERE name IN (${names.joinToString(separator = ",") { "?" }})" val cursor = mDb.rawQuery(query, names.toTypedArray()) 

Yo uso Stream API para esto:

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

Enfrenté el mismo problema y considero que la respuesta aceptada es realmente complicada. Prefiero el siguiente:

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