IN子句和占位符

Nic*_*ick 99 sqlite android

我正在尝试在Android中执行以下SQL查询:

    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});
Run Code Online (Sandbox Code Playgroud)

但是,Android不会使用正确的值替换问号.我可以执行以下操作,但是,这不能防止SQL注入:

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

如何解决此问题并能够使用IN子句?

小智 183

表单的字符串"?, ?, ..., ?"可以是动态创建的字符串,并安全地放入原始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);
Run Code Online (Sandbox Code Playgroud)

只需确保传递与地点一样多的值.SQLite 中主机参数的默认最大限制是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();
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 整个`makePlaceholders`可以替换为`TextUtils.join(",",Collections.nCopies(len,"?"))`.不那么冗长. (14认同)
  • 是的,这是在SQLite和几乎任何其他SQL数据库中使用参数化IN()查询的(唯一)方法. (7认同)
  • 当然,关于这一点的愚蠢之处在于,如果您要使用N个问号制作自己的String,那么您也可以直接对数据进行编码.假设它被消毒了. (3认同)

Yul*_*mok 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;
}
Run Code Online (Sandbox Code Playgroud)


flo*_*n h 5

可悲的是,没有办法做到这一点(显然'name1', 'name2'这不是一个单一的值,因此不能在准备好的语句中使用)。

因此,您将不得不放低视线(例如,通过创建非常具体的,不可重用的查询,例如WHERE name IN (?, ?, ?))或不使用存储过程,并尝试使用其他一些技术来防止SQL注入...

  • 实际上,您只需做一些工作即可构建一个参数化的IN查询。请参阅下面的pst答案。结果查询已参数化且注入安全。 (4认同)

Kal*_*hel 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);
Run Code Online (Sandbox Code Playgroud)