Introduktion till OpenXML i SQL Server
Author: Kristofer Gafvert
First Published: April 3, 2004
Last Updated: April 3, 2004
Last Reviewed: April 3, 2004
PDF: OpenXML-SE.pdf
Innehållsförteckning
Innehållsförteckning
Innan vi börjar
Läsa ett XML-dokument
Ett mer avancerat exempel
Lägga in i en tabell
Innan vi börjar
Lika viktigt som att få XML utifrån data i SQL Server, är det att lyckas lägga
in data i SQL Server från ett XML-dokument. Detta använder vi en funktion som
heter OpenXML för, men för att det ska fungera, så finns det ett par
saker vi måste göra. Innan vi använder OpenXML måste nämligen XML-dokumentet
analyseras och byggas upp som en trädstruktur, med de olika noderna,
attributen, texterna med mera i XML-dokumentet, i minnet på datorn. Detta
använder man en lagrad procedur till som heter sp_xml_preparedocument.
Denna lagrade procedur returnerar ett handtag (eng. handle) som man använder
för att komma åt detta dokument, som nu finns i minnet med en egen struktur,
som SQL Server (och OpenXML) kan tolka.
Eftersom XML-dokumentet nu finns i minnet, betyder det att vi gärna vill ta
bort det när vi är klar med det. Om vi inte gör det riskerar vi att göra slut
på minnet i datorn, vilket leder till otrevliga saker. Så därför finns det en
lagrad procedur för även detta, nämligen sp_xml_removedocument. Om vi nu
skulle glömma att ta bort det, så kommer det ändå inte finnas i minnet för all
framtid, utan tills anslutningen (den anslutning som användes för att lägga in
det i minnet) avbrutits.
Om ni får för sig att kopiera exempel direkt från denna text, istället för att
skriva av, och det inte fungerar, kan det bero på att det används en annan
teckentabell i detta dokument än vad SQL Query Analyzer använder, vilket
betyder att vissa tecken kommer den inte att godkänna.
Läsa ett XML-dokument
Nu när vi vet lite om bakgrunden, är det dags att börja skriva lite T-SQL. Men
vi kommer inte gå direkt på att lägga in data i tabeller, utan istället börjar
vi med att göra lite vanliga Select frågor, liknande de vi gör för existerande
tabeller i en databas, men nu ska vi istället göra det mot det XML-dokument vi
har.
1 Declare @resultDoc int, @xmlDoc varchar(4000)
2
3 Set @xmlDoc = N'
4 <?xml version="1.0" encoding="ISO-8859-1"?>
5 <Anställda>
6 <Person>
7 <Förnamn>Anna</Förnamn>
8 <Efternamn>Andersson</Efternamn>
9 <Ålder>26</Ålder>
10 </Person>
11 <Person>
12 <Förnamn>Sivert</Förnamn>
13 <Efternamn>Persson</Efternamn>
14 <Ålder>35</Ålder>
15 </Person>
16 </Anställda>'
17
18 EXEC sp_xml_preparedocument @resultDoc OUTPUT, @xmlDoc
19
20 SELECT *
21 FROM OpenXML(@resultDoc, N'/Anställda/Person', 2)
22 WITH (
23 Förnamn VARCHAR(15),
24 Efternamn VARCHAR(15),
25 Ålder INTEGER
26 )
27
28 EXEC sp_xml_removedocument @resultDoc
Det först ni nog tänker på, varför är XML-koden här, ska inte den vara i en
fil? Jo, den kan mycket väl finnas i en fil, men för att saker och ting ska bli
enklare, så utelämnas detta (normalt sett är denna kod förmodligen sparad som
en lagrad procedur, som exekveras från en applikation, som skickar med
XML-dokumentet som text, och inte hänvisar till en fil).
På första raden deklarerar vi två variabler. Den sista av dessa kommer
innehålla XML-dokumentet. Denna är deklarerad som varchar, och som ni ser så är
den också väldigt stor. För detta lilla exempel hade den inte behövt vara så
stor, men man bör tänka på antalet tecken blir otroligt stort när det blir lite
större XML-dokument. Om man då anger en för liten storlek på datatypen, kommer
det resultera i underliga fel. Till exempel om jag enbart hade angivit
storleken 200 i ovanstående exempel, hade vi fått detta felmeddelande:
XML parsing error: The following tags were not closed: Anställda, Person,
Efternamn.
Anledningen till detta är för att när den storlek jag angivit har uppnåtts,
kommer den strunta i allt som kommer därefter, vilket resulterar att en massa
text i XML-dokumentet fattas (vilket inkluderar sluttaggarna för de där
elementen).
På rad 3 sparar vi XML-dokumentet (som är på raderna 4 till 16) i variabeln
@xmlDoc.
På rad 18 kör vi den lagrade proceduren xp_xml_preparedocument som läser in
XML-dokumentet i minnet, formaterar det, och sedan returnerar ett handtag till
det, som vi sparar i variabeln @resultDoc.
Därefter, på raderna 20 till 26 gör vi själva frågan, som kommer resultera i
detta:
|
|
Förnamn
|
Efternamn
|
Ålder
|
|
1
|
Anna
|
Andersson
|
26
|
|
2
|
Sivert
|
Persson
|
35
|
OpenXML har som vi ser tre parametrar, och syntaxen ser ut såhär:
OPENXML(idoc int [in],rowpattern nvarchar[in],[flags byte[in]])
[WITH (SchemaDeclaration | TableName)]
Den första parametern (idoc) är handtaget till det som skapades med sp_xml_preparedocument.
Den andra parametern (rowpattern) används för att tala om vad som är
intressant. I vårt fall säger vi åt den att starta i /Anställda/Person (den
första / kan utelämnas). Som flagga satte vi nummer 2, detta talade om för den
att vi var intresserade av element. Om vi hade satt en 1:a istället, hade vi
istället fått attribut (vi saknar attribut helt i vårt exempel, så det hade
resulterat i en massa NULL). Man kan också kombinera dessa två nummer till en
3:a, vilket gör att vi får både attribut och element. Därefter använder vi WITH
för att tala om vilka kolumner vi vill ha, samt vilken datatyp det är. I vårt
fall vill vi ha Förnamn, Efternamn och Ålder från XML-filen, så därför skriver
vi det.
På rad 28 rensar vi upp efter oss, och tar bort det som vi lade i minnet.
Ett mer avancerat exempel
Nu när vi har använt SQL för att få ut det vi vill från ett XML-dokument, är
det dags att ta ett litet mer avancerat exempel, innan vi slutligen lägger in
resultatet i en tabel i vår databas.
1 Declare @resultDoc int, @xmlDoc varchar(4000)
2
3 Set @xmlDoc = N'
4 <?xml version="1.0" encoding=”ISO-8859-1”?>
5 <Anställda>
6 <Avdelning Id="3">
7 <Person
Id="1">
8 <Förnamn>Anna</Förnamn>
9 <Efternamn>Andersson</Efternamn>
10 <Ålder>26</Ålder>
11 </Person>
12 <Person Id="2">
13 <Förnamn>Bertil</Förnamn>
14 <Efternamn>Johansson</Efternamn>
15 <Ålder>48</Ålder>
16 </Person>
17 </Avdelning>
18 <Avdelning Id="5">
19 <Person Id="3">
20 <Förnamn>Sivert</Förnamn>
21 <Efternamn>Persson</Efternamn>
22 <Ålder>35</Ålder>
23 </Person>
24 </Avdelning>
25 </Anställda>'
26
27 EXEC sp_xml_preparedocument @resultDoc OUTPUT, @xmlDoc
28
29 SELECT *
30 FROM OpenXML(@resultDoc, N'Anställda/Avdelning/Person', 3)
31 WITH (
32 PersonId INTEGER '@Id',
33 Förnamn VARCHAR(15),
34 Efternamn VARCHAR(15),
35 Ålder INTEGER,
36 AvdelningsId INTEGER '../@Id'
37 )
38
39 EXEC sp_xml_removedocument @resultDoc
Utskrift
|
|
PersonId
|
Förnamn
|
Efternamn
|
Ålder
|
AvdelningsId
|
| 1
|
1
|
Anna
|
Andersson
|
26
|
3
|
| 2
|
2
|
Bertil
|
Johansson
|
48
|
3
|
| 3
|
3
|
Sivert
|
Persson
|
35
|
5
|
Som vi ser så är XML-koden lite ändrad. Vi har ni infört några attribut, och vi
har också ett helt nytt element – Avdelning. Man skulle kunna likna det här vid
två tabeller i en vanlig databas. En tabell som heter Avdelning, och en som
heter Person. I Person finns det sedan en Främmande Nyckel från Avdelning, som
talar om på vilken avdelning som personen arbetar på. I XML ser detta ut som
det som är skrivet här ovan.
På rad 30 och 31, är det lite ändrat. Vi har såklart ändrat ”sökvägen”, då vi
fortfarande vill utgå från Person. Vi har också skrivit en 3:a istället för en
2:a, detta för att vi är intresserade av både element och attribut.
Det är på rad 33 till 37 som det intressanta kommer. Vi börjar med rad 33.
Precis som tidigare har vi angivit datatypen integer, men nu har vi skrivit ett
annat namn för kolumnen. Eftersom vi gjorde detta, och detta namn inte finns i
XML-dokumentet, behöver vi en tredje del som talar om vad vi vill ha i
XML-dokumentet, nämligen attributet Id. @-tecknet talar om att det är ett
attribut vi vill ha, hade vi utelämnat det hade den letat efter ett element som
heter Id, vilket vi inte har, och hade resulterar i att det enda vi får är NULL
istället för de Id-nummer vi fick. Tilläggas ska också att detta är
Case-sensitive. Vi har ett attribut som heter Id, men vi har inget som heter
ID, och kan därför inte skriva:
PersonId INTEGER '@ID'
Detta har att göra med att XML är känslig för stora och små bokstäver, och
</anställda> kan inte avsluta start-taggen <Anställda>.
På rad 37 kommer något nytt igen. Här anger vi att den ska gå upp en nivå i
hierarkin, och hämta attributet Id. Så uppenbarligen är vi inte knytna till en
nivå i vårt XML-dokument, utan kan befinna oss var som helst i det och hämta
data.
Lägga in i en tabell
Äntligen kommer vi till det mest intressanta, att lägga in det i en tabell. Vi
kommer använda oss av föregående exempel, och skapar därför en tabell som heter
Person. Därefter lägger vi in data från XML-filen, till denna tabell. Detta
blir SQL-frågorna:
1 CREATE TABLE Person
2 (
3 PersonId INTEGER IDENTITY (1,1) NOT
NULL,
4 Fornamn VARCHAR(15),
5 Efternamn VARCHAR(15),
6 Alder INTEGER,
7 AvdelningsID INTEGER
8 )
9 GO
10 Declare @resultDoc int, @xmlDoc varchar(4000)
11
12 Set @xmlDoc = N'
13 <?xml version="1.0" encoding="ISO-8859-1"?>
14 <Anställda>
15 <Avdelning Id="3">
16 <Person Id="1">
17 <Förnamn>Anna</Förnamn>
18 <Efternamn>Andersson</Efternamn>
19 <Ålder>26</Ålder>
20 </Person>
21 <Person Id="2">
22 <Förnamn>Bertil</Förnamn>
23 <Efternamn>Johansson</Efternamn>
24 <Ålder>48</Ålder>
25 </Person>
26 </Avdelning>
27 <Avdelning Id="5">
28 <Person Id="3">
29 <Förnamn>Sivert</Förnamn>
30 <Efternamn>Persson</Efternamn>
31 <Ålder>35</Ålder>
32 </Person>
33 </Avdelning>
34 </Anställda>'
35
36 EXEC sp_xml_preparedocument @resultDoc OUTPUT, @xmlDoc
37
38 SET IDENTITY_INSERT Person ON
39 INSERT INTO Person (PersonId, Efternamn, Fornamn, Alder,
AvdelningsID)
40 SELECT PersonId, Efternamn, Förnamn, Ålder, AvdelningsID
41 FROM OpenXML(@resultDoc, N'Anställda/Avdelning/Person', 3)
42 WITH (
43 PersonId INTEGER '@Id',
44 Förnamn VARCHAR(15),
45 Efternamn VARCHAR(15),
46 Ålder INTEGER,
47 AvdelningsID INTEGER '../@Id'
48 )
49 SET IDENTITY_INSERT Person OFF
50
51 EXEC sp_xml_removedocument @resultDoc
På rad 1 till 8 skapar vi en ny tabell som heter Person. Därefter på rad 10
till 36 är det exakt likadant som tidigare. Det är inte förren på rad 38 som
det blir något nytt. Eftersom vi har en Primär Nyckel som ökas med ett (1)
automatiskt, så kan vi inte sätta in vilket värde som helst i den kolumnen när
vi gör en INSERT. Utan för att vi ska få sätta in de värden vi har fått från
XML-dokumentet, måste vi sätta IDENTITY_INSERT till ON, vilket vi gör här.
På rad 39 och 40 (en rad) börjar vår INSERT-fråga. Den är egentligen inte så
konstig, om vi tänker på en vanlig INSERT-fråga med en SELECT-del. För det är
precis det vi har här, en INSERT-fråga, som hämtar värdena från en annan tabell
genom att använda SELECT.
På rad 41 och 42 väljer vi ut vad vi vill lägga in i tabellen Person. Precis
som vid en vanlig INSERT så spelare det ingen roll vad kolumnerna heter, utan
det är ordningen på dem.
Rad 43 till 51 är lika som förut, och på rad 52 gör vi motsatsen till vad vi
gjorde på rad 38.
Om vi nu gör denna enkla fråga:
SELECT * FROM Person
Får vi detta resultat:
|
|
PersonId
|
Förnamn
|
Efternamn
|
Ålder
|
AvdelningsId
|
| 1
|
1
|
Anna
|
Andersson
|
26
|
3
|
| 2
|
2
|
Bertil
|
Johansson
|
48
|
3
|
| 3
|
3
|
Sivert
|
Persson
|
35
|
5
|
Vilket är exakt samma tabell som när vi gjorde en SELECT-fråga direkt mot
XML-filen. Kort sagt, vi har lyckats få in vår data i tabellen Person!