Trick (?) z processing-instruction()

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 (CtrlC, CtrlN, CtrlV), 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 &#x0D:
FOR_XML_PATH
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ść.
FOR_XML_wynik.

Podsumowując:
TYPE się przydaje, bo unikamy problemów z COLLATION, a i ten dodatkowy znak &#x0D 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).FOR_XML_TYPE

Advertisements

Skomentuj

Wprowadź swoje dane lub kliknij jedną z tych ikon, aby się zalogować:

Logo WordPress.com

Komentujesz korzystając z konta WordPress.com. Log Out / Zmień )

Zdjęcie z Twittera

Komentujesz korzystając z konta Twitter. Log Out / Zmień )

Facebook photo

Komentujesz korzystając z konta Facebook. Log Out / Zmień )

Google+ photo

Komentujesz korzystając z konta Google+. Log Out / Zmień )

Connecting to %s