Ostatnim razy wykonaliśmy konwersje kolumn na wiersze z wykorzystaniem funkcji APPLY
w T-SQL. Dziś wykonamy to samo, ale wykorzystanym operator – PIVOT
i UNPIVOT
.
Postaram się jeszcze dzisiaj wrzucić GIST z tabelkami.
PIVOT
i UNPIVOT
umożliwiają zmienić wyrażenie table-valued na inną tabelę. PIVOT
umożliwia zamienić wiersze/wartości na kolumny i wykonuje agregacje na pozostałych kolumnach które są potrzebne w ostatecznym wyniku. UPIVOT
zaś wykonuje odwrotną operację – kolumny zamienia na wiersze. Czyli coś co my chcemy osiągnąć. A więc pominę PIVOT i skoncentruje się tutaj na UNPIVOT
.
To co jednak jest tutaj BARDZO ważne, to to, że kolumny muszą mieć taki sam typ danych by operacja się powiodła – i to tak dosłownie musi być taki sam (choć polecam przeczytać komentarze pod ostatnim postem). Na przykład NVARCHAR(4000)
i NVARCHAR(250)
już nie pójdą ze sobą w parze.
W dalszej części trzymam bazowego zapytania z poprzedniego postu:
SELECT [T].[WaterbodyCode], [W].[WaterbodyTypeName], [W].[OrganisationName], [W].[Tier1Risk] FROM [TracGroundwaterCharacterisation] AS [T] INNER JOIN [Waterbody] AS [W] ON [W].[WaterbodyId] = [T].[WaterbodyId]
Niestety, ma on pewien problem, to zapytanie ma pewien problem. Mam różne typy i UNPIVOT
sobie z tym nie radzi. Dla ułatwienia więc na razie wytniemy kolumnę, która powoduje u mnie problem (Tier1Risk
). Więc mamy taki o to wejściowy SELECT
:
SELECT [T].[WaterbodyCode], [W].[WaterbodyTypeName], [W].[OrganisationName] FROM [TracGroundwaterCharacterisation] AS [T] INNER JOIN [Waterbody] AS [W] ON [W].[WaterbodyId] = [T].[WaterbodyId]
Jeżeli byśmy chcieli to zamienić na ColumnName
i ColumnValue
to musimy po naszym FROM
wykonać operację UNPIVOT
. Schemat UNPIVOT
ma następujący:
UNPIVOT ( NEW_COLUMN_NAME_CONTAINING_VALUE FOR NEW_COLUMN_NAME_CONTAINING_NAME IN (COLUMNS, WHICH, WE, WANT) ) AS UNVPT
A więc stosując tę zasadę i to co zrobiliśmy ostatnio, takie coś powinno nam zadziałać:
SELECT [T].[WaterbodyCode], [ColumnName], [ColumnValue] FROM [TracGroundwaterCharacterisation] AS [T] INNER JOIN [Waterbody] AS [W] ON [W].[WaterbodyId] = [T].[WaterbodyId] UNPIVOT ( [ColumnValue] FOR [ColumnName] IN([W].[WaterbodyTypeName], [W].[OrganisationName]) ) AS UNPIV
A nie działa. Nie działa z dwóch powodów.
Msg 8156, Level 16, State 1, Line 64 The column 'WaterbodyId' was specified multiple times for 'UNPIV'. Msg 4104, Level 16, State 1, Line 55 The multi-part identifier "T.WaterbodyCode" could not be bound.
Do pierwszego zaraz wrócimy, drugi to taki, że UNPIVOT
ubija to co jest w FROM
i zwraca nam to co jest pod UNPIV
. A tam nie ma [T].[WaterbodyCode]
tylko jest [UNPIV].[WaterbodyCode]
, zamiana tego na:
SELECT [WaterbodyCode], [ColumnName], [ColumnValue] FROM [TracGroundwaterCharacterisation] AS [T] INNER JOIN [Waterbody] AS [W] ON [W].[WaterbodyId] = [T].[WaterbodyId] UNPIVOT ( [ColumnValue] FOR [ColumnName] IN([W].[WaterbodyTypeName], [W].[OrganisationName]) ) AS UNPIV
Załatwia jeden problem, ale pozostawia pierwszy spowodowany przez INNER JOIN
z tabelą która ma kolumny o tej samej nazwie. W tym WaterbodyId
. Więc natrafiamy na problem, że nie możemy zrobić UNPIVOT
bo UNPIVOT
tworzy sobie tabelę w pamięci i ona już zawiera WaterbodyId
.
Możemy to rozwiązać pomijając INNER JOIN
jeżeli mamy taką możliwość:
SELECT [WaterbodyCode], [ColumnName], [ColumnValue] FROM [Waterbody] AS [W] UNPIVOT ( [ColumnValue] FOR [ColumnName] IN([W].[WaterbodyTypeName], [W].[OrganisationName]) ) AS UNPIV
Lub możemy to załatwić SELECT
w FROM
:
SELECT [WaterbodyCode], [ColumnName], [ColumnValue] FROM ( SELECT [W].[WaterbodyCode], [W].[WaterbodyTypeName], [W].[OrganisationName] FROM [TracGroundwaterCharacterisation] AS [T] INNER JOIN [WaterbodyDetailsView] AS [W] ON [W].[WaterbodyId] = [T].[WaterbodyId] ) AS T UNPIVOT ( [ColumnValue] FOR [ColumnName] IN([WaterbodyTypeName], [OrganisationName]) ) AS UNPIV
I jeżeli już mamy ten SELECT
w FROM
to też możemy zrobić sobie CAST na odpowiednie typy danych:
SELECT [WaterbodyCode], [ColumnName], [ColumnValue] FROM ( SELECT [T].[WaterbodyCode], CAST([W].[WaterbodyTypeName] AS NVARCHAR(250)) AS [Waterbody Category], CAST([W].[OrganisationName] AS NVARCHAR(250)) AS [Local Authority], CAST([W].[Tier1Risk] AS NVARCHAR(250)) AS [WFD Risk] FROM [TracGroundwaterCharacterisation] AS [T] INNER JOIN [Waterbody] AS [W] ON [W].[WaterbodyId] = [T].[WaterbodyId] ) AS [Tab] UNPIVOT ( [ColumnValue] FOR[ ColumnName] IN([Waterbody Category], [Local Authority], [WFD Risk]) ) AS UNPIV
I tak o to, mogliśmy przekonwertować wartości na kolumny.
Mimo iż może się wydawać, że UNPIVOT
wcale taki fajny nie jest, to, nie. On jest naprawdę fajny i jego głównych zadaniem właśnie jest konwersja kolumn na wiersze. Dla prosty potrzeb albo odpowiednio z preparowanych danych daje nam masę fajnych możliwości. I jeżeli naprawdę wszystko jest po naszej myśli to jest on SUPER zwięzły i czytelny.
Znacie to rozwiązanie? Może znacie jeszcze jeden/inny sposób zamiany kolumn na wiersze?
Paradygmaty analogowe FTW ;)
https://goo.gl/20c84h
Gdzie ten GIST?
dostaliśmy bana dzisiaj z powodu ransomware i nie mam dostępu do żadnej z baz i żadnego schematu. do odwołania :(
ban jest do odwołania, więc dopiero jak mi go zdejmą będzie gist
Comments are closed.