2011-09-14 124 views
90

我試圖內的Android做下面的SQL查詢:IN子句和佔位符

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子句?

回答

176

的字符串表格"?, ?, ..., ?"可以是一個動態創建的字符串,並可以安全地放入原始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默認最大limit of host parameters是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(); 
    } 
} 
+7

是的,這是在SQLite和幾乎任何其他SQL數據庫中使用參數化IN()查詢的唯一方法。 –

+0

使用這種方法,我增加了我使用的ContentProvider,並在query()方法中添加了邏輯來測試存在:「IN?」如果找到了,則計算「?」的出現次數在原始選擇中,與傳遞的參數的長度相比,組裝「?,?,...?」爲了區別並取代原來的「IN?」與生成的問號集合。這使得邏輯幾乎可以用於全球,對我的使用來說,它似乎運行良好。我不得不添加一些特殊的配置來過濾空的IN列表,在這些情況下,「IN?」現在替換爲「1」。 – SandWyrm

+2

當然,這個愚蠢的事情是,如果你打算用N個問號來創建你自己的字符串,那麼你可能直接對數據進行編碼。假設它已經過消毒。 – Christopher

4

可悲的是,沒有辦法做到這一點(顯然'name1', 'name2'不是一個單一的值,因此不能在準備好的語句中使用)。

所以,你將不得不降低您的目光(例如,通過創建非常具體的,不能重複使用的查詢像WHERE name IN (?, ?, ?)),或不使用存儲過程,並試圖防止SQL注入與其他一些技術...

+4

實際上,你可以,用一點點工作,建立一個參數IN查詢。請參閱下面的pst的答案。生成的查詢是參數化的並且是注入安全的。 –

0

其實你可以使用查詢,而不是rawQuery的Android的原生方式:

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

您可以使用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()]) 
); 
+0

如果其中一個「參數」需要爲空? –

8

短例如,基於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

作爲公認的答案,但沒有使用自定義函數生成逗號分隔的「建議? 」。請檢查下面的代碼。

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

我面臨同樣的問題,我認爲接受的答案非常複雜。 我更喜歡如下因素:

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

創建名稱字符串可能會產生一些Sql錯誤,使用參數來避免它。 –

0

這不是有效的

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

在科特林你可以使用joinToString

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