Mệnh đề IN và phần giữ chỗ


104

Tôi đang cố gắng thực hiện truy vấn SQL sau trong Android:

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

Tuy nhiên, Android không thay thế dấu hỏi bằng các giá trị chính xác. Tôi có thể làm như sau, tuy nhiên, điều này không bảo vệ khỏi việc đưa vào SQL:

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

Làm cách nào để giải quyết vấn đề này và có thể sử dụng mệnh đề IN?

Câu trả lời:


188

Một chuỗi của biểu mẫu "?, ?, ..., ?"có thể là một chuỗi được tạo động và được đưa vào truy vấn SQL gốc một cách an toàn (vì nó là một biểu mẫu bị hạn chế không chứa dữ liệu bên ngoài) và sau đó trình giữ chỗ có thể được sử dụng như bình thường.

Hãy xem xét một hàm String makePlaceholders(int len)trả về lendấu chấm hỏi được phân tách bằng dấu phẩy, sau đó:

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

Chỉ cần đảm bảo chuyển chính xác nhiều giá trị như các địa điểm. Giới hạn tối đa mặc định của tham số máy chủ lưu trữ trong SQLite là 999 - ít nhất là trong một bản dựng bình thường, không chắc chắn về Android :)

Chúc bạn viết mã vui vẻ.


Đây là một cách triển khai:

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
Có, đây là cách (duy nhất) để sử dụng truy vấn IN () được tham số hóa trong SQLite và khá nhiều bất kỳ cơ sở dữ liệu SQL nào khác.
Larry Lustig

Sử dụng phương pháp này, tôi đã tăng cường ContentProvider mà tôi đã sử dụng và trong phương thức query () đã thêm logic để kiểm tra sự hiện diện: "IN?" và nếu được tìm thấy, có đếm số lần xuất hiện của "?" trong lựa chọn ban đầu, so với độ dài của các đối số đã truyền, tập hợp một "?,?, ...?" cho sự khác biệt và thay thế "IN?" ban đầu với bộ sưu tập dấu hỏi đã tạo. Điều này làm cho logic có sẵn gần như toàn cầu và đối với mục đích sử dụng của tôi, nó dường như hoạt động tốt. Tôi đã phải thêm một số cấp phép đặc biệt để lọc danh sách IN trống, trong những trường hợp đó, "IN?" hiện được thay thế bằng "1".
SandWyrm

3
Tất nhiên, điều ngớ ngẩn về điều này là nếu bạn định tạo Chuỗi của riêng mình với N dấu hỏi, bạn cũng có thể mã hóa dữ liệu trực tiếp. Giả sử nó đã được khử trùng.
Christopher

16
Toàn bộ này makePlaceholderscó thể được thay thế bằng TextUtils.join(",", Collections.nCopies(len, "?")). Ít dài dòng hơn.
Konrad Morawski

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

9

Ví dụ ngắn gọn, dựa trên câu trả lời của 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

Đáng buồn thay, không có cách nào để làm điều đó (rõ ràng 'name1', 'name2'không phải là một giá trị duy nhất và do đó không thể được sử dụng trong một câu lệnh đã chuẩn bị).

Vì vậy, bạn sẽ phải hạ thấp tầm nhìn của mình (ví dụ: bằng cách tạo các truy vấn rất cụ thể, không thể tái sử dụng như WHERE name IN (?, ?, ?)) hoặc không sử dụng các thủ tục được lưu trữ và cố gắng ngăn chặn việc tiêm SQL bằng một số kỹ thuật khác ...


4
Bạn thực sự có thể, với một chút công việc, xây dựng một truy vấn IN được tham số hóa. Xem câu trả lời của pst, bên dưới. Truy vấn kết quả được tham số hóa và tiêm an toàn.
Larry Lustig,

@LarryLustig bạn đang đề cập đến câu trả lời nào với câu trả lời của "pst"? Nó đã bị xóa? Đây dường như là sự xuất hiện duy nhất của pst đây ...
Stefan Haustein

1
@StefanHaustein " câu trả lời của pst " (người dùng đã xóa).
user4157124 Ngày

5

Như đề xuất trong câu trả lời được chấp nhận nhưng không sử dụng hàm tùy chỉnh để tạo '?' Được phân tách bằng dấu phẩy. Vui lòng kiểm tra mã bên dưới.

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

Bạn có thể sử dụng TextUtils.join(",", parameters)để tận dụng các tham số liên kết sqlite, đâu parameterslà danh sách có "?"trình giữ chỗ và chuỗi kết quả giống như vậy "?,?,..,?".

Đây là một ví dụ nhỏ:

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

Điều gì xảy ra nếu một trong các "tham số" cần phải là giá trị rỗng?
nhà phát triển Android

0

Trên thực tế, bạn có thể sử dụng cách truy vấn gốc của android thay vì 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

Điều này không hợp lệ

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

Điều này hợp lệ

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

Sử dụng 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

Trong Kotlin, bạn có thể sử dụng joinToString

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

0

Tôi sử dụng StreamAPI cho việc này:

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);
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.