2012-03-29 56 views
9

我在User s和Task s之間有多對多的關係。當我刪除TaskUser時,我想要清除「輔助表」(意思是便利多對多關係的表)。我如何爲此配置SQLAlchemy?Python的SQLAlchemy不會清除輔助(多對多)表?

這是一些示例python代碼,演示了我遇到的問題。注意:此代碼完全獨立,只需要sqlalchemy模塊。如果你複製並粘貼這段代碼,你應該可以運行它而沒有任何副作用,並且你自己也可以看到相同的行爲。該腳本的最後一行顯示,在刪除相應任務時,「輔助表」中的相關行未被刪除。在這個例子中所有的斷言都通過了。

from sqlalchemy import create_engine, Column, Integer, Text, Table, ForeignKey 
from sqlalchemy.ext.declarative import declarative_base 
from sqlalchemy.orm import Session, relationship 

Model = declarative_base() 

class User(Model): 
    __tablename__ = 'users' 
    id = Column('user_id', Integer, primary_key=True) 
    email = Column('email', Text, unique=True) 

    def __init__(self, email): 
     self.email = email 

user_tasks = Table('user_tasks', Model.metadata, 
    Column('user_id', Integer, ForeignKey('users.user_id')), 
    Column('task_id', Integer, ForeignKey('tasks.task_id'))) 

class Task(Model): 
    __tablename__ = 'tasks' 
    id = Column('task_id', Integer, primary_key=True) 
    description = Column('description', Text) 
    assigned_to = relationship('User', secondary=user_tasks, backref='tasks') 

    def __init__(self, description): 
     self.description = description 

if __name__ == '__main__': 
    engine = create_engine('sqlite:///:memory:') 
    Model.metadata.create_all(engine) 
    s = Session(engine) 
    the_user = User('user') 
    s.add(the_user) 
    s.commit() 
    assert s.query(User).all() == [the_user] 
    user_task = Task('user_one task') 
    user_task.assigned_to.append(the_user) 
    s.add(user_task) 
    s.commit() 
    assert s.query(Task).all() == [user_task] 
    assert s.query(user_tasks).all() == [(1,1)] 
    s.query(Task).delete() 
    s.commit() 
    assert s.query(Task).all() == [] 
    assert s.query(User).all() == [the_user] 
    assert s.query(user_tasks).all() == [(1,1)] # I was expecting [] . 

回答

12

delete(synchronize_session='evaluate')

的方法不-Python中關係的級聯報價 - 假定ON DELETE CASCADE爲其設定任何需要它的外鍵引用。會話需要過期(在commit()之後自動發生,或者調用expire_all()),以使依賴對象的狀態受到刪除或刪除孤兒級聯的正確表示。

也就是說,SQLAlchemy無法找到您要刪除的所有Task對象,並找出要從user_tasks中刪除的每一行 - 最好的方法是在外部使用ON DELETE CASCADE鍵(不使用MySQL的MyISAM表或SQLite的工作,如果沒有啓用外鍵):

http://docs.sqlalchemy.org/en/latest/core/constraints.html#on-update-and-on-delete

+0

我相信我的版本的sqlite3的(?3.7.3我認爲)支持外表?我需要專門打開它嗎?或者有什麼其他方式讓它與SQLite3一起工作? – Buttons840 2012-03-29 18:27:10

+0

SQLite3的功能在http://sqlite.org/foreignkeys.html中有描述。如果你想使用它,你可以使用SQLAlchemy中的事件監聽器在每次創建新連接時設置該PRAGMA(「連接」事件)。另一種方法是直接在每個Task對象上使用Session.delete(),SQLA將通過額外的努力來維護「assigned_to」關係。 – zzzeek 2012-03-29 22:55:39

+0

鏈接http://docs.sqlalchemy.org/en/latest/core/schema.html#on-update-and-on-delete是有效的,但它沒有找到「on-update-and-上delete'。它似乎丟失了,以及在版本0.9和1.0。 – fedorqui 2017-02-01 15:21:36

1

當我想你的代碼sqlite它不給錯誤,但是當我用MySQL數據庫試過我有錯誤

2012-03-29 10:43:15,330 INFO sqlalchemy.engine.base.Engine SELECT DATABASE() 
2012-03-29 10:43:15,331 INFO sqlalchemy.engine.base.Engine() 
2012-03-29 10:43:15,332 INFO sqlalchemy.engine.base.Engine SHOW VARIABLES LIKE 'character_set%%' 
2012-03-29 10:43:15,332 INFO sqlalchemy.engine.base.Engine() 
2012-03-29 10:43:15,333 INFO sqlalchemy.engine.base.Engine SHOW VARIABLES LIKE 'lower_case_table_names' 
2012-03-29 10:43:15,333 INFO sqlalchemy.engine.base.Engine() 
2012-03-29 10:43:15,334 INFO sqlalchemy.engine.base.Engine SHOW COLLATION 
2012-03-29 10:43:15,334 INFO sqlalchemy.engine.base.Engine() 
2012-03-29 10:43:15,337 INFO sqlalchemy.engine.base.Engine SHOW VARIABLES LIKE 'sql_mode' 
2012-03-29 10:43:15,338 INFO sqlalchemy.engine.base.Engine() 
2012-03-29 10:43:15,339 INFO sqlalchemy.engine.base.Engine DESCRIBE `user_tasks` 
2012-03-29 10:43:15,339 INFO sqlalchemy.engine.base.Engine() 
2012-03-29 10:43:15,355 INFO sqlalchemy.engine.base.Engine DESCRIBE `users` 
2012-03-29 10:43:15,355 INFO sqlalchemy.engine.base.Engine() 
2012-03-29 10:43:15,356 INFO sqlalchemy.engine.base.Engine DESCRIBE `tasks` 
2012-03-29 10:43:15,356 INFO sqlalchemy.engine.base.Engine() 
2012-03-29 10:43:15,357 INFO sqlalchemy.engine.base.Engine 
DROP TABLE user_tasks 
2012-03-29 10:43:15,357 INFO sqlalchemy.engine.base.Engine() 
2012-03-29 10:43:15,439 INFO sqlalchemy.engine.base.Engine COMMIT 
2012-03-29 10:43:15,440 INFO sqlalchemy.engine.base.Engine 
DROP TABLE users 
2012-03-29 10:43:15,440 INFO sqlalchemy.engine.base.Engine() 
2012-03-29 10:43:15,573 INFO sqlalchemy.engine.base.Engine COMMIT 
2012-03-29 10:43:15,573 INFO sqlalchemy.engine.base.Engine 
DROP TABLE tasks 
2012-03-29 10:43:15,573 INFO sqlalchemy.engine.base.Engine() 
2012-03-29 10:43:15,623 INFO sqlalchemy.engine.base.Engine COMMIT 
2012-03-29 10:43:15,624 INFO sqlalchemy.engine.base.Engine DESCRIBE `tasks` 
2012-03-29 10:43:15,624 INFO sqlalchemy.engine.base.Engine() 
2012-03-29 10:43:15,632 INFO sqlalchemy.engine.base.Engine ROLLBACK 
2012-03-29 10:43:15,633 INFO sqlalchemy.engine.base.Engine DESCRIBE `users` 
2012-03-29 10:43:15,633 INFO sqlalchemy.engine.base.Engine() 
2012-03-29 10:43:15,634 INFO sqlalchemy.engine.base.Engine ROLLBACK 
2012-03-29 10:43:15,634 INFO sqlalchemy.engine.base.Engine DESCRIBE `user_tasks` 
2012-03-29 10:43:15,634 INFO sqlalchemy.engine.base.Engine() 
2012-03-29 10:43:15,635 INFO sqlalchemy.engine.base.Engine ROLLBACK 
2012-03-29 10:43:15,635 INFO sqlalchemy.engine.base.Engine 
CREATE TABLE tasks (
    task_id INTEGER NOT NULL AUTO_INCREMENT, 
    description TEXT, 
    PRIMARY KEY (task_id) 
) 


2012-03-29 10:43:15,635 INFO sqlalchemy.engine.base.Engine() 
2012-03-29 10:43:15,732 INFO sqlalchemy.engine.base.Engine COMMIT 
2012-03-29 10:43:15,733 INFO sqlalchemy.engine.base.Engine 
CREATE TABLE users (
    user_id INTEGER NOT NULL AUTO_INCREMENT, 
    email VARCHAR(20), 
    PRIMARY KEY (user_id), 
    UNIQUE (email) 
) 


2012-03-29 10:43:15,733 INFO sqlalchemy.engine.base.Engine() 
2012-03-29 10:43:15,841 INFO sqlalchemy.engine.base.Engine COMMIT 
2012-03-29 10:43:15,842 INFO sqlalchemy.engine.base.Engine 
CREATE TABLE user_tasks (
    user_id INTEGER, 
    task_id INTEGER, 
    FOREIGN KEY(user_id) REFERENCES users (user_id), 
    FOREIGN KEY(task_id) REFERENCES tasks (task_id) 
) 


2012-03-29 10:43:15,842 INFO sqlalchemy.engine.base.Engine() 
2012-03-29 10:43:15,959 INFO sqlalchemy.engine.base.Engine COMMIT 
2012-03-29 10:43:15,964 INFO sqlalchemy.engine.base.Engine BEGIN (implicit) 
2012-03-29 10:43:15,965 INFO sqlalchemy.engine.base.Engine INSERT INTO users (email) VALUES (%s) 
2012-03-29 10:43:15,965 INFO sqlalchemy.engine.base.Engine ('user',) 
2012-03-29 10:43:15,966 INFO sqlalchemy.engine.base.Engine COMMIT 
2012-03-29 10:43:16,010 INFO sqlalchemy.engine.base.Engine BEGIN (implicit) 
2012-03-29 10:43:16,010 INFO sqlalchemy.engine.base.Engine SELECT users.user_id AS users_user_id, users.email AS users_email 
FROM users 
2012-03-29 10:43:16,011 INFO sqlalchemy.engine.base.Engine() 
2012-03-29 10:43:16,013 INFO sqlalchemy.engine.base.Engine INSERT INTO tasks (description) VALUES (%s) 
2012-03-29 10:43:16,014 INFO sqlalchemy.engine.base.Engine ('user_one task',) 
2012-03-29 10:43:16,015 INFO sqlalchemy.engine.base.Engine INSERT INTO user_tasks (user_id, task_id) VALUES (%s, %s) 
2012-03-29 10:43:16,016 INFO sqlalchemy.engine.base.Engine (1L, 1L) 
2012-03-29 10:43:16,016 INFO sqlalchemy.engine.base.Engine COMMIT 
2012-03-29 10:43:16,085 INFO sqlalchemy.engine.base.Engine BEGIN (implicit) 
2012-03-29 10:43:16,086 INFO sqlalchemy.engine.base.Engine SELECT tasks.task_id AS tasks_task_id, tasks.description AS tasks_description 
FROM tasks 
2012-03-29 10:43:16,086 INFO sqlalchemy.engine.base.Engine() 
2012-03-29 10:43:16,087 INFO sqlalchemy.engine.base.Engine SELECT user_tasks.user_id AS user_tasks_user_id, user_tasks.task_id AS user_tasks_task_id 
FROM user_tasks 
2012-03-29 10:43:16,088 INFO sqlalchemy.engine.base.Engine() 
2012-03-29 10:43:16,089 INFO sqlalchemy.engine.base.Engine SELECT user_tasks.user_id AS user_tasks_user_id, user_tasks.task_id AS user_tasks_task_id 
FROM user_tasks 
2012-03-29 10:43:16,089 INFO sqlalchemy.engine.base.Engine() 
[(1L, 1L)] 
2012-03-29 10:43:16,091 INFO sqlalchemy.engine.base.Engine DELETE FROM tasks 
2012-03-29 10:43:16,091 INFO sqlalchemy.engine.base.Engine() 
Traceback (most recent call last): 
    File "/tmp/test2.py", line 46, in <module> 
    s.query(Task).delete() 
    File "/home/npatel/.local/lib/python2.7/site-packages/sqlalchemy/orm/query.py", line 2283, in delete 
    result = session.execute(delete_stmt, params=self._params) 
    File "/home/npatel/.local/lib/python2.7/site-packages/sqlalchemy/orm/session.py", line 762, in execute 
    clause, params or {}) 
    File "/home/npatel/.local/lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 1399, in execute 
    params) 
    File "/home/npatel/.local/lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 1532, in _execute_clauseelement 
    compiled_sql, distilled_params 
    File "/home/npatel/.local/lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 1640, in _execute_context 
    context) 
    File "/home/npatel/.local/lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 1633, in _execute_context 
    context) 
    File "/home/npatel/.local/lib/python2.7/site-packages/sqlalchemy/engine/default.py", line 325, in do_execute 
    cursor.execute(statement, parameters) 
    File "/usr/lib64/python2.7/site-packages/MySQLdb/cursors.py", line 174, in execute 
    self.errorhandler(self, exc, value) 
    File "/usr/lib64/python2.7/site-packages/MySQLdb/connections.py", line 36, in defaulterrorhandler 
    raise errorclass, errorvalue 
sqlalchemy.exc.IntegrityError: (IntegrityError) (1451, 'Cannot delete or update a parent row: a foreign key constraint fails (`test1`.`user_tasks`, CONSTRAINT `user_tasks_ibfk_2` FOREIGN KEY (`task_id`) REFERENCES `tasks` (`task_id`))') 'DELETE FROM tasks'() 

所以在那之後我才知道,sqlite無法保持外鍵約束。現在我改變你的代碼並檢查輸出。

from sqlalchemy import create_engine, Column, Integer, Text, Table, ForeignKey, String 
from sqlalchemy.ext.declarative import declarative_base 
from sqlalchemy.orm import Session, relationship 

Model = declarative_base() 


class User(Model): 
    __tablename__ = 'users' 
    id = Column('user_id', Integer, primary_key=True) 
    email = Column('email', String(length=20), unique=True) 

    def __init__(self, email): 
     self.email = email 

user_tasks = Table('user_tasks', Model.metadata, 
    Column('user_id', Integer, ForeignKey('users.user_id')), 
    Column('task_id', Integer, ForeignKey('tasks.task_id'))) 


class Task(Model): 
    __tablename__ = 'tasks' 
    id = Column('task_id', Integer, primary_key=True) 
    description = Column('description', Text) 
    assigned_to = relationship('User', secondary=user_tasks, backref='tasks') 

    def __init__(self, description): 
     self.description = description 

if __name__ == '__main__': 
    engine = create_engine('mysql://test:[email protected]/test', echo=True) 
    Model.metadata.drop_all(engine) 
    Model.metadata.create_all(engine) 
    s = Session(engine) 
    the_user = User('user') 
    s.add(the_user) 
    s.commit() 
    assert s.query(User).all() == [the_user] 
    user_task = Task('user_one task') 
    user_task.assigned_to.append(the_user) 
    s.add(user_task) 
    s.commit() 
    assert s.query(Task).all() == [user_task] 
    assert s.query(user_tasks).all() == [(1, 1)] 
    the_user.tasks = [] 
    s.query(Task).delete() 
    s.commit() 
    assert s.query(Task).all() == [] 
    assert s.query(User).all() == [the_user] 
    assert s.query(user_tasks).all() == [(1,1)] # I was expecting [] .