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에 대해 확실하지 않습니다. :)

즐거운 코딩입니다.


다음은 한 가지 구현입니다.

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 개의 물음표가있는 자신 만의 문자열을 만들려는 경우 데이터를 직접 인코딩하는 것이 좋습니다. 위생 처리되었다고 가정합니다.
Christopher

16
이 전체 makePlaceholdersTextUtils.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의"대답으로 어떤 대답을 언급하고 있습니까? 삭제 되었습니까? 이 ... 여기에 태평양 표준시의 유일한 선두로부터 것 같다
스테판 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()])
);

"매개 변수"중 하나가 null이어야하는 경우 어떻게합니까?
안드로이드 개발자

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

Kotlin에서는 joinToString 을 사용할 수 있습니다 .

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

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.