Nie napisałbyś aplikacji z funkcjami o długości 200 linii. Rozłożyłbyś te długie funkcje na mniejsze funkcje, z których każda ma jedną jasno określoną odpowiedzialność.
Po co pisać w ten sposób swój SQL?
Dekomponuj swoje zapytania, tak jak rozkładasz swoje funkcje. To sprawia, że są krótsze, prostsze, łatwiejsze do zrozumienia, łatwiejsze do przetestowania , łatwiejsze do refaktoryzacji. Pozwala też na dodawanie „podkładek” między nimi i „otoków” wokół nich, tak jak w kodzie proceduralnym.
Jak Ty to robisz? Dokonując każdej istotnej rzeczy, jaką zapytanie robi w widoku. Potem komponować bardziej złożonych zapytań z tych prostszych poglądów, tak jak komponować bardziej złożone funkcje z funkcjami bardziej prymitywnych.
A wspaniałe jest to, że w przypadku większości kompozycji widoków uzyskasz dokładnie taką samą wydajność ze swojego RDBMS. (Dla niektórych tego nie zrobisz; i co z tego? Przedwczesna optymalizacja jest źródłem wszelkiego zła. Najpierw koduj poprawnie, a następnie optymalizuj, jeśli zajdzie taka potrzeba).
Oto przykład użycia kilku widoków do dekompozycji skomplikowanego zapytania.
W tym przykładzie, ponieważ każdy widok dodaje tylko jedną transformację, każdą można niezależnie przetestować w celu znalezienia błędów, a testy są proste.
Oto tabela podstawowa w przykładzie:
create table month_value(
eid int not null, month int, year int, value int );
Ta tabela jest wadliwa, ponieważ używa dwóch kolumn, miesiąca i roku, do reprezentowania jednej wartości odniesienia, bezwzględnego miesiąca. Oto nasza specyfikacja nowej kolumny obliczeniowej:
Zrobimy to jako transformację liniową, taką, że będzie ona sortowana tak samo jak (rok, miesiąc) i taka, że dla każdej krotki (rok, miesiąc) jest jedna i jedyna wartość, a wszystkie wartości są następujące po sobie:
create view cm_absolute_month as
select *, year * 12 + month as absolute_month from month_value;
Teraz to, co musimy przetestować, jest nieodłącznym elementem naszej specyfikacji, a mianowicie, że dla każdej krotki (roku, miesiąca) jest jeden i tylko jeden (bezwzględny_miesiąc), a te (bezwzględne_miesiąc) następują po sobie. Napiszmy kilka testów.
Naszym testem będzie select
zapytanie SQL o następującej strukturze: nazwa testu i instrukcja case zebrane razem. Nazwa testu to po prostu dowolny ciąg. Instrukcja case to tylko case when
instrukcje testowe then 'passed' else 'failed' end
.
Instrukcje testowe będą po prostu selekcjami SQL (podzapytaniami), które muszą być prawdziwe, aby test zakończył się pomyślnie.
Oto nasz pierwszy test:
--a select statement that catenates the test name and the case statement
select concat(
-- the test name
'For every (year, month) there is one and only one (absolute_month): ',
-- the case statement
case when
-- one or more subqueries
-- in this case, an expected value and an actual value
-- that must be equal for the test to pass
( select count(distinct year, month) from month_value)
--expected value,
= ( select count(distinct absolute_month) from cm_absolute_month)
-- actual value
-- the then and else branches of the case statement
then 'passed' else 'failed' end
-- close the concat function and terminate the query
);
-- test result.
Uruchomienie tego zapytania daje następujący wynik: For every (year, month) there is one and only one (absolute_month): passed
Tak długo, jak istnieje wystarczająca ilość danych testowych w month_value, ten test działa.
Możemy również dodać test dla wystarczających danych testowych:
select concat( 'Sufficient and sufficiently varied month_value test data: ',
case when
( select count(distinct year, month) from month_value) > 10
and ( select count(distinct year) from month_value) > 3
and ... more tests
then 'passed' else 'failed' end );
Teraz przetestujmy to po kolei:
select concat( '(absolute_month)s are consecutive: ',
case when ( select count(*) from cm_absolute_month a join cm_absolute_month b
on ( (a.month + 1 = b.month and a.year = b.year)
or (a.month = 12 and b.month = 1 and a.year + 1 = b.year) )
where a.absolute_month + 1 <> b.absolute_month ) = 0
then 'passed' else 'failed' end );
Teraz umieśćmy nasze testy, które są tylko zapytaniami, w pliku i uruchommy ten skrypt w bazie danych. Rzeczywiście, jeśli przechowujemy nasze definicje widoków w skrypcie (lub skryptach, polecam jeden plik na powiązane widoki) do uruchomienia w bazie danych, możemy dodać nasze testy dla każdego widoku do tego samego skryptu, tak aby akt (ponownie -) tworzenie naszego widoku uruchamia również testy widoku. W ten sposób obaj otrzymujemy testy regresji podczas ponownego tworzenia widoków, a gdy tworzenie widoku działa w środowisku produkcyjnym, widok zostanie również przetestowany w środowisku produkcyjnym.