SQL

Idempotent

Idempotent – idempotentny, czyli jaki?

Idempotentny, idempotentnośćidempotent – czyli właściwie co?

W dzisiejszym odcinku zajmiemy się dobrą praktyką programistyczną, która polega na pisaniu idempotentnego kodu.

Z tego odcinka dowiesz się:

  • co to jest idempotentny kod i dlaczego warto go pisać?
  • jak pisać skrypty bazodanowe odporne na wielokrotne uruchamianie;
  • jakie są dobre praktyki związane z pisaniem usług sieciowych typu REST;
  • co ma wspólnego rodzenie dzieci z informatyką?
[SprawnyProgramista_intro]

Idempotentny – w uproszczeniu oznacza to, że niezależnie od tego, ile razy wykonamy daną operację, to wynik zawsze będzie taki sam.

Zobaczmy to na kilku przykładach, żeby było to bardziej zrozumiałe.
Przyjrzymy się idempotentności w kontekście matematyki, codziennego życia oraz oczywiście informatyki.
Jeżeli chodzi o programowanie, przedstawię Ci kilka przykładów z wykorzystaniem baz danych oraz usług sieciowych typu REST.

Idempotent – matematyka

  1. Mnożenie przez zero
    Niezależnie od tego, ile razy pomnożymy coś przez zero, wynik zawsze będzie taki sam – zero.
    Przykładowo: 10*0=0, 100*0=0 itp.
  2. Mnożenie przez jeden
    Mnożenie przez jeden nie zmienia wyniku, dlatego bez względu na to, ile razy byśmy nie pomnożyli naszej liczby przez jeden, to wynikiem zawsze będzie ta liczba.
    Przykładowo: 10*1=10, 100*1=100 itp.

Idempotent – prawdziwe/codzienne życie

W prawdziwym życiu też mamy przykłady idempotentnych zachowań.

  1. Ciąża
    Jeżeli kobieta jest już  raz w ciąży, to bardziej już w ciąży nie będzie.
  2. Śmierć
    Podobnie jest ze śmiercią. Raz zabitej osoby nie można już bardziej zabić… choćbyś nie wiem, jak bardzo się starał. Pomijamy tu oczywiście filmy z serii: „Zabili go i uciekł”.

Idempotent – a informatyka

No dobrze, ale co to wszystkiego ma wspólnego z programowaniem?

Informatyka, a w szczególności różnego rodzaju systemy rozproszone bardzo chętnie korzystają z idempotentnych rozwiązań. Głównie dlatego, że idempotentne operacje można bezkarnie powtarzać. Jeżeli nie mamy pewności, że pierwsza operacja doszła do skutku, zamiast weryfikować to, zwyczajnie ją powtarzamy. Jest to dużo prostsze i bezpieczniejsze podejście.

Przejdźmy teraz już do przykładów ściśle związanych z informatyką.

Idempotent – a bazy danych

  1. Usuwanie rekordów – usuwanie rekordów z natury jest idempotentne – raz usunięty rekord, niezależnie od tego, ile razy byśmy nie próbowali go usunąć, będzie już usunięty.
    delete from table where id = 1;
  2. W podobny sposób funkcjonuje aktualizacja danych – można tylko raz nadpisać dane, kolejne próby aktualizacji nie zmienią już naszego wpisu.
    update table set title = 'Note 1' where id = 1;
  3. Odczyt danych z natury też jest idempotentny.
    select * from table where id = 1;

Co jednak z bardziej skomplikowanymi problemami, jak np. dodawanie nowych rekordów czy modyfikacja struktury bazy danych?
Tutaj niestety musimy radzić sobie już samodzielnie i będzie to wymagało napisania przez nad odrobiny kodu.

Dodawanie danych bez duplikatów

Standardowy insert, jeżeli będzie wielokrotnie uruchamiany, za każdym razem doda nowy wiersz do tabeli. Jest to jak najbardziej naturalne i zazwyczaj pożądane zachowanie.

W niektórych sytuacjach jednak chcemy uniknąć duplikatów, które mogły powstać przez przypadkowe wielokrotne uruchomienie skryptu lub wcześniejsze ręczne dodanie danych.

  1. UNIQUE
    W takiej sytuacji możemy skorzystać np. z ograniczenia UNIQUE na kluczu biznesowym, którego unikatowość chcemy zachować. Nie jest to złe rozwiązanie, jednak w przypadku próby dodania duplikatu zostanie zwrócony błąd, a co za tym idzie, cały nasz skrypt będzie wycofany.

    alter table note add constraint unique_title unique(title);
    SQL duplicated key

    SQL duplicated key

  2. Odczyt przed dodaniem
    Alternatywnie przed dodaniem takiego rekordu możemy najpierw sprawdzić, czy nie został on już wcześniej dodany. Czyli najpierw robimy select, a potem insert, tylko jeżeli nie znaleźliśmy odpowiedniego rekordu.

    DO $$ 
    	DECLARE
    		row record;
    	
    	BEGIN
    	    SELECT * INTO STRICT row FROM note WHERE id = 1;
    	    EXCEPTION
    	        WHEN NO_DATA_FOUND then
    	        	insert into note (id, title, content) values (1, 'Note 1', 'Note...');
    	END;
    $$
  3. Usunięcie przed dodaniem
    Na początku skryptu możemy również podjąć próbę usunięcia takiego rekordu, a potem go dodać. Nie zawsze jest to bezpieczne wyjście, ponieważ możemy stracić dodane w ten sposób dane, jednak czasami to wystarczy.
    Należy też uważać, czy podczas takiej operacji nie zmienią nam się klucze główne.

    delete from note where id = 1;
    insert into note (id, title, content) values (1, 'Note 1', 'Note...');

Modyfikacja struktury

Jedną z dobrych praktyk wdrażania zmian na bazie danych jest wykonywanie ich w ramach transakcji – dzięki czemu będziemy mieli pewność, że się wykonają całe lub wcale.

Jest to jak najbardziej słuszne podejście, jednak nie za każdym razem sprawdza się idealnie. Wyobraź sobie sytuację, w której chcesz dodać nowe pole do tabeli, a następnie dodać nowy wpis do tej samej tabeli.

alter table note add column title varchar(255);
insert into note (id, title, content) values (1, 'Note 1', 'Note...');

Jeżeli to pole istniało już wcześniej, to cały skrypt zostanie wycofany, a co za tym idzie, nasz insert nie zadziała.

DO $$ 
    BEGIN
        ALTER TABLE note ADD COLUMN title varchar(255);
    EXCEPTION
        WHEN duplicate_column THEN RAISE NOTICE 'column title already exists in note.';
    END;  
$$

Jeżeli natomiast przewidzimy w naszym skrypcie taką sytuację i przechwycimy taki wyjątek, to bez wycofywania całej transakcji możemy kontynuować wykonywanie skryptu.

Idempotent – a usługi REST

Z pojęciem idempotent można też dość często spotkać się w kontekście usług typu REST i żądań HTTP.

Nie chcę w tym miejscu skupiać się na samych usługach sieciowych, jednak tym, co dla nas jest ważne, jest to, że REST to styl architektoniczny, określający zbiór reguł i ograniczeń wykorzystywanych do tworzenia usług sieciowych. Jest to między innymi alternatywa do usług typu SOAP.

Poszczególne metody w tym podejściu określone są, między innymi, przez adres URL oraz wykorzystywaną metodę HTTP. Przykładowo, wpisując adres w przeglądarce internatowej, np. Google Chrome, i naciskając enter, wysyłamy do serwera żądanie HTTP typu GET na ten adres. Natomiast wysłanie dowolnego formularza najczęściej realizowane jest metodą POST.

Przyjrzymy się teraz kolejno większości najczęściej wykorzystywanych metod HTTP i krótko je omówimy.

Pamiętaj jednak, że poniższe założenia to tylko dobre praktyki, których my jako programiści powinniśmy przestrzegać. Niestety na te założenia należy uważać, bo może się zdarzyć, że ktoś nie przestrzegał tych reguł i zaimplementował swoją usługę z pominięciem tych dobrych praktyk.

Metoda HTTP Idempotent Bezpieczna
OPTIONS tak tak
GET tak tak
HEAD tak tak
PUT tak nie
POST nie nie
DELETE tak nie
PATCH nie nie

HTTP GET

Metoda GET wykorzystywana jest zazwyczaj tylko do odczytu danych, np. do pobrania strony przez przeglądarkę, przez co jest idempotentna oraz bezpieczna.

HTTP HEAD

Metoda HEAD działa podobnie do GET, zwracając analogiczne nagłówki – jednak bez ciała żądania – przez co jest również idempotentna i bezpieczna.

HTTP OPTIONS

Metoda OPTIONS wyświetla dostępne opcje komunikacji. Ponieważ jest metodą tylko do odczytu, jest również idempotentna i bezpieczna.

HTTP PUT

Metoda PUT wykorzystywana jest do aktualizacji danych, dlatego nie możemy jej uznać za bezpieczną, gdyż modyfikuje nasze dane. Jest natomiast idempotentna, ponieważ możemy ją wywołać wiele razy i za każdym razem wynik powinien być taki sam.

HTTP POST

Metoda POST służy do tworzenia nowych obiektów. Każde jej wywołanie powinno zakończyć się utworzeniem nowego rekordu, dlatego nie jest ani bezpieczna, ani idempotentna.

HTTP DELETE

Metoda DELETE służy do usuwania danych, przez co jej wykorzystanie jest potencjalnie niebezpieczne. Jest natomiast idempotentna – raz usunięty obiekt będzie zawsze usunięty. Tutaj jednak mała uwaga – kolejne wywołania tej metody mogą zwrócić nagłówek http 404 oznaczający brak takiego obiektu.

HTTP PATCH

Metoda PATCH służy do częściowej aktualizacji obiektu, gdy chcemy np. zaktualizować tylko imię danej osoby, ignorując całkowicie pozostałe pola. Takie wywołanie jest potencjalne niebezpieczne i nie jest idempotentne.

📢📣 Podsumowanie. Uwaga!

Pisanie idempotentnego kodu jest jedną z dobrych praktyk programistycznych, jednak i tutaj należy kierować się zdrowym rozsądkiem i korzystać z niej tylko wtedy, jeżeli tego potrzebujemy. Przygotowanie kodu, który będzie odporny na wielokrotne uruchomienie, zazwyczaj zwyczajnie jest bardziej problematyczne i czasochłonne, a może się okazać, że wcale tego nie potrzebujemy.

Koniec/ogłoszenia

Na koniec mam jeszcze kilka ogłoszeń technicznych:

  • Po pierwszych dwóch odcinkach dostałem bardzo dużo pozytywnych wiadomości – jeszcze raz bardzo mocno za nie dziękuję.
    Jest to niezmiernie miłe uczucie móc przeczytać, że to, co robię, jest dla kogoś przydatne.
  • Dzisiejszy odcinek zostanie opublikowany pod adresem stormit.pl/003 – zachęcam do odwiedzania bloga, ponieważ znajdziesz tam, między innymi, transkrypt tego odcinka, przykłady omawianych dzisiaj skryptów oraz jeszcze więcej materiałów na temat programowania.
  • Podcast został zgłoszony do iTunes i w momencie, gdy tego słuchasz, powinien być dostępny w większości aplikacji do podcastów.
    Proszę, daj mi znać, czy rzeczywiście tak jest. Będę również wdzięczny za informację, z jakiej aplikacji korzystasz do słuchania audycji.

Na dzisiaj to już wszystko, bardzo Ci dziękuję za wspólnie spędzony czas.

Pozdrawiam i miłego dnia!

 

Dodatkowe materiały

 

kierunek java

2 komentarze
Share:
Pytania rekrutacyjne SQL

Pytania rekrutacyjne SQL

Jest to pierwsza część z serii pytań i odpowiedzi na rozmowę kwalifikacyjną Java Developera – zakres bazy danych SQL.

Ich pełne zrozumienie powinno dać obraz podstawowych wymagań wobec developera, dla którego SQL jest dodatkowym językiem programowania.

Zapraszam do dzielenia się swoimi pytaniami z rozmów kwalifikacyjnych tutaj w komentarzach lub na grupie.

Co to jest indeks w bazach danych?

Indeks to specjalna struktura danych mająca na celu zwiększenie prędkości wykonywania operacji na tabeli.

Indeks w bazie danych można porównać do spisu treści w książce. Zamiast przeszukiwać całą książkę kartka po kartce, można sprawdzić, na której dokładnie stronie znajduje się dane zagadnienie i przejść bezpośrednio do niego.

CREATE INDEX UsersAgeIndex ON Users (age);

Jakie są wady i zalety stosowania indeksów?

Ponieważ tworzenie i aktualizacja indeksów wymaga dodatkowych operacji na bazie danych, operacje dodawania (INSERT) i aktualizacji (UPDATE) są wolniejsze. Natomiast operacje pobierania danych (SELECT) mogą być szybsze, jeżeli do ich wykonania zostanie wykorzystany indeks.

Czy można założyć indeks na kilku kolumnach?

Można, robi się to analogicznie jak dla jednej kolumny.

CREATE INDEX UsersIdAgeIndex ON Users (id,age);

Jakie znasz funkcje agregujące i co to jest?

SQL udostępnia kilka funkcji agregujących, działających na całej grupie wartości zamiast na pojedynczym polu.

Funkcje tego typu działają na wszystkich wierszach ograniczonych przez klauzulę WHERE lub na każdej z podgrup osobno, jeżeli w zapytaniu jest klauzula GROUP BY.

COUNT – ilość wierszy

Funkcja COUNT zlicza ilość wierszy zwróconą przez zapytanie.

SELECT COUNT(*)
 FROM Users
 WHERE age > 10

Powyższy przykład policzy użytkowników starszych niż 10 lat.

Funkcja może również przyjąć jako argument nazwę pola, np. COUNT(age), jednak wtedy zliczy tylko wiersze, dla których to pole jest różne od NULL.

SUM – suma wszystkich wartości

Funkcja SUM wylicza sumę wszystkich wartości.

SELECT SUM(age)
 FROM Users

Powyższy przykład policzy łączny wiek wszystkich użytkowników. W tym przykładzie widać również, że klauzula WHERE jest opcjonalna.

AVG – średnia

Funkcja AVG wylicza średnią wartość dla zwróconych rekordów.

SELECT AVG(age)
 FROM Users
 GROUP BY city

To zapytanie wyliczy średni wiek użytkowników z każdego miasta.

Jakie są rodzaje złączeń?

Wyróżniamy dwa główne kategorie złączeń: wewnętrzne (inner) oraz zewnętrzne (outer).

  • inner join
    Jest to domyślny typ złączeń. Wyniki tych zapytań zawierają tylko wiersze, które spełniają warunek złączenia.
  • outer join
    W przypadku tego złączenia wiersze niespełniające wszystkich warunków są również dołączone do wyniku, a brakujące dane są uzupełnione wartościami pustymi (null’ami).

INNER JOIN – wewnętrzne

Wynikiem tego złączenia są tylko wiersze, które spełniają warunek klauzuli złączenia ON.

SELECT * 
 FROM Users 
 INNER JOIN UserGroups 
  ON Users.group_id = UserGroups.id

W zapytaniu słowo INNER jest opcjonalne, ponieważ jest to domyślny typ. W wyniku tego złączenia pojawią się tylko te wiersze z obu tabel, które spełnią warunki wymienione po klauzuli ON.

Self JOIN – własne

Złączenie typu Self JOIN występuje, jeżeli łączymy ze sobą tę samą tabelę. W takiej sytuacji konieczne jest użycie aliasów dla nazwy tabeli.

SELECT * 
 FROM UserGroups g1
 JOIN UserGroups g2
  ON g1.parent = g2.id

Equi-JOIN

To bardzo często występująca kategoria złączeń i pasują do niej również powyższe przykłady. Nazywana bywa również równozłączeniem. Zawierają się w niej wszystkie zapytania, dla których w warunku złączenia klauzuli ON oraz WHERE występuje znak równości =.

Theta Join

Złączenie Theta Join (nierównozłączenie) jest przeciwieństwem dla typu Equi-JOIN. Do tej kategorii należą wszystkie złączenia, w których występują porównania, inne niż zwykłe „równa się”, czyli np. >, != lub BETWEEN.

Anti Join

Jest to szczególny przypadek Theta Join (nierównozłączenia), w którym wykorzystano operator !=.

NATURAL JOIN – naturalne

Złączenie typu NATURAL JOIN występuje, jeżeli obie kolumny występujące w warunku złączenia mają taką samą nazwę.

SELECT * 
 FROM UserGroups
 NATURAL JOIN Users

W tym wypadku tabele zostały połączone na podstawie pola id, ponieważ tylko ono jest takie same w obu tabelach.

Nie jest to jednak zalecana praktyka, ponieważ w wyniku swojej niejednoznaczności może prowadzić do pomyłek. Poniżej równoznaczne zapytanie z wykorzystaniem klauzuli ON.

SELECT * 
 FROM UserGroups g
 JOIN Users u ON (u.id = g.id)

Semi Join – częściowe

Złączenie częściowe występuje, jeżeli w klauzuli SELECT są wymienione tylko kolumny z jednej tabeli.

SELECT u.id, u.age
 FROM UserGroups g
 JOIN Users u ON (u.group_id = g.id)

CROSS JOIN – krzyżowe

Złączenie CROSS JOIN (krzyżowe) wykonuje iloczyn kartezjański z łączonych tabel. W efekcie czego łączy każdy wiersz z pierwszej tabeli z każdym wierszem z drugiej.

SELECT *
 FROM UserGroups g
 CROSS JOIN Users u
SELECT * FROM UserGroups, Users

Powyższe zapytania zwracają ten sam wynik.

LEFT OUTER JOIN

Złączenia typu OUTER pozwalają uwzględnić w końcowym wyniku wiersze, które nie spełniają wszystkich warunków złączenia. W przypadku LEFT OUTER JOIN wiodąca jest pierwsza tabela, a brakujące dane z drugiej tabeli zostaną uzupełnione pustymi wartościami NULL.

SELECT u.id, u.age, g.name
 FROM Users u
 LEFT OUTER JOIN UserGroups g ON (u.group_id = g.id)

Można w ten sposób uwzględnić opcjonalność danej relacji. Powyższy przykład zwróci wszystkich użytkowników, nawet jeżeli nie będą mieli przypisanej grupy.

Słowo kluczowe OUTER w zapytaniu jest opcjonalne.

RIGHT OUTER JOIN

Złączenia RIGHT i LEFT OUTER JOIN działają bardzo podobnie, ale w przypadku RIGHT w wyniku uwzględnione są wiersze z drugiej tabeli, które nie spełniły warunków złączenia.

SELECT u.id, u.age, g.name
 FROM UserGroups g 
 RIGHT OUTER JOIN Users u ON (u.group_id = g.id)

FULL OUTER JOIN

Jest to złączenie obustronne, które można rozumieć jako sumę złączeń LEFT i RIGHT. W końcowym wyniku uwzględnione są wiersze z obu tabel, również te, które nie spełniły wszystkich warunków złączenia.

SELECT u.id, u.age, g.name
 FROM UserGroups g 
 FULL OUTER JOIN Users u ON (u.group_id = g.id)

Co to jest bulk insert?

Bulk insert jest to alternatywne podejście do dodawania danych do tabeli po jednym wierszu, mające na celu przyspieszenie procesu.

Najprostszym sposobem na dodanie kilku wierszy jednocześnie jest wykorzystanie metody COPY lub INSERT INTO.

COPY UserGroups (id, name) FROM stdin;
1       Admin
2       User
\.
INSERT INTO UserGroups (id, name) VALUES (1, 'Admin'), (2, 'User');

Co to jest trigger na bazie danych?

Trigger, czyli wyzwalacz to procedura wykonywana automatycznie przez bazę danych jako reakcja na pewne zdarzenie, np. dodanie (INSERT), aktualizację (UPDATE) lub usunięcie (DELETE) danych.

Triggery nie przyjmują żadnych argumentów oraz nie mogą zatwierdzać (COMMIT) i anulować (ROLLBACK) transakcji, ponieważ są wywoływane automatycznie w kontekście danej operacji (instrukcji SQL).

Jak dodać trigger?

Ogólna postać wyrażenia:

CREATE TRIGGER nazwa [BEFORE | AFTER] [INSERT | UPDATE | DELETE] ON table FOR EACH ROW instruction

Przykład w PostgreSQL:

CREATE FUNCTION UsersUpdateTrigger() RETURNS trigger AS $$ 
BEGIN 
 NEW.modification = NOW();
 RETURN NEW; 
END; $$ LANGUAGE 'plpgsql';

CREATE TRIGGER UsersUpdate BEFORE UPDATE ON Users
 FOR EACH ROW
 EXECUTE PROCEDURE UsersUpdateTrigger();

Przykładowy trigger wywoływany jest bezpośrednio przed każdą aktualizacją użytkownika i ustawia datę modyfikacji na aktualny czas.

Co to jest transakcja?

Transakcja jest to zbiór operacji wykonywanych na bazie danych, które stanowią jedną spójną całość. Dlatego operacje zawarte w jednej transakcji powinny być wykonane wszystkie lub żadna z nich.

Przykładem operacji wykonywanych w ramach jednej transakcji jest przelew bankowy. Podczas wykonywania jednej transakcji pieniądze są pobierane z jednego konta i przekazywane na drugie. W przypadku niepowodzenia obie operacje muszą zostać wycofane.

Co to jest ACID?

Co to jest save point?

Co to jest autocommit?

Jaka jest różnica między TRUNCATE a DELETE?

Co to jest widok bazodanowy i dlaczego warto z niego korzystać?

Jaka jest różnica między klauzulą WHERE, a HAVING?

Czym różni się operacja UNION od UNION ALL?

Na czym polega stronicowanie rekordów?

 

Co jest szybsze „=” czy „LIKE”?

Jak zrobić klucz główny o samoczynnie powiększającym się numerze?

 

Co to jest sekwencja?

Wymień znane Ci typy danych w bazie danych

Najczęściej wystarczy wymienić tylko kilka podstawowych typów, np.: date, time, timestamp, int i text.

Pełną listę typów dla PostgreSQL można znaleźć tu.

Zaprojektuj schemat bazy danych dla…

To bardzo ogólne i częste pytanie. Najczęściej trafiają się: wypożyczalnia, księgarnia, sklep itp. Wszystkie mają podobną strukturę, dlatego warto zaprojektować  przynajmniej jedną przykładową bazę danych.

Przykładowe zapytania

Bardzo często na rozmowach trzeba napisać całe zapytania SQL, korzystając z omawianej struktury bazy danych. Poniżej przykładowe zapytania.

  1. Pobierz imiona wszystkich użytkowników
    SELECT name FROM Users
  2. Pobierz użytkowników oraz nazwę ich grupy
    SELECT u.*, g.name as groupName 
     FROM Users u 
     JOIN UserGroups g ON (u.group_id = g.id)
  3. Policz wszystkich użytkowników
    SELECT COUNT(*) FROM Users
  4. Pobierz 3 ostatnio zmodyfikowanych użytkowników
    SELECT * FROM Users ORDER BY modification DESC LIMIT 3

A Ty jakie miałeś pytania na swojej rozmowie kwalifikacyjnej?

 

Chcesz przygotować fantastyczne CV i zabłysnąć na rozmowie kwalifikacyjnej? Sprawdź  najnowsze e-booki:

CV Programisty oraz Rozmowa kwalifikacyjna Programisty

3 komentarze
Share: