IN yan tümcesi ve yer tutucular


104

Android içinde aşağıdaki SQL sorgusunu yapmaya çalışıyorum:

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

Ancak Android, soru işaretini doğru değerlerle değiştirmez. Aşağıdakileri yapabilirim, ancak bu SQL yerleştirmeye karşı koruma sağlamaz:

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

Bu sorunu nasıl aşabilirim ve IN cümlesini nasıl kullanabilirim?

Yanıtlar:


188

Formun bir dizesi, "?, ?, ..., ?"dinamik olarak oluşturulmuş bir dizge olabilir ve orijinal SQL sorgusuna güvenli bir şekilde yerleştirilebilir (çünkü bu, harici verileri içermeyen kısıtlı bir formdur) ve ardından yer tutucular normal şekilde kullanılabilir.

Virgülle ayrılmış soru işaretlerini String makePlaceholders(int len)döndüren bir işlevi düşünün len, ardından:

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

Sadece yerler kadar çok değeri ilettiğinizden emin olun. SQLite'daki varsayılan maksimum ana bilgisayar parametresi sınırı 999'dur - en azından normal bir yapıda, Android hakkında emin değilim :)

Mutlu kodlamalar.


İşte bir uygulama:

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
Evet, SQLite'de ve hemen hemen tüm diğer SQL veritabanlarında parametreli IN () sorgularını kullanmanın (tek) yolu budur.
Larry Lustig

Bu yöntemi kullanarak, kullandığım ContentProvider'ı artırdım ve query () yönteminde varlığı test etmek için mantık ekledim: "IN?" ve eğer bulunursa, "?" orijinal seçimde, iletilen argümanların uzunluğu ile karşılaştırıldığında bir "?,?, ...?" fark için ve orijinal "IN?" oluşturulan soru işareti koleksiyonu ile. Bu, mantığı neredeyse küresel hale getiriyor ve kullanımlarım için iyi çalışıyor gibi görünüyor. Boş IN listelerini filtrelemek için bazı özel provizyon eklemem gerekiyordu, bu durumlarda, "IN?" şimdilik "1" ile değiştirilmiştir.
SandWyrm

3
Bununla ilgili aptalca şey, elbette, N soru işaretiyle kendi String'inizi oluşturacaksanız, verileri doğrudan kodlayabilirsiniz. Sterilize edildiğini varsayarsak.
Christopher

16
Bu bütün makePlaceholdersile değiştirilebilir TextUtils.join(",", Collections.nCopies(len, "?")). Daha az ayrıntılı.
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'ın cevabına göre kısa bir örnek:

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

Ne yazık ki bunu yapmanın bir yolu yok (belli ki 'name1', 'name2'tek bir değer değildir ve bu nedenle hazırlanmış bir ifadede kullanılamaz).

Bu nedenle, (örneğin çok özel, yeniden kullanılamaz sorgular oluşturarak WHERE name IN (?, ?, ?)) veya saklanan prosedürleri kullanmadan ve diğer bazı tekniklerle SQL enjeksiyonlarını engellemeye çalışmanız gerekecek ...


4
Aslında küçük bir çalışma ile parametreleştirilmiş bir IN sorgusu oluşturabilirsiniz. Aşağıda pst'nin cevabına bakın. Sonuçta elde edilen sorgu parametreleştirilmiştir ve enjeksiyon güvenlidir.
Larry Lustig

@LarryLustig "pst" cevabı ile hangi cevaba atıfta bulunuyorsunuz? Silindi mi? Bu, burada pst'nin tek oluşumu gibi görünüyor ...
Stefan Haustein

1
@StefanHaustein "pst'nin cevabı " (kullanıcı kaldırıldı).
user4157124

5

Kabul edilen yanıtta önerildiği gibi, ancak virgülle ayrılmış '?' Oluşturmak için özel işlev kullanmadan. Lütfen aşağıdaki kodu kontrol edin.

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 bağlama parametrelerinden yararlanmak için kullanabilirsiniz , burada yer tutucular parametersiçeren bir liste "?"ve sonuç dizesi buna benzer "?,?,..,?".

İşte küçük bir örnek:

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

Ya "parametrelerden" birinin boş olması gerekiyorsa?
android geliştiricisi

0

Aslında, rawQuery yerine android'in yerel sorgulama yöntemini kullanabilirsiniz:

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

Bu geçerli değil

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

Bu geçerlidir

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'ı kullanma

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'yi bunun için kullanıyorum :

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);
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.