在冲突替换上创建唯一约束

时间:2014-10-02 06:26:53

标签: android sqlite

Android Studio 0.8.11

您好,

我在FEED_NUMBER列上有以下唯一约束。因此,当我获得需要插入的新记录时,我将只用相同的FEED_NUMBER替换记录。但是,当插入运行时,它总是添加新记录并忽略约束。我在这里做错了吗?

这是我的onCreate:

public void onCreate(SQLiteDatabase db) {
         String sql = "create table " + FottContract.TABLE
                + "(" + FottContract.Column.ID + " integer primary key autoincrement, "
                 + FottContract.Column.FEED_NUMBER + " text, "
                 + FottContract.Column.TITLE + " text, "
                 + FottContract.Column.DESCRIPTION + " text, "
                 + FottContract.Column.IMAGE + " text, "
                 + "unique (" + FottContract.Column.FEED_NUMBER + ") on conflict replace" + ")";

        db.execSQL(sql);
    }

这就是我插入的方式:

 private void saveToSQLite() {
        ContentValues contentValues = new ContentValues();

        /* For each item in the memory database insert into sqlite */
        for (int i = 0; i < mNewsFeedDB.size(); i++) {
            contentValues.put(FottContract.Column.FEED_NUMBER, mNewsFeedDB.get(i).getId());
            contentValues.put(FottContract.Column.TITLE, mNewsFeedDB.get(i).getTitle());
            contentValues.put(FottContract.Column.DESCRIPTION, mNewsFeedDB.get(i).getDescription());
            contentValues.put(FottContract.Column.IMAGE, mNewsFeedDB.get(i).getImage());

            /* Insert it */
            long row = mDb.insert(FottContract.TABLE, null, contentValues);
        }
    }

非常感谢任何建议,

5 个答案:

答案 0 :(得分:4)

使用解决方案进行编辑: 这个问题让我恼火,所以我决定使用与问题相同的代码创建一个小测试项目。所以首先要注意的是UNIQUE约束是受到尊重的。数据库中只有一行具有给定的FeedNumber。但是,此行的ID随着UNIQUE约束上的ON CONFLICT REPLACE的计数(现有行被删除并替换为新行)和ID列上的AUTOINCREMENT(将ID设置为最高ID + 1)而变化。

其次,我尝试使用现有ID插入,这当然是失败的,因为您无法插入具有相同PRIMARY ID的两行。

所以解决方法是先检查行是否存在(尝试在id上插入),如果失败(即row == -1),请使用update方法。这显然假设您实际上从mNewsFeedDB.get(i).getId()方法获得了正确的行ID。

Here is a link to my test project if anyone is interested in how I tested it.不是最漂亮的代码,但它可以胜任。


我希望在插入之前和之后看到打印出来的表格,但是这条线让我觉得有点可疑:

  

contentValues.put(FottContract.Column.FEED_NUMBER,mNewsFeedDB.get(i).getId());

您在这里做的是获取给定NewsFeedItem的ID并将该值分配给FEED_NUMBER字段,但ID是否与现有feedNumber相同?如果ID为null(例如SQLite)将每个FEED_NUMBER视为唯一(因为不同的空值一起被视为不同的值)。

我之前遇到的另一件事是重用ContentValues对象。您可能需要为for循环中的每个传递创建一个新的Contentvalues对象。

答案 1 :(得分:1)

<强>更新

您尝试使用insertWithConflict功能而不是mDb.insert(..)吗?类似的东西:

/* Insert it*/
mDb.insertWithOnConflict(FottContract.TABLE, null, contentValues, SQLiteDatabase.CONFLICT_REPLACE);

以前的语言

(正如@ corsair992在评论中指出的那样,我认为他说删除后会触发触发器是正确的。)

代码似乎没问题,但可能会出现以下问题:

可能的问题:

  • 您使用的是版本3.6.18还是以上?我想你将在2009年发布。如果你看到release notes of SQL lite for version 3.6.18,它会说:

      

    由于REPLACE冲突解决而删除行时,会触发删除触发器。 此功能仅在递归时启用   触发器已启用。

  • 如果您阅读了REPLACE的详细信息,则会提及类似的内容 on Conflict Clause页面上写着:

      

    ...当REPLACE冲突解决策略删除行以满足约束时,删除触发器会激活当且仅当   已启用递归触发器

可能的解决方案:

可能是recursive_triggers被禁用。您可以通过运行以下SQL查询来启用递归触发器:

PRAGMA recursive_triggers='ON'

您可以使用在给定问题的sniplet中使用的相同execSQL方法在代码中执行查询。结帐SQLite fire DELETE trigger on a REPLACE博客了解详情。他们写博客的人提到他的团队有类似的问题。当他们在表格中输入REPLACE时,会在其中输入重复的条目。

所以我假设以上可能解决你的问题。希望这有点帮助。

答案 2 :(得分:0)

  

但是,当插入运行时,它总是添加新记录和   无视禁令。

听起来你正在观察REPLACE的预期行为。在插入发生之前,此冲突解决算法将首先删除具有给定FEED_NUMBER值的任何现有行。然后,插入将使用指定的源编号创建一个具有新ID的新行。在该插入之后,返回的行ID将是表中包含该特定源编号的唯一行。

如果值为NULL,则最终只能包含多个包含相同Feed编号的行。来自https://www.sqlite.org/lang_createtable.html#uniqueconst

  

出于UNIQUE约束的目的,NULL值被认为与所有其他值不同,包括其他NULL。

答案 3 :(得分:-1)

根据Green Dao生成的文件(即使你没有使用它生成有效的sql lite代码)我会使用以下sintax:

创建表格

String constraint = ifNotExists? "IF NOT EXISTS ": "";
    db.execSQL("CREATE TABLE " + constraint + "'FottContract.TABLE' (" + //
            "'FottContract.Column.ID' INTEGER PRIMARY KEY ," + // 0: id
            "'FottContract.Column.FEED_NUMBER' TEXT NOT NULL UNIQUE," + // 1: text
            "'FottContract.Column.TITLE' TEXT," + // 2: title
            "'FottContract.Column.DESCRIPTION' TEXT," + // 3: description
            "'FottContract.Column.ID' IMAGE,");"); // 4: image

    // Add Index
     // Add Index
    db.execSQL("CREATE INDEX " + constraint + "IDX_" + FottContract.TABLE + "_"+ FottContract.TABLE + "ON FottContract.TABLE" +
            " (FottContract.Column.FEED_NUMBER);");

进行插入

private void saveToSQLite() {
    ContentValues contentValues = new ContentValues();

    /* For each item in the memory database insert into sqlite */
    for (int i = 0; i < mNewsFeedDB.size(); i++) {
        contentValues.put(FottContract.Column.FEED_NUMBER, mNewsFeedDB.get(i).getId());
        contentValues.put(FottContract.Column.TITLE, mNewsFeedDB.get(i).getTitle());
        contentValues.put(FottContract.Column.DESCRIPTION, mNewsFeedDB.get(i).getDescription());
        contentValues.put(FottContract.Column.IMAGE, mNewsFeedDB.get(i).getImage());

        /* Insert it */
        long row = mDb.insertWithOnConflict(FottContract.TABLE, null, contentValues, SQLiteDatabase.CONFLICT_REPLACE);
    }
} 

答案 4 :(得分:-1)

编辑正如corsair992所指出的那样,awnser bellow是不正确的

删除因autoincrement算法而失败的autoincrement,以避免重复使用列值,因为IDprimary key您不需要它(请参阅吼叫的原因) 正如文件所述

If the AUTOINCREMENT keyword appears after INTEGER PRIMARY KEY, 
that changes the automatic ROWID assignment algorithm to prevent the reuse 
of ROWIDs over the lifetime of the database. 
In other words, the purpose of AUTOINCREMENT is to prevent the reuse of ROWIDs 
from previously deleted rows.

由于replace通过删除发生冲突的前一行工作,因此AUTOINCREMENT算法将失败,因为新插入将使用先前使用的一个值。

因此明确违反了您的意图,使用on replace您基本上说“好吧我不介意重复使用此主键的值”但是AUTOINCREMENT你说的是“我永远不希望为这个主键重用相同的值“。

请注意,由于主键别名为AUTOINCREMENT

,因此您无需使用ROWID
In SQLite, a column with type INTEGER PRIMARY KEY is an alias for the ROWID 
(except in WITHOUT ROWID tables) which is always a 64-bit signed integer.

https://www.sqlite.org/autoinc.html