Comment interroger les valeurs et les attributs Xml de la table dans SQL Server?

88

J'ai une table qui contient une Xmlcolonne:

SELECT * 
FROM Sqm

entrez la description de l'image ici

Un échantillon des xmldonnées d'une ligne serait:

<Sqm version="1.2">
  <Metrics>
    <Metric id="TransactionCleanupThread.RecordUsedTransactionShift" type="timer" unit="µs" count="1" sum="21490"   average="21490"   minValue="73701"    maxValue="73701"                               >73701</Metric>
    <Metric id="TransactionCleanupThread.RefundOldTrans"             type="timer" unit="µs" count="1" sum="184487"  average="184487"  minValue="632704"   maxValue="632704"                              >632704</Metric>
    <Metric id="Database.CreateConnection_SaveContextUserGUID"       type="timer" unit="µs" count="2" sum="7562"    average="3781"    minValue="12928"    maxValue="13006"    standardDeviation="16"     >12967</Metric>
    <Metric id="Global.CurrentUser"                                  type="timer" unit="µs" count="6" sum="4022464" average="670411"  minValue="15"       maxValue="13794345" standardDeviation="1642047">2299194</Metric>
    <Metric id="Global.CurrentUser_FetchIdentityFromDatabase"        type="timer" unit="µs" count="1" sum="4010057" average="4010057" minValue="13752614" maxValue="13752614"                            >13752614</Metric>
  </Metrics>
</Sqm>

Dans le cas de ces données, je voudrais:

SqmId  id                                                   type   unit  count  sum      minValue  maxValue  standardDeviation  Value
=====  ===================================================  =====  ====  =====  ======   ========  ========  =================  ======
1      TransactionCleanupThread.RecordUsedTransactionShift  timer  µs    1      21490    73701     73701     NULL               73701
1      TransactionCleanupThread.RefundOldTrans              timer  µs    1      184487   632704    632704    NULL               632704
1      Database.CreateConnection_SaveContextUserGUID        timer  µs    2      7562     12928     13006     16                 12967
1      Global.CurrentUser                                   timer  µs    6      4022464  15        13794345  1642047            2299194
1      Global.CurrentUser_FetchIdentityFromDatabase         timer  µs    1      4010057  13752614  13752614  NULL               13752614
2      ...

En fin de compte, je vais effectivement assurer SUM(), MIN(), l' MAX()agrégation. Mais pour l'instant, j'essaie simplement d' interroger une colonne xml.

En pseudo-code, j'essaierais quelque chose comme:

SELECT
    SqmId,
    Data.query('/Sqm/Metrics/Metric/@id') AS id,
    Data.query('/Sqm/Metrics/Metric/@type') AS type,
    Data.query('/Sqm/Metrics/Metric/@unit') AS unit,
    Data.query('/Sqm/Metrics/Metric/@sum') AS sum,
    Data.query('/Sqm/Metrics/Metric/@count') AS count,
    Data.query('/Sqm/Metrics/Metric/@minValue') AS minValue,
    Data.query('/Sqm/Metrics/Metric/@maxValue') AS maxValue,
    Data.query('/Sqm/Metrics/Metric/@standardDeviation') AS standardDeviation,
    Data.query('/Sqm/Metrics/Metric') AS value
FROM Sqm

Mais cette requête SQL ne fonctionne pas:

Msg 2396, niveau 16, état 1, ligne 2
XQuery [Sqm.data.query ()]: l'attribut peut ne pas apparaître en dehors d'un élément

J'ai chassé, et c'est incroyable à quel point les requêtes Xml sont mal documentées ou examinées. La plupart des ressources plutôt que d'interroger une table , interrogez une variable ; ce que je ne fais pas. La plupart des ressources n'utilisent que des requêtes XML pour le filtrage et la sélection, plutôt que pour la lecture des valeurs. La plupart des ressources lisent les nœuds enfants codés en dur (par index), plutôt que les valeurs réelles.

Ressources connexes que j'ai lues

Mise à jour: .value plutôt que .query

J'ai essayé d'utiliser au hasard .value, à la place de .query:

SELECT
    Sqm.SqmId,
    Data.value('/Sqm/Metrics/Metric/@id', 'varchar(max)') AS id,
    Data.value('/Sqm/Metrics/Metric/@type', 'varchar(max)') AS type,
    Data.value('/Sqm/Metrics/Metric/@unit', 'varchar(max)') AS unit,
    Data.value('/Sqm/Metrics/Metric/@sum', 'varchar(max)') AS sum,
    Data.value('/Sqm/Metrics/Metric/@count', 'varchar(max)') AS count,
    Data.value('/Sqm/Metrics/Metric/@minValue', 'varchar(max)') AS minValue,
    Data.value('/Sqm/Metrics/Metric/@maxValue', 'varchar(max)') AS maxValue,
    Data.value('/Sqm/Metrics/Metric/@standardDeviation', 'varchar(max)') AS standardDeviation,
    Data.value('/Sqm/Metrics/Metric', 'varchar(max)') AS value
FROM Sqm

Mais cela ne fonctionne pas non plus:

Msg 2389, niveau 16, état 1, ligne 3 XQuery [Sqm.data.value ()]:
'value ()' nécessite un singleton (ou séquence vide), opérande trouvé de type 'xdt: untypedAtomic *'

Ian Boyd
la source

Réponses:

113

En fait, vous êtes proche de votre objectif, il vous suffit d'utiliser la méthode nodes () pour diviser vos lignes puis obtenir des valeurs:

select
    s.SqmId,
    m.c.value('@id', 'varchar(max)') as id,
    m.c.value('@type', 'varchar(max)') as type,
    m.c.value('@unit', 'varchar(max)') as unit,
    m.c.value('@sum', 'varchar(max)') as [sum],
    m.c.value('@count', 'varchar(max)') as [count],
    m.c.value('@minValue', 'varchar(max)') as minValue,
    m.c.value('@maxValue', 'varchar(max)') as maxValue,
    m.c.value('.', 'nvarchar(max)') as Value,
    m.c.value('(text())[1]', 'nvarchar(max)') as Value2
from sqm as s
    outer apply s.data.nodes('Sqm/Metrics/Metric') as m(c)

sql fiddle demo

Roman Pekar
la source
1
Comment obtenir la «valeur» du nœud lui-même? Il semble qu'il n'y ait aucun moyen de select m.*voir la table intermédiaire secrète, magique, qu'il a construite. Quelle est la syntaxe pour interroger la valeur d'un élément? Par exemple, la valeur de <Metric>8675309</Metric>est "8675309"
Ian Boyd
1
@IanBoyd désolé, j'ai raté ça, voir mise à jour. Vous pouvez utiliser '.' ou texte s'il pourrait y avoir des éléments imbriqués
Roman Pekar
2
Que représentent les alias s, met cdans cette requête?
Ian R. O'Brien
3
@ IanR.O'Brien mest l'ensemble de résultats retourné par la nodes()fonction, sest la sqmtable elle-même, cest la colonne avec le type de données xml dans l'ensemble de résultats retourné par la nodes()fonction
Roman Pekar
11

J'ai essayé de faire quelque chose de très similaire mais sans utiliser les nœuds. Cependant, ma structure xml est un peu différente.

Vous l'avez comme ça:

<Metrics>
    <Metric id="TransactionCleanupThread.RefundOldTrans" type="timer" ...>

Si c'était comme ça à la place:

<Metrics>
    <Metric>
        <id>TransactionCleanupThread.RefundOldTrans</id>
        <type>timer</type>
        .
        .
        .

Ensuite, vous pouvez simplement utiliser cette instruction SQL.

SELECT
    Sqm.SqmId,
    Data.value('(/Sqm/Metrics/Metric/id)[1]', 'varchar(max)') as id,
    Data.value('(/Sqm/Metrics/Metric/type)[1]', 'varchar(max)') AS type,
    Data.value('(/Sqm/Metrics/Metric/unit)[1]', 'varchar(max)') AS unit,
    Data.value('(/Sqm/Metrics/Metric/sum)[1]', 'varchar(max)') AS sum,
    Data.value('(/Sqm/Metrics/Metric/count)[1]', 'varchar(max)') AS count,
    Data.value('(/Sqm/Metrics/Metric/minValue)[1]', 'varchar(max)') AS minValue,
    Data.value('(/Sqm/Metrics/Metric/maxValue)[1]', 'varchar(max)') AS maxValue,
    Data.value('(/Sqm/Metrics/Metric/stdDeviation)[1]', 'varchar(max)') AS stdDeviation,
FROM Sqm

Pour moi, c'est beaucoup moins déroutant que d'utiliser l'application externe ou l'application croisée.

J'espère que cela aidera quelqu'un d'autre à la recherche d'une solution plus simple!

Ryan Dorendorf
la source
1
le code manque les crochets ouvrants. également ajouter /text()après id, etc. pour augmenter les performances
Danny Rancher
C'est le plus simple. Merci, travaillé parfaitement.
SE
Comment interroger une table avec une colonne de type XML avec cette approche? Merci.
FMFF
10

utiliser à la valueplace de query(doit spécifier l'index du nœud à renvoyer dans XQuery ainsi que passer le type de données sql à renvoyer comme deuxième paramètre):

select
    xt.Id
    , x.m.value( '@id[1]', 'varchar(max)' ) MetricId
from
    XmlTest xt
    cross apply xt.XmlData.nodes( '/Sqm/Metrics/Metric' ) x(m)
Moho
la source
8

Je ne comprends pas pourquoi certaines personnes suggèrent d'utiliser cross applyou outer applyde convertir le XML en une table de valeurs. Pour moi, cela a simplement rapporté beaucoup trop de données.

Voici mon exemple de la façon dont vous créez un xml objet, puis le transformez en table.

(J'ai ajouté des espaces dans ma chaîne xml, juste pour la rendre plus facile à lire.)

DECLARE @str nvarchar(2000)

SET @str = ''
SET @str = @str + '<users>'
SET @str = @str + '  <user>'
SET @str = @str + '     <firstName>Mike</firstName>'
SET @str = @str + '     <lastName>Gledhill</lastName>'
SET @str = @str + '     <age>31</age>'
SET @str = @str + '  </user>'
SET @str = @str + '  <user>'
SET @str = @str + '     <firstName>Mark</firstName>'
SET @str = @str + '     <lastName>Stevens</lastName>'
SET @str = @str + '     <age>42</age>'
SET @str = @str + '  </user>'
SET @str = @str + '  <user>'
SET @str = @str + '     <firstName>Sarah</firstName>'
SET @str = @str + '     <lastName>Brown</lastName>'
SET @str = @str + '     <age>23</age>'
SET @str = @str + '  </user>'
SET @str = @str + '</users>'

DECLARE @xml xml
SELECT @xml = CAST(CAST(@str AS VARBINARY(MAX)) AS XML) 

--  Iterate through each of the "users\user" records in our XML
SELECT 
    x.Rec.query('./firstName').value('.', 'nvarchar(2000)') AS 'FirstName',
    x.Rec.query('./lastName').value('.', 'nvarchar(2000)') AS 'LastName',
    x.Rec.query('./age').value('.', 'int') AS 'Age'
FROM @xml.nodes('/users/user') as x(Rec)

Et voici la sortie:

entrez la description de l'image ici

Mike Gledhill
la source
Curieux ... pourquoi la distribution imbriquée Varbinary(max)avant la distribution XML s'il vous plaît?
EvilDr
Comment interroger une table avec une colonne de type XML avec cette approche? Merci.
FMFF