ในประโยคและตัวยึดตำแหน่ง


104

ฉันพยายามทำแบบสอบถาม SQL ต่อไปนี้ใน 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});

อย่างไรก็ตาม 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
ใช่นี่เป็นวิธี (เท่านั้น) ในการใช้แบบสอบถาม IN () ที่กำหนดพารามิเตอร์ใน SQLite และฐานข้อมูล SQL อื่น ๆ
Larry Lustig

เมื่อใช้วิธีนี้ฉันได้เพิ่ม ContentProvider ที่ฉันใช้และในเมธอด query () ได้เพิ่มตรรกะเพื่อทดสอบการมีอยู่: "IN?" และหากพบจะนับการเกิด "?" ในการเลือกเดิมเมื่อเทียบกับความยาวของอาร์กิวเมนต์ที่ผ่านไปจะประกอบ "?,?, ... ?" สำหรับความแตกต่างและแทนที่ "IN?" เดิม ด้วยการรวบรวมเครื่องหมายคำถามที่สร้างขึ้น สิ่งนี้ทำให้ตรรกะใช้งานได้เกือบทั่วโลกและสำหรับการใช้งานของฉันดูเหมือนว่าจะทำงานได้ดี ฉันต้องเพิ่มการจัดเตรียมพิเศษบางอย่างเพื่อกรองรายการ IN ที่ว่างเปล่าในกรณีเหล่านี้คือ "IN?" จะถูกแทนที่ด้วย "1" ในตอนนี้
SandWyrm

3
สิ่งที่โง่เกี่ยวกับเรื่องนี้คือแน่นอนว่าถ้าคุณจะสร้าง String ของคุณเองด้วยเครื่องหมายคำถาม N คุณอาจเข้ารหัสข้อมูลโดยตรงก็ได้เช่นกัน สมมติว่าได้รับการฆ่าเชื้อแล้ว
Christopher

16
ทั้งหมดนี้makePlaceholdersสามารถแทนที่ด้วยTextUtils.join(",", Collections.nCopies(len, "?")). verbose น้อยลง
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

ตัวอย่างสั้น ๆ ตามคำตอบของ 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

@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

ฉันใช้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.