2012-11-15 29 views
22

Tôi có một bảng SQLite:Tại sao insertWithOnConflict (..., CONFLICT_IGNORE) trả lại -1 (lỗi)?

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

Và một số mã Android:

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

Mở hàng trùng lặp insertWithOnConflict() được trở về -1, chỉ ra một lỗi, và Validate sau đó ném với:

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

SQLite ON CONFLICT documentation (phần nhấn mạnh của tôi) nêu rõ:

Khi xảy ra vi phạm ràng buộc áp dụng, thuật toán phân giải IGNORE bỏ qua hàng có chứa vi phạm ràng buộc và tiếp tục xử lý các hàng tiếp theo của câu lệnh SQL như thể không có gì sai. Các hàng khác trước và sau hàng có chứa vi phạm ràng buộc được chèn hoặc cập nhật bình thường. Không có lỗi nào được trả về khi thuật toán giải quyết xung đột IGNORE được sử dụng.

Các Android insertWithOnConflict() documentation trạng thái:

Returns hàng ID của hàng mới được chèn HOẶC khóa chính của hàng hiện tại nếu các param đầu vào 'conflictAlgorithm' = CONFLICT_IGNORE OR -1 nếu có lỗi

CONFLICT_REPLACE không phải là một lựa chọn, bởi vì hàng thay thế sẽ thay đổi khóa chính của họ thay vì chỉ trả lại chìa khóa hiện có:

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 

Tôi nghĩ rằng insertWithOnConflict() nên, trên các hàng trùng lặp, trả lại cho tôi chìa khóa chính (cột _id) của hàng trùng lặp — vì vậy tôi nên không bao giờ nhận được một lỗi cho chèn này. Tại sao insertWithOnConflict() ném một lỗi? Tôi cần gọi hàm nào để tôi luôn nhận được ID hàng hợp lệ?

+1

Kiểm tra LogCat của bạn. Vì bạn đang nhận được một mã lỗi, bạn sẽ thấy một số cảnh báo từ SQLite. – Sam

+0

OK, không phải vậy - Tôi đã sửa lỗi "không có khóa cơ sở dữ liệu!", Nhưng tôi vẫn gặp lỗi. Ảnh chụp màn hình nhật ký tại http://i.imgur.com/KIlWH.png. – George

Trả lời

31

Câu trả lời cho câu hỏi của bạn, thật không may là tài liệu chỉ đơn giản là sai và không có chức năng như vậy.

an open bug from 2010 giải quyết chính xác vấn đề này và mặc dù hơn 80 người đã gắn dấu sao nó, không có phản hồi chính thức từ nhóm Android.

Vấn đề là also discussed on SO here.

Nếu trường hợp sử dụng của bạn là xung đột nặng (tức là phần lớn thời gian bạn mong muốn tìm thấy hồ sơ hiện có và muốn trả lại ID đó), cách giải quyết được đề xuất của bạn có vẻ như là cách để đi. Nếu, mặt khác, trường hợp sử dụng của bạn là như vậy mà phần lớn thời gian bạn mong đợi cho có được không có hồ sơ hiện có, sau đó thực hiện giải pháp sau đây có thể thích hợp hơn:

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

Đây là một thực hiện khép kín của giải pháp này:

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

Cách giải quyết này sẽ không hoạt động. Nó giả định rằng chèn xung đột 1: 1 với hàng hiện tại, ví dụ như một DB với những điều sau đây: '0 | tên | 123 giả st' Chạy một chèn với các giá trị:' {id: 0, name: "john" } 'sẽ thất bại vì id 0 đã có, sau đó chọn sẽ thất bại vì tên =? 'john' không tồn tại trong cơ sở dữ liệu. – TheHebrewHammer

3

Mặc dù mong đợi của bạn về hành vi của insertWithOnConflict có vẻ hoàn toàn hợp lý (bạn nên lấy pk cho hàng va chạm), đó không phải là cách hoạt động của nó. Những gì thực sự xảy ra là bạn: cố gắng chèn, nó không chèn một hàng nhưng không có lỗi, khuôn khổ đếm số hàng chèn vào, phát hiện ra rằng số là 0, và, rõ ràng, trả về -1.

Edited thêm:

Btw, câu trả lời này được dựa trên mã mà, cuối cùng, thực hiện 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 là tình trạng tốt; sqlite3_changes là số lần chèn trong cuộc gọi cuối cùng và sqlite3_last_insert_rowid là hàng cho hàng mới được chèn vào, nếu có.

Edited để trả lời câu hỏi thứ 2:

Sau khi đọc câu hỏi, tôi nghĩ rằng những gì bạn đang tìm kiếm là một phương pháp mà thực hiện điều này:

  • chèn một hàng mới vào db, nếu có thể
  • nếu nó không thể chèn hàng, không thành công và trả về hàng cho hàng hiện tại xung đột (không thay đổi hàng đó)

Toàn bộ cuộc thảo luận về thay thế có vẻ như cá trích đỏ.

Câu trả lời cho câu hỏi thứ 2 của bạn, sau đó, là không có chức năng như vậy.

-1

Vấn đề đã được giải quyết, nhưng đây có thể là một tùy chọn giải quyết được sự cố của tôi. Chỉ cần thay đổi thông số cuối cùng thành CONFLICT_REPLACE.

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

Hy vọng điều đó sẽ hữu ích.

+0

"CONFLICT_REPLACE không phải là một tùy chọn, bởi vì thay thế hàng sẽ thay đổi khóa chính thay vì chỉ trả lại khóa hiện có" – Alpha

Các vấn đề liên quan