Saya pasti melewatkan sesuatu yang sepele dengan opsi kaskade SQLAlchemy karena saya tidak bisa menghapus kaskade sederhana untuk beroperasi dengan benar - jika elemen induk dihapus, anak-anak tetap ada, dengan null
kunci asing.
Saya telah menempatkan kasus uji singkat di sini:
from sqlalchemy import Column, Integer, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class Parent(Base):
__tablename__ = "parent"
id = Column(Integer, primary_key = True)
class Child(Base):
__tablename__ = "child"
id = Column(Integer, primary_key = True)
parentid = Column(Integer, ForeignKey(Parent.id))
parent = relationship(Parent, cascade = "all,delete", backref = "children")
engine = create_engine("sqlite:///:memory:")
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
parent = Parent()
parent.children.append(Child())
parent.children.append(Child())
parent.children.append(Child())
session.add(parent)
session.commit()
print "Before delete, children = {0}".format(session.query(Child).count())
print "Before delete, parent = {0}".format(session.query(Parent).count())
session.delete(parent)
session.commit()
print "After delete, children = {0}".format(session.query(Child).count())
print "After delete parent = {0}".format(session.query(Parent).count())
session.close()
Keluaran:
Before delete, children = 3
Before delete, parent = 1
After delete, children = 3
After delete parent = 0
Ada hubungan sederhana satu-ke-banyak antara Induk dan Anak. Skrip membuat induk, menambahkan 3 anak, lalu melakukan. Selanjutnya, ini menghapus induknya, tetapi anaknya tetap ada. Mengapa? Bagaimana cara menghapus kaskade anak?
python
database
sqlalchemy
carl
sumber
sumber
Jawaban:
Masalahnya adalah sqlalchemy dianggap
Child
sebagai orang tua, karena di sanalah Anda mendefinisikan hubungan Anda (tidak peduli Anda menyebutnya "Anak" tentunya).Jika Anda mendefinisikan hubungan di
Parent
kelas, ini akan berhasil:(catatan
"Child"
sebagai string: ini diperbolehkan saat menggunakan gaya deklaratif, sehingga Anda bisa merujuk ke kelas yang belum ditentukan)Anda mungkin ingin menambahkan
delete-orphan
juga (delete
menyebabkan anak dihapus saat induknya dihapus,delete-orphan
juga menghapus semua anak yang "dihapus" dari induknya, meskipun induk tidak dihapus)EDIT: baru saja mengetahui: jika Anda benar - benar ingin menentukan hubungan di
Child
kelas, Anda dapat melakukannya, tetapi Anda harus menentukan kaskade di backref (dengan membuat backref secara eksplisit), seperti ini:(menyiratkan
from sqlalchemy.orm import backref
)sumber
Child
objek dariparent.children
, apakah objek itu harus dihapus dari database, atau hanya referensi ke induknya saja yang dihapus (misalnya, setelparentid
kolom ke null, alih-alih menghapus baris)relationship
tidak mendikte penyiapan orang tua-anak. Menggunakan diForeignKey
atas meja adalah yang mengaturnya sebagai anak. Tidak masalah jikarelationship
ada pada orang tua atau anak.@ Jawaban Steven bagus saat Anda menghapus
session.delete()
yang tidak pernah terjadi dalam kasus saya. Saya perhatikan bahwa sebagian besar waktu saya menghapussession.query().filter().delete()
(yang tidak memasukkan elemen ke dalam memori dan menghapus langsung dari db). Menggunakan metode ini sqlalchemycascade='all, delete'
tidak berfungsi. Namun ada solusinya:ON DELETE CASCADE
melalui db (catatan: tidak semua database mendukungnya).sumber
session.query().filter().delete()
dan berjuang untuk menemukan masalahnyapassive_deletes='all'
untuk mendapatkan anak-anak yang akan dihapus oleh kaskade database ketika orang tua dihapus. Denganpassive_deletes=True
, objek turunan dipisahkan (set induk ke NULL) sebelum induk dihapus, sehingga kaskade database tidak melakukan apa-apa.passive_deletes=True
berfungsi dengan benar dalam skenario ini.Pos yang cukup lama, tetapi saya hanya menghabiskan satu atau dua jam untuk ini, jadi saya ingin membagikan temuan saya, terutama karena beberapa komentar lain yang tercantum kurang tepat.
TL; DR
Berikan tabel anak asing atau modifikasi yang sudah ada, dengan menambahkan
ondelete='CASCADE'
:Dan salah satu dari hubungan berikut:
a) Ini di tabel induk:
b) Atau ini di meja anak:
Detail
Pertama, terlepas dari apa yang dikatakan jawaban yang diterima, hubungan orang tua / anak tidak dibuat dengan menggunakan
relationship
, itu dibuat dengan menggunakanForeignKey
. Anda dapat meletakkannyarelationship
di tabel induk atau anak dan itu akan bekerja dengan baik. Meskipun, tampaknya pada tabel anak, Anda harus menggunakanbackref
fungsi selain argumen kata kunci.Opsi 1 (lebih disukai)
Kedua, SqlAlchemy mendukung dua jenis cascading. Yang pertama, dan yang saya rekomendasikan, dibangun ke dalam database Anda dan biasanya berbentuk batasan pada deklarasi kunci asing. Di PostgreSQL terlihat seperti ini:
Ini berarti bahwa ketika Anda menghapus record dari
parent_table
, maka semua baris terkaitchild_table
akan dihapus untuk Anda oleh database. Ini cepat dan andal dan mungkin taruhan terbaik Anda. Anda mengatur ini di SqlAlchemy melaluiForeignKey
seperti ini (bagian dari definisi tabel anak):Ini
ondelete='CASCADE'
adalah bagian yang membuat diON DELETE CASCADE
atas meja.Kena kau!
Ada peringatan penting di sini. Perhatikan bagaimana saya
relationship
menentukan denganpassive_deletes=True
? Jika Anda tidak memilikinya, semuanya tidak akan berfungsi. Ini karena secara default ketika Anda menghapus catatan induk SqlAlchemy melakukan sesuatu yang sangat aneh. Ini menetapkan kunci asing dari semua baris anak keNULL
. Jadi jika Anda menghapus baris dariparent_table
whereid
= 5, maka pada dasarnya itu akan dijalankanMengapa Anda menginginkan ini, saya tidak tahu. Saya akan terkejut jika banyak mesin database bahkan mengizinkan Anda untuk mengatur kunci asing yang valid
NULL
, membuat yatim piatu. Sepertinya ide yang buruk, tapi mungkin ada kasus penggunaan. Bagaimanapun, jika Anda membiarkan SqlAlchemy melakukan ini, Anda akan mencegah database untuk dapat membersihkan anak-anak menggunakanON DELETE CASCADE
yang Anda atur. Ini karena bergantung pada kunci asing tersebut untuk mengetahui baris anak mana yang akan dihapus. Setelah SqlAlchemy mengatur semuanya keNULL
, database tidak dapat menghapusnya. Mengaturpassive_deletes=True
mencegah SqlAlchemyNULL
keluar dari kunci asing.Anda dapat membaca lebih lanjut tentang penghapusan pasif di dokumen SqlAlchemy .
pilihan 2
Cara lain yang dapat Anda lakukan adalah membiarkan SqlAlchemy melakukannya untuk Anda. Ini diatur menggunakan
cascade
argumen darirelationship
. Jika Anda memiliki hubungan yang ditentukan pada tabel induk, terlihat seperti ini:Jika hubungannya ada pada anak, lakukan seperti ini:
Sekali lagi, ini adalah anak sehingga Anda harus memanggil metode yang dipanggil
backref
dan meletakkan data kaskade di sana.Dengan ini, ketika Anda menghapus baris induk, SqlAlchemy akan benar-benar menjalankan pernyataan delete untuk Anda membersihkan baris anak. Ini kemungkinan tidak akan seefisien membiarkan database ini menangani jika untuk Anda jadi saya tidak merekomendasikannya.
Berikut adalah dokumen SqlAlchemy tentang fitur berjenjang yang didukungnya.
sumber
Column
di tabel anak sebagaiForeignKey('parent.id', ondelete='cascade', onupdate='cascade')
tidak berhasil? Saya berharap anak-anak akan dihapus ketika baris tabel induk mereka juga dihapus. Sebaliknya, SQLA menetapkan turunan ke aparent.id=NULL
atau membiarkannya "sebagaimana adanya", tetapi tidak ada penghapusan. Itu setelah awalnya mendefinisikanrelationship
dalam induk sebagaichildren = relationship('Parent', backref='parent')
ataurelationship('Parent', backref=backref('parent', passive_deletes=True))
; DB menunjukkancascade
aturan di DDL (bukti konsep berbasis SQLite3). Pikiran?backref=backref('parent', passive_deletes=True)
saya mendapatkan peringatan berikut:,SAWarning: On Parent.children, 'passive_deletes' is normally configured on one-to-many, one-to-one, many-to-many relationships only. "relationships only." % self
menyarankan itu tidak suka penggunaanpassive_deletes=True
dalam hubungan orang tua-anak satu-ke-banyak (jelas) ini untuk beberapa alasan.delete
mubazircascade='all,delete'
?delete
IS redundancascade='all,delete'
, karena menurut dokumen SQLAlchemy ,all
adalah sinonim untuk:save-update, merge, refresh-expire, expunge, delete
Steven benar karena Anda perlu membuat backref secara eksplisit, ini menghasilkan kaskade yang diterapkan pada induk (bukan diterapkan ke anak seperti dalam skenario pengujian).
Namun, mendefinisikan hubungan pada Child TIDAK membuat sqlalchemy menganggap Child sebagai orang tua. Tidak masalah di mana hubungan didefinisikan (anak atau induk), kunci asing yang menghubungkan dua tabel yang menentukan mana induk dan yang mana anak.
Masuk akal untuk tetap berpegang pada satu konvensi, dan berdasarkan tanggapan Steven, saya mendefinisikan semua hubungan anak saya dengan orang tua.
sumber
Saya kesulitan dengan dokumentasinya juga, tetapi ternyata docstringnya sendiri cenderung lebih mudah daripada manual. Misalnya, jika Anda mengimpor hubungan dari sqlalchemy.orm dan melakukan bantuan (hubungan), ini akan memberi Anda semua opsi yang dapat Anda tentukan untuk kaskade. Peluru untuk
delete-orphan
mengatakan:Saya menyadari masalah Anda lebih pada cara dokumentasi untuk mendefinisikan hubungan orang tua-anak. Tetapi tampaknya Anda mungkin juga mengalami masalah dengan opsi kaskade, karena
"all"
termasuk"delete"
."delete-orphan"
adalah satu-satunya opsi yang tidak termasuk dalam"all"
.sumber
help(..)
padasqlalchemy
obyek membantu banyak! Terima kasih :-)))! PyCharm tidak menampilkan apa pun di dok konteks, dan jelas lupa memeriksa filehelp
. Terima kasih banyak!Jawaban Steven jelas. Saya ingin menunjukkan implikasi tambahan.
Dengan menggunakan
relationship
, Anda membuat lapisan aplikasi (Flask) bertanggung jawab atas integritas referensial. Itu berarti proses lain yang mengakses database tidak melalui Flask, seperti utilitas database atau orang yang terhubung ke database secara langsung, tidak akan mengalami kendala tersebut dan dapat mengubah data Anda dengan cara yang merusak model data logis yang telah Anda rancang dengan susah payah. .Jika memungkinkan, gunakan
ForeignKey
pendekatan yang dijelaskan oleh d512 dan Alex. Mesin DB sangat pandai dalam benar-benar menegakkan batasan (dengan cara yang tidak dapat dihindari), jadi sejauh ini strategi terbaik untuk menjaga integritas data. Satu-satunya saat Anda perlu mengandalkan aplikasi untuk menangani integritas data adalah ketika database tidak dapat menanganinya, misalnya versi SQLite yang tidak mendukung kunci asing.Jika Anda perlu membuat tautan lebih lanjut di antara entitas untuk mengaktifkan perilaku aplikasi seperti menavigasi hubungan objek induk-anak, gunakan
backref
bersama denganForeignKey
.sumber
Jawaban dari Stevan sempurna. Tetapi jika Anda masih mendapatkan kesalahan. Percobaan lain yang mungkin di atas itu adalah -
http://vincentaudebert.github.io/python/sql/2015/10/09/cascade-delete-sqlalchemy/
Disalin dari tautan-
Tip cepat jika Anda mendapatkan masalah dengan ketergantungan kunci asing bahkan jika Anda telah menentukan penghapusan kaskade dalam model Anda.
Menggunakan SQLAlchemy, untuk menentukan penghapusan kaskade yang harus Anda miliki
cascade='all, delete'
di tabel induk Anda. Ok tapi kemudian ketika Anda menjalankan sesuatu seperti:Ini sebenarnya memicu kesalahan tentang kunci asing yang digunakan di tabel anak Anda.
Solusi yang saya gunakan untuk menanyakan objek dan kemudian menghapusnya:
Ini harus menghapus catatan induk Anda DAN semua anak yang terkait dengannya.
sumber
.first()
diperlukan? Kondisi filter apa yang mengembalikan daftar objek, dan semuanya harus dihapus? Bukankah menelepon.first()
hanya mendapat objek pertama? @PrashantJawaban Alex Okrushko hampir bekerja paling baik untuk saya. Digunakan ondelete = 'CASCADE' dan passive_deletes = True digabungkan. Tapi saya harus melakukan sesuatu yang ekstra untuk membuatnya berfungsi untuk sqlite.
Pastikan untuk menambahkan kode ini untuk memastikannya berfungsi untuk sqlite.
Dicuri dari sini: bahasa ekspresi SQLAlchemy dan SQLite di delete cascade
sumber
TLDR: Jika solusi di atas tidak berfungsi, coba tambahkan nullable = False ke kolom Anda.
Saya ingin menambahkan poin kecil di sini untuk beberapa orang yang mungkin tidak mendapatkan fungsi kaskade untuk bekerja dengan solusi yang ada (yang hebat). Perbedaan utama antara pekerjaan saya dan contohnya adalah saya menggunakan automap. Saya tidak tahu persis bagaimana hal itu dapat mengganggu pengaturan kaskade, tetapi saya ingin mencatat bahwa saya menggunakannya. Saya juga bekerja dengan database SQLite.
Saya mencoba setiap solusi yang dijelaskan di sini, tetapi baris di tabel anak saya terus memiliki kunci asing yang disetel ke nol ketika baris induk dihapus. Saya telah mencoba semua solusi di sini tetapi tidak berhasil. Namun, kaskade berfungsi setelah saya mengatur kolom anak dengan kunci asing ke nullable = False.
Di meja anak, saya menambahkan:
Dengan pengaturan ini, kaskade berfungsi seperti yang diharapkan.
sumber