Django-DB-Migrations: nie można ALTER TABLE, ponieważ ma oczekujące zdarzenia wyzwalające

122

Chcę usunąć null = True z TextField:

-    footer=models.TextField(null=True, blank=True)
+    footer=models.TextField(blank=True, default='')

Utworzyłem migrację schematu:

manage.py schemamigration fooapp --auto

Ponieważ niektóre kolumny stopki zawierają NULL, otrzymuję to, errorjeśli uruchomię migrację:

django.db.utils.IntegrityError: kolumna „footer” zawiera wartości null

Dodałem to do migracji schematu:

    for sender in orm['fooapp.EmailSender'].objects.filter(footer=None):
        sender.footer=''
        sender.save()

Teraz dostaję:

django.db.utils.DatabaseError: cannot ALTER TABLE "fooapp_emailsender" because it has pending trigger events

Co jest nie tak?

guettli
źródło
1
To pytanie jest podobne: stackoverflow.com/questions/28429933/ ... i otrzymałem odpowiedzi, które były dla mnie bardziej przydatne.
SpoonMeiser

Odpowiedzi:

139

Innym powodem może być to, że próbujesz ustawić kolumnę, NOT NULLgdy faktycznie ma już NULLwartości.

maazza
źródło
7
Aby rozwiązać ten problem, możesz użyć migracji danych lub ręcznie (powłoka manage.py) wejść i zaktualizować niezgodne wartości
mgojohn
@mgojohn Jak to robisz?
pyramidface
1
@pyramidface Jeśli nie jesteś zbyt wybredny, możesz po prostu zaktualizować wartości null w powłoce django. Jeśli szukasz czegoś bardziej formalnego i testowalnego, zależy to od używanych wersji. Jeśli używasz południa, zobacz: south.readthedocs.org/en/latest/tutorial/part3.html, a jeśli używasz migracji django, zobacz sekcję „migracje danych” tutaj: docs.djangoproject.com/en/1.8/topics/ migracje
mgojohn
131

Każda migracja odbywa się wewnątrz transakcji. W PostgreSQL nie wolno aktualizować tabeli, a następnie zmieniać schematu tabeli w jednej transakcji.

Musisz podzielić migrację danych i migrację schematu. Najpierw utwórz migrację danych za pomocą tego kodu:

 for sender in orm['fooapp.EmailSender'].objects.filter(footer=None):
    sender.footer=''
    sender.save()

Następnie utwórz migrację schematu:

manage.py schemamigration fooapp --auto

Teraz masz dwie transakcje i migracja w dwóch krokach powinna działać.

guettli
źródło
8
PostgreSQL prawdopodobnie zmienił swoje zachowanie w odniesieniu do takich transakcji, ponieważ udało mi się przeprowadzić migrację ze zmianami danych i schematu na mojej maszynie deweloperskiej (PostgreSQL 9.4), podczas gdy nie powiodła się na serwerze (PostgreSQL 9.1).
Bertrand Bordage
1
Prawie to samo dla mnie. Do dziś działał bezbłędnie w przypadku ponad 100 migracji (w tym ~ 20 migracji danych), dodając jednocześnie unikalne ograniczenie razem z migracją danych, usuwając wcześniejsze duplikaty. PostgreSQL 10.0
LinPy fan
Jeśli używasz operacji RunPython w migracji do migracji danych, musisz tylko upewnić się, że jest to ostatnia operacja. Django wie, że jeśli operacja RunPython jest ostatnia, otwiera własną transakcję.
Dougyfresh
1
@Dougyfresh czy to udokumentowana funkcja django?
guettli
Właściwie to nigdzie tego nie widzę, po prostu zauważyłem. docs.djangoproject.com/en/2.2/ref/migration-operations/…
Dougyfresh
9

Właśnie napotkałem ten problem. Możesz również użyć db.start_transaction () i db.commit_transaction () w migracji schematu, aby oddzielić zmiany danych od zmian schematu. Pewnie nie na tyle czysta, żeby mieć osobną migrację danych, ale w moim przypadku potrzebowałbym schematu, danych, a potem kolejnej migracji schematu, więc postanowiłem zrobić to wszystko naraz.

klimat
źródło
7
Problem z tym rozwiązaniem jest następujący: Co się stanie, jeśli migracja zakończy się niepowodzeniem po wykonaniu czynności db.commit_transaction ()? Wolę używać trzech migracji, jeśli tego potrzebujesz: schema-mig, data-mig, schema-mig.
guettli
5
Zobacz: django.readthedocs.io/en/latest/ref/migration-operations.html W bazach danych obsługujących transakcje DDL (SQLite i PostgreSQL) operacje RunPython nie mają automatycznie dodawanych żadnych transakcji poza transakcjami utworzonymi dla każdej migracji. Dlatego na przykład w PostgreSQL należy unikać łączenia zmian schematu i operacji RunPython w tej samej migracji. W przeciwnym razie mogą wystąpić błędy, takie jak OperationalError: nie można ALTER TABLE „mytable”, ponieważ ma oczekujące zdarzenia wyzwalające.
Iasmini Gomes
5

Na operacjach ustawiłem OGRANICZENIA:

operations = [
    migrations.RunSQL('SET CONSTRAINTS ALL IMMEDIATE;'),
    migrations.RunPython(migration_func),
    migrations.RunSQL('SET CONSTRAINTS ALL DEFERRED;'),
]
sluge
źródło
Lepiej używać SeparateDatabaseAndState
bdoubleu
0

Zmieniasz schemat kolumny. Ta kolumna stopki nie może już zawierać pustej wartości. Najprawdopodobniej w bazie danych są już zapisane puste wartości dla tej kolumny. Django zaktualizuje te puste wiersze w twojej bazie danych z pustych do obecnie domyślnej wartości za pomocą polecenia migrate. Django próbuje zaktualizować wiersze, w których kolumna stopki ma pustą wartość i jednocześnie zmienić schemat (nie jestem pewien).

Problem polega na tym, że nie możesz zmienić tego samego schematu kolumny, dla którego próbujesz zaktualizować wartości w tym samym czasie.

Jednym z rozwiązań byłoby usunięcie pliku migracji aktualizującego schemat. Następnie uruchom skrypt, aby zaktualizować wszystkie te wartości do wartości domyślnych. Następnie ponownie uruchom migrację, aby zaktualizować schemat. W ten sposób aktualizacja jest już wykonana. Migracja Django zmienia tylko schemat.

Uzzi Emuchay
źródło
1
Uruchomienie jakiegoś skryptu nie jest dla mnie rozwiązaniem. Mam kilka instancji bazy danych, a proces ciągłego wdrażania wywołuje po prostu „manage.py migrate”. To pytanie jest już prawidłowymi odpowiedziami, które działają dobrze.
guettli