Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: Softdeleting i relacje
Forum PHP.pl > Forum > PHP
markonix
Softdeleting jest mi potrzebny, uwielbiam kaskadowe usuwanie przez bazę (klucze obce) ale musi zostać możliwość przywrócenia.

Tylko czy da się go naprawdę dobrze użyć aby uniknąć problemów przy relacjach.
Powiedzmy mamy:
- szkoła
- budynek
- klasa
Usuwamy klasę (salę) i jest ok.
Gdy jednak usuniemy budynek to przy wyświetlaniu wszystkich klas te z usuniętego budynku będą się wyświetlały.
Sytuacja taka sama gdy usuniemy całą szkołę.

Możliwości widzę dwie (mam nadzieje, że jest jakaś trzecia):
- komplikuje każde możliwe zapytanie i JOINuje obiekty rodzica
- każdorazowo robię softdeleting na relacjach (na dzieciach)

Drugie rozwiązanie ma jednak wady:
- Przy przywracaniu danych czyli gdy robimy to do czego SD jest stworzony mamy małą zagwozdkę, nie wiemy które klasy były usunięte przed usunięciem budynku
- Jest też oczywiście troszkę więcej pracy aniżeli przy pierwszej opcji gdy modyfikujemy tylko jeden wiersz.

Jedyne co mi przychodzi go głowy to zamiast flagi 0/1 zapisywać 0/time() co praktykuje się coraz częściej (daje więcej informacji, a nie wymusza zmian zapytań).
Wtedy mógłbym jakoś wzrokowo ocenić, które relacje przywrócić, a które nie. Czemu wzrokowo? Bo powiedzmy, że usuwamy budynek, który ma 1000 klas i przy usuwaniu robimy jakieś dodatkowe operacje więc na pewno nie wszystko wykona się w jednej sekundzie.

Jak rozwiązuje się to w dobrych aplikacjach, które podejście jest najczęściej praktykowane?

edit:

Jak zwykle pisząc posta coś mi zaświtało.

Jeżeli chodzi o problem z time() to zamiast każdorazowo go generować to lepiej go ustalić raz na początku operacji i robić update za pomocą tej zmiennej na wszystkich obiektach w relacji.

Zastanawiam się czy dałoby się ustawić jakiś trigger na bazię aby usuwając budynek wykonał zapytanie `deleted_at` = @time WHERE `bulding_id` = @parent AND `deleted_at` = 0
Czy jednak nie bawić się w taką wygodną automatyzacje i robić to z poziomu modeli/php.
lukaskolista
Szczerze mówiąc nie do końca zrozumiałem problem, ale:
- jeżeli chcesz usuwać kaskadowo rekordy, to utwórz odpowiednie klucze obce z ON DELETE CASCADE
- jak chcesz coś przywracać w przypadku problemów, to używaj transakcji
viking
A jeśli chcesz archiwizować to utwórz trigger który będzie wykonywał kopiowanie.
Pyton_000
Przecież do softdelete wystarczy dodatkowa kolumna z time i jeśli jest pusta to rekord istnieje a jeśli nie to nie
markonix
Cytat(lukaskolista @ 3.10.2016, 15:21:42 ) *
Szczerze mówiąc nie do końca zrozumiałem problem, ale:
- jeżeli chcesz usuwać kaskadowo rekordy, to utwórz odpowiednie klucze obce z ON DELETE CASCADE
- jak chcesz coś przywracać w przypadku problemów, to używaj transakcji

ON DELETE CASCADE nie jest mechanizmem, który da się wg mojej wiedzy użyć przy soft delete.
Transakcje nie pozwalają na przywracanie usuniętych danych przez użytkownika, to tylko rollback..

@Pyton_000 to już w ogóle mam wrażenie, że przeczytałeś tylko tytuł tematu..
Pyton_000
Czepiasz się tongue.gif Dobra rozwinę, niech Ci będzie tongue.gif

Generalnie z softdelete jeste taki problem że całość relacji musisz mieć ogarnięte na poziomie aplikacyjnym. Oczywiście na poziomie BD też dobrze to mieć.
Tak jak pisałem timestamp czy tam datetime jako wyznacznik czy rekord jest usunięty czy nie.

Jeśli masz jasne relacje to nie widzę problemu. ustawiasz ten sam timestamp w rekordzie nadrzędnym jak i w dzieciach. Sprawa się komplikuje jak jest relacja dużo bardziej zagłębiona.
Dla tego dobrze by było sobie pisać całe metody do softdelete gdzie ręcznie powybierasz tabele powiązane i poustawiasz dane.
markonix
No czy użyje timestamp czy int z unixem wydaje mi się mało znaczące (choć bardziej int mi przypadł go gustu jako bardziej optymalny, i jakiś bardziej naturalny przy zapytaniach).
  1. deleted_at > 0 vs deleted_at > no i właśnie co, musimy nullować


"Oczywiście na poziomie BD też dobrze to mieć"
No to pytanie w jaki sposób, bo wydaje mi się, że MySQL nie ma żadnych mechanizmów, które wspomogą.

Przy relacjach wielopoziomowych raczej procedura taka sama, aż może pokusić o jakąś funkcję w oparciu o rekurencje.

Ciekawostką dla mnie jest, że jak jakiekolwiek szukam informacje o koncepcjach to ciągle trafiam na fora Laravela tak jakby to był ich "wynalazek".. hehe.
Pilsener
1. Dziś nikt już prawie nie robi tego ręcznie, są ORMy (np. doctrine), które to wspierają.
2. Przy tradycyjnym usuwaniu też masz przecież identyczne problemy z relacjami, trzeba zaimplementować:
- kaskadowość
- sieroty (orphan removal)
- transakcje
Jedyna różnica, to, że zamiast DELETE robisz UPDATE a do każdego pobieranego rekordu dodajesz where "deleted_timestamp is NULL"
3. Lepiej jak najmniej rzeczy robić w bazie z prostego powodu - trudność w debugowaniu czy zarządzaniu wersjami
4. Prawdziwy problem przy tak zwanym soft-delete leży gdzie indziej. Masz np. tabelę "user", login unikalny, uzytkownik "abc" się zarejestrował, usunął konto, po czym kolejny użytkownik "abc" chce się zarejstrować - co teraz? Przykład oczywiście banalny, ale używa się dużo bardziej skomplikowanych kluczy. Identycznie przy walidacji.
markonix
1. No właśnie np. Eloquent Laravela natywnie wspiera softdeleting ale tylko na poziomie jednego wiersza. Jeżeli chodzi o kaskadowość musisz sobie ją zaimplementować samodzielnie tak więc zostawiają tu swobodę programiście więc problem najlepszego rozwiązania pozostaje otwarty.

2. Kaskadowość zapewnia odpowiedni klucz - poleci wszystko raz a porządnie. Klucze też zabezpieczają przez pozostawieniem sierot, nie da się ich mieć jeżeli nigdy się nie wykonuje zapytań z jakimś IGNORE keys.

4. Tak, przykład banalny ale dość często na niego trafiam i też chętnie poznam jakieś ciekawe metody.
Zazwyczaj infantylnie po prostu dodaje jakiś stały przedrostek typu #DELETED#, potem jakby tamten poprzedni user chciał wrócić to musiałby mieć zmieniony login.

edit:

Problem powrócił jak bumerang tym razem już konkretne w Laravelu.
W CI sobie stosunkowo lepiej w nim poradziłem, ale już z Eqoluent jest troszkę trudniej.

  1. protected static function boot() {
  2. parent::boot();
  3.  
  4. static::deleting(function($team) {
  5.  
  6. //$team->members()->delete();
  7. foreach ($team->seasons as $season) {
  8. $season->delete();
  9. }
  10. });
  11. }


Przy usuwaniu jeżeli użyje tej pierwszej, zakomentowanej instrukcji usuwają się ładnie "members" ale nie odpalane są już metody/eventy dzieci czyli jeżeli w members mam kolejne relacje pozostaną nietknięte.
Jest to zgodne z tym co jest napisane w dokumentacji. Tak więc muszę nieoptymalnie iterować po podrzędnych obiektach i o ile optymalizacja mnie mniej martwi to niestety - w pierwszy przypadku deleted_at ustawiało się na jedną, wspólną wartość. Przy iteracji już może być i kilka sekund różnicy gdy wierszy jest sporo.

W ogóle nie potrafię znaleźć informacji w kontekście Laravela o fragmentarycznym przywracaniu rekordów. Metoda restore przywraca wszystko, na szczęście przyjmuje warunki tak więc można sobie dopisać warunek z datą no ale ze względu na powyższe zwykły warunek nie starszy, musiałby to być jakiś like, beetween tak aby brać poprawkę na te kilka sekund różnicy. Logiczne jest, że przy ewentualnym przywróceniu nie chcemy przywrócić team'u z usuniętym miesiąc wcześniej członkami.
Pyton_000
A patrzyłeś do usuwania tego? https://packagist.org/packages/iatstuti/lar...de-soft-deletes

Co do przywracania to tak jak mówisz, warunek z datą +/- np. 1 min względem daty rodzica i pozamiatane.
markonix
Tak, widziałem jego "trait" choć jak patrzę na kod to wydaje mi się, że on nie rozwiązuje problemu różnych timestampów.
Może wypróbuje.

Ale jeszcze tak sobie myślę z tym restore...
User usunął 12 members z team, w każdym miesiącu po jednym i w końcu przypadkiem wywalił sobie w grudniu cały team i teraz prosi o przywrócenie.
I tak na logikę, nie mógł usunąć niczego później niż deleted_at team'u. Więc zamiast dodawania marginesu błędu, który jest troszkę słabym rozwiązaniem można po po prostu zrobić restore na where deleted_at >= teams.deleted_at.
Jedynie ważne jest tu aby rodzic miał najwcześniejszy timestamp, a dzieci, wnuki mogą mieć i 10 min opóźnienie.
Pyton_000
Nie no jasne że nie będzie czasu większego niż rodzica. No chyba że usuwany w odwrotnej kolejności,
markonix
Programowanie jest takie przyjemne ale czasem trzeba przysiąść pół dnia nad taką mało efektowną rozkminą :/

W każdym razie ostateczny rezultat.

  1. public function delete()
  2. {
  3. parent::delete();
  4.  
  5. foreach ($this->members as $member) {
  6. $member->delete();
  7. }
  8. }


Lub to co wcześniej tylko zamiast deleting lepszy jest deleted jako, że zapewni właściwą chronologie z tym co wcześniej napisałem.
To jest wersja lo-fi głównej zawartości. Aby zobaczyć pełną wersję z większą zawartością, obrazkami i formatowaniem proszę kliknij tutaj.
Invision Power Board © 2001-2025 Invision Power Services, Inc.