Całkiem niedawno szukając materiałów na temat przetwarzania XML znalazłem opis pewnego tricku: jeśli chcesz ładnie wyświetlić wielolinijkowy tekst wyświetlany w SSMS w siatce wyników – zastosuj taki kawałek kodu:
SELECT SomeCol AS 'processing-instruction(q)' FROM Whatever FOR XML PATH(''), TYPE
I tyle. Akurat ten fragment pochodzi ze strony Davida Wentzela, ale widziałem go u kilku innych osób.
No super, ale dlaczego tak i co to w ogóle oznacza? Czemu działa? I co pokazuje?
W pokazanym kodzie widzimy, że podstawowym elementem jest zastosowanie FOR XML
– czyli w rzeczywistości nie do końca chodzi o ładne wyświetlanie w siatce wyników, tylko wyświetlanie tego wyniku ogólnie w ramach SSMS. Ale umówmy się – zaznaczenie komórki z wynikiem, skopiowanie jej i wklejenie do notatnika (albo nowej karty w SSMS) – to nie jest jakoś przesadnie skomplikowane. Tyle, że trzeba nacisnąć razem raz guzik myszy (przejście do siatki wyników) i cztery klawisze (Ctrl–C, Ctrl–N, Ctrl–V), a kliknięcie myszą komórki i otwarcie wyniku XML w nowej karcie to jeden klik. Profit!
Wiemy, że zastosowanie FOR XML
daje nam wynik jako XML i dzięki temu ładnie się prezentuje w SSMS. Do tego PATH('')
nie dorzuca nam zbędnych tagów. Po co zatem dorzucać element TYPE
i co nam daje processing-instruction(q)
? Po kolei.
Pierwsza rzecz – pokazany na samym początku przykład działa dobrze, jeśli w danych źródłowych mamy jedną kolumnę i jeden rekord – wtedy dostajemy bardzo ładnie prezentujący się wynik. Zróbmy test i zobaczmy kilka parametrów bazy master
:
SELECT 'baza: ' + d.name + char(13) + char(10) + 'stan: ' + d.state_desc + char(13) + char(10) + 'recovery model: ' + d.recovery_model_desc + char(13) + char(10) + 'containment: ' + d.containment_desc AS 'processing-instruction(baza)' FROM sys.databases d WHERE 1 = 1 AND d.name = 'master' FOR XML PATH(''), TYPE
Wynik:
<?baza baza: master stan: ONLINE recovery model: SIMPLE containment: NONE?>
Czyli działa – dzieli ładnie na linie, a znaczki <?baza
oraz ?>
to wynik właśnie processing-instruction(baza)
. Zanim jeszcze o samym processing-instruction()
– co będzie jeśli usuniemy warunek na bazę master?
<?baza baza: master stan: ONLINE recovery model: SIMPLE containment: NONE?> <?baza baza: tempdb stan: ONLINE recovery model: SIMPLE containment: NONE?> <?baza baza: model stan: ONLINE recovery model: FULL containment: NONE?> <?baza baza: msdb stan: ONLINE recovery model: SIMPLE containment: NONE?>
Ten przykład był uruchamiany na dopiero co postawionym środowisku z SQL Server 2014, stąd same bazy systemowe.
Jeśli zastanawiasz się o co chodzi z warunkiem WHERE 1 = 1
– to jest wyłącznie z powodu wygody i przyzwyczajenia przy pisaniu kodu. Kiedy pierwszym warunkiem WHERE jest coś, co zwraca true
, a potem są warunki AND – to jesli podam je w odrębnych liniach, to łatwo mi zakomentować pojedyncze linie zamiast przepisywać kod, gdybym chciał wyłączyć pierwszy warunek. Oczywiście takie 1 = 1 może zmienić plan zapytania ale jeśli wygenerowany plan okaże się totalnym badziewiem – zawsze można z warunku zrezygnować.
Na pewno rzucił Ci się w oczy fragment TYPE
. Po co on, skoro samo FOR XML PATH('')
zwróci wynik w formie klikalnego linku (*)? Zróbmy jeszcze jeden test na boku:
SELECT 'A' + char(13) + char(10) + 'B' FOR XML PATH('')
Bez zastosowania TYPE
też otrzymaliśmy XML, jak najbardziej z linkiem do kliknięcia. Ogólny wygląd psuje tylko ten znaczek 
:
Oprócz tego kolumna z wynikiem inaczej się nazywa (a dokładniej raz się nazywa, a raz nie). Wygląda, że trochę zbędny ten TYPE
. To sprawdźmy jeszcze raz wcześniejsze zapytanie o bazę master bez TYPE
: pojawia się śliczny komunikat
Msg 451, Level 16, State 1, Line 3
Cannot resolve collation conflict between "Latin1_General_CI_AS_KS_WS" and "Polish_CI_AS" in add operator occurring in SELECT statement column 1.
Okazuje się, że użycie TYPE
załatwia nam problemy z różnymi collation przy łączeniu tekstów (sam błąd to bardzo fajny temat na odrębny wpis) – zatem przydaje się.
Pozostaje wyjaśnić po co processing-instruction()
.
Kiedy za pomocą FOR XML PATH
budujemy jakąś strukturę XML zazwyczaj interesuje nas sama struktura elementów i atrybutów. Stąd całą uwagę (albo przynajmniej jej większość) skupiamy na tym, żeby poprawnie ponazywać zwracane kolumny – a to jako element, a to jako atrybut poprzez zastosowanie znaczka @. Albo przy większym wyrafinowaniu czegoś w stylu ‚element1/element2’ dzięki czemu możemy zagnieździć strukturę zwracanego XML.
FOR XML PATH
to jednak także instrukcje specjalne określane w dokumentacji jako XPath node tests. Wymieniona jest czwórka takich przypadków:
text()
– sam tekst,
node()
– to samo co gwiazdka – *,
comment()
– komentarz
i własnie processing-instruction()
– czyli instrukcja procesująca (np. jak <?xsl ... ?>
w arkuszach XSLT.
No dobrze – wiemy, że jest, że można użyć i jak wygląda na wyniku – ale po co jest to processing-instruction()
? Jeśli przyjrzymy się wynikowi bez i z processing-instruction()
to zobaczymy, że właściwie jedynie tym, że pokazywany XML nie podświetla się w pierwszym znaku sugerując błąd składni XML – jak na załączonym niżej obrazku. I tyle. Wynika z tego, że zamiast processing-instruction()
można równie dobrze dać comment()
i też będzie dobrze. Chyba że znajdę przypadek podobny do tego z sys.databases
i okaże się, że to wręcz kluczowa funkcjonalność.
.
Podsumowując:
– TYPE
się przydaje, bo unikamy problemów z COLLATION, a i ten dodatkowy znak 
się nie wyświetla
– processing-instruction()
to dodatek, żeby przy prezentacji treści jako XML nie pokazywało podświetleń sugerujących błąd składni; a sama instrukcja to jeden z czterech XPath node tests (jakkolwiek to dobrze przetłumaczyć na język polski)
(*) Wyświetlanie w takiej formie to cecha narzędzia – SSMS. W rzeczywistości dzięki TYPE
dostajemy wynik jako XML, a bez TYPE
mamy ntext
(oznaczony jako deprecated).