2011-09-14 20 views
90

Estoy intentando hacer la siguiente consulta SQL dentro de Android:EN cláusula y marcadores de posición

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 de SQL:

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

¿Cómo puedo solucionar este problema y ser capaz de utilizar la cláusula IN?

Respuesta

176

Una cadena de el formulario "?, ?, ..., ?" 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.

Considérese una función que devuelve String makePlaceholders(int len)len cuestión de marcas separadas por comas, entonces:

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

Sólo asegúrese de pasar exactamente tantos valores como lugares. El máximo predeterminado limit of host parameters en SQLite es 999 - al menos en una versión normal, no estoy seguro acerca de Android :)

Happy coding.


Aquí es 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(); 
    } 
} 
+7

Sí, esta es la (única) forma de utilizar consultas parametrizadas IN() en SQLite y casi cualquier otra base de datos SQL. –

+0

Al usar este método, aumenté el ContentProvider que utilicé y en el método query() se agregó la lógica para probar la presencia: "IN?" y si se encuentra, ¿se cuenta la ocurrencia de "?" en la selección original, en comparación con la duración de los argumentos aprobados, ensambla un "?,?, ...?" por la diferencia y reemplaza el original "IN"? con la colección generada del signo de interrogación. Esto hace que la lógica esté disponible casi global y, para mi uso, parece estar funcionando bien. Tuve que agregar un aprovisionamiento especial para filtrar listas IN vacías, en esos casos, el "IN". es reemplazado por "1" por ahora. – SandWyrm

+2

Lo tonto de esto es, por supuesto, que si vas a hacer tus propios signos de interrogación String with N, también podrías codificar los datos directamente. Suponiendo que está desinfectado. – Christopher

4

Lamentablemente no hay forma de hacer que (obviamente 'name1', 'name2' no es un valor único y por lo tanto no puede ser utilizado en una declaración preparada).

Por lo que tendrá que reducir sus lugares (por ejemplo, mediante la creación de consultas, no reutilizables muy específicas como WHERE name IN (?, ?, ?)) o no uso de procedimientos almacenados y tratar de evitar que las inyecciones SQL con algunas otras técnicas ...

+4

En realidad se puede, con un poco de trabajo, construir una parametrizado en consulta. Ver la respuesta de pst, a continuación. La consulta resultante está parametrizada y es segura para inyección. –

0

En realidad, usted podría utilizar forma nativa de Android de la consulta en lugar de 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; 
} 
1

Puede utilizar TextUtils.join(",", parameters) para tomar ventaja de los parámetros de unión sqlite , donde parameters es una lista con "?" marcadores de posición y la cadena resultado es algo así como "?,?,..,?".

Aquí hay un pequeño ejemplo:

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

¿Qué pasa si uno de los "parámetros" deben ser nulos? –

8

ejemplo corto, basado en la respuesta de 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; 
} 
5

Como sugieren en respuesta aceptada pero sin utilizar función personalizada para generar separadas 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); 
-2

Me enfrenté al mismo problema y considero que la respuesta aceptada es realmente complicada. prefiero el Folowing:

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

creando la cadena de nombres puede producir algunos errores Sql, usando parámetros para evitarlo. –

0

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

En Kotlin puede utilizar joinToString

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