IN子句和占位符


104

我正在尝试在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});

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

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

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

Answers:


188

形式的字符串"?, ?, ..., ?"可以是动态创建的字符串,并可以安全地放入原始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 中主机参数的默认最大限制为 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();
    }
}

8
是的,这是在SQLite和几乎所有其他SQL数据库中使用参数化IN()查询的(唯一)方法。
拉里·卢斯蒂格

使用此方法,我扩充了我使用的ContentProvider,并在query()方法中添加了逻辑来测试是否存在:“ IN?” 如果找到,是否计算“?”的出现 在原始选择中,与传递的参数长度相比,汇编一个“?,?,...?” 并替换原始的“ IN?” 与生成的问号集合。这使得逻辑几乎可以全局使用,并且对于我来说,它似乎运行良好。我确实必须添加一些特殊的配置来过滤空的IN列表,在这种情况下,是“ IN?” 现在被替换为“ 1”。
SandWyrm

3
当然,这很愚蠢,如果您要使用N个问号创建自己的String,那么最好直接对数据进行编码。假设已经过消毒。
Christopher

16
这整个makePlaceholders可以替换为TextUtils.join(",", Collections.nCopies(len, "?"))。不太冗长。
Konrad Morawski

1
Caused by: android.database.sqlite.SQLiteException: near ",": syntax error (code 1): , while compiling: SELECT url FROM tasks WHERE url=?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?
伊曼·马拉希

9

简短示例,基于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

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

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


4
实际上,您只需做一些工作即可构建一个参数化的IN查询。请参阅下面的pst答案。结果查询已参数化且注入安全。
拉里·卢斯蒂格

@LarryLustig您用“ pst's”答案指的是哪个答案?它被删除了吗?这似乎是这里唯一的pst发生...
Stefan Haustein,

1
@StefanHaustein“ pst的答案 ”(已删除用户)。
user4157124

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

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

如果“参数”之一需要为空,该怎么办?
android开发人员

0

实际上,您可以使用android的本机查询方式来代替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;
}

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

Stream为此使用API:

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);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.