Usuwając coś permanentnie z bazy danych musimy być bardzo ostrożni, bowiem przywrócenie danych jest bardzo trudne, czasem niemożliwe. Podstawową strukturę bazy danych powinno się budować na samym początku tworzenia aplikacji, z biegiem czasu rozbudowywać ją, ale unikać przebudowywania. Niestety są przypadki, gdzie trzeba przebudować jedną rzecz, co powoduje zmianę w wielu warstwach nie tyle aplikacji, co strukturze bazodanowej.
Dziś postaram się opisać, jakie kroki trzeba wykonać, aby bezpiecznie usunąć zdublowane rekordy z bazy danych nie tracąc żadnych danych:
Po wykonaniu kroku pierwszego zabieramy się za kolejny. Jest to najważniejszy moment naszych operacji. Aby ułatwić zrozumienie problemu, podam przykład z życia. Aplikacja posiadała poważny błąd, który umożliwiał zdublowanie użytkowników, ściślej: można było zdublować username. Za każdym razem, gdy użytkownik się logował i pisał komentarze, był ich właścicielem, ale comment_author posiadały różne ID tego samego użytkownika. Zaraz po skopiowaniu bazy danych spróbowałem przepisać ID autorów komentarzy na pierwszy rekord identyfikujący użytkownika, jaki istnieje w tabeli użytkowników. Skonstruowałem zapytanie:
UPDATE cms_comments JOIN cms_members AS user_original ON(user_original.user_id = comment_author) SET comment_author = ( SELECT user_first.user_id FROM cms_members AS user_first WHERE user_first.user_name = user_original.user_name ORDER BY user_first.user_id ASC LIMIT 0, 1)
Usunięcie zdublowanych użytkowników było już tylko formalnością. Teraz się okaże, dlaczego zależało mi na wyciągnięciu dokładnie pierwszego rekordu reprezentującego “unikalnego” użytkownika: poniższe zapytanie (ALTER IGNORE TABLE ADD UNIQUE) usunie wszystkie kolejne rekordy oznaczone jako duplicated:
ALTER IGNORE TABLE cms_members ADD UNIQUE INDEX(user_name);
Krótki komentarz z manuala do ALTER TABLE:
IGNORE is a MySQL extension to standard SQL. It controls how ALTER TABLE works if there are duplicates on unique keys in the new table or if warnings occur when strict mode is enabled. If IGNORE is not specified, the copy is aborted and rolled back if duplicate-key errors occur. If IGNORE is specified, only the first row is used of rows with duplicates on a unique key, The other conflicting rows are deleted. Incorrect values are truncated to the closest matching acceptable value.
Pisząc ostatnie posty związane z bazami danych, mam nadzieję, że komuś się przydadzą.
Im więcej nietypowych rzeczy programuję, tym więcej nietypowych problemów musze pokonać. Co powiecie na sumę 2 liczb, z których jedna jest wartością NULL powstałą w wyniku działania SUM() lub pochodnych, gdzie nie odnaleziono żadnego rekordu.
Badamy:
SELECT 1+2+3
>> 6
SELECT 1+2+NULL
>> NULL
SELECT COALESCE( NULL, 0 )
>> 0
Zatem analogicznie do powyższego przykładu:
UPDATE users SET user_points = user_points + COALESCE((SELECT SUM( ... ) WHERE ...), 0)
Punkty użytkownika już zawsze będą się sumowały poprawnie :-)
Ostatnimi czasy potrzebowałem danych z sąsiedniej tabeli przy UPDATE jedngo z pól w bazie danych. Danych do przetworzenia było sporo, więc zwracałem uwagę na wydajność zapytania. Aby zebrać potrzebne informacje, można użyć jednego ze sposobów:
Kartkując manual nie natrafiłem się w standardowej dokumentacji na nic konkretnego, aż nie spojrzałem na bardzo przydatne komentarze użytkowników. Okazało się, że przy UPDATE można wykonywać dowolne JOIN‘y, schemat jest następujący:
UPDATE TABLE JOIN another_table SET ...
W tym momencie mamy do dyspozycji wszystkie pola z dołączonej tabeli. Bardzo przydatne.
Przykład z życia.
Miałem za zadanie odznaczyć typy bukmacherskie na trafione, nietrafione, odwołane z przyczyn odwołania całego meczu piłkarskiego oraz te, które jeszcze nie mogą zostać oznaczone jako trafione lub nie, gdyż mecz się jeszcze nie odbył.
UPDATE typer_tickets_items LEFT JOIN typer_events ON(event_id = item_event) SET item_status = ( CASE WHEN event_status IS NULL THEN NULL # mecz nie zostal rozegrany WHEN event_status = -1 THEN -1 # mecz anulowany WHEN event_status = item_bet THEN 1 # typ trafiony ELSE 0 END # typ nietrafiony ) WHERE item_status IS NULL
Mam nadzieję, że komuś się przyda…
Wspólne wieczory z przyjaciółmi na wycieczce – to jest to – chyba każdy doświadczył gry w karty po nocach. Ale co jak na wycieczkę jedzie delegacja “komputerowców”, którzy nie biorą ze sobą nic oprócz ubrań, kawy i notebooków? Programista ma jedną przewagę nad innymi ludźmi – może stworzyć sobie własny wirtualny entertejment, kiedy pod ręką brak np… grę w kości kości.
Do szczęścia potrzebny był mi paint, notatnik i znajomość JavaScript. Stworzenie mini-gry kości zajęło niecałe 10 minut i fun na całą noc :-P
Zasady gry:
Chyba każdemu znane, ale przypomnijmy:
Kości to gra dla 2-4 osób, w której gracze turlają kostkami, by uzyskać określone układy oczek, za które otrzymuje się punkty. Wbrew pozorom nie jest to gra zależna tylko od szczęścia — liczy się w niej raczej umiejętność kalkulacji.
W każdej z 13 kolejek każdy z graczy ma do dyspozycji trzy rzuty kostkami. Pierwszy z nich odbywa się zawsze wszystkimi pięcioma kostkami, a w drugim i trzecim, które nie są obowiązkowe, wybrane kostki mogą zostać zatrzymane; rzut odbywa się wtedy tylko niezatrzymanymi.
Have fun ;p
Podobnie jak w PHP, baza danych MySQL ma odpowiednik if, czyli przypadków (inaczej serii warunków, instrukcji warunkowych). Różnicą między implementacją CASE‘a w MySQL i ifa PHP jest to, że baza danych zwraca konkretną wartość z case’a, a nie wykonuje dowolnej ilości dowolnych akcji.
CASE Syntax:
Najprostsza struktura CASE’aprzedstawia się nastepująco:
CASE WHEN [conditions] THEN ... ELSE ... END
Składnia powinna rozpocząć się słowem kluczowym CASE, a zakończyć END. Pomiędzy znajdują się warunki WHEN oraz operacja zwrócenia odpowiedniej wartości, która po nich następuje THEN (mamy możliwość uwzględnić nieskończenie wiele warunków). Jeżeli żaden warunek nie zostanie spełniony możemy użyć opcjonalnie ELSE.
Przykłady z życia.
Wyobraźmy sobie, że mamy posortować listę aukcji przedmiotów na Allegro od najtańszych, do najdroższych. Należy założyć, że są 2 typy aukcji: kup teraz i licytacja. Pole licytacji w bazie danych zawiera największą zaproponowaną kwotę przez użytkowników w procesie licytacji, a cena kup teraz ustalana jest przez sprzedającego. Są to dwa różne pola w bazie danych, a jedno kryterium sortowania, dlatego trzeba scalić cenę w jedną, wybierając odpowiednią. Musimy przewidzieć sytuację, w której aukcja jest typu kup teraz oraz licytacji, wówczas jeżeli najwyższa oferta jest większa od ceny kup teraz, wówczas wybieramy pole z największa propozycją:
SELECT ( CASE WHEN (auction_type = 'bidding' OR auction_price_bid > auction_price_buynow) THEN auction_price_bid ELSE auction_price_buynow END) AS auction_price
Stworzyliśmy pole auction_price, po którym można sortować aukcje od najtańszej do najdroższej i na odwrót.
Mam nadzieję, że krótki wpis przyda się początkującym. Nic więcej nie trzeba opisywać, temat wydaje się co najmniej trywialny.