Kembalikan urutan xml di mana atribut tidak mengandung karakter tertentu

10

Pertimbangkan XML sederhana berikut ini:

<xml>
  <customer name="Max">
    <email address="[email protected]" />
  </customer>
  <customer name="Erik">
    <email address="[email protected]" />
  </customer>
  <customer name="Brent">
    <email address="brentcom" />
  </customer>
</xml>

Saya ingin mendapatkan daftar <Customer>urutan di mana addressatribut <email>item tidak mengandung @.

Jadi, saya ingin output yang terlihat seperti:

<customer name="Brent">
  <email address="brentcom" />
</customer>

mcve :

DECLARE @x XML = '<xml>
<customer name="Max"><email address="[email protected]" /></customer>
<customer name="Erik"><email address="[email protected]" /></customer>
<customer name="Brent"><email address="brentcom" /></customer>
</xml>';

Kueri ini:

SELECT WithValidEmail = @x.query('/xml/customer/email[contains(@address, "@")]')
    , WithInvalidEmail = @x.query('/xml/customer/email[contains(@address, "@")] = False');

Pengembalian:

╔═══════════════════════════════════════╦══════════════════╗
            WithValidEmail              WithInvalidEmail 
╠═══════════════════════════════════════╬══════════════════╣
 <email address="[email protected]" />                          
 <email address="[email protected]" />  false            
╚═══════════════════════════════════════╩══════════════════╝

Kueri ini:

SELECT WithInValidEmail = @x.query('/xml/customer/email')
WHERE @x.exist('/xml/customer/email[contains(@address, "@")]') = 0;

Pengembalian:

╔══════════════════╗
 WithInValidEmail 
╚══════════════════╝
    (no results)

The WHEREklausul dalam query di atas adalah menghilangkan seluruh himpunan XML karena setidaknya urutan tunggal ada di mana alamat email berisi tanda "@".

Max Vernon
sumber

Jawaban:

11

Cara mudah untuk melakukan ini adalah dengan menggunakan nodes metode untuk langsung ke addressatribut dan memeriksa @tanda Anda .

Masalah dengan cara Anda melihat sekarang adalah hanya memeriksa apakah ada alamat email @di dalamnya. Mengurai node XML memungkinkan Anda memeriksa setiap email untuknya.

DECLARE @x XML
    = '<xml>
<customer name="Max"><email address="[email protected]" /></customer>
<customer name="Erik"><email address="[email protected]" /></customer>
<customer name="Brent"><email address="brentcom" /></customer>
</xml>';


SELECT x.c.value('@address', 'VARCHAR(100)') AS [email]
FROM   @x.nodes('/xml/customer/email') AS x(c)
WHERE  x.c.exist('@address[contains(., "@")]') = 0;

Jika Anda perlu query tabel aktual dengan kolom XML seperti ini, Anda hanya perlu CROSS APPLYmetode node seperti:

SELECT x.c.value('@address', 'VARCHAR(100)') AS [email]
FROM @x_table AS xt
CROSS APPLY xt.x.nodes('/xml/customer/email') AS x(c)
WHERE  x.c.exist('@address[contains(., "@")]') = 0;

Jika Anda ingin mengembalikan semua <customer>...</customer>XML untuk "baris" itu, Anda dapat menjalankan porosnya kembali. Ketahuilah bahwa berjalan kembali dapat membuat kinerja menjadi sedikit rumit untuk blok XML besar.

SELECT x.c.query('..')
FROM @x_table AS xt
CROSS APPLY xt.x.nodes('/xml/customer/email') AS x(c)
WHERE  x.c.exist('@address[contains(., "@")]') = 0;

Cara lain untuk melakukannya adalah:

SELECT @x.query('/xml/customer[email/@address[not(contains(., "@"))]]') answer

Memindahkan tanda kurung siku untuk membungkus simpul email secara efektif membuat WHEREklausa tersebut diterapkan pada customersimpul tersebut. Menerjemahkan XQuery ini ke bahasa Inggris terlihat seperti:

Dapatkan saya semua xml/customernode dengan emailsimpul yang memiliki addressatribut yang tidak mengandung @simbol

Erik Darling
sumber
4

Kamu sangat dekat. Anda pasti berada di jalur yang benar dengan menggunakan .query()fungsi dan menggunakan containsfungsi XQuery. Apa yang salah adalah:

  1. Menempatkan = False luar dari [...](artinya, itu bukan bagian dari contains()ekspresi)
  2. Menggunakan kata Falsesebagai ganti fungsifalse()
  3. Tidak menentukan simpul induk dengan menambahkan /..ke ujung jalan (sehingga hasilnya akan mencakup <customer>elemen dan bukan hanya <email>elemen)

Memperbaiki ketiga hal tersebut menghasilkan ekspresi XQuery berikut yang memberi Anda apa yang Anda inginkan:

'/xml/customer/email[contains(@address, "@") = false()]/..'

Memasukkannya ke dalam contoh asli Anda dari pertanyaan memberi Anda:

DECLARE @x XML = '<xml>
<customer name="Max"><email address="[email protected]" /></customer>
<customer name="Erik"><email address="[email protected]" /></customer>
<customer name="Brent"><email address="brentcom" /></customer>
</xml>';

SELECT
@x.query('/xml/customer/email[contains(@address, "@")]/..') AS [WithValidEmail],
@x.query('/xml/customer/email[contains(@address, "@")=false()]/..') AS [WithInvalidEmail;

Kueri itu mengembalikan kumpulan hasil berikut dari satu baris dengan dua bidang XML:

WithValidEmail                            |     WithInvalidEmail
<customer name="Max">                     |     <customer name="Brent">
  <email address="[email protected]" />          |       <email address="brentcom" />
</customer>                               |     </customer>
<customer name="Erik">                    |
  <email address="[email protected]" />   |
</customer>                               |

Ini mungkin lebih efisien daripada memecah dokumen dengan .nodes()fungsi karena dapat mengurai XML dalam satu pemotretan dan tidak perlu memulai dan menghentikan parser per setiap node.

Manfaat lain dari menyimpannya di dalam .query()adalah Anda mendapatkan satu dokumen XML yang dikembalikan. Jadi, jika Anda menerima dokumen / nilai XML yang mengandung banyak nilai node, Anda dapat mempertahankan pendekatan nilai skalar menjadi sebuah entitas tunggal tanpa harus merekonstruksi node yang dihasilkan kembali ke dokumen lagi. Ini juga memungkinkan Anda menggunakannya dalam subquery / CTE tanpa mengubah jumlah baris yang diharapkan dikembalikan.

Solomon Rutzky
sumber