Devo estar faltando algo trivial com as opções de cascata do SQLAlchemy porque não consigo fazer uma exclusão em cascata simples operar corretamente - se um elemento pai for excluído, os filhos persistem, com null
chaves estrangeiras.
Coloquei um caso de teste conciso aqui:
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()
Resultado:
Before delete, children = 3
Before delete, parent = 1
After delete, children = 3
After delete parent = 0
Existe uma relação simples de um para muitos entre pai e filho. O script cria um pai, adiciona 3 filhos e depois confirma. Em seguida, ele exclui o pai, mas os filhos persistem. Por quê? Como faço para excluir os filhos em cascata?
python
database
sqlalchemy
Carl
fonte
fonte
Respostas:
O problema é que sqlalchemy considera
Child
como o pai, porque é onde você definiu seu relacionamento (não importa se você o chamou de "Filho" é claro).Se você definir o relacionamento na
Parent
classe, funcionará:(observe
"Child"
como uma string: isso é permitido ao usar o estilo declarativo, para que você possa se referir a uma classe que ainda não foi definida)Você pode querer adicionar
delete-orphan
também (delete
faz com que os filhos sejam excluídos quando o pai é excluído,delete-orphan
também exclui todos os filhos que foram "removidos" do pai, mesmo se o pai não for excluído)EDIT: acabei de descobrir: se você realmente deseja definir o relacionamento na
Child
classe, pode fazê-lo, mas terá que definir a cascata no backref (criando o backref explicitamente), assim:(implicando
from sqlalchemy.orm import backref
)fonte
Child
objeto deparent.children
, esse objeto deve ser excluído do banco de dados ou apenas sua referência ao pai deve ser removida (ou seja, definirparentid
coluna como nula, em vez de excluir a linha)relationship
não dita a configuração pai-filho. UsarForeignKey
sobre a mesa é o que o configura como criança. Não importa serelationship
é do pai ou da criança.A resposta de @Steven é boa quando você está deletando através do
session.delete()
que nunca acontece no meu caso. Percebi que na maioria das vezes eu apago atravéssession.query().filter().delete()
(o que não coloca elementos na memória e apaga diretamente do db). Usar este método sqlalchemycascade='all, delete'
não funciona. Porém, há uma solução:ON DELETE CASCADE
por meio de db (nota: nem todos os bancos de dados o suportam).fonte
session.query().filter().delete()
e lutando para encontrar o problemapassive_deletes='all'
para que os filhos fossem excluídos pela cascata do banco de dados quando o pai fosse excluído. Compassive_deletes=True
, os objetos filhos estavam sendo desassociados (pai definido como NULL) antes de o pai ser excluído, portanto, a cascata do banco de dados não estava fazendo nada.passive_deletes=True
funciona corretamente neste cenário.Postagem bem antiga, mas acabei de gastar uma ou duas horas nisso, então gostaria de compartilhar minha descoberta, especialmente porque alguns dos outros comentários listados não estão certos.
TL; DR
Dê à tabela filho um estrangeiro ou modifique o existente, adicionando
ondelete='CASCADE'
:E um dos seguintes relacionamentos:
a) Isso na tabela pai:
b) Ou isso na mesa infantil:
Detalhes
Em primeiro lugar, apesar do que diz a resposta aceita, a relação pai / filho não se estabelece usando
relationship
, é estabelecida usandoForeignKey
. Você pode colocar orelationship
nas tabelas pai ou filho e funcionará bem. Embora, aparentemente nas tabelas filho, você tenha que usar abackref
função além do argumento de palavra-chave.Opção 1 (preferencial)
Em segundo lugar, SqlAlchemy oferece suporte a dois tipos diferentes de cascata. O primeiro, e o que eu recomendo, está embutido em seu banco de dados e geralmente assume a forma de uma restrição na declaração de chave estrangeira. No PostgreSQL, é assim:
Isso significa que quando você exclui um registro de
parent_table
, todas as linhas correspondentes emchild_table
serão excluídas para você pelo banco de dados. É rápido e confiável e provavelmente sua melhor aposta. Você configura isso no SqlAlchemyForeignKey
assim (parte da definição da tabela filho):A
ondelete='CASCADE'
é a parte que cria oON DELETE CASCADE
sobre a mesa.Peguei vocês!
Há uma advertência importante aqui. Observe como eu tenho um
relationship
especificado compassive_deletes=True
? Se você não tiver isso, a coisa toda não funcionará. Isso ocorre porque, por padrão, quando você exclui um registro pai, o SqlAlchemy faz algo realmente estranho. Ele define as chaves estrangeiras de todas as linhas filhas comoNULL
. Portanto, se você excluir uma linha deparent_table
ondeid
= 5, ela basicamente executaráPor que você iria querer isso, eu não tenho ideia. Eu ficaria surpreso se muitos mecanismos de banco de dados permitissem que você definisse uma chave estrangeira válida para
NULL
, criando um órfão. Parece uma má ideia, mas talvez haja um caso de uso. De qualquer forma, se você deixar o SqlAlchemy fazer isso, você impedirá que o banco de dados seja capaz de limpar os filhos usando oON DELETE CASCADE
que você configurou. Isso ocorre porque ele depende dessas chaves estrangeiras para saber quais linhas filho excluir. Depois que o SqlAlchemy definir todos comoNULL
, o banco de dados não poderá excluí-los. A configuraçãopassive_deletes=True
evita que o SqlAlchemyNULL
saia das chaves estrangeiras.Você pode ler mais sobre exclusões passivas nos documentos do SqlAlchemy .
opção 2
A outra maneira de fazer isso é deixar o SqlAlchemy fazer isso por você. Isso é configurado usando o
cascade
argumento dorelationship
. Se você tiver o relacionamento definido na tabela pai, será assim:Se o relacionamento é por conta da criança, você faz assim:
Novamente, este é o filho, então você deve chamar um método chamado
backref
e colocar os dados em cascata nele.Com isso no lugar, quando você exclui uma linha pai, SqlAlchemy irá realmente executar instruções delete para você limpar as linhas filho. Provavelmente, isso não será tão eficiente quanto deixar esse banco de dados manipular se for para você, então eu não o recomendo.
Aqui estão os documentos do SqlAlchemy sobre os recursos em cascata suportados.
fonte
Column
na tabela filho comoForeignKey('parent.id', ondelete='cascade', onupdate='cascade')
não funciona também? Eu esperava que os filhos fossem excluídos quando a linha da tabela pai também fosse excluída. Em vez disso, o SQLA define os filhos como aparent.id=NULL
ou os deixa "como estão", mas não exclui. Isso depois de definir originalmente orelationship
no pai comochildren = relationship('Parent', backref='parent')
ourelationship('Parent', backref=backref('parent', passive_deletes=True))
; O DB mostracascade
regras no DDL (prova de conceito baseada em SQLite3). Pensamentos?backref=backref('parent', passive_deletes=True)
, recebo o seguinte aviso:,SAWarning: On Parent.children, 'passive_deletes' is normally configured on one-to-many, one-to-one, many-to-many relationships only. "relationships only." % self
sugerindo que não gosto do uso depassive_deletes=True
nesta (óbvia) relação pai-filho (óbvia) um para muitos por algum motivo.delete
redundante emcascade='all,delete'
?delete
É redundante emcascade='all,delete'
, pois de acordo com os documentos do SQLAlchemy ,all
é sinônimo de:save-update, merge, refresh-expire, expunge, delete
Steven está correto ao dizer que você precisa criar explicitamente o backref, o que resulta na aplicação da cascata no pai (ao contrário de ser aplicada no filho como no cenário de teste).
No entanto, definir o relacionamento no filho NÃO faz a sqlalchemy considerar o filho o pai. Não importa onde o relacionamento está definido (filho ou pai), é a chave estrangeira que liga as duas tabelas que determina qual é o pai e qual é o filho.
No entanto, faz sentido seguir uma convenção e, com base na resposta de Steven, estou definindo todos os meus relacionamentos de filho no pai.
fonte
Eu também tive problemas com a documentação, mas descobri que as próprias docstrings tendem a ser mais fáceis do que o manual. Por exemplo, se você importar relacionamento de sqlalchemy.orm e ajudar (relacionamento), ele fornecerá todas as opções que você pode especificar para cascata. O marcador para
delete-orphan
diz:Sei que seu problema era mais com a forma como a documentação para definir as relações pai-filho. Mas parecia que você também pode estar tendo problemas com as opções em cascata, porque
"all"
inclui"delete"
."delete-orphan"
é a única opção que não está incluída em"all"
.fonte
help(..)
nossqlalchemy
objetos ajuda muito! Obrigado :-))) ! O PyCharm não mostra nada nas docas de contexto e simplesmente se esqueceu de verificar ohelp
. Muito obrigado!A resposta de Steven é sólida. Eu gostaria de apontar uma implicação adicional.
Ao usar
relationship
, você está tornando a camada de aplicativo (Flask) responsável pela integridade referencial. Isso significa que outros processos que acessam o banco de dados não por meio do Flask, como um utilitário de banco de dados ou uma pessoa se conectando ao banco de dados diretamente, não terão essas restrições e podem alterar seus dados de uma forma que quebra o modelo de dados lógico que você trabalhou tão arduamente para projetar .Sempre que possível, use a
ForeignKey
abordagem descrita por d512 e Alex. O mecanismo de banco de dados é muito bom em realmente impor restrições (de maneira inevitável), portanto, essa é de longe a melhor estratégia para manter a integridade dos dados. O único momento em que você precisa confiar em um aplicativo para lidar com a integridade dos dados é quando o banco de dados não pode lidar com eles, por exemplo, versões do SQLite que não oferecem suporte a chaves estrangeiras.Se você precisar criar vínculos adicionais entre entidades para habilitar comportamentos de aplicativos, como navegar em relacionamentos de objeto pai-filho, use
backref
em conjunto comForeignKey
.fonte
A resposta de Stevan é perfeita. Mas se você ainda está recebendo o erro. Outra possível tentativa além disso seria -
http://vincentaudebert.github.io/python/sql/2015/10/09/cascade-delete-sqlalchemy/
Copiado do link-
Dica rápida se você tiver problemas com uma dependência de chave estrangeira, mesmo que tenha especificado uma exclusão em cascata em seus modelos.
Usando SQLAlchemy, para especificar uma exclusão em cascata que você deve ter
cascade='all, delete'
em sua tabela pai. Ok, mas quando você executa algo como:Na verdade, ele dispara um erro sobre uma chave estrangeira usada nas tabelas filhas.
A solução que usei para consultar o objeto e excluí-lo:
Isso deve excluir seu registro pai E todos os filhos associados a ele.
fonte
.first()
necessário ligar ? Quais condições de filtro retornam uma lista de objetos e tudo deve ser excluído? A chamada não.first()
obtém apenas o primeiro objeto? @PrashantA resposta de Alex Okrushko quase funcionou melhor para mim. Ondelete = 'CASCADE' e passive_deletes = True combinados usados. Mas eu tive que fazer algo extra para fazê-lo funcionar no sqlite.
Certifique-se de adicionar este código para garantir que ele funcione para sqlite.
Roubado daqui: linguagem de expressão SQLAlchemy e SQLite em cascata de exclusão
fonte
TLDR: se as soluções acima não funcionarem, tente adicionar nullable = False à sua coluna.
Eu gostaria de acrescentar um pequeno ponto aqui para algumas pessoas que podem não conseguir que a função cascata funcione com as soluções existentes (que são ótimas). A principal diferença entre o meu trabalho e o exemplo é que usei o automap. Não sei exatamente como isso pode interferir na configuração das cascatas, mas quero ressaltar que usei. Também estou trabalhando com um banco de dados SQLite.
Tentei todas as soluções descritas aqui, mas as linhas em minha tabela filho continuaram a ter sua chave estrangeira definida como nula quando a linha pai foi excluída. Eu tentei todas as soluções aqui sem sucesso. No entanto, a cascata funcionou assim que defini a coluna filho com a chave estrangeira como nullable = False.
Na mesa filho, adicionei:
Com essa configuração, a cascata funcionou conforme o esperado.
fonte