Do napisania dzisiejszego artykułu skłonił mnie fakt, iż naprawdę niewiele osób piszących zapytania TSQL używa tzw. zaawansowanych elementów języka. Dlaczego tak się dzieje? Nie jestem pewien ale wydaje mi się, że słowo “zaawansowanych” już dostatecznie odstrasza potencjalnych zainteresowanych – w moim mniemaniu całkowicie niesłusznie. Jednym ze “straszniejszych” elementów TSQL jest bez wątpienia APPLY – część z was z pewnością go używa, inni słyszeli ale nie widzą zastosowania, dla niektórych to zupełna nowość. Poniżej postaram się krótko przedstawić gdzie i w jaki sposób możemy niniejszego narzędzia użyć, a jest to narzędzie naprawdę potężne. Zaczynajmy!
Aby zrozumieć specyfikę działania naszego dzisiejszego bohatera musimy przypomnieć sobie teorię z zakresu operatorów złączenia czyli popularnych JOINów. W teorii relacyjnych baz danych mamy do czynienia z tak zwanymi złączeniami wewnętrznymi (ang. INNER JOIN) oraz zewnętrznymi (ang. OUTER JOIN). W zależności od rodzaju złączenia dwa zestawy danych są ze sobą w określony sposób porównywane i zwracany jest rezultat. To co jest niezmierne istotne w działaniu złączeń to fakt, iż oba zestawy danych (na obrazku nazwane Table 1 oraz Table 2) muszą być predefiniowane – silnik musi mieć dane na temat obu stron operatora:
Co natomiast jeśli jedna ze stron, które chcemy dołączać nie zawiera predefiniowanego zbioru danych i nie możemy go zapisać przy pomocy znanych nam metod? Możemy użyć APPLY! Poniższy rysunek w pełni oddaje istotę działania APPLY.
Jak można zauważyć APPLY oczekuje predefiniowanego zestawu po lewej stronie złączenia. Tak więc jeśli chcemy dla przykładu użyć funkcji tabelarycznej wiersz po wierszu nie możemy tego zrobić przy użyciu tradycyjnych złączeń. W tym momencie może Wam przyjść na myśl użycie kursorów – myślę, że możecie zapomnieć o tej myśli tak szybko jak przyszła Wam do głowy. Jak to działa sprawdźmy to na przykładzie! W naszych przykładach standardowo będziemy używać bazy Adventure Works. Jako nasz problem biznesowy weźmiemy sobie typową sytuację: potrzebujemy zapytania, które w dynamiczny sposób zwróci nam N ostatnich dat zamówień dla określonego klienta.
Po pierwsze potrzebujemy funkcji tabelarycznej, która nam coś takie zwróci – nie skupijamy się konkretnie jak ona działa – przyjmijmy ją jako czarną skrzynkę:
CREATE FUNCTION [dbo].[test] ( @custId int, @topn int ) RETURNS TABLE AS RETURN SELECT DISTINCT TOP (@topn) dc.CustomerKey, fis.OrderDate FROM AdventureWorksDW2012.dbo.DimCustomer AS dc JOIN AdventureWorksDW2012.dbo.FactInternetSales AS fis ON fis.CustomerKey=dc.CustomerKey WHERE dc.CustomerKey=@custId
Przetestujmy jej działanie w pierwszym parametrze podając identyfikator wybranego klienta, w drugim natomiast liczbę dat jakie chcemy otrzymać.
SELECT * FROM dbo.test(11001,2)
Jak widać poniżej funkcja działa zgodnie z przeznaczeniem.
Wreszcie nadszedł czas na przetestowanie naszego APPLY. Złączmy tabelę z klientami tak aby pokazać dwie ostatnie daty zakupów dla każdego z klientów:
USE AdventureWorksDW2012 GO SELECT dc.CustomerKey, t.OrderDate FROM dbo.DimCustomer AS dc CROSS APPLY dbo.test(dc.CustomerKey,2) AS t ORDER BY 1 ASC, 2 DESC
Rezultat jest taki jak się spodziewaliśmy – powinniśmy pamiętać o tym, że konkretnie CROSS APPLY logicznie działa bardzo podobnie do złączenia wewnętrznego czyli zwraca tylko te wiersze, które znajdują się w obu zestawach.
Wygląda to świetnie, co jednak gdy chcemy poznać klientów, którzy w ogóle nie złożyli żadnego zamówienia? Jak już wspomniałem CROSS APPLY działa jak INNER JOIN – tak więc czy jest coś podobnego do OUTER JOIN? Oczywiście! Mamy do dyspozycji OUTER APPLY. Sprawdźmy to na przykładzie. W bazie Adventureworks2012DW nie ma klienta, który nigdy nie złożył zamówienia, więc możemy sobie to zasymulować np. poprzez dodanie wiersza do tabeli DimCustomer. W zapytaniu dodamy również klauzulę WHERE gdzie pokażemy sobie tylko tych klientów, którzy nie mają połączenia do tabeli z zamówieniami.
USE AdventureWorksDW2012 GO SELECT dc.CustomerKey, t.OrderDate FROM dbo.DimCustomer AS dc OUTER APPLY dbo.test(dc.CustomerKey,2) AS t WHERE OrderDate IS NULL ORDER BY 1 ASC, 2 DESC
Jak widać poniżej w rezultacie otrzymaliśmy jednego klienta, który nigdy nie złożył żadnego zamówienia.
Tak więc użycie operatorów APPLY jest naprawdę proste i przyjemne w użyciu. Ponadto rozwiązują one całą masę problemów w naszej codziennej pracy. Co jednak z kwestą wydajności? Jak możecie się domyślać nie jest najlepiej ze względu na fakt, iż funkcje tabelaryczne nie są najwydajniejszą strukturą – jednak mimo wszystko warto znać te funkcjonalności języka TSQL gdyż w wielu przypadkach naprawdę się sprawdzają.
- Avoiding Issues: Monitoring Query Pushdowns in Databricks Federated Queries - October 27, 2024
- Microsoft Fabric: Using Workspace Identity for Authentication - September 25, 2024
- Executing SQL queries from Azure DevOps using Service Connection credentials - August 28, 2024
Przydatne i faktycznie mało kto korzysta z tego operatora 🙂 Dzięki za podzielenie się wiedzą!
Polecam testowanie w swoich procedurach – w moim przypadku plan zapytania z użyciem cross apply w porównaniu do inline sql był o wiele bardziej wydajny 🙂
Ciekawostka:
cross apply zadziała tak jak outer apply gdy będziem używać go na agregatach
np.
cross apply( select SUM(bar) from foo where 1=0) as xxx
Będę musiał przetestować 🙂 Dzięki za wskazówkę!
Tzw zaawansowane elementy T-SQLa nie są omijane ze strachu, tylko z przykrej konieczności. Otóż większość programistów tworzy oprogramowanie komercyjne, dla klienta. A klienci w ogromnej większości są tacy, że w czasie, kiedy pięć razy zmienią swój samochód, na upgrade serwera SQLowego nie wydadzą ani złotówki, skutkiem czego w 2020 roku trzeba się trzymać składni sprzed 15 lat na przykład.
Oczywiście nie przeszkadza im to krzywić się, jak cała aplikacja chodzi coraz wolniej i wolniej…
Niestety czasem i tak bywa – cakowita racja
A jaką wartość dla klienta ma wdrożenie nowej wersji platformy? Z jego punktu widzenia to koszt, który jest niezasadny jeśli można zrealizować jego cele tą platformą, którą obecnie ma.
To czego nie wydał na nieuzasadnione z perspektywy jego biznesu (cel został osiągnięty na obecnej platformie) jest jego zyskiem, który przeznaczył na samochody 😉
Dzięki za komentarz!
Wyższa wersja platformy = dłużej będzie wspierana przez dostawcę oprogramowania. Posiada również dodatkowe możliwości związane np. z zabezpieczaniem, analizą czy też wizualizacją danych. Wszystko zależy od tego jak zdefiniujemy “cel biznesowy”. Oczywiście jeśli obecne oprogramowanie spełnia założone cele to wszystko jest w jak najlepszym porządku, jednak zawsze są jakieś koszty utrzymania, a one często rosną jeśli to oprogramowanie jest po prostu stare. Sam widziałem takie przypadki gdzie ktoś miał wszystko ustawione na oprogramowaniu z zeszłego wieku i możliwości migracji były bardzo ograniczone żeby nie powiedzieć żadne co przysporzyło tej firmie wielu problemów wydajnościowych i nie tylko. Tak więc czym innym jest pogoń za tym co najnowsze jak najszybciej, a zasiedzenie się w technologii która utraciła wsparcie dostawcy:)