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句を使用するにはどうすればよいですか?

回答:


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については不明です:)

ハッピーコーディング。


ここに1つの実装があります:

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()クエリを使用する(唯一の)方法です。
Larry Lustig、2011

このメソッドを使用して、使用したContentProviderを拡張し、query()メソッドにロジックを追加して、存在をテストします: "IN?" 見つかった場合、「?」の出現回数はカウントされますか?元の選択では、渡された引数の長さと比較して、「?、?、...?」をアセンブルします 違いのために、元の「IN?」を置き換えます 生成された疑問符コレクション。これにより、ロジックがほぼグローバルに利用可能になり、私の用途ではそれはうまく機能しているようです。空のINリストをフィルタリングするために、特別なプロビジョニングを追加する必要がありました。その場合、「IN?」今のところ「1」に置き換えられます。
SandWyrm 2013

3
もちろん、これについてばかげていることは、N個の疑問符を使用して独自の文字列を作成する場合は、データを直接エンコードすることです。それが消毒されていると仮定します。
クリストファー

16
この全体makePlaceholdersをで置き換えることができますTextUtils.join(",", Collections.nCopies(len, "?"))。冗長ではない。
Konrad Morawski、2015

1
Caused by: android.database.sqlite.SQLiteException: near ",": syntax error (code 1): , while compiling: SELECT url FROM tasks WHERE url=?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?
Iman Marashi

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の回答を参照してください。結果のクエリはパラメーター化され、注入セーフです。
Larry Lustig、2011

@LarryLustig "pst's"の回答でどの回答を参照していますか?削除されましたか?これがここでのpstの唯一の発生であると思われます...
Stefan Haustein

1
@StefanHaustein " pst's answer "(ユーザーは削除しました)。
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()])
);

「パラメータ」の1つをnullにする必要がある場合はどうなりますか?
Android開発者

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

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

私はこれにStreamAPI を使用します:

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);
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.