Sqlite - удаление строки, на которую ссылаются как на внешний ключ

При попытке удалить строку, на которую имеется ссылка как на внешний ключ (в экспериментальных целях), я получаю исключение времени выполнения. Я понимаю, что пытаюсь нарушить ограничение внешнего ключа, меня просто смущает сообщение 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();
}

Здесь я заполняю таблицу исходными вопросами, а затем пытаюсь удалить строку из таблицы категорий, намеренно нарушить ограничение внешнего ключа (проверить его):

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 (которых нет в моей таблице категорий)?

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) Почему он выдает исключение во время выполнения, тогда как при попытке добавить вопрос с категорией, которой нет в таблице категорий, просто игнорируется команда SQLite, но не происходит сбой приложения? Является ли исключение времени выполнения ожидаемым поведением при нарушении ограничения внешнего ключа при попытке удалить столбец, на который имеется ссылка?

0
0
1 686
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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 и почему оно не удалось, потому что кажется, что вы перепутали столбцы таблицы. Однако я полагаю, что это не проблема.

В частности, вы определяете ограничение 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_????)

Для столбца в другой таблице у вас есть QuestionsTable._ID, он должен быть CategoriesTable._ID. Однако, вероятно, они называются одинаково. Так что это не вызывает проблем.

Сообщение об ошибке включает category_id = 6

Предполагая, что они оба решают идентифицировать, вы фактически говорите

Разрешить вставку строки в таблицу вопросов, только если таблица category_id * столбец ** вопросов соответствует (может ссылаться) на строку в таблице категории, где столбец Я БЫ совпадает с category_id (т. Е. 6).

Короче говоря, отображаемые сообщения говорят, что нет категории с идентификатором 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 Ограничения отложенного внешнего ключа


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

Повторюсь, вы не зашли так далеко.

Однако вы можете захотеть проверить 4.3 Действия при удалении и обновлении

Изменить - объяснение путаницы.

Короче говоря, сбой на самом деле происходит из-за ограничения 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.access$600(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.access$600(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.access$600(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 (номер вашей строки будет другим, как и пакет) указывает на

enter image description here

  • Обратите внимание, что я изменил строку, чтобы использовать КОНСТАНТУ (CategoriesTable._ID) для столбца идентификатора категории.

Но дело в том, что если я пропущу часть удаления, мое приложение НЕ аварийно завершит работу при попытке добавить вопрос с несуществующей категорией. Он просто не добавляет вопрос (и поддерживает работу приложения). Вот что меня смущает. У меня нет этого исключения времени выполнения, если я не пытаюсь удалить категорию.

Florian Walther 11.04.2018 13:40

Тогда то, что вы, вероятно, делаете, не просматривает весь журнал / правую трассировку стека. Я предполагаю, что у вас есть вставки, завернутые в try / catch, поэтому вы захватываете их, поэтому обработка продолжается, и дальше вы получаете фактическую трассировку стека сбоев.

MikeT 11.04.2018 13:44

Это объясняет, почему, если вы удалите удаление, все будет хорошо.

MikeT 11.04.2018 13:57

Имеет смысл, но у меня нет блоков try / catch

Florian Walther 11.04.2018 14:02

Другой потенциальный фактор - то, что onCreate запускается внутри транзакции. Чтобы запустить его, нужно будет увидеть больше вашего кода.

MikeT 11.04.2018 14:17

Хорошо, я могу вставить SQLiteOpenHelper, но в нем 200 строк

Florian Walther 11.04.2018 14:18

Мне также понадобится класс Question. Однако здесь уже поздно, так что все равно не смогу разобраться до завтра.

MikeT 11.04.2018 14:20

@FlorianWalther решил, ну хотя бы причину. Ответ отредактирован.

MikeT 12.04.2018 00:29

Большое спасибо за подробный ответ. Я прочитаю это более подробно как можно скорее.

Florian Walther 12.04.2018 19:57

Так что попытка добавить вопрос, у которого нет категории, будет просто проигнорирована, но не вызовет сбоя, верно? В то время как попытка удалить категорию, на которую имеется ссылка в 1 или более вопросах без «каскадного удаления», приводит к сбою. Ваши наблюдения такие же?

Florian Walther 12.04.2018 23:36

@FlorianWalther, так как он у вас настроен, тогда да. insert по умолчанию - insert or ignore (метод insertOrThrow - нет) и охватывает другие ограничения, например. NOT NULL, UNIQUE (нет проблем со ссылочной целостностью). Однако удаление будет проблемой, так как это нарушит ссылочную целостность. Так что да такие же / похожие наблюдения.

MikeT 13.04.2018 00:01

Большой! Я не могу отблагодарить вас за все ваши усилия!

Florian Walther 13.04.2018 11:24

Я только что понял, почему приложение вылетело .. Потому что я написал «WHERE ID =» вместо «WHERE _ID =»

Florian Walther 07.08.2018 21:30

Другие вопросы по теме