2012-11-15 13 views
22

Ich habe eine SQLite-Tabelle:Warum gibt InsertWithOnConflict (..., CONFLICT_IGNORE) -1 (Fehler) zurück?

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

und einige Android-Code:

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

Auf doppelte Zeilen insertWithOnConflict() -1 Rückkehr, einen Fehler anzeigt, und Validate dann wirft mit:

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

Die SQLite ON CONFLICT documentation (Hervorhebung von mir) heißt es:

Wenn eine anwendbare Einschränkungsverletzung auftritt, überspringt der IGNORE-Auflösungsalgorithmus die eine Zeile, die die Einschränkungsverletzung enthält, und verarbeitet die nachfolgenden Zeilen der SQL-Anweisung weiter, als ob nichts schief gelaufen wäre. Andere Zeilen vor und nach der Zeile, die die Einschränkungsverletzung enthielt, werden normal eingefügt oder aktualisiert. Wenn der Konfliktlösungsalgorithmus IGNORE verwendet wird, wird kein Fehler zurückgegeben.

Die Android insertWithOnConflict() documentation Zustände:

Returns die Zeilen ID der neu eingefügte Zeile oder der Primärschlüssel der vorhandenen Zeile, wenn das Eingang param ‚conflictAlgorithm‘ = CONFLICT_IGNORE oder -1 wenn ein Fehler

CONFLICT_REPLACE ist keine Option, weil ersetzen Reihen ihre Primärschlüssel statt ändern nur den vorhandenen Schlüssel Rückkehr:

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 

Ich denke, dass insertWithOnConflict() soll auf doppelte Zeilen, kehrt mir den Primärschlüssel (_id Spalte) der doppelten Zeile — so soll ich nie einen Fehler für diesen Einsatz erhalten. Warum gibt insertWithOnConflict() einen Fehler aus? Welche Funktion muss ich anrufen, damit ich immer eine gültige Zeilen-ID zurückbekomme?

+1

Überprüfen Sie Ihre LogCat. Da Sie einen Fehlercode erhalten, sollten Sie einige Warnungen von SQLite sehen. – Sam

+0

OK, das ist es nicht - ich habe die festen „nicht Datenbank-Sperre hat!“, Aber ich habe noch den Fehler. Screenshot des Logs unter http://i.imgur.com/KIlWH.png. – George

Antwort

31

Die Antwort auf Ihre Frage ist leider, dass die Dokumente einfach falsch sind und es keine solche Funktionalität gibt.

Es gibt an open bug from 2010, die genau dieses Problem anspricht und obwohl 80+ Leute es gespielt haben, gibt es keine offizielle Antwort vom Android Team.

Das Problem ist also discussed on SO here.

Wenn Ihr Anwendungsfall konfliktträchtig ist (d. H. Die meiste Zeit erwarten Sie, einen vorhandenen Datensatz zu finden und diese ID zurückzugeben), ist die vorgeschlagene Problemumgehung der richtige Weg. Wenn auf der anderen Seite, Ihren Anwendungsfall so ist, dass die meisten der Zeit, die Sie für dort erwarten keine vorhandenen Datensatz zu sein, dann könnte die folgende Abhilfe besser geeignet sein:

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

Hier ist eine in sich geschlossene Ausführung dieser Problemumgehung:

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

Diese Problemumgehung funktioniert nicht. Es geht davon aus, dass die Einfügung 1: 1 mit der vorhandenen Zeile in Konflikt steht, z. B. eine DB mit den folgenden: '0 | name | 123 gefälschte st' Ausführen einer Einfügung mit Werten:' {ID: 0, Name: "John" } 'wird fehlschlagen, da ID 0 schon da ist, dann wird die Auswahl fehlschlagen, da name =? 'john' existiert nicht in der Datenbank. – TheHebrewHammer

3

Während Ihre Erwartungen an das Verhalten von insertWithOnConflict völlig vernünftig erscheinen (Sie sollten den pk für die kollidierende Zeile erhalten), funktioniert das einfach nicht. Was ist eigentlich passiert ist, dass Sie: Versuchen Sie die Einfügung, fügt es nicht eine Zeile einfügen, aber kein Fehler signalisiert, das Framework zählt die Anzahl der Zeilen eingefügt, entdeckt, dass die Zahl 0 ist, und gibt explizit -1 zurück.

Edited hinzufügen:

Btw, ist diese Antwort basiert auf dem Code, der schließlich insertWithOnConflict implementiert:

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

SQLITE_DONE ist guter Zustand; sqlite3_changes ist die Anzahl der Einfügungen im letzten Aufruf und sqlite3_last_insert_rowid ist die Rowid für die neu eingefügte Zeile, falls vorhanden.

Edited zweite Frage zu beantworten:

Nach der Frage erneut zu lesen, denke ich, dass für das, was Sie suchen, ist ein Verfahren, das dies tut:

  • eine neue Zeile in die Einsätze db, wenn das möglich ist
  • , wenn es nicht die Zeile einfügen kann, schlägt fehl und gibt die ZeilenID für die bestehende Zeile, die in Konflikt gerieten (ohne dass rowid Wechsel)
012.

Die ganze Diskussion ersetzen scheint, wie Ablenkungsmanöver.

Die Antwort auf Ihre zweite Frage ist dann, dass es keine solche Funktion gibt.

-1

Problem wurde bereits gelöst, aber das kann eine Option sein, die mein Problem löste. Ändern Sie nur den letzten Parameter zu CONFLICT_REPLACE.

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

Ich hoffe, es hilft.

+0

"CONFLICT_REPLACE ist keine Option, da das Ersetzen von Zeilen ihren Primärschlüssel ändert, anstatt nur den vorhandenen Schlüssel zurückzugeben" – Alpha

Verwandte Themen