2012-11-15 14 views
22

Mam tabeli SQLite:Dlaczego InsertWithOnConflict (..., CONFLICT_IGNORE) zwraca -1 (błąd)?

CREATE TABLE regions (_id INTEGER PRIMARY KEY, name TEXT, UNIQUE(name)); 

a niektóre kodu Androida:

Validate.notBlank(region); 
ContentValues cv = new ContentValues(); 
cv.put(Columns.REGION_NAME, region); 
long regionId = 
    db.insertWithOnConflict("regions", null, cv, SQLiteDatabase.CONFLICT_IGNORE); 
Validate.isTrue(regionId > -1, 
    "INSERT ON CONFLICT IGNORE returned -1 for region name '%s'", region); 

Na zduplikowanych wierszy insertWithOnConflict() zwraca -1, wskazujący na błąd, a następnie wyrzuca z Validate:

INSERT ON CONFLICT IGNORE returned -1 for region name 'Overseas' 

Numer SQLite ON CONFLICT documentation (podkreślenie):

Gdy ma miejsce naruszenie ograniczenia, algorytm rozwiązywania IGNORE pomija jeden wiersz zawierający naruszenie ograniczenia i kontynuuje przetwarzanie kolejnych wierszy instrukcji SQL, tak jakby nic się nie stało. Pozostałe wiersze przed i po wierszu zawierającym naruszenie ograniczenia są wstawiane lub aktualizowane normalnie. Nie jest zwracany błąd, gdy używany jest algorytm rozwiązywania konfliktów IGNORE.

W Android insertWithOnConflict() documentation stanowi:

Zwraca jak identyfikator wiersz wstawianego wiersza lub klucz pierwotny istniejących rzędu jeśli param wejście „conflictAlgorithm” = CONFLICT_IGNORE lub -1 jakiegokolwiek błędu

CONFLICT_REPLACE nie jest opcją, ponieważ wymiana wiersze będą zamiast zmienić swój podstawowy klucz prostu powrocie istniejący klucz:

sqlite> INSERT INTO regions (name) VALUES ("Southern"); 
sqlite> INSERT INTO regions (name) VALUES ("Overseas"); 
sqlite> SELECT * FROM regions; 
1|Southern 
2|Overseas 
sqlite> INSERT OR REPLACE INTO regions (name) VALUES ("Overseas"); 
sqlite> SELECT * FROM regions; 
1|Southern 
3|Overseas 
sqlite> INSERT OR REPLACE INTO regions (name) VALUES ("Overseas"); 
sqlite> SELECT * FROM regions; 
1|Southern 
4|Overseas 

myślę że insertWithOnConflict() powinny na zduplikowane wiersze, wróć mi klucz podstawowy (kolumna _id) z duplikatu rzędu — więc mam nigdy komunikat o błędzie dla tej wkładki. Dlaczego insertWithOnConflict() zgłasza błąd? Jaką funkcję muszę wywołać, aby zawsze uzyskać prawidłowy identyfikator rzędu?

+1

Sprawdź swój LogCat. Ponieważ otrzymujesz kod błędu, powinieneś zobaczyć kilka ostrzeżeń z SQLite. – Sam

+0

OK, to nie to - Naprawiłem "nie mam blokady bazy danych!", Ale nadal dostaję błąd. Zrzut ekranu dziennika pod adresem http://i.imgur.com/KIlWH.png. – George

Odpowiedz

31

Odpowiedź na twoje pytanie, niestety, jest taka, że ​​dokumenty są po prostu błędne i nie ma takiej funkcji.

Jest to an open bug from 2010, który zajmuje się właśnie tym zagadnieniem i mimo że ponad 80 osób go obejrzało, nie ma oficjalnej odpowiedzi od zespołu Androida.

Emisja to also discussed on SO here.

Jeśli twój przypadek użycia jest konfliktem ciężkim (tj. Przez większość czasu, gdy spodziewasz się znaleźć istniejący rekord i chcesz go zwrócić), sugerowane obejście wydaje się być właściwą drogą. Jeśli, z drugiej strony, Twój przypadek użycia jest taki, że przez większość czasu można oczekiwać, aby istniał żaden istniejący rekord, a następnie następujące obejście może być bardziej odpowiedni:

try { 
    insertOrThrow(...) 
} catch(SQLException e) { 
    // Select the required record and get primary key from it 
} 

Oto realizacja samowystarczalny tego obejścia:

public static long insertIgnoringConflict(SQLiteDatabase db, 
              String table, 
              String idColumn, 
              ContentValues values) { 
    try { 
     return db.insertOrThrow(table, null, values); 
    } catch (SQLException e) { 
     StringBuilder sql = new StringBuilder(); 
     sql.append("SELECT "); 
     sql.append(idColumn); 
     sql.append(" FROM "); 
     sql.append(table); 
     sql.append(" WHERE "); 

     Object[] bindArgs = new Object[values.size()]; 
     int i = 0; 
     for (Map.Entry<String, Object> entry: values.valueSet()) { 
      sql.append((i > 0) ? " AND " : ""); 
      sql.append(entry.getKey()); 
      sql.append(" = ?"); 
      bindArgs[i++] = entry.getValue(); 
     } 

     SQLiteStatement stmt = db.compileStatement(sql.toString()); 
     for (i = 0; i < bindArgs.length; i++) { 
      DatabaseUtils.bindObjectToProgram(stmt, i + 1, bindArgs[i]); 
     } 

     try { 
      return stmt.simpleQueryForLong(); 
     } finally { 
      stmt.close(); 
     } 
    } 
} 
+0

To obejście nie działa. Zakłada on, że wstawienie powoduje konflikty 1: 1 z istniejącym wierszem, na przykład DB z następującymi: '0 | name | 123 fake st' Uruchamianie wstawki z wartościami:' {id: 0, nazwa: "john" } 'nie powiedzie się, ponieważ id 0 już istnieje, to wybór się nie powiedzie, ponieważ name =? 'john' nie istnieje w bazie danych. – TheHebrewHammer

3

Podczas gdy twoje oczekiwania co do zachowania insertWithOnConflict wydają się być całkiem rozsądne (powinieneś dostać pk dla kolizyjnego rzędu), to po prostu nie działa. W rzeczywistości dzieje się tak: próba wstawienia, nie wstawia wiersza, ale nie sygnalizuje błędu, struktura liczy liczbę wstawionych wierszy, odkrywa, że ​​liczba wynosi 0, i jawnie zwraca -1.

Edited by dodać:

Btw, ta odpowiedź jest oparta na kodzie, który ostatecznie realizuje insertWithOnConflict:

int err = executeNonQuery(env, connection, statement); 
return err == SQLITE_DONE && sqlite3_changes(connection->db) > 0 
     ? sqlite3_last_insert_rowid(connection->db) : -1; 

SQLITE_DONE jest dobry stan; sqlite3_changes to liczba wstawek w ostatnim połączeniu, a sqlite3_last_insert_rowid to wiersz z nowo wstawionego wiersza, jeśli taki istnieje.

Edited odpowiedzieć 2nd pytanie:

Po ponownym przeczytaniu pytanie, myślę, że to, czego szukasz, jest to metoda, która wykonuje to:

  • wstawia nowy wiersz do db, jeśli jest to możliwe
  • jeśli nie można wstawić wiersz, nie powiedzie się i zwraca ROWID dla istniejącego wiersza, który skonfliktowany (bez zmiany tego ROWID)

Cała dyskusja na temat zamiany wydaje się być czerwonym śledziem.

Odpowiedź na twoje drugie pytanie brzmi, że nie ma takiej funkcji.

-1

Problem został już rozwiązany, ale może to być opcja, która rozwiązała mój problem. Wystarczy zmienić ostatni parametr na CONFLICT_REPLACE.

long regionId = 
db.insertWithOnConflict("regions", null, cv, SQLiteDatabase.CONFLICT_REPLACE); 

Mam nadzieję, że to pomaga.

+0

"CONFLICT_REPLACE nie jest opcją, ponieważ zastąpienie wierszy zmieni ich klucz podstawowy zamiast tylko zwracania istniejącego klucza" – Alpha

Powiązane problemy