آموزش متد query() در SQL Server
مقدمه
متد query() نتیجه یک عبارت XQuery را به شکل یک مقدار xml بدون نوع برمیگرداند و برای انتخاب، بازسازی یا شکلدهی یک قطعه XML مناسب است. شناخت دقیق نقش این متد باعث میشود میان پردازش رابطهای و XQuery مرز روشنی ایجاد شود و Query بهجای تبدیلهای ضمنی و رفتار حدسی، خروجی قابل پیشبینی داشته باشد.
در این مقاله از مثالهای کوچک شروع میکنیم و سپس به جدول، شرط، NULL، حالت مرزی، سناریوی سازمانی، خطای رایج و نکته کارایی میرسیم. همه Queryها مستقل و قابل اجرا هستند و نتیجه نمونه کنار هر کد آمده است تا بتوانید رفتار را در SSMS سریع کنترل کنید.
بازگشت به راهنمای جامع توابع XML در SQL Server
تعریف و مدل ذهنی query()
متد query() نتیجه یک عبارت XQuery را به شکل یک مقدار xml بدون نوع برمیگرداند و برای انتخاب، بازسازی یا شکلدهی یک قطعه XML مناسب است.
query() برای بازگرداندن گره یا قطعه XML طراحی شده است و جای value() برای اسکالر را نمیگیرد.
سازندههای XML در XQuery اجازه میدهند خروجی تازهای مانند summary بسازید.
نحو استاندارد
xml_expression.query('XQuery')
پارامترها
| پارامتر | توضیح |
|---|
| XQuery | عبارت XQuery ثابت که میتواند مسیر، سازنده XML، حلقه FLWOR یا اعلان Namespace داشته باشد. |
نوع خروجی
خروجی از نوع xml و بهصورت untyped XML است. اگر توالی نتیجه خالی باشد یک قطعه XML خالی برگردانده میشود و اگر ورودی SQL NULL باشد نتیجه SQL NULL خواهد بود.
نکات فنی پایه
- query() برای بازگرداندن گره یا قطعه XML طراحی شده است و جای value() برای اسکالر را نمیگیرد.
- سازندههای XML در XQuery اجازه میدهند خروجی تازهای مانند summary بسازید.
- ترتیب گرهها مطابق ترتیب توالی XQuery حفظ میشود.
- Namespace پیشفرض باید در Static Context معرفی شود تا مسیرها نتیجه درست بدهند.
مثالهای عملی از مقدماتی تا حرفهای
مثال 1: انتخاب یک زیرشاخه
بخش آدرس مشتری را بدون تبدیل به متن از سند اصلی جدا میکنیم.
DECLARE @x xml = N'<customer><name>سارا</name><address><city>تبریز</city></address></customer>';
SELECT @x.query('/customer/address') AS AddressXml;
| خروجی نمونه | تفسیر نتیجه |
|---|
| <address><city>تبریز</city></address> | نتیجه مورد انتظار پس از اجرای Query در SQL Server |
نکته کاربردی: نوع خروجی همچنان xml است و میتوان متدهای XML دیگر را روی آن اجرا کرد.
مثال 2: اجرای query() روی جدول
از هر سفارش فقط مجموعه خطوط سفارش را برای پردازش بعدی برمیگردانیم.
DECLARE @Orders table(ID int, Doc xml);
INSERT INTO @Orders VALUES (1,N'<order><items><i sku="A"/></items></order>');
SELECT ID, Doc.query('/order/items') AS Items
FROM @Orders;
| خروجی نمونه | تفسیر نتیجه |
|---|
| برای ID=1 عنصر items برمیگردد | نتیجه مورد انتظار پس از اجرای Query در SQL Server |
نکته کاربردی: ستون رابطهای ID کنار قطعه XML، ردیابی نتیجه را ساده نگه میدارد.
مثال 3: ساخت XML جدید در SELECT
از داده موجود، یک عنصر summary با شناسه و مبلغ میسازیم.
DECLARE @x xml = N'<order id="9"><total>450</total></order>';
SELECT @x.query('<summary id="{data(/order/@id)}"><amount>{data(/order/total)}</amount></summary>') AS SummaryXml;
| خروجی نمونه | تفسیر نتیجه |
|---|
| <summary id="9"><amount>450</amount></summary> | نتیجه مورد انتظار پس از اجرای Query در SQL Server |
نکته کاربردی: آکولادها در سازنده مستقیم XML عبارتهای XQuery را داخل نتیجه ارزیابی میکنند.
مثال 4: فیلتر درست پیش از query()
فقط سفارشهای دارای آیتم فوری فیلتر و سپس قطعه آیتمها انتخاب میشوند.
DECLARE @T table(ID int, Doc xml);
INSERT INTO @T VALUES (1,N'<o><i urgent="1"/></o>'),(2,N'<o><i urgent="0"/></o>');
SELECT ID, Doc.query('/o/i') AS Items
FROM @T
WHERE Doc.exist('/o/i[@urgent="1"]') = 1;
| خروجی نمونه | تفسیر نتیجه |
|---|
| فقط ID = 1 | نتیجه مورد انتظار پس از اجرای Query در SQL Server |
نکته کاربردی: query() خروجی میسازد؛ برای Predicate وجود گره، exist() معنای دقیقتری دارد.
مثال 5: ترکیب query() و value()
ابتدا قطعه پروفایل را میگیریم و همزمان شناسه اسکالر را در ستون جدا برمیگردانیم.
DECLARE @x xml = N'<user id="12"><profile><city>رشت</city></profile></user>';
SELECT @x.value('(/user/@id)[1]','int') AS UserID,
@x.query('/user/profile') AS ProfileXml;
| خروجی نمونه | تفسیر نتیجه |
|---|
| UserID=12 و قطعه profile | نتیجه مورد انتظار پس از اجرای Query در SQL Server |
نکته کاربردی: ترکیب متدها زمانی خوب است که مصرفکننده هم ستون رابطهای و هم XML ساختیافته نیاز دارد.
مثال 6: رفتار ورودی NULL
روی متغیر xml تهی query() اجرا میشود تا تفاوت NULL و XML خالی مشخص شود.
DECLARE @x xml = NULL;
SELECT @x.query('/root') AS Fragment;
| خروجی نمونه | تفسیر نتیجه |
|---|
| Fragment = NULL | نتیجه مورد انتظار پس از اجرای Query در SQL Server |
نکته کاربردی: در طراحی API، SQL NULL را از xml خالی مانند مقدار cast شده از رشته خالی تفکیک کنید.
مثال 7: کار با Namespace
سند دارای Namespace پیشفرض است و باید آن را در XQuery معرفی کنیم.
DECLARE @x xml = N'<r xmlns="urn:shop"><item>A1</item></r>';
SELECT @x.query('declare default element namespace "urn:shop"; /r/item') AS ItemXml;
| خروجی نمونه | تفسیر نتیجه |
|---|
| <item xmlns="urn:shop">A1</item> | نتیجه مورد انتظار پس از اجرای Query در SQL Server |
نکته کاربردی: بدون اعلان Namespace، مسیر /r/item هیچ گرهای در این سند پیدا نمیکند.
مثال 8: ساخت خروجی گزارش سازمانی
مبالغ بیش از صد را با یک عبارت FLWOR در یک ریشه جدید جمع میکنیم.
DECLARE @x xml = N'<sales><s id="1" amount="80"/><s id="2" amount="170"/></sales>';
SELECT @x.query('<highSales>{for $s in /sales/s where $s/@amount > 100 return $s}</highSales>') AS ReportXml;
| خروجی نمونه | تفسیر نتیجه |
|---|
| <highSales><s id="2" amount="170"/></highSales> | نتیجه مورد انتظار پس از اجرای Query در SQL Server |
نکته کاربردی: فیلتر در XQuery برای سند منفرد مناسب است؛ روی جدول بزرگ ابتدا ردیفهای نامرتبط را حذف کنید.
مثال 9: اصلاح جستوجوی Namespace اشتباه
نسخه اشتباه مسیر بدون پیشوند خالی است؛ نسخه صحیح Namespace را با پیشوند تعریف میکند.
DECLARE @x xml = N'<a:root xmlns:a="urn:a"><a:item>OK</a:item></a:root>';
SELECT @x.query('declare namespace a="urn:a"; /a:root/a:item') AS CorrectResult;
| خروجی نمونه | تفسیر نتیجه |
|---|
| <a:item xmlns:a="urn:a">OK</a:item> | نتیجه مورد انتظار پس از اجرای Query در SQL Server |
نکته کاربردی: نتیجه خالی همیشه به معنی نبود داده نیست؛ Static Context و Namespace نخستین نقاط بررسیاند.
مثال 10: کاهش حجم خروجی برای کارایی
به جای بازگرداندن سند کامل، فقط عناصر موردنیاز داشبورد را بازسازی میکنیم.
DECLARE @x xml = N'<order><customer>نازنین</customer><total>700</total><internal>secret</internal></order>';
SELECT @x.query('<orderView>{/order/customer,/order/total}</orderView>') AS CompactXml;
| خروجی نمونه | تفسیر نتیجه |
|---|
| <orderView><customer>نازنین</customer><total>700</total></orderView> | نتیجه مورد انتظار پس از اجرای Query در SQL Server |
نکته کاربردی: کاهش اندازه قطعه خروجی، انتقال شبکه و پردازش سمت برنامه را کم میکند و داده حساس را نیز حذف میکند.
خطاهای رایج و روش تشخیص
عیبیابی query() باید از نمونه XML واقعی و عبارت XQuery دقیق شروع شود. پیام خطا را همراه نوع ستون، Namespace، Compatibility Level و پارامترهای همان اجرا نگه دارید. بازنویسی تصادفی مسیر معمولاً علت را پنهان میکند و ممکن است نتیجه ظاهراً صحیح اما ناقص تولید کند.
- مقایسه خروجی query() با رشته باعث تبدیلهای غیرشفاف و Query شکننده میشود.
- استفاده از مسیر بدون Namespace روی XML نامدار نتیجه خالی تولید میکند.
- ساخت قطعه بسیار بزرگ برای هر ردیف مصرف حافظه و CPU را بالا میبرد.
- قرار دادن ورودی پویا با الحاق رشته در XQuery هم نگهداری را سخت و هم امنیت را ضعیف میکند.
برای بازتولید، یک متغیر xml با کوچکترین سندی که خطا را نشان میدهد بسازید، سپس هر بخش مسیر را جدا بررسی کنید. تفاوت SQL NULL، توالی خالی، گره متنی خالی و چند گره را در تستهای مستقل قرار دهید. این چهار حالت در پروژه واقعی اغلب معنای کسبوکاری متفاوت دارند.
ملاحظات کارایی و ابزارهای اندازهگیری
هزینه query() به اندازه سند، تعداد ردیفهای ورودی، پیچیدگی مسیر، تعداد دفعات اجرای اپراتور و ایندکسهای XML وابسته است. درصد هزینه نمایشی در Execution Plan بهتنهایی معیار کافی نیست؛ Baseline قابل تکرار بسازید و مقدار CPU، Elapsed Time و Logical Reads را کنار تعداد ردیف واقعی ثبت کنید.
- فقط گرههای لازم را انتخاب کنید و از بازگرداندن کل سند در گزارشهای پرتکرار بپرهیزید.
- پیش از اجرای query()، ردیفها را با Predicateهای رابطهای یا exist() محدود کنید.
- Primary XML Index و Secondary PATH Index میتوانند پیمایش مسیرهای پرتکرار را بهبود دهند، اما هزینه ذخیرهسازی دارند.
- حجم قطعه خروجی و Memory Grant را در Actual Plan بررسی کنید.
برای آزمایش محلی از SET STATISTICS IO,TIME ON و Actual Execution Plan استفاده کنید. برای مشاهده روند در محیط پایدار Query Store مفید است و Extended Events میتواند اجرای کند یا خطاهای منتخب را بدون Trace گسترده ثبت کند. هر تغییر ایندکس باید هم مسیر خواندن و هم سربار نوشتن، Backup و فضای ذخیرهسازی را پوشش دهد.
بهترین روشها
- برای خروجی اسکالر value() و برای آزمون وجود exist() را ترجیح دهید.
- Namespaceها را در WITH XMLNAMESPACES متمرکز کنید.
- ساختار خروجی XML را مانند یک قرارداد API نسخهبندی و تست کنید.
- در Queryهای تولید سند، ترتیب، Encoding و رفتار داده تهی را تست کنید.
بهترین راه استفاده پایدار از query() این است که قرارداد داده و Query کنار هم نسخهبندی شوند. اگر تولیدکننده XML ساختار یا Namespace را عوض کند، تستهای یکپارچه باید پیش از انتشار شکست را آشکار کنند. نمونههای مرزی را از رخدادهای واقعی پشتیبانی جمع کنید و به Regression Suite بیفزایید.
کاربردهای واقعی در پروژه
query() در سناریوهایی مانند ساخت بخش خلاصه سفارش، انتخاب زیرشاخه مشخص از پیام، بازسازی XML برای سرویس دیگر، حذف عناصر حساس از خروجی، تهیه قطعه XML برای آرشیو یا گزارش کاربرد دارد. بااینحال وجود XML بهمعنی اجرای همه منطق داخل XQuery نیست. کلیدهای پرتکرار، تاریخهای فیلتر، وضعیت و ستونهای Join معمولاً باید رابطهای باشند یا هنگام ورود داده استخراج شوند.
در سامانه سازمانی، دسترسی به داده XML باید از Stored Procedure یا View کنترلشده عبور کند، ورودی اعتبارسنجی شود و عملیات نوشتن Audit داشته باشد. برای تصمیم خرید سختافزار یا طراحی ایندکس، ابتدا Queryهای پرتکرار و حجم رشد واقعی را اندازهگیری کنید؛ بهینهسازی بدون Baseline بهسادگی هزینه را از یک بخش به بخش دیگر منتقل میکند.
سؤالات متداول
۱. query() در SQL Server دقیقاً چه کاری انجام میدهد؟
متد query() نتیجه یک عبارت XQuery را به شکل یک مقدار xml بدون نوع برمیگرداند و برای انتخاب، بازسازی یا شکلدهی یک قطعه XML مناسب است. انتخاب این متد باید بر اساس نوع خروجی موردنیاز باشد، نه صرفاً کوتاهتر بودن Query.
۲. مهمترین نکته آموزشی هنگام نوشتن query() چیست؟
query() برای بازگرداندن گره یا قطعه XML طراحی شده است و جای value() برای اسکالر را نمیگیرد. بهتر است این قاعده با تستهای کوچک روی NULL، گره غایب و چند گره تثبیت شود.
۳. آیا آموزش سازمانی query() برای تیمهای داده ارزش تجاری دارد؟
بله؛ خطا در استفاده از query() میتواند هزینه پردازش، خطای گزارش و زمان پشتیبانی را بالا ببرد. یک کارگاه مبتنی بر Queryهای واقعی سازمان معمولاً سریعتر از آموزش صرفاً نظری به نتیجه میرسد.
۴. چه زمانی بازبینی حرفهای Queryهای query() مقرونبهصرفه است؟
وقتی جدول بزرگ، XML Index، گزارش حساس یا SLA جدی دارید، بازبینی Execution Plan و اندازهگیری CPU و Logical Reads میتواند هزینه توسعه و زیرساخت را کاهش دهد.
۵. query() با متدهای دیگر XML چه تفاوتی دارد؟
query() برای «بازیابی قطعه XML با XQuery» طراحی شده است؛ value() اسکالر میدهد، query() قطعه XML میسازد، exist() وجود را میآزماید، nodes() ردیف تولید میکند و modify() داده را تغییر میدهد.
۶. برای پیادهسازی query() در یک پروژه واقعی از کجا شروع کنیم؟
ابتدا قرارداد XML، Namespace، حجم اسناد و Queryهای پرتکرار را ثبت کنید؛ سپس نمونه کوچک، تست صحت، Baseline کارایی و برنامه استقرار مرحلهای بسازید. در پروژه حساس، مشاوره SQL Server باید مبتنی بر همین شواهد باشد.
۷. خطای رایج query() چیست؟
مقایسه خروجی query() با رشته باعث تبدیلهای غیرشفاف و Query شکننده میشود. پیام خطا، XQuery دقیق و نمونه XML مسئلهدار را کنار هم نگه دارید تا علت بهجای حدسزدن قابل بازتولید باشد.
۸. چگونه Performance متد query() را بررسی کنیم؟
فقط گرههای لازم را انتخاب کنید و از بازگرداندن کل سند در گزارشهای پرتکرار بپرهیزید. از SET STATISTICS IO,TIME ON، Actual Execution Plan و Query Store برای مقایسه قبل و بعد استفاده کنید.
۹. بهترین روش استفاده از query() چیست؟
برای خروجی اسکالر value() و برای آزمون وجود exist() را ترجیح دهید. علاوه بر آن، تست واحد دادههای مرزی و مستندسازی Namespace مانع بازگشت خطا در نسخههای بعد میشود.
۱۰. query() با کدام نسخههای SQL Server سازگار است؟
متدهای نوع داده xml از SQL Server 2005 در دسترساند، اما رفتار دقیق Optimizer و مزیت ایندکسها را باید روی نسخه و Compatibility Level محیط مقصد آزمایش کرد. پیش از مهاجرت نیز Regression Test اجرا کنید.
سؤالات مصاحبه
- نقش اصلی query() چیست و نوع خروجی آن چه اثری بر انتخاب متد دارد؟
- رفتار query() با SQL NULL و مسیر بدون نتیجه چگونه است؟
- Static Typing یا Context Item چه محدودیتی برای query() ایجاد میکند؟
- یک خطای رایج query() را چگونه با نمونه حداقلی بازتولید میکنید؟
- برای سنجش Performance متد query() چه شاخصها و ابزارهایی به کار میبرید؟
- چه زمانی مدل رابطهای را به استفاده بیشتر از query() ترجیح میدهید؟
در پاسخ حرفهای، فقط Syntax کافی نیست. نام بردن از یک Trade-off، یک حالت مرزی و یک روش اندازهگیری نشان میدهد داوطلب تجربه طراحی و عملیات واقعی دارد.
چکلیست نهایی
- نقش query() با نوع خروجی موردنیاز منطبق است.
- Namespace و Context مسیر صریح و تستشدهاند.
- حالتهای NULL، گره غایب، چند گره و مقدار نامعتبر تست شدهاند.
- Query روی داده نماینده با Actual Plan و STATISTICS IO,TIME سنجیده شده است.
- ساخت XML Index فقط با مقایسه قبل و بعد انجام شده است.
- امنیت، مجوز، Audit و قرارداد تغییر ساختار مستند هستند.
جمعبندی
متد query() نتیجه یک عبارت XQuery را به شکل یک مقدار xml بدون نوع برمیگرداند و برای انتخاب، بازسازی یا شکلدهی یک قطعه XML مناسب است. استفاده درست از آن نیازمند فهم توالی XQuery، نوع خروجی، Namespace و رفتار دادههای مرزی است. ده مثال این مقاله الگوهای خواندن، شرط، جدول، NULL، خطا و Performance را در قالب قابل اجرای SQL Server پوشش دادند.
برای مقایسه این متد با چهار متد دیگر و انتخاب معماری مناسب، راهنمای جامع توابع XML در SQL Server را مطالعه کنید. پیش از انتقال هر Query به محیط تولید، آن را با حجم و توزیع داده واقعی، Plan واقعی و سیاست امنیتی همان سامانه اعتبارسنجی کنید.