در ادامهی مباحث XQuery، سایر قابلیتهای توکار SQL Server را برای کار با اسناد XML بررسی خواهیم کرد.
کوئری گرفتن از اسناد XML دارای فضای نام، توسط XQuery
در مثال زیر، تمام المانهای سند XML، در فضای نام http://www.people.com تعریف شدهاند.
اگر کوئری فوق را برای یافتن اشخاص اجرا کنیم، خروجی آن خالی خواهد بود (و
یا یک empty sequence)؛ زیرا کوئری نوشته شده به دنبال اشخاصی است که در
فضای نام خاصی تعریف نشدهاند.
سعی دوم احتمالا روش ذیل خواهد بود
که به خطای زیر منتهی میشود:
برای حل این مشکل باید از مفهومی به نام prolog استفاده کرد. هر XQuery از
دو قسمت prolog و body تشکیل میشود. قسمت prolog میتواند شامل تعاریف
فضاهای نام، متغیرها، متدها و غیره باشد و قسمت body، همان کوئری تهیه
شدهاست. البته SQL Server از قسمت prolog استاندارد XQuery، فقط تعریف
فضاهای نام آنرا مطابق مثال ذیل پشتیبانی میکند:
یک سند XML ممکن است با بیش از یک فضای نام تعریف شود. در این حالت خواهیم داشت:
در اینجا در قسمت prolog، برای فضای نام تعریف شده در سند XML، یک پیشوند را تعریف کرده و سپس، استفاده از آن مجاز خواهد بود.
روش دیگر تعریف فضای نام، استفاده از WITH XMLNAMESPACES، پیش از تعریف کوئری است:
البته باید دقت داشت، زمانیکه WITH XMLNAMESPACES تعریف میشود، عبارت
T-SQL پیش از آن باید با یک سمیکالن خاتمه یابد؛ و گرنه یک خطای دستوری
خواهید گرفت.
در اینجا نیز امکان کار با چندین فضای نام وجود دارد و برای این منظور تنها کافی است از تعریف Alias استفاده شود. فضاهای نام بعدی با یک کاما از هم مجزا خواهند شد.
عبارات XPath و FLOWR
XQuery از دو نوع عبارت XPath و FLOWR میتواند استفاده کند. XQuery همیشه از XPath برای انتخاب دادهها و نودها استفاده میکند. در اینجا هر نوع XPath سازگار با استاندارد 2 آن، یک XQuery نیز خواهد بود. برای انجام اعمالی بجز انتخاب دادهها، باید از عبارات FLOWR استفاده کرد؛ برای مثال برای ایجاد حلقه، مرتب سازی و یا ایجاد نودهای جدید.
در مثال زیر که data آن در قسمت قبل تعریف شد، دو کوئری نوشته شده یکی هستند:
اولین کوئری به روش FLOWR تهیه شدهاست و دومین کوئری از استاندارد XPath
استفاده میکند. از دیدگاه SQL Server این دو یکی بوده و حتی Query Plan
یکسانی نیز دارند.
XPath بسیار شبیه به مسیر دهیهای یونیکسی است. بسیار فشرده بوده و همچنین مناسب است برای کار با ساختارهای تو در تو و سلسله مراتبی. مثال زیر را درنظر بگیرید:
در اینجا books، المان ریشه است. سپس به اولین کتاب این ریشه اشاره میشود.
سپس به المان عنوان و مسیر نهایی، به فصل ختم میشود. البته همانطور که در
قسمتهای پیشین نیز ذکر شد، حالت content، پیش فرض بوده و یک فیلد XML
میتواند دارای چندین ریشه باشد.
در XPath توسط قابلیتی به نام محور میتوان به المانهای قبلی یا بعدی دسترسی پیدا کرد. این محورهای پشتیبانی شده در SQL Server عبارتند از self (خود نود)، child (فرزند نود)، parent (والد نود)، decedent (فرزند فرزند فرزند ...)و attribute (دسترسی به ویژگیها). محورهای استانداردی مانند preceding-sibling و following-sibling در SQL Server با عملگرهایی مانند >> و << پشتیبانی میشوند.
مثالهایی از نحوهی استفاده از محورهای XPath
اینبار قصد داریم یک سند XML نسبتا پیچیده را بررسی کرده و اجزای مختلف آنرا به کمک XPath بدست بیاوریم.
در این سند، کارمند و کارمندانی را که باید به یک کارمند گزارش دهند، ملاحظه میکنید.
در XPath، محور پیش فرض، child است (اگر مانند کوئری زیر مورد خاصی ذکر نشود):
و اگر بخواهیم این محور را به صورت صریح ذکر کنیم، به نحو ذیل خواهد بود:
خروجی آن User1 است.
برای ذکر محور decedent-or-self میتوان از // نیز استفاده کرد:
با خروجی
در این حالت به تمام نودهای سند، در سطوح مختلف آن مراجعه شده و به دنبال نام کارمند خواهیم گشت.
برای کار با ویژگیها و attributes از [] به همراه علامت @ استفاده میشود:
در این کوئری، تمام کارمندانی که دارای ویژگی assigned-to واقع در فضای نام urn:annotations هستند، یافت خواهند شد. با خروجی:
معادل طولانیتر آن ذکر کامل محور attribute است بجای @
و برای یافتن کارمندانی که دارای ویژگی assigned-to نیستند، میتوان از عملگر not استفاده کرد:
با خروجی
و اگر بخواهیم تعداد کارمندانی را که به user 1 مستقیما گزارش میدهند را بیابیم، میتوان از count به نحو ذیل استفاده کرد:
در XPath برای یافتن والد از .. استفاده میشود:
برای مثال در کوئری فوق، کارمندانی که والد آنها user 1 هستند، یافت میشوند.
استفاده از .. در SQL Server به دلایل کارآیی پایین توصیه نمیشود. بهتر است از همان روش قبلی کوئری تعداد کارمندانی که به user 1 مستقیما گزارش میدهند، استفاده شود.
عبارات FLOWR
FLOWR هستهی XQuery را تشکیل داده و قابلیت توسعه XPath را دارد. FLOWR مخفف for، let، order by، where و retrun است. از for برای تشکیل حلقه، از let برای انتساب، از where و order by برای فیلتر و مرتب سازی اطلاعات و از return برای بازگشت نتایج کمک گرفته میشود. FLOWR بسیار شبیه به ساختار SQL عمل میکند.
معادل عبارت SQL
با عبارات FLOWR، به صورت زیر است:
همانطور که مشاهده میکنید علت انتخاب FLOWR در اینجا عمدی بودهاست؛ زیرا
افرادی که SQL میدانند به سادگی میتوانند شروع به کار با عبارات FLOWR
کنند.
تنها تفاوت مهم، در اینجا است که در عبارات SQL، خروجی کار توسط select، در ابتدای کوئری ذکر میشود، اما در عبارات FLOWR در انتهای آنها.
از let برای انتساب مجموعهای از نودها استفاده میشود:
تفاوت آن با for در این است که در هر بار اجرای حلقهی for، تنها با یک نود
کار خواهد شد، اما در let با مجموعهای از نودها سر و کار داریم. همچنین
let از نگارش 2008 اس کیوال سرور به بعد قابل استفادهاست.
یک نکته
اگر به order by دقت کنید، به اولین سن اشاره میکند. Order by در اینجا با تک مقدارها کار میکند و امکان کار با مجموعهای از نودها را ندارد. به همین جهت باید طوری آنرا تنظیم کرد که هربار فقط به یک مقدار اشاره کند.
هر زمانیکه به خطای requires a singleton برخوردید، یعنی دستورات مورد استفاده با یک سری از نودها کار نکرده و نیاز است دقیقا مشخص کنید، کدام مقدار مدنظر است.
مثالهایی از عبارات FLOWR
دو کوئری ذیل یک خروجی 1 2 3 را تولید میکنند
در کوئری اول، هر بار که حلقه اجرا میشود، به یکی از اعضای توالی دسترسی
خواهیم داشت. در کوئری دوم، یکبار توالی تعریف شده و کار با آن در یک مرحله
صورت میگیرد.
در ادامه اگر سعی کنیم به این کوئریها یک order by را اضافه کنیم، کوئری اول با موفقیت اجرا شده،
اما کوئری دوم با خطای ذیل متوقف میشود:
در خطا عنوان شدهاست که مطابق تعریف، order by با یک مجموعه از نودها،
مانند حاصل let کار نمیکند و همانند حلقه for نیاز به singleton یا atomic
values دارد.
ساخت المانهای جدید XML توسط عبارات FLOWR
ابتدا همان سند XML قسمت قبل را درنظر بگیرید:
در ادامه قصد داریم، المانهای اشخاص را صرفا بر اساس مقدار givenName آنها بازگشت دهیم:
در اینجا نحوهی تولید پویای تگهای XML را توسط FLOWR مشاهده میکنید.
عبارات داخل {} به صورت خودکار محاسبه و جایگزین میشوند و خروجی آن به شرح
زیر است:
سؤال: اگر به این خروجی بخواهیم یک root element اضافه کنیم، چه باید کرد؟ اگر المان root دلخواهی را در return قرار دهیم، به ازای هر آیتم یافت شده، یکبار تکرار میشود که مدنظر ما نیست.
بله. در این حالت نیز میتوان از همان روشی که در return استفاده کردیم،
برای کل حلقه و return آن استفاده کنیم. المان root به صورت استاتیک محاسبه
میشود و هر آنچه که داخل {} باشد، به صورت پویا. با این خروجی:
مفهوم quantification در FLOWR
همان سند Team name=Project 1 ابتدای بحث جاری را درنظر بگیرید.
به عبارات some و every در اینجا quantification گفته میشود. در کوئری
اول، میخواهیم بررسی کنیم، آیا در بین کارمندان، بعضی از آنها دارای
ویژگی (با @ شروع شده) years بیشتر از 5 هستند. در کوئری دوم، عبارت «بعضی»
به «هر» تغییر یافته است.
کوئری گرفتن از اسناد XML دارای فضای نام، توسط XQuery
در مثال زیر، تمام المانهای سند XML، در فضای نام http://www.people.com تعریف شدهاند.
DECLARE @doc XML SET @doc =' <p:people xmlns:p="http://www.people.com"> <p:person name="Vahid" /> <p:person name="Farid" /> </p:people> ' SELECT @doc.query('/people/person')
سعی دوم احتمالا روش ذیل خواهد بود
SELECT @doc.query('/p:people/p:person')
XQuery [query()]: The name "p" does not denote a namespace.
SELECT @doc.query(' declare default element namespace "http://www.people.com"; /people/person ')
SELECT @doc.query(' declare namespace aa="http://www.people.com"; /aa:people/aa:person ')
روش دیگر تعریف فضای نام، استفاده از WITH XMLNAMESPACES، پیش از تعریف کوئری است:
WITH XMLNAMESPACES(DEFAULT 'http://www.people.com') SELECT @doc.query('/people/person')
در اینجا نیز امکان کار با چندین فضای نام وجود دارد و برای این منظور تنها کافی است از تعریف Alias استفاده شود. فضاهای نام بعدی با یک کاما از هم مجزا خواهند شد.
WITH XMLNAMESPACES('http://www.people.com' AS aa) SELECT @doc.query('/aa:people/aa:person')
عبارات XPath و FLOWR
XQuery از دو نوع عبارت XPath و FLOWR میتواند استفاده کند. XQuery همیشه از XPath برای انتخاب دادهها و نودها استفاده میکند. در اینجا هر نوع XPath سازگار با استاندارد 2 آن، یک XQuery نیز خواهد بود. برای انجام اعمالی بجز انتخاب دادهها، باید از عبارات FLOWR استفاده کرد؛ برای مثال برای ایجاد حلقه، مرتب سازی و یا ایجاد نودهای جدید.
در مثال زیر که data آن در قسمت قبل تعریف شد، دو کوئری نوشته شده یکی هستند:
SELECT @data.query(' (: FLOWE :) for $p in /people/person where $p/age > 30 return $p ') SELECT @data.query(' (: XPath :) /people/person[age>30] ')
XPath بسیار شبیه به مسیر دهیهای یونیکسی است. بسیار فشرده بوده و همچنین مناسب است برای کار با ساختارهای تو در تو و سلسله مراتبی. مثال زیر را درنظر بگیرید:
/books/book[1]/title/chapter
در XPath توسط قابلیتی به نام محور میتوان به المانهای قبلی یا بعدی دسترسی پیدا کرد. این محورهای پشتیبانی شده در SQL Server عبارتند از self (خود نود)، child (فرزند نود)، parent (والد نود)، decedent (فرزند فرزند فرزند ...)و attribute (دسترسی به ویژگیها). محورهای استانداردی مانند preceding-sibling و following-sibling در SQL Server با عملگرهایی مانند >> و << پشتیبانی میشوند.
مثالهایی از نحوهی استفاده از محورهای XPath
اینبار قصد داریم یک سند XML نسبتا پیچیده را بررسی کرده و اجزای مختلف آنرا به کمک XPath بدست بیاوریم.
DECLARE @doc XML SET @doc=' <Team name="Project 1" xmlns:a="urn:annotations"> <Employee id="544" years="6.5"> <Name>User 1</Name> <Title>Architect</Title> <Expertise>Games</Expertise> <Expertise>Puzzles</Expertise> <Employee id="101" years="7.1" a:assigned-to="C1"> <Name>User 2</Name> <Title>Dev lead</Title> <Expertise>Video Games</Expertise> <Employee id="50" years="2.3" a:assigned-to="C2"> <Name>User 3</Name> <Title>Developer</Title> <Expertise>Hardware</Expertise> <Expertise>Entertainment</Expertise> </Employee> </Employee> </Employee> </Team> '
در XPath، محور پیش فرض، child است (اگر مانند کوئری زیر مورد خاصی ذکر نشود):
SELECT @doc.query('/Team/Employee/Name')
SELECT @doc.query('/Team/Employee/child::Name')
<Name>User 1</Name>
SELECT @doc.query('//Employee/Name')
<Name>User 1</Name> <Name>User 2</Name> <Name>User 3</Name>
برای کار با ویژگیها و attributes از [] به همراه علامت @ استفاده میشود:
SELECT @doc.query(' declare namespace a = "urn:annotations"; //Employee[@a:assigned-to]/Name ')
<Name>User 2</Name> <Name>User 3</Name>
SELECT @doc.query(' declare namespace a = "urn:annotations"; //Employee[attribute::a:assigned-to]/Name ')
SELECT @doc.query(' declare namespace a = "urn:annotations"; //Employee[not(@a:assigned-to)]/Name ')
<Name>User 1</Name>
SELECT @doc.query('count(//Employee[Name="User 1"]/Employee)')
در XPath برای یافتن والد از .. استفاده میشود:
SELECT @doc.query('//Employee[../Name="User 1"]')
استفاده از .. در SQL Server به دلایل کارآیی پایین توصیه نمیشود. بهتر است از همان روش قبلی کوئری تعداد کارمندانی که به user 1 مستقیما گزارش میدهند، استفاده شود.
عبارات FLOWR
FLOWR هستهی XQuery را تشکیل داده و قابلیت توسعه XPath را دارد. FLOWR مخفف for، let، order by، where و retrun است. از for برای تشکیل حلقه، از let برای انتساب، از where و order by برای فیلتر و مرتب سازی اطلاعات و از return برای بازگشت نتایج کمک گرفته میشود. FLOWR بسیار شبیه به ساختار SQL عمل میکند.
معادل عبارت SQL
Select p.name, p.job from people as p where p.age > 30 order by p.age
for $p in /people/person where $p.age > 30 order by $p.age[1] return ($p/name, $p/job)
تنها تفاوت مهم، در اینجا است که در عبارات SQL، خروجی کار توسط select، در ابتدای کوئری ذکر میشود، اما در عبارات FLOWR در انتهای آنها.
از let برای انتساب مجموعهای از نودها استفاده میشود:
let $p := /people/person return $p
یک نکته
اگر به order by دقت کنید، به اولین سن اشاره میکند. Order by در اینجا با تک مقدارها کار میکند و امکان کار با مجموعهای از نودها را ندارد. به همین جهت باید طوری آنرا تنظیم کرد که هربار فقط به یک مقدار اشاره کند.
هر زمانیکه به خطای requires a singleton برخوردید، یعنی دستورات مورد استفاده با یک سری از نودها کار نکرده و نیاز است دقیقا مشخص کنید، کدام مقدار مدنظر است.
مثالهایی از عبارات FLOWR
دو کوئری ذیل یک خروجی 1 2 3 را تولید میکنند
DECLARE @x XML = ''; SELECT @x.query(' for $i in (1,2,3) return $i '); SELECT @x.query(' let $i := (1,2,3) return $i ');
در ادامه اگر سعی کنیم به این کوئریها یک order by را اضافه کنیم، کوئری اول با موفقیت اجرا شده،
DECLARE @x XML = ''; SELECT @x.query(' for $i in (1,2,3) order by $i descending return $i '); SELECT @x.query(' let $i := (1,2,3) order by $i descending return $i ');
XQuery [query()]: 'order by' requires a singleton (or empty sequence), found operand of type 'xs:integer +'
ساخت المانهای جدید XML توسط عبارات FLOWR
ابتدا همان سند XML قسمت قبل را درنظر بگیرید:
DECLARE @doc XML =' <people> <person> <name> <givenName>name1</givenName> <familyName>lname1</familyName> </name> <age>33</age> <height>short</height> </person> <person> <name> <givenName>name2</givenName> <familyName>lname2</familyName> </name> <age>40</age> <height>short</height> </person> <person> <name> <givenName>name3</givenName> <familyName>lname3</familyName> </name> <age>30</age> <height>medium</height> </person> </people> '
SELECT @doc.query(' for $p in /people/person return <person> {$p/name[1]/givenName[1]/text()} </person> ');
<person>name1</person> <person>name2</person> <person>name3</person>
سؤال: اگر به این خروجی بخواهیم یک root element اضافه کنیم، چه باید کرد؟ اگر المان root دلخواهی را در return قرار دهیم، به ازای هر آیتم یافت شده، یکبار تکرار میشود که مدنظر ما نیست.
SELECT @doc.query(' <root> { for $p in /people/person return <person> {$p/name[1]/givenName[1]/text()} </person> } </root> ');
<root> <person>name1</person> <person>name2</person> <person>name3</person> </root>
مفهوم quantification در FLOWR
همان سند Team name=Project 1 ابتدای بحث جاری را درنظر بگیرید.
SELECT @doc.query('some $emp in //Employee satisfies $emp/@years >5') -- true SELECT @doc.query('every $emp in //Employee satisfies $emp/@years >5') -- false