Contraintes de clé étrangère dans Android utilisant SQLite? sur Supprimer la cascade

91

J'ai deux tableaux: les traces et les waypoints, une trace peut avoir plusieurs waypoints, mais un waypoint est assigné à une seule trace.

Dans le tableau des points de chemin, j'ai une colonne appelée "trackidfk" qui insère le track_ID une fois qu'une piste est créée, mais je n'ai pas configuré de contraintes de clé étrangère sur cette colonne.

Lorsque je supprime une trace, je veux supprimer les waypoints attribués, est-ce possible?. J'ai lu sur l'utilisation des déclencheurs mais je ne pense pas qu'ils soient pris en charge par Android.

Pour créer la table des waypoints:

public void onCreate(SQLiteDatabase db) {
    db.execSQL( "CREATE TABLE " + TABLE_NAME 
                + " (" 
                + _ID         + " INTEGER PRIMARY KEY AUTOINCREMENT, " 
                + LONGITUDE   + " INTEGER," 
                + LATITUDE    + " INTEGER," 
                + TIME        + " INTEGER,"
                + TRACK_ID_FK + " INTEGER"
                + " );"
              );

    ...
}
jcrowson
la source

Réponses:

237

Les contraintes de clé étrangère avec une cascade de suppression sont prises en charge, mais vous devez les activer.
Je viens d'ajouter ce qui suit à mon SQLOpenHelper , ce qui semble faire l'affaire.

@Override
public void onOpen(SQLiteDatabase db) {
    super.onOpen(db);
    if (!db.isReadOnly()) {
        // Enable foreign key constraints
        db.execSQL("PRAGMA foreign_keys=ON;");
    }
}

J'ai déclaré ma colonne de référencement comme suit.

mailbox_id INTEGER REFERENCES mailboxes ON DELETE CASCADE
Phil
la source
59
Ce qui signifie que cela ne fonctionne que depuis Android 2.2 Froyo qui a SQLite 3.6.22
Intrications
@RedPlanet - c'est parce que la seule fois où cette contrainte est appliquée, c'est quand quelque chose est écrit dans la base de données. (Vous ne pouvez pas briser cette contrainte si vous ne faites que lire à partir de la base de données) De plus, Phil, au lieu de la méthode onOpen, il est probablement préférable de le faire dans la méthode onConfigure. Source: developer.android.com/reference/android/database/sqlite/…
Aneem
12
Google recommande d'écrire des PRAGMAdéclarations dans onConfigure()mais cela nécessite le niveau d'API 16 (Android 4.1), et d'ici là, vous pouvez simplement appeler setForeignKeyConstraintsEnabled.
Pang
Vous devrez peut-être également envisager d'activer les contraintes de clé étrangère dans onCreate/ onDowngrade/ onUpgrade, qui sont antérieures onOpen. Voir le code source dans Android 4.1.1 .
Pang
1
@Natix incluant l'appel à super assure une fonctionnalité correcte si une classe intermédiaire est introduite entre la classe implémentée et son parent.
tbm
55

Depuis Android 4.1 (API 16), SQLiteDatabase prend en charge:

public void setForeignKeyConstraintsEnabled (boolean enable)
e.shishkin
la source
26

Comme le message de e.shishkin le dit à partir de l'API 16, vous devez activer les contraintes de clé étrangère dans la SqLiteOpenHelper.onConfigure(SqLiteDatabase)méthode en utilisant ledb.setForeignKeyConstraintsEnabled(boolean)

@Override
public void onConfigure(SQLiteDatabase db){
    db.setForeignKeyConstraintsEnabled(true);
}
Malcolm
la source
10

Jamais une question trop ancienne pour y répondre par une réponse plus complète.

@Override public void onOpen(SQLiteDatabase db) {
    super.onOpen(db);
    if (!db.isReadOnly()) {
        setForeignKeyConstraintsEnabled(db);
    }
    mOpenHelperCallbacks.onOpen(mContext, db);
}

private void setForeignKeyConstraintsEnabled(SQLiteDatabase db) {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
        setForeignKeyConstraintsEnabledPreJellyBean(db);
    } else {
        setForeignKeyConstraintsEnabledPostJellyBean(db);
    }
}

private void setForeignKeyConstraintsEnabledPreJellyBean(SQLiteDatabase db) {
    db.execSQL("PRAGMA foreign_keys=ON;");
}

@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
private void setForeignKeyConstraintsEnabledPostJellyBean(SQLiteDatabase db) {
    db.setForeignKeyConstraintsEnabled(true);
}
Code inversé
la source
6

Tout ce que @phil a mentionné est bon. Mais vous pouvez utiliser une autre méthode par défaut disponible dans la base de données elle-même pour définir la clé étrangère. C'est setForeignKeyConstraintsEnabled (true).

@Override
public void onOpen(SQLiteDatabase db) {
    super.onOpen(db);
    if (!db.isReadOnly()) {
        // Enable foreign key constraints
        db.execSQL("PRAGMA foreign_keys=ON;"); 
              //(OR)
        db.setForeignKeyConstraintsEnabled (true)
    }
}

Pour les documents, reportez-vous à SQLiteDatabase.setForeignKeyConstraintsEnabled

anand krish
la source
3
La documentation que vous avez publiée suggère: A good time to call this method is right after calling openOrCreateDatabase(File, SQLiteDatabase.CursorFactory) or in the onConfigure(SQLiteDatabase) callback. Donc, au lieu de onOpen, onConfiguresemble être le bon endroit.
Paul Woitaschek
4

Je ne pense pas que SQLite prenne en charge cela hors de la boîte. Ce que je fais dans mes applications, c'est:

  1. Créer une transaction
  2. Supprimer les données détaillées (waypoints dans votre exemple)
  3. Supprimer les données de base (pistes dans votre exemple)
  4. Valider la transaction en cas de succès

De cette façon, je suis sûr que toutes les données sont supprimées ou aucune.

Thorsten Dittmar
la source
Mais supprimez-vous des deux tables en utilisant une méthode?
jcrowson
Oui, je suis allé à peu près avec l'exemple Notes de l'API. Lorsque je dois supprimer ce qui serait une trace dans votre cas, je crée la transaction, je supprime la trace et les waypoints et je valide la transaction. C'est tout en une seule fois.
Thorsten Dittmar
4

Les déclencheurs sont pris en charge par Android et ce type de suppression en cascade n'est pas pris en charge par sqlite. Un exemple d'utilisation de déclencheurs sur Android peut être trouvé ici . Bien que l'utilisation de transactions, comme l'a déclaré Thorsten, est probablement aussi simple qu'un déclencheur.

Dave.B
la source
3

La version de SQLite dans Android 1.6 est 3.5.9 donc elle ne prend pas en charge les clés étrangères ...

http://www.sqlite.org/foreignkeys.html "Ce document décrit la prise en charge des contraintes de clé étrangère SQL introduites dans SQLite version 3.6.19."

Dans Froyo, c'est la version 3.6.22 de SQLite, donc ...

EDIT: pour voir la version de sqlite: adb shell sqlite3 -version

GBouerat
la source
Alors, y a-t-il un moyen de forcer de telles contraintes ... Je veux dire, y a-t-il un moyen de mettre à niveau la version de sqlite .. parce que nous devons avoir à prendre en charge la version du logiciel vers Android 2.1 qui a la version 3.5.9 de sqlite comme ci
NullPointerException
Non, vous devez tout gérer vous-même :(
GBouerat
1

Les clés étrangères avec "on delete cascade" sont prises en charge dans SQLite sous Android 2.2 et plus. Mais soyez prudent lorsque vous les utilisez: parfois une erreur est signalée lors du déclenchement d'une clé étrangère sur une colonne, mais le vrai problème réside soit dans une autre contrainte de clé étrangère de colonne dans la table enfant, soit dans une autre table qui fait référence à cette table.

On dirait que SQLite vérifie toutes les contraintes lors de l'activation de l'une d'entre elles. Il est en fait mentionné dans la documentation. Vérifications de contraintes DDL et DML.

Yar
la source
0

Si vous utilisez Android Room, procédez comme indiqué ci-dessous.

Room.databaseBuilder(context, AppDatabase::class.java, DATABASE_NAME)
    .addCallback(object : RoomDatabase.Callback() {
        // Called when the database has been opened.
        override fun onOpen(db: SupportSQLiteDatabase) {
            super.onOpen(db)
            //True to enable foreign key constraints
            db.setForeignKeyConstraintsEnabled(true)
        }

        // Called when the database is created for the first time. 
        override fun onCreate(db: SupportSQLiteDatabase) {
            super.onCreate(db)
        }
    }).build()
krishnakumarp
la source