Ostatnio mam dziwne sytuacje związane z kodem i SQL – na przykład zamiana prostej tabelki na nieskończenie zagnieżdżone drzewo. Albo zwróć mi wszystkie dane, ale chcę je w postaci dwóch kolumn: ColumnName
i ColumnValue
. Czy to są dziwne rozwiązania czy też nie, to pewnie zależy od specyfiki naszej pracy. Ja mam z takimi zapytaniami rzadki kontakt ale jak już mam, to staram się to zawsze udokumentować – dla siebie. By zapamiętać.
Tak też było w tym przypadku. Zapytanie zawracające paręnaście wierszy danych i około 10 kolumn miałem zamienić na trzy kolumny: Identifier
, ColumnName
i ColumnValue
. Dla zachowania prostoty ograniczę się w przykładzie do mniejszej liczby kolumn w domyślnym zapytaniu.
Więc jakie mamy opcje przekonwertowania danych? Jedną z nich jest opcja APPLY
, która wykonuje funkcję dla każdego wiersza danych. Dokładnie mówiąc wykonuje ona funkcję na każdym wierszu z zapytania nadrzędnego. Zaraz sobie powiemy co to jest zapytanie nadrzędne. Możemy wyróżnić dwie funkcje APPLY
– CROSS APPLY
i OUTER APPLY
. Różnica jest taka, że CROSS
zwróci nam jedynie wiersze z nadrzędnego zapytania które zostały zwrócone przez funkcje table-valued. Przy OUTER
za to, zostanie zwrócone wszystko. Nawet jeżeli coś nie zostało przekazane lub obliczone przez funkcję table-valued.
Warto powiedzieć, że wynikiem są kolumny zwrócone przez zapytanie nadrzędne i kolumny dodane przez APPLY
.
Zanim przejdziemy dalej, zapytanie nadrzędne to, zapytanie SELECT
które wykonujemy tuż przed wywołaniem CROSS|OUTER APPLY
. Weźmy prosty przykład ode mnie z pracy:
SELECT [T].[WaterbodyCode], [W].[WaterbodyTypeName], [W].[OrganisationName], [W].[Tier1Risk] FROM [TracGroundwaterCharacterisation] AS [T] INNER JOIN [Waterbody] AS [W] ON [W].[WaterbodyId] = [T].[WaterbodyId]
To jest bazowe zapytanie, które zwraca nam jakieś tam dane. My chcemy to zamienić na mniej więcej taką postać:
SELECT [T].[WaterbodyCode], [ColumnName], [ColumnValue] FROM [TracGroundwaterCharacterisation] AS [T] INNER JOIN [Waterbody] AS [W] ON [W].[WaterbodyId] = [T].[WaterbodyId]
Gdzie ColumnName
i ColumnValue
będą odpowiadały nazwie kolumny dla WaterbodyTypeName
i jej wartości. I tak dal pozostałych kolumn.
Na razie załóżmy, że nie mamy kolumn ColumnName
i ColumnValue
więc nasze zapytanie wygląda tak:
SELECT [T].[WaterbodyCode] FROM [TracGroundwaterCharacterisation] AS [T] INNER JOIN [Waterbody] AS [W] ON [W].[WaterbodyId] = [T].[WaterbodyId]
Na tym zapytaniu będziemy wywoływać funkcję CROSS APPLY
.
SELECT [T].[WaterbodyCode] FROM [TracGroundwaterCharacterisation] AS [T] INNER JOIN [Waterbody] AS [W] ON [W].[WaterbodyId] = [T].[WaterbodyId] CROSS APPLY ( ) AS CA
Kod powyżej nie zadziała! Ale to jest krok który chciałem pokazać. Teraz naszym zapytaniem nadrzędnym jest SELECT..FROM
i wszystkie rekordy z niego zostaną podane funkcji table-valued.
By uzyskać wynik, który nas interesuje, możemy założyć, że CROSS APPLY
będzie zwracało nam dwie kolumny – ColumnName
i ColumnValue
, które będziemy chcieli wyświetlić:
SELECT [T].[WaterbodyCode], [CA].[ColumnName], [CA].[ColumnValue] FROM [TracGroundwaterCharacterisation] AS [T] INNER JOIN [Waterbody] AS [W] ON [W].[WaterbodyId] = [T].[WaterbodyId] CROSS APPLY ( ) AS CA([ColumnName], [ColumnValue])
To jest zrozumiałe na razie? Wszystkie wiersze z FROM
zostaną poddane funkcji CROSS APPLY
która zwróci nam dwie ekstra kolumny. Teraz w CROSS APPLY
możemy wybrać wiersze do naszej funkcji zwracającej tabele:
SELECT [T].[WaterbodyCode], [CA].[ColumnName], [CA].[ColumnValue] FROM [TracGroundwaterCharacterisation] AS [T] INNER JOIN [Waterbody] AS [W] ON [W].[WaterbodyId] = [T].[WaterbodyId] CROSS APPLY ( SELECT 'Waterbody Category', [W].[WaterbodyTypeName] UNION ALL SELECT 'Local Authority', [W].[OrganisationName] UNION ALL SELECT 'WFD Risk', [W].[Tier1Risk] ) AS CA([ColumnName], [ColumnValue])
Lub za pomocą VALUES
(szybciej, więcej informacji na temat opcji VALUES
, oraz dokumentacja):
SELECT [T].[WaterbodyCode], [CA].[ColumnName], [CA].[ColumnValue] FROM [TracGroundwaterCharacterisation] AS [T] INNER JOIN [Waterbody] AS [W] ON [W].[WaterbodyId] = [T].[WaterbodyId] CROSS APPLY ( VALUES ('Waterbody Category', [W].[WaterbodyTypeName]), ('Local Authority', [W].[OrganisationName]), ('WFD Risk', [W].[Tier1Risk]) ) AS CA([ColumnName], [ColumnValue])
I tak o to uzyskamy wynik:
WaterbodyCode ColumnName ColumnValue GBNIIE6NB010 Waterbody Category Coastal GBNIIE6NB010 Local Authority N.A. GBNIIE6NB010 WFD Risk Review
Do tego wszystkiego, możemy zastosować WHERE
na końcu (po wykonaniu CROSS APPLY
):
SELECT [T].[WaterbodyCode], [CA].[ColumnName], [CA].[ColumnValue] FROM [TracGroundwaterCharacterisation] AS [T] INNER JOIN [Waterbody] AS [W] ON [W].[WaterbodyId] = [T].[WaterbodyId] CROSS APPLY ( VALUES ('Waterbody Category', [W].[WaterbodyTypeName]), ('Local Authority', [W].[OrganisationName]), ('WFD Risk', [W].[Tier1Risk]) ) AS CA([ColumnName], [ColumnValue]) WHERE [T].[WaterbodyCode] = 'GBNIIE6NB010'
Bardzo fajne i przyjemnie. Jedyny problem jaki ja miałem to zrozumienie tego nadrzędnego SELECT
i tego, że APPLY
zwraca mi tabelę. Jak to opanowałem to już zapytania szły mi znacznie lepiej.
Ale jak w we wszystkim, to nie jest jedyna opcja jaką możemy zrobić/wykorzystać. Na innym sposobem skoncentruje się następnym razem.
Byłoby fajnie gdybyś przykład zmienił na bardziej ogólny w sensie nazwy kolumn i tabel. Dodatkowo pokazał tabelę z danymi wejściowymi i końcowym rezultatem
Good point – pomyśle jak to można lepiej zalatwic
W SQL akurat czuje się świetnie i cross apply można do tego zaprzęgnąć, ale to zostało dodane do tego by mozna joinować się z funkcjami wywoływanymi dla każdego wiersza.
Mnie zastanawia czemu użyłeś akurat tego zamiast dedykowanej ku temu składni UNPIVOT, prosty przykład:
Taki sposób wydaje mi się czytelniejszy, bo jak wrócisz do tego i zobaczysz UNPIVOT to od razu wiesz czego się spodziewać, a tak musisz dochodzic jaki cel jest z użycia APPLY
p.s. da się jakoś formatować kod w komentarzach?
Oczywiście zrobiłem błąd, ale tak to jest jak się pisze air code
linijka:
,[W].[Tier1Risk] as
powinna mieć postać:
,[W].[Tier1Risk] as [WFD Risk]
Próbowałeś wrzucić kod do ?
@Gutek pomyśl o jakimś pluginie do kolorowania kodu, będzie przyjemniej się czytało :)
Nie próbowalem, bo już sam tsql z apostrofami uruchomił jakieś zabezpieczenia z zaznaczeniem, ze nie jestem robotem
da się za pomoca pre tagu.
i tak, zrobilem z unpivot ale tam byl jeszcze jeden krok ekstra – konwersja danych i to stanowilo problem z Unpivot i spowodowalo ze CROSS byl czytelnijeszy – ale to widze kazdy z nas ma inny poglada na to co jest czytelne a co nie ;) zgadzam sie ze jak widzisz UNPIVOT to juz wiesz o co chodzi.
No i UNPIVOT bedzie opisany nastepnym razem i na jakie problemy natrafilem i dlaczego bylem zmuszony do zrobienia SELECT w FROM.
No to skoro opiszesz UNPIVOT w następnym wpisie to nie będę wyprzedzał, nadmienię tylko że cross apply też będzie miało z tym problem…
Bazując na takim przykładzie:
Wyrzuci błąd:
Więc też trzeba zaznaczyć, że problem może się pojawić bez względu na wynraną metodę.
W poście wyżej dodałe pre tag i przy dodaniu posta wywaliło….
Ja w swoim komentarzu dodałem pre i całość się wycięła :)
Głupi wp :)
@Michał
przy intach możliwe, ale to jest dosłownie ten sam przykład i te same dane i dla UNPIVOT musiałem castować a dla cross apply nie. Postarm sie jescze udostepnic zestaw danych – jakis gist stworze tak by kazdy byl na tym samym.
ale dzieki, tak, przy intach to wszedzie sie wywali, roznica jest gdzie ja moge to castowac a gdzie nie moge.
Zestaw danych, rzeczywiście by się przydał wtedy byśmy mówili o tym samym ;)
Uzupełniając tylko, to niejawna konwersja jaka się dokonuje wynika z pierwszeństwa typów (https://docs.microsoft.com/en-us/sql/t-sql/data-types/data-type-precedence-transact-sql) i tak varchar z int zawsze będzie castowany do int.
O unpivocie pogadamy jak o nim napiszesz ;)
Hehe ;) Sam nie wiem czy juz chce on nim pisać ;)
Próbowałeś osiągnać ten sam rezultat zwykłym PVT / UNPVT ?
Tak, opisze to następnym postem. Pewne problemy miałem i zapytanie było dużo bardziej skomplikowane
[…] razy wykonaliśmy konwersje kolumn na wiersze z wykorzystaniem funkcji APPLY w T-SQL. Dziś wykonamy to samo, ale wykorzystanym operator – […]
Comments are closed.