¿Restricciones de clave externa en Android usando SQLite? en Eliminar cascada

91

Tengo dos tablas: tracks y waypoints, un track puede tener muchos waypoints, pero un waypoint se asigna a solo 1 track.

En la tabla de puntos de ruta, tengo una columna llamada "trackidfk" que inserta el track_ID una vez que se crea una pista, sin embargo, no he configurado las restricciones de clave externa en esta columna.

Cuando borro un track quiero borrar los waypoints asignados, ¿es posible ?. Leí sobre el uso de Triggers, pero no creo que sean compatibles con Android.

Para crear la tabla de 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
fuente

Respuestas:

237

Se admiten las restricciones de clave externa con cascada de eliminación, pero debe habilitarlas.
Acabo de agregar lo siguiente a mi SQLOpenHelper , que parece funcionar.

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

Declaré mi columna de referencia de la siguiente manera.

mailbox_id INTEGER REFERENCES mailboxes ON DELETE CASCADE
Phil
fuente
59
Lo que significa que sólo funciona desde Android 2.2 Froyo que tiene SQLite 3.6.22
Intrications
@RedPlanet: es porque la única vez que se aplica esta restricción es cuando se escribe algo en la base de datos. (No puede romper esta restricción si todo lo que hace es leer desde la base de datos) Además, Phil, en lugar del método onOpen, probablemente sea mejor hacerlo en el método onConfigure. Fuente: developer.android.com/reference/android/database/sqlite/…
Aneem
12
Google recomienda escribir PRAGMAdeclaraciones, onConfigure()pero requiere API nivel 16 (Android 4.1), y para entonces simplemente puede llamar setForeignKeyConstraintsEnabled.
Pang
También puede ser necesario considerar la habilitación de restricciones de clave externa en onCreate/ onDowngrade/ onUpgrade, que son anteriores onOpen. Consulte el código fuente en Android 4.1.1 .
Pang
1
@Natix, incluida la llamada a super, garantiza la funcionalidad correcta si se introduce una clase intermedia entre la clase implementada y su padre.
tbm
55

Desde Android 4.1 (API 16), SQLiteDatabase admite:

public void setForeignKeyConstraintsEnabled (boolean enable)
e.shishkin
fuente
26

Como dice la publicación de e.shishkin de API 16 en adelante, debe habilitar las restricciones de clave externa en el SqLiteOpenHelper.onConfigure(SqLiteDatabase)método utilizando eldb.setForeignKeyConstraintsEnabled(boolean)

@Override
public void onConfigure(SQLiteDatabase db){
    db.setForeignKeyConstraintsEnabled(true);
}
malcolm
fuente
10

Nunca una pregunta demasiado vieja para responder con una respuesta más completa.

@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);
}
Codificado
fuente
6

Lo que sea que @phil mencione es bueno. Pero puede usar otro método predeterminado disponible en la base de datos para configurar la clave externa. Eso es setForeignKeyConstraintsEnabled (verdadero).

@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)
    }
}

Para documentos, consulte SQLiteDatabase.setForeignKeyConstraintsEnabled

anand krish
fuente
3
La documentación que publicó sugiere: A good time to call this method is right after calling openOrCreateDatabase(File, SQLiteDatabase.CursorFactory) or in the onConfigure(SQLiteDatabase) callback. Entonces, en lugar de onOpen, onConfigureparece ser el lugar correcto.
Paul Woitaschek
4

No creo que SQLite admita esto de inmediato. Lo que estoy haciendo en mis aplicaciones es:

  1. Crear transacción
  2. Eliminar datos detallados (waypoints en su ejemplo)
  3. Eliminar datos maestros (pistas en su ejemplo)
  4. Comprometer la transacción en caso de éxito

De esa manera, estoy seguro de que se eliminan todos los datos o ninguno.

Thorsten Dittmar
fuente
¿Pero está eliminando de ambas tablas usando un método?
jcrowson
Sí, seguí más o menos el ejemplo de Notes de la API. Cuando debo eliminar lo que sería una pista en su caso, creo la transacción, elimino la ruta y los puntos de referencia y confirmo la transacción. Eso es todo de una vez.
Thorsten Dittmar
4

Los activadores son compatibles con Android y ese tipo de eliminación en cascada no es compatible con sqlite. Aquí puede encontrar un ejemplo del uso de activadores en Android . Aunque usar transacciones como dijo Thorsten es probablemente tan fácil como un disparador.

Dave.B
fuente
3

La versión de SQLite en Android 1.6 es 3.5.9, por lo que no admite claves externas ...

http://www.sqlite.org/foreignkeys.html "Este documento describe la compatibilidad con las restricciones de clave externa de SQL introducidas en la versión 3.6.19 de SQLite".

En Froyo es SQLite versión 3.6.22, así que ...

EDITAR: para ver la versión de sqlite: adb shell sqlite3 -version

GBouerat
fuente
Entonces, ¿hay alguna forma de forzar tales restricciones? Quiero decir, ¿hay alguna forma de actualizar la versión de sqlite ... porque debemos tener que admitir la versión de software para Android 2.1 que tiene la versión de sqlite 3.5.9 como la anterior
NullPointerException
No, tienes que manejar todo tú mismo :(
GBouerat
1

Las claves externas con "en cascada de eliminación" son compatibles con SQLite en Android 2.2 y versiones posteriores. Pero tenga cuidado al usarlos: a veces se informa un error al activar una clave externa en una columna, pero el problema real radica en otra restricción de clave externa de columna en la tabla secundaria, o en alguna otra tabla que hace referencia a esta tabla.

Parece que SQLite comprueba todas las restricciones al activar una de ellas. De hecho, se menciona en la documentación. Verificaciones de restricciones DDL versus DML.

Yar
fuente
0

Si está utilizando Android Room, haga lo que se muestra a continuación.

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
fuente