Tytu艂 dzisiejszego wpisu, gdyby go potraktowa膰 powa偶nie, m贸g艂by brzmie膰:
"Jak wykona膰 zapytanie SQL na danych w formacie XML i po co?"
Ale poniewa偶 to nie jest powa偶ny blog, zostawi臋 jak jest.
No wi臋c, o sssso chozzzzi?
Chozzzi o t臋 nieszcz臋sn膮 technologi臋 przechowywania danych, kt贸ra - pomimo swych niedoskona艂o艣ci - zdo艂a艂a si臋 utrzyma膰 na powierzchni, a nawet zdominowa膰 niekt贸re obszary 艣wiata IT- i 偶yje sobie w najlepsze. M贸wi臋 o XML-u.
Je偶eli kto艣 jeszcze nie s艂ysza艂 starego przys艂owia pszcz贸艂 o XML-u, teraz ma okazj臋:
"XML is like violence. If it doesn't work for you, you're not using enough of it.
Nie lubi臋 XML-a z dw贸ch powod贸w: po pierwsze, jest strasznie rozwlek艂y. Zapisanie kilku bajt贸w danych w XML-u wymaga kilkukrotnie wi臋kszej ilo艣ci danych kontrolnych. Przy wi臋kszej ilo艣ci danych oraz rozs膮dnym gospodarowaniu d艂ugo艣ci膮 znacznik贸w mo偶na 贸w procenta偶 nieco zmniejszy膰, ale w dalszym ci膮gu mamy do czynienia z ca艂kiem sporym "narzutem protoko艂u". Drugi aspekt XML-a, kt贸rego nie znosz臋 jeszcze bardziej, to brak jednolito艣ci sk艂adni: t臋 sama informacj臋 mo偶emy w XML zapisa膰 na kilka r贸偶nych sposob贸w, co teoretycznie jest fajne (lubimy elastyczno艣膰!), ale w praktyce prowadzi do ba艂aganu. Na przyk艂ad dane typu identyfikator / imi臋 / nazwisko mog膮 by膰 zapisane tak:
<osoba><id>177436</id><imie>Adam</imie><nazwisko>Kowalski</nazwisko></osoba>
... lub tak:
<osoba id="177436" imie="Adam" nazwisko="Kowalski"></osoba>
... lub tak:
<osoba id="177436"><imie>Adam</imie><nazwisko>Kowalski</nazwisko></osoba>
... lub tak:
<osoba id="177436"><imie value="Adam"></imie><nazwisko value="Kowalski"></nazwisko></osoba>
I tak dalej. Taka niejednolito艣膰 powoduje zwi臋kszenie z艂o偶ono艣ci j臋zyk贸w wykonywania zapyta艅 do takich danych (tak, j臋zyk贸w, jest ich wi臋cej ni偶 jeden!), a tak偶e utrudnia analiz臋 danych i og贸lnie komplikuje 偶ycie programi艣cie, kt贸ry ma takimi danymi 偶onglowa膰.
No ale, jak to m贸wi膮, jak si臋 nie ma, co si臋 lubi, to si臋 na pochy艂e drzewo skacze, dop贸ki si臋 ucho nie urwie. Mamy XML, b臋dziemy 偶onglowa膰 XML. Trudno.
Zobaczmy teraz, w jaki spos贸b mo偶na do danych w formacie XML wykonywa膰 zapytania SQL. U偶yjemy SQL-a od Microsoftu, bo akurat taki mam pod r臋k膮, ale zapewniam, 偶e wi臋kszo艣膰 pozosta艂ych wiod膮cych silnik贸w baz danych r贸wnie偶 radzi sobie z danymi XML, w ca艂kiem podobny (偶eby nie u偶y膰 zbyt d艂ugiego s艂owa: analogiczny) spos贸b.
Zaczniemy od zdefiniowania danych wej艣ciowych:
<?xml version="1.0"?> <Osoby> <Osoba id="1"> <Imie>Adam</Imie> <Nazwisko>Kowalski</Nazwisko> </Osoba> <Osoba id="2"> <Imie>Jan</Imie> <Nazwisko>Kosiarz</Nazwisko> </Osoba> <Osoba id="3"> <Imie>Janina</Imie> <Nazwisko>Michalska</Nazwisko> </Osoba> </Osoby>
Powy偶sze dane w formacie XML reprezentuj膮 niedu偶膮 tabelk臋 o nazwie Osoby, z trzema kolumnami (id, nazwisko, imie) i trzema rekordami. Spr贸bujmy teraz dobra膰 si臋 do tych danych za pomoc膮 SQL-a:
DECLARE @dane_xml XML = '<?xml version="1.0"?> <Osoby> <Osoba id="1259"> <Imie>Adam</Imie> <Nazwisko>Kowalski</Nazwisko> </Osoba> <Osoba id="2532"> <Imie>Jan</Imie> <Nazwisko>Kosiarz</Nazwisko> </Osoba> <Osoba id="6433"> <Imie>Janina</Imie> <Nazwisko>Michalska</Nazwisko> </Osoba> </Osoby>';
Najpierw co艣 prostego: spr贸bujmy wyci膮gn膮膰 pojedynczy rekord z tej gmatwaniny:
select @dane_xml.value('(/Osoby/Osoba/@id)[1]','nvarchar(max)') as id , @dane_xml.value('(/Osoby/Osoba/Imie)[1]','nvarchar(max)') as imie , @dane_xml.value('(/Osoby/Osoba/Nazwisko)[1]','nvarchar(max)') as nazwisko;
Powy偶sze zapytanie SQL zwr贸ci jeden rekord z trzema kolumnami:
Prosz臋 zwr贸ci膰 uwag臋 na to, w jaki spos贸b podajemy lokalizacj臋 elementu XML:
/Osoby/Osoba/@id
/Osoby/Osoba/Imie
I tak dalej. Taka sk艂adnia nazywa si臋 XPath i jest do艣膰 rozbudowana (ch臋tnych oraz samob贸jc贸w zapraszam na Google po wi臋cej szczeg贸艂贸w). Zalet膮 XPath jest to, 偶e nie musimy si臋 bawi膰 w XSLT, przy kt贸rym XML jest prosty i klarowny 馃槈 W naszym przypadku wyci膮gamy warto艣膰 atrybutu ID oraz warto艣ci dw贸ch w臋z艂贸w: Imie oraz Nazwisko.
Je偶eli chcemy znale藕膰 rekord o znanym ID, robimy tak:
select @dane_xml.value('(/Osoby/Osoba[@id=''6433'']/@id)[1]','nvarchar(max)') as id , @dane_xml.value('(/Osoby/Osoba[@id=''6433'']/Imie)[1]','nvarchar(max)') as imie , @dane_xml.value('(/Osoby/Osoba[@id=''6433'']/Nazwisko)[1]','nvarchar(max)') as nazwisko;
Wynik:
W tym przypadku u偶yli艣my sk艂adni Pole[@atrybut='warto艣膰'], kt贸ra wyszuka nam wszystkie rekordy o tej warto艣ci atrybutu.
Dociekliwy Czytelnik zauwa偶y jeszcze na ko艅cu zapytania wyra偶enie "[1]" - jest to numer kolejny rekordu, kt贸ry chcemy zwr贸ci膰. W pierwszym przyk艂adzie mo偶emy u偶y膰 [1], [2] lub [3], poniewa偶 mamy trzy rekordy typu Osoba. W drugim przyk艂adzie, poniewa偶 szukamy konkretnego rekordu (o id=6433), zadzia艂a tylko [1], bo wi臋cej rekord贸w o takim id nie ma.
No dobrze. Wiemy ju偶, jak znale藕膰 pojedynczy rekord i go wy艣wietli膰 w postaci tabeli. A co, je偶eli chcemy z takiego XML-a wyci膮gn膮膰 wi臋cej rekord贸w?
Tu w sukurs przychodzi metoda .nodes() obiektu XML, kt贸ra zwraca wiele rekord贸w, a nie tylko jeden.
O, tak:
select osoba.c.value('@id', 'nvarchar(max)') as id , osoba.c.value('(Imie)[1]', 'nvarchar(max)') as imie , osoba.c.value('(Nazwisko)[1]', 'nvarchar(max)') as nazwisko from @dane_xml.nodes('Osoby') as osoby(c) outer apply osoby.c.nodes('Osoba') as osoba(c);
Wynik powy偶szego zapytania wygl膮da tak:
Szczeg贸艂owej sk艂adni powy偶szego zapytania ju偶 mi si臋 dzi艣 nie chce szczeg贸艂owo omawia膰. W skr贸cie: @dane_xml.nodes() zwraca wszystkie rekordy, a osoby.c.nodes(...) zwraca wszystkie kolumny dla ka偶dego rekordu. O wyra偶eniu OUTER APPLY by膰 mo偶e napisz臋 kiedy indziej - jest to do艣膰 pot臋偶ne narz臋dzie j臋zyka SQL, kt贸re - u偶ywane z g艂ow膮 - daje ca艂kiem ciekawe mo偶liwo艣ci.
jakby to kolega translatnal, to by bylo easier pokazac mniej dzikim tribes 馃槈
Jakbym to translatn膮艂, to bym musia艂 sam zrozumie膰 zagadnienie najpierw :]