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:
drop table tab go CREATE TABLE tab ([id] int, [k1] varchar(10), [k2] varchar(10), [k3] varchar(10), [k4] varchar(10), [k5] varchar(10), [k6] varchar(10), [k7] varchar(10), [k8] varchar(10), [k9] varchar(10), [k10] varchar(10)) ; INSERT INTO tab ([id], [k1], [k2], [k3], [k4], [k5], [k6], [k7], [k8], [k9], [k10]) VALUES (1, 'w1', 'w2', 'w3', 'w4', 'w5', 'w6', 'w7', 'w8', 'w9', 'w10'), (2, 'w1', 'w2', 'w3', 'w4', 'w5', 'w6', 'w7', 'w8', 'w9', 'w10') ; select id ,ColumnName ,ColumnValue from (Select * from tab) p UNPIVOT (ColumnValue FOR ColumnName in ([k1], [k2], [k3], [k4], [k5], [k6], [k7], [k8], [k9], [k10])) as unpvt Oczywiście to może wymagać konwersji typów bo unpivot tego wymaga, czyli jeżeli nie pokręciłem to twoje zapytanie można przedstawić w takiej postaci: select WaterbodyCode ,ColumnName ,ColumnValue from (SELECT [T].[WaterbodyCode], ,[W].[WaterbodyTypeName] as [Waterbody Category] ,[W].[OrganisationName] as [Local Authority] ,[W].[Tier1Risk] as FROM [TracGroundwaterCharacterisation] AS [T] INNER JOIN [Waterbody] AS [W] ON [W].[WaterbodyId] = [T].[WaterbodyId] WHERE [T].[WaterbodyCode] = 'GBNIIE6NB010') as pvt UNPIVOT (ColumnValue FOR ColumnName in ([Waterbody Category], [Local Authority], [WFD Risk])) as unpvtTaki 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:
drop table tab go CREATE TABLE tab( [id] int , [k1] int , [k2] varchar(10) , [k3] varchar(10) , [k4] varchar(10) , [k5] varchar(10) , [k6] varchar(10) , [k7] varchar(10) , [k8] varchar(10) , [k9] varchar(10) , [k10] varchar(10) ) go INSERT INTO tab ([id], [k1], [k2], [k3], [k4], [k5], [k6], [k7], [k8], [k9], [k10]) VALUES (1, 1, 'w2', 'w3', 'w4', 'w5', 'w6', 'w7', 'w8', 'w9', 'w10'), (2, 1, 'w2', 'w3', 'w4', 'w5', 'w6', 'w7', 'w8', 'w9', 'w10') go Zwrócę tylko uwagę, że kolumna k1 jest int cała reszta to varchar, to zapytanie z CROSS APPLY: SELECT [T].[ID] ,[CA].[ColumnName] ,[CA].[ColumnValue] FROM tab t CROSS APPLY ( VALUES ('K1', [t].[k1]), ('K2', [t].[k2]), ('K3', [t].[k3]) ) AS CA([ColumnName], [ColumnValue])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.