web-gelistirme-sc.com

Android/SQLite: Tanımlayıcıyı tutmak için Insert-Update tablo sütunları

Şu anda, bir Android cihazdaki bir SQLite veritabanında tablo oluşturmak için aşağıdaki ifadeyi kullanıyorum.

CREATE TABLE IF NOT EXISTS 'locations' (
  '_id' INTEGER PRIMARY KEY AUTOINCREMENT, 'name' TEXT, 
  'latitude' REAL, 'longitude' REAL, 
  UNIQUE ( 'latitude',  'longitude' ) 
ON CONFLICT REPLACE );

Çelişki cümlesi, satırların droppped 'nin aynı koordinatlara sahip yeni kesici uçlar yapıldığında olmasına neden olur. SQLite dokümantasyonu / ihtilaf cümlesi hakkında daha fazla bilgi içermektedir.

Bunun yerine, keep önceki satırları ve sadece update sütünlarını istiyorum. Bunu Android/SQLite ortamında yapmanın en etkili yolu nedir?

  • CREATE TABLE ifadesinde bir çatışma maddesi olarak.
  • INSERT tetikleyicisi olarak.
  • ContentProvider#insert yönteminde koşullu bir madde olarak.
  • ... aklınıza gelebilecek daha iyi

Veritabanındaki bu tür çatışmaların üstesinden gelmenin daha etkili olduğunu düşünüyorum. Ayrıca, insert-update senaryosunu değerlendirmek için ContentProvider#insert yöntemini yeniden yazmakta zorlanıyorum. İşte insert yönteminin kodu:

public Uri insert(Uri uri, ContentValues values) {
    final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    long id = db.insert(DatabaseProperties.TABLE_NAME, null, values);
    return ContentUris.withAppendedId(uri, id);
}

Veri arka uçtan ulaştığında, tek yaptığım verileri aşağıdaki gibi eklemek.

getContentResolver.insert(CustomContract.Locations.CONTENT_URI, contentValues);

Burada ContentProvider#update 'a alternatif bir çağrının nasıl uygulanacağını bulmakta zorlanıyorum. Ek olarak, bu zaten benim tercih ettiğim çözüm değil.


Düzenle:

@CommonsWare: INSERT OR REPLACE kullanmak için önerinizi uygulamaya çalıştım. Bu çirkin kod parçası ile geldim.

private static long insertOrReplace(SQLiteDatabase db, ContentValues values, String tableName) {
    final String COMMA_SPACE = ", ";
    StringBuilder columnsBuilder = new StringBuilder();
    StringBuilder placeholdersBuilder = new StringBuilder();
    List<Object> pureValues = new ArrayList<Object>(values.size());
    Iterator<Entry<String, Object>> iterator = values.valueSet().iterator();
    while (iterator.hasNext()) {
        Entry<String, Object> pair = iterator.next();
        String column = pair.getKey();
        columnsBuilder.append(column).append(COMMA_SPACE);
        placeholdersBuilder.append("?").append(COMMA_SPACE);
        Object value = pair.getValue();
        pureValues.add(value);
    }
    final String columns = columnsBuilder.substring(0, columnsBuilder.length() - COMMA_SPACE.length());
    final String placeholders = placeholderBuilder.substring(0, placeholdersBuilder.length() - COMMA_SPACE.length());
    db.execSQL("INSERT OR REPLACE INTO " + tableName + "(" + columns + ") VALUES (" + placeholders + ")", pureValues.toArray());

    // The last insert id retrieved here is not safe. Some other inserts can happen inbetween.
    Cursor cursor = db.rawQuery("SELECT * from SQLITE_SEQUENCE;", null);
    long lastId = INVALID_LAST_ID;
    if (cursor != null && cursor.getCount() > 0 && cursor.moveToFirst()) {
        lastId = cursor.getLong(cursor.getColumnIndex("seq"));
    }
    cursor.close();
    return lastId;
}

Ancak, SQLite veritabanını kontrol ettiğimde eşit sütunlar hala kaldırılıyor ve yeni idslerle ekleniyor . Bunun neden olduğunu anlamıyorum ve sebebin benim çatışma maddesim olduğunu düşündüm. Ancak belgeler bunun tam tersini belirtir.

Bir INSERT veya UPDATE öğesinin OR yan tümcesinde belirtilen algoritma geçersiz kılınır CREATE TABLE'da belirtilen herhangi bir algoritma. Herhangi bir algoritma Hiçbir yerde belirtilmezse, ABORT algoritması kullanılır.

Bu girişimin bir diğer dezavantajı, insert'in ifadesiyle döndürülen kimliğin değerini kaybetmenizdir. Bunu telafi etmek için nihayet last_insert_rowid istemek için bir seçenek buldum. Açıklandığı gibi dtmilano ve swiz . Ancak, aralarında başka bir uç olabileceğinden, bunun güvenli olup olmadığından emin değilim.

25
JJD

SQL'deki tüm bu mantığı yapmanın performans için en iyi olduğu fikrini anlayabiliyorum, ama belki de en basit (en az kod) çözüm bu durumda en iyisidir? Neden önce güncellemeyi denemiyorsunuz, sonra eklemek için (gerekirse) ve ihtiyacınız olan satır kimliğini almak için insertWithOnConflict() işlevini CONFLICT_IGNORE ile kullanın:

public Uri insert(Uri uri, ContentValues values) {
    final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    String selection = "latitude=? AND longitude=?"; 
    String[] selectionArgs = new String[] {values.getAsString("latitude"),
                values.getAsString("longitude")};

    //Do an update if the constraints match
    db.update(DatabaseProperties.TABLE_NAME, values, selection, null);

    //This will return the id of the newly inserted row if no conflict
    //It will also return the offending row without modifying it if in conflict
    long id = db.insertWithOnConflict(DatabaseProperties.TABLE_NAME, null, values, CONFLICT_IGNORE);        

    return ContentUris.withAppendedId(uri, id);
}

Daha basit bir çözüm, update()'nin dönüş değerini kontrol etmek ve sadece eğer etkilenen sayım sıfırsa, eki yapmak olacaktır, ancak daha sonra, mevcut satırın kimliğini ek bir seçim yapmadan elde edemediğiniz bir durum olacaktır. Bu ekleme biçimi size her zaman Uriiçinde geri dönmek için doğru kimliği döndürür ve veritabanını gerekenden daha fazla değiştirmez.

Bunları bir kerede çok sayıda yapmak istiyorsanız, sağlayıcınızdaki bulkInsert() yöntemine bakabilirsiniz; burada, tek bir işlem içinde birden fazla ek çalıştırabilirsiniz. Bu durumda, güncellenmiş kaydın idname__'sini döndürmeniz gerekmediğinden, "basit" çözümün düzgün çalışması gerekir:

public int bulkInsert(Uri uri, ContentValues[] values) {
    final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    String selection = "latitude=? AND longitude=?";
    String[] selectionArgs = null;

    int rowsAdded = 0;
    long rowId;
    db.beginTransaction();
    try {
        for (ContentValues cv : values) {
            selectionArgs = new String[] {cv.getAsString("latitude"),
                cv.getAsString("longitude")};

            int affected = db.update(DatabaseProperties.TABLE_NAME, 
                cv, selection, selectionArgs);
            if (affected == 0) {
                rowId = db.insert(DatabaseProperties.TABLE_NAME, null, cv);
                if (rowId > 0) rowsAdded++;
            }
        }
        db.setTransactionSuccessful();
    } catch (SQLException ex) {
        Log.w(TAG, ex);
    } finally {
        db.endTransaction();
    }

    return rowsAdded;
}

Gerçekte, işlem kodu veritabanı belleğinin dosyaya yazılma sayısını en aza indirerek işleri daha hızlı yapan şeydir, bulkInsert() sadece birden fazla ContentValues'nin sağlayıcıya yapılan tek bir çağrı ile iletilmesine izin verir.

19
Devunwired

Çözümlerden biri, görünümde INSTEAD OF tetikleyici bulunan locations tablosu için bir görünüm oluşturmak ve görünüme eklemek. İşte bunun neye benzeyeceği:

Görünüm:

CREATE VIEW locations_view AS SELECT * FROM locations;

Tetik:

CREATE TRIGGER update_location INSTEAD OF INSERT ON locations_view FOR EACH ROW 
  BEGIN 
    INSERT OR REPLACE INTO locations (_id, name, latitude, longitude) VALUES ( 
       COALESCE(NEW._id, 
         (SELECT _id FROM locations WHERE latitude = NEW.latitude AND longitude = NEW.longitude)),
       NEW.name, 
       NEW.latitude, 
       NEW.longitude
    );
  END;

locations tablosuna eklemek yerine, locations_view görünümüne eklersiniz. Tetikleyici alt seçimi kullanarak doğru _id değerini sağlamaya özen gösterir. Bir nedenden dolayı, ek zaten bir _id içeriyorsa, COALESCE onu koruyacak ve tablodaki mevcut olanı geçersiz kılacaktır.

Muhtemelen alt seçimin performansı ne kadar etkilediğini kontrol etmek isteyebilir ve bunu diğer olası değişikliklerle karşılaştırabilirsiniz, ancak bu mantığı kodunuzdan uzak tutmanıza izin verir.

INSERT OR IGNORE'a dayalı olarak masada kendi kendine tetikleyiciler içeren başka çözümler de denedim, ancak ÖDEME SONRASI ve SONRASI sadece gerçekte tabloya eklenecekse tetiklenir.

Tetikleyicinin temeli olan bu cevap yararlı olabilir.

Düzenleme: BORE ÖNCE ve AFTER, bir kesici uç dikkate alınmadığında tetiklemediğinden tetikler (bunun yerine güncellenebilir), ek parçayı bir INSTEAD OF tetikleyici ile yeniden yazmamız gerekir. Ne yazık ki, bu tablolarla çalışmaz - kullanmak için bir görünüm oluşturmak zorundayız.

5
blazeroni

INSERT OR REPLACE, ON CONFLICT REPLACE gibi çalışır. Benzersiz sütunu olan satır zaten varsa ve bir ekleme yaparsa satırı siler. Asla güncelleme yapmaz.

Şu anki çözümünüze bağlı kalmanızı tavsiye ederim, ON CONFLICT clausule ile tablo oluşturursunuz, ancak bir satır eklediğinizde ve sınırlama ihlali gerçekleştiğinde, yeni satırınız Origin satırı silineceği için yeni _id olacaktır.

Veya ON CONFLICT cümlesi olmadan tablo oluşturabilir ve INSERT OR REPLACE kullanabilirsiniz, bunun için insertWithOnConflict () metodunu kullanabilirsiniz, ancak API seviyesi 8'den daha fazla kodlama gerektirdiğinden ve ON CONFLICT cümlesiyle aynı çözüme yol açtığından beri kullanılabilir.

Kökeni satırınızı hala korumak istiyorsanız, aynı _id işlevini korumak istediğiniz anlamına gelir, ilk önce bir satır eklemek için iki sorgu yapmak zorundasınız, ikincisi ekleme başarısız olursa bir satır güncellemek için (veya tersi). Tutarlılığı korumak için bir işlemde sorguları yürütmeniz gerekir.

    db.beginTransaction();
    try {
        long rowId = db.insert(table, null, values);
        if (rowId == -1) {
            // insertion failed
            String whereClause = "latitude=? AND longitude=?"; 
            String[] whereArgs = new String[] {values.getAsString("latitude"),
                    values.getAsString("longitude")};
            db.update(table, values, whereClause, whereArgs);
            // now you have to get rowId so you can return correct Uri from insert()
            // method of your content provider, so another db.query() is required
        }
        db.setTransactionSuccessful();
    } finally {
        db.endTransaction();
    }
3
biegleux

insertWithOnConflict öğesini kullanın ve son parametreyi (conflictAlgorithm) CONFLICT_REPLACE olarak ayarlayın.

Aşağıdaki bağlantılarda devamını okuyun:

insertWithOnConflict belgeleri

CONFLICT_REPLACE bayrağı

0
Muzikant