SQLite - 删除引用为外键的行

SQLite - Deleting a row that is referenced as a foreign key

在尝试删除引用为外键的行(用于实验目的)时,出现运行时异常。我知道我试图违反外键约束,我只是对 logcat 消息感到困惑,因为它指的是我代码中完全不同的部分。

这是我的SQLiteOpenHelperonCreate方法:

@Override
public void onCreate(SQLiteDatabase db) {
    this.db = db;

    final String SQL_CREATE_CATEGORIES_TABLE = "CREATE TABLE " +
            CategoriesTable.TABLE_NAME + "( " +
            CategoriesTable._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
            CategoriesTable.COLUMN_NAME + " TEXT " +
            ")";

    final String SQL_CREATE_QUESTIONS_TABLE = "CREATE TABLE " +
            QuestionsTable.TABLE_NAME + " ( " +
            QuestionsTable._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
            QuestionsTable.COLUMN_QUESTION + " TEXT, " +
            QuestionsTable.COLUMN_OPTION1 + " TEXT, " +
            QuestionsTable.COLUMN_OPTION2 + " TEXT, " +
            QuestionsTable.COLUMN_OPTION3 + " TEXT, " +
            QuestionsTable.COLUMN_ANSWER_NR + " INTEGER, " +
            QuestionsTable.COLUMN_DIFFICULTY + " TEXT, " +
            QuestionsTable.COLUMN_CATEGORY + " INTEGER, " +
            "FOREIGN KEY(" + QuestionsTable.COLUMN_CATEGORY + ") REFERENCES " +
            CategoriesTable.TABLE_NAME + "(" + QuestionsTable._ID + ")" + 
            ")";

    db.execSQL(SQL_CREATE_CATEGORIES_TABLE);
    db.execSQL(SQL_CREATE_QUESTIONS_TABLE);
    fillCategoriesTable();
    fillQuestionsTable();
}

这里我用初始问题填充table,然后我尝试从类别table中删除一行,故意违反外键约束(测试一下):

private void fillQuestionsTable() {
    Question q1 = new Question("Easy: A is correct",
            "A", "B", "C", 1, Question.DIFFICULTY_EASY, 1);
    addQuestion(q1);
    Question q2 = new Question("Medium: B is correct",
            "A", "B", "C", 2, Question.DIFFICULTY_MEDIUM, 2);
    addQuestion(q2);
    Question q3 = new Question("Medium: C is correct",
            "A", "B", "C", 3, Question.DIFFICULTY_MEDIUM, 3);
    addQuestion(q3);
    Question q4 = new Question("Hard: A is correct",
            "A", "B", "C", 1, Question.DIFFICULTY_HARD, 4);
    addQuestion(q4);
    Question q5 = new Question("Hard: B is correct",
            "A", "B", "C", 2, Question.DIFFICULTY_HARD, 5);
    addQuestion(q5);
    Question q6 = new Question("Hard: C is correct",
            "A", "B", "C", 3, Question.DIFFICULTY_HARD, 6);
    addQuestion(q6);

    db.execSQL("DELETE FROM " + CategoriesTable.TABLE_NAME + " WHERE ID = 1");
}

我遇到运行时异常。 请注意,我试图故意违反外键约束,只是想了解异常情况。

我的第一个问题是:

1)崩溃是因为删了,但是为什么logcat只说把题加到类别4,5和6(不在我的类别table中第一名)?

Error inserting difficulty=Hard option1=A question=Hard: C is correct category_id=6 answer_nr=3 option3=C option2=B android.database.sqlite.SQLiteConstraintException: FOREIGN KEY constraint failed (code 787)

为什么它没有像我期望的那样谈论删除引用的列?

2) 为什么它会抛出运行时异常,而尝试添加一个类别不存在于类别 table 中的问题只是忽略 SQLite 命令,但不会崩溃应用程序? 通过尝试删除引用的列来违反外键约束时,运行时异常是否是预期的行为?

1) The crash is because of the deletion, but why does logcat only talk about adding the questions to category 4,5 and 6 (which are not in my categories table in the first place)?

没有。崩溃,或者至少显示的消息,是因为它所说的:FOREIGN KEY 约束违规,发生在删除之前(在 addQuestion(q6); 中),因此删除永远不会发生。

解释 FOREIGN KEY 约束及其失败的原因有点困难,因为您似乎混淆了 table 列。但是,我的猜测是这本身不是问题。

更具体地说,您使用

定义了一个 FOREIGN KEY 约束
FOREIGN KEY(column_in_this_table_which_you_have_right)
    REFERENCES the_other_table_which_you_have_right
        (column_in_the_other_table_????)

对于另一个 table 中的列,您有 QuestionsTable._ID,它应该是 CategoriesTable._ID。但是,它们可能被称为相同的。所以这不会引起问题。

报错信息包括category_id=6

假设他们都解析为 ID 那么你实际上是在说

仅允许在 questions table 中插入一行,前提是 **questions[ 的 category_id* 列=93=] table 匹配(可以引用)categories table 中的一行,其中 ID 列是与 category_id(即 6)相同。

简而言之,显示的消息说没有 id 为 6 的类别。


Why does it not talk about deleting a referenced column as I would expect?

因为它在到达 db.execSQL("DELETE FROM " + CategoriesTable.TABLE_NAME + " WHERE ID = 1");

之前崩溃了

2) Why does it throw a runtime exception, whereas trying to add a question that has a category which does not exist in the categories table, just ignores the SQLite command, but doesn't crash the app?

因为很可能你的应用程序会在很多地方崩溃而没有。你是说这对设计来说非常重要。我毫不怀疑,如果忽略选项是这种情况,你会更加相反(即可用的最佳选项没有人强迫你定义约束)。虽然如果那是你想要的,你可能希望查看 4.2 Deferred Foreign Key Constraints


Is a runtime exception the expected behavior when violating a foreign key constraint by trying to delete a referenced column?

重申一下,您还没有走到那一步。

不过,您不妨看看4.3 ON DELETE and ON UPDATE Actions

编辑 - 解释混乱。

简而言之,崩溃实际上是由于DELETE约束造成的。 INSERT 约束被捕获但显示为正在使用标准 insert 方法。日志,如果你仔细看,会显示这一点,如下所述。

这是我根据您的代码整理的示例:-

04-11 21:47:10.241 1927-1927/? E/SQLiteDatabase: Error inserting option1=A category_id=6 option2=B option3=C difficulty=Hard answer_nr=3 question=Hard: C is correct
    android.database.sqlite.SQLiteConstraintException: foreign key constraint failed (code 19)
        at android.database.sqlite.SQLiteConnection.nativeExecuteForLastInsertedRowId(Native Method)
        at android.database.sqlite.SQLiteConnection.executeForLastInsertedRowId(SQLiteConnection.java:775)
        at android.database.sqlite.SQLiteSession.executeForLastInsertedRowId(SQLiteSession.java:788)
        at android.database.sqlite.SQLiteStatement.executeInsert(SQLiteStatement.java:86)
        at android.database.sqlite.SQLiteDatabase.insertWithOnConflict(SQLiteDatabase.java:1469)
        at android.database.sqlite.SQLiteDatabase.insert(SQLiteDatabase.java:1339)
        at askit.questionaire.DatabaseHelper.addQuestion(DatabaseHelper.java:70)
        at askit.questionaire.DatabaseHelper.fillQuestionsTable(DatabaseHelper.java:97)
        at askit.questionaire.DatabaseHelper.onCreate(DatabaseHelper.java:52)
        at android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked(SQLiteOpenHelper.java:252)
        at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:164)
        at askit.questionaire.DatabaseHelper.<init>(DatabaseHelper.java:17)
        at askit.questionaire.MainActivity.onCreate(MainActivity.java:14)
        at android.app.Activity.performCreate(Activity.java:5008)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1079)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2023)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2084)
        at android.app.ActivityThread.access0(ActivityThread.java:130)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1195)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:137)
        at android.app.ActivityThread.main(ActivityThread.java:4745)
        at java.lang.reflect.Method.invokeNative(Native Method)
        at java.lang.reflect.Method.invoke(Method.java:511)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:786)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
        at dalvik.system.NativeStart.main(Native Method)
04-11 21:47:10.245 1927-1927/? D/AndroidRuntime: Shutting down VM
04-11 21:47:10.245 1927-1927/? W/dalvikvm: threadid=1: thread exiting with uncaught exception (group=0xa6294288)
04-11 21:47:10.245 1927-1927/? E/AndroidRuntime: FATAL EXCEPTION: main
    java.lang.RuntimeException: Unable to start activity ComponentInfo{askit.questionaire/askit.questionaire.MainActivity}: android.database.sqlite.SQLiteConstraintException: foreign key constraint failed (code 19)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2059)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2084)
        at android.app.ActivityThread.access0(ActivityThread.java:130)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1195)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:137)
        at android.app.ActivityThread.main(ActivityThread.java:4745)
        at java.lang.reflect.Method.invokeNative(Native Method)
        at java.lang.reflect.Method.invoke(Method.java:511)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:786)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
        at dalvik.system.NativeStart.main(Native Method)
     Caused by: android.database.sqlite.SQLiteConstraintException: foreign key constraint failed (code 19)
        at android.database.sqlite.SQLiteConnection.nativeExecuteForChangedRowCount(Native Method)
        at android.database.sqlite.SQLiteConnection.executeForChangedRowCount(SQLiteConnection.java:727)
        at android.database.sqlite.SQLiteSession.executeForChangedRowCount(SQLiteSession.java:754)
        at android.database.sqlite.SQLiteStatement.executeUpdateDelete(SQLiteStatement.java:64)
        at android.database.sqlite.SQLiteDatabase.executeSql(SQLiteDatabase.java:1665)
        at android.database.sqlite.SQLiteDatabase.execSQL(SQLiteDatabase.java:1594)
        at askit.questionaire.DatabaseHelper.fillQuestionsTable(DatabaseHelper.java:99)
        at askit.questionaire.DatabaseHelper.onCreate(DatabaseHelper.java:52)
        at android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked(SQLiteOpenHelper.java:252)
        at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:164)
        at askit.questionaire.DatabaseHelper.<init>(DatabaseHelper.java:17)
        at askit.questionaire.MainActivity.onCreate(MainActivity.java:14)
        at android.app.Activity.performCreate(Activity.java:5008)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1079)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2023)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2084) 
        at android.app.ActivityThread.access0(ActivityThread.java:130) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1195) 
        at android.os.Handler.dispatchMessage(Handler.java:99) 
        at android.os.Looper.loop(Looper.java:137) 
        at android.app.ActivityThread.main(ActivityThread.java:4745) 
        at java.lang.reflect.Method.invokeNative(Native Method) 
        at java.lang.reflect.Method.invoke(Method.java:511) 
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:786) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553) 
        at dalvik.system.NativeStart.main(Native Method) 

您看到的相当于:-

04-11 21:47:10.241 1927-1927/? E/SQLiteDatabase: Error inserting option1=A category_id=6 option2=B option3=C difficulty=Hard answer_nr=3 question=Hard: C is correct
    android.database.sqlite.SQLiteConstraintException: foreign key constraint failed (code 19)

但是,这不会导致应用程序崩溃。

  • 如果您 运行 应用程序在删除数据库后将 DELETE 注释掉并查看日志,即使应用程序没有崩溃,您也会在日志中看到相同的内容。

您需要做的是继续查看日志,您会发现相当于:-

04-11 21:47:10.245 1927-1927/? D/AndroidRuntime: Shutting down VM
04-11 21:47:10.245 1927-1927/? W/dalvikvm: threadid=1: thread exiting with uncaught exception (group=0xa6294288)
04-11 21:47:10.245 1927-1927/? E/AndroidRuntime: FATAL EXCEPTION: main
    java.lang.RuntimeException: Unable to start activity ComponentInfo{askit.questionaire/askit.questionaire.MainActivity}: android.database.sqlite.SQLiteConstraintException: foreign key constraint failed (code 19)

这是崩溃,即 FATAL EXCEPTION:

如果您继续,您将看到 引起,这是您查看的位置,因此您有:-

 Caused by: android.database.sqlite.SQLiteConstraintException: foreign key constraint failed (code 19)
    at android.database.sqlite.SQLiteConnection.nativeExecuteForChangedRowCount(Native Method)
    at android.database.sqlite.SQLiteConnection.executeForChangedRowCount(SQLiteConnection.java:727)
    at android.database.sqlite.SQLiteSession.executeForChangedRowCount(SQLiteSession.java:754)
    at android.database.sqlite.SQLiteStatement.executeUpdateDelete(SQLiteStatement.java:64)
    at android.database.sqlite.SQLiteDatabase.executeSql(SQLiteDatabase.java:1665)
    at android.database.sqlite.SQLiteDatabase.execSQL(SQLiteDatabase.java:1594)
    at askit.questionaire.DatabaseHelper.fillQuestionsTable(DatabaseHelper.java:99)
    at askit.questionaire.DatabaseHelper.onCreate(DatabaseHelper.java:52)
    at android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked(SQLiteOpenHelper.java:252)
    at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:164)
    at askit.questionaire.DatabaseHelper.<init>(DatabaseHelper.java:17)
    at askit.questionaire.MainActivity.onCreate(MainActivity.java:14)
    at android.app.Activity.performCreate(Activity.java:5008) 

如果您查看 引起的日志,您会看到提到您的包裹的第一行是:-

at askit.questionaire.DatabaseHelper.fillQuestionsTable(DatabaseHelper.java:99)

第 99 行(您的行号与包裹不同)指向

  • 请注意,我将行更改为使用 CONSTANT (CategoriesTable._ID) 作为类别 ID 列。