فکر کنم این مورد را در یک ویدیو دیده بود که sql server بین دو تا سه برابر در واکشی دادهها سریعتر عمل کرده بود.
استفاده از Flashback Table در Oracle
دریافت و نصب افزونهی SQL Server مخصوص VSCode
برای افزودن این افزونه، ابتدا در برگهی Extensions، عبارت mssql را جستجو کرده و سپس آنرا نصب کنید:
پس از نصب آن، مرحلهی بعد، ایجاد یک فایل خالی با پسوند sql است.
انجام اینکار ضروری است و شبیه به حالت نصب افزونهی #C میباشد. به این ترتیب وابستگیهای اصلی آن دریافت، نصب و فعال خواهند شد. این ابزارها نیز سورس باز بوده و موتور SQL Formatter، اجرای SQL و Intellisense آنرا فراهم میکند و چون مبتنی بر NET Core. تهیه شدهاست، چندسکویی است.
تا اینجا مزیتی را که به دست خواهیم آورد Syntax highlighting و Intellisense جهت درج واژههای کلیدی عبارات SQL است:
و یا اگر بر روی فایل sql جاری کلیک راست کنیم، گزینهی Format Document آن سبب میشود تا کدهای SQL نوشته شده، با فرمتی استاندارد، مرتب و یکدست شوند:
بنابراین اگر علاقمندید تا فایلها و عبارات SQL خود را فرمت کنید، این افزونهی سبک وزن چندسکویی، یک چنین قابلیت توکاری را به همراه دارد.
همچنین اگر علاقمندید به یک کتابخانهی سورس باز چندسکویی SQL Formatter و SQL Parser دات نتی دسترسی داشته باشید، کدهای Microsoft/sqltoolsservice در دسترس هستند.
اتصال به SQL Server و کار با آن
پس از نصب مقدماتی افزونهی mssql، دکمههای ctrl+shift+p (و یا F1) را فشرده و عبارت sql را جستجو کنید:
در اینجا سایر قابلیتهای این افزونهی نصب شده را میتوان مشاهده کرد. در لیست ظاهر شده، گزینهی Connect را انتخاب کنید. بلافاصله گزینهی انتخاب پروفایل ظاهر میشود. چون هنوز پروفایلی را تعریف نکردهایم، گزینهی Create connection profile را انتخاب خواهیم کرد:
در ادامه باید نام سرور را وارد کرد. یا میتوانید نام سرور کامل SQL خود را وارد کنید و یا اگر با LocalDB کار میکنید نیز امکان اتصال به آن با تایپlocaldb\MSSQLLocalDB وجود دارد:
سپس نام بانک اطلاعاتی را که میخواهیم به آن متصل شویم ذکر میکنیم:
در مرحلهی بعد، باید نوع اعتبارسنجی اتصال مشخص شود:
چون در ویندوز هستیم، میتوان گزینهی Integrated را نیز انتخاب کرد (یا همان Windows Authentication).
در آخر، جهت تکمیل کار و دخیرهی این اطلاعات وارد شده، میتوان نام پروفایل دلخواهی را وارد کرد:
اکنون کار اتصال به این بانک اطلاعاتی انجام شده و اگر به status bar دقت کنید، نمایش میدهد که در حال به روز رسانی اطلاعات intellisense است.
برای نمونه اینبار دیگر intellisense ظاهر شده منحصر به درج واژههای کلیدی SQL نیست. بلکه شامل تمام اشیاء بانک اطلاعاتی که به آن متصل شدهایم نیز میباشد:
در ادامه برای اجرا این کوئری میتوان دکمههای Ctrl+Shift+E را فشرد و یا ctrl+shift+p (و یا F1) را فشرده و در منوی ظاهر شده، گزینهی execute query را انتخاب کنید (این گزینه بر روی منوی کلیک راست ظاهر شدهی بر روی فایل sql جاری نیز قرار دارد):
نگاهی به محل ذخیره سازی اطلاعات اتصال به بانک اطلاعاتی
پروفایلی را که در قسمت قبل ایجاد کردیم، در منوی File->Preferences->Settings قابل مشاهده است:
// Place your settings in this file to overwrite the default settings { "workbench.colorTheme": "Default Light+", "files.autoSave": "afterDelay", "typescript.check.tscVersion": false, "terminal.integrated.shell.windows": "cmd.exe", "workbench.iconTheme": "material-icon-theme", "vsicons.dontShowNewVersionMessage": true, "mssql.connections": [ { "server": "(localdb)\\MSSQLLocalDB", "database": "TestASPNETCoreIdentityDb", "authenticationType": "Integrated", "profileName": "testLocalDB", "password": "" } ] }
برای مثال پروفایلی را که تعریف کردیم، در دفعات بعدی انتخاب گزینهی Connect، به صورت ذیل ظاهر میشود:
تهیهی خروجی از کوئری اجرا شده
اگر به نوار ابزار سمت راست نتیجهی کوئری اجرا شده دقت کنید، سه دکمهی تهیهی خروجی با فرمتهای csv، json و اکسل نیز در اینجا قرار داده شدهاست:
برای مثال اگر گزینهی json آنرا انتخاب کنید، بلافاصله نام فایلی را پرسیده و سپس این نتیجه را با فرمت JSON نمایش میدهد:
ضمن اینکه حتی میتوان سطرها و سلولهای خاصی را نیز از این خروجی انتخاب کرد و سپس با کلیک بر روی آنها، تنها از این انتخاب، یک خروجی ویژه را تهیه نمود:
مشاهدهی ساختار اشیاء
اگر بر روی هر کدام از اجزای یک کوئری SQL متصل به بانک اطلاعاتی، کلیک راست کنیم، گزینهی Go to definition نیز ظاهر میشود:
با انتخاب آن، بلافاصله عبارت کامل CREATE TABLE [dbo].[AppRoles] ظاهر میشود که در اینجا میتوان ساختار این جدول را به صورت یک عبارت SQL مشاهده کرد.
تغییر تنظیمات افزونهی MSSql
در منوی File->Preferences->Settings با جستجوی mssql میتوان تنظیمات پیش فرض این افزونه را یافت. برای مثال اگر میخواهید تا SQL Formatter آن به صورت خودکار تمام واژههای کلیدی را با حروف بزرگ نمایش دهد، گزینهی mssql.format.keywordCasing را انتخاب کنید. در کنار آن آیکن قلم ویرایش ظاهر میشود. با کلیک بر روی آن، منوی انتخاب uppercase را خواهیم داشت:
پس از این تغییر، اکنون بر روی صفحه کلیک راست کرده و گزینهی Format Document را انتخاب کنید. در این حالت علاوه بر تغییر فرمت سند SQL جاری، تمام واژههای کلیدی آن نیز uppercase خواهند شد.
مقدمه
در اکثر موارد در یک Landscape عملیاتی، چنانچه به تجمیع و انتقال دادهها از بانکهای اطلاعاتی مختلف نیاز باشد، از SSIS Package اختصار (SQL Server Integration Service) استفاده میشود و معمولاً با تعریف یک Job در سطح SQL Server به اجرای Package در زمانهای مشخص میپردازند. چنانچه در موقعیتی لازم باشد که از طریق برنامه کاربردی توسعه یافته، به اجرای Package مبادرت ورزیده شود و البته نخواهیم Job تعریف شده را از طریق کد برنامه، اجرا کنیم و در واقع این امکان را داشته باشیم که همانند یک رویه ذخیره شده تعریف شده در سطح بانک اطلاعاتی به اجرای عمل فوق بپردازیم، یک راه حل میتواند تعریف یک CLR Stored Procedures باشد. در این مقاله به بررسی این موضوع پرداخته میشود، در ابتدا لازم است به بیان تئوری موضوع پرداخته شود (قسمتهای 1 الی 5) در ادامه به ذکر پیاده سازی روش پیشنهادی پرداخته میشود.
1- اجرای Integration Service Package
جهت اجرای یک Package از ابزارهای زیر میتوان استفاده کرد:• command-line ابزار خط فرمان dtexec.exeتوجه: همچنین یک Package را در زمان طراحی در Business Intelligence Development Studio) BIDS) میتوان اجرا نمود.
• ابزار اجرائی پکیج dtexecui.exe
• استفاده از SQL Server Agent job
2- استفاده از dtexec جهت اجرای Package
با استفاده از ابزار dtexec میتوان Packageهای ذخیره شده در فایل سیستم، یک SQL Instance و یا Packageهای ذخیره شده در Integration Service را اجرا نمود.توجه: در سیستم عاملهای 64 بیتی، ابزار dtexec موجود در Integration Service با نسخه 64 بیتی نصب میشود. چنانچه بایست Packageهای معینی را در حالت 32 بیتی اجرا کنید، لازم است ابزار dtexec نسخه 32 بیتی نصب شود. ابزار dtexec دستیابی به تمامی ویژگیهای پیکربندی و اجرای Package از قبیل اتصالات، مشخصات(Properties)، متغیرها، logging و شاخصهای پردازشی را فراهم میکند.
توجه: زمانی که از نسخهی ابزار dtexec که با SQL Server 2008 ارائه شده استفاده میکنید برای اجرای یک SSIS Package نسخه 2005، Integration Service به صورت موقت Package را به نسخه 2008 ارتقا میدهد، اما نمیتوان از ابزار dtexec برای ذخیره این تغییرات استفاده کرد.
2-1- ملاحظات نصب dtexec روی سیستمهای 64 بیتی
به صورت پیش فرض، یک سیستم عامل 64 بیتی که هر دو نسخه 64 بیتی و 32 بیتی ابزار خط فرمان Integration Service را دارد، نسخه 32 بیتی نصب شده را در خط فرمان اجرا خواهد کرد. نسخه 32 بیتی بدین دلیل اجرا میشود که در متغیر محیطی (Path (Path environment variable مسیر directory نسخه 32 بیتی قرار گرفته است.به طور معمول:2-2- تفسیر کدهای خروجی
هنگامی که یک Package اجرا میشود، dtexec یک کد خروجی (Return Code) بر میگرداند:مقدار | توصیف |
0 | Package با موفقیت اجرا شده است. |
1 | Package با خطا مواجه شده است. |
3 | Package در حال اجرا توسط کاربر لغو شده است. |
4 | Package پیدا نشده است. |
5 | Package بارگذاری نشده است. |
6 | ابزار با یک خطای نحوی یا خطای معنایی در خط فرمان برخورد کرده است. |
2-3- قوانین نحوی dtexec
تمامی گزینهها (Options) باید با یک علامت Slash (/) و یا Minus (-) شروع شوند.یک آرگومان باید در یک quotation mark محصور شود چنانچه شامل یک فاصله خالی باشد.
گزینهها و آرگومانها بجز رمزعبور حساس به حروف کوچک و بزرگ نیستند.
2-3-1- Syntax
dtexec /option [value] [/option [value]]…
2-3-2- Parameters
نکته: در Integration Service، ابزار خط فرمان dtsrun که برایData Transformation Service) DTS)های نسخه SQL Server 2000 استفاده میشد، با ابزار خط فرمان dtexec جایگزین شده است.• تعدادی از گزینههای خط فرمان dtsrun به طور مستقیم در dtexec معادل دارند برای مثال نام Server و نام Package.
• تعدادی از گزینههای dtsrun به طور مستقیم در dtexec معادل ندارند.
• تعدادی گزینههای خط فرمان جدید dtsexec وجود دارد که در ویژگیهای جدید Integration Service پشتیبانی میشود.
2-3-3- مثال
1) به منظور اجرای یک SSIS Package که در SQL Server ذخیره شده است، با استفاده از Windows Authentication :dtexec /sq <Package Name> /ser <Server Name>
2) به منظور اجرای یک SSIS Package که در پوشه File System در SSIS Package Store ذخیره شده است :
dtexec /dts “\File System\<Package File Name>”
3) به منظور اجرای یک SSIS Package که در سیستم فایل ذخیره شده است و مشخص کردن گزینه logging:
dtexec /f “c:\<Package File Name>” /l “DTS.LogProviderTextFile; <Log File Name>”
4) به منظور اجرای یک SSIS Package که در SQL Server ذخیره شده با استفاده از SQL Server Authentication برای نمونه(user:ssis;pwd:ssis@ssis)و رمز (Package(123:
dtexec /server “<Server Name>” /sql “<Package Name>” / user “ssis” /Password “ssis@ssis” /De “123”
3- تنظیمات سطح حفاظتی یک Package
به منظور حفاظت از دادهها در Packageهای Integration Service میتوانید یک سطح حفاظتی (protection level) را تنظیم کنید که به حفاظت از دادههای صرفاً حساس یا تمامی دادههای یک Package کمک نماید. به علاوه میتوانید این دادهها را با یک Password یا یک User Key رمزگذاری نمائید یا به رمزگذاری دادهها در بانک اطلاعاتی اعتماد کنید. همچنین سطح حفاظتی که برای یک Package استفاده میکنید، الزاماً ایستا (static) نیست و در طول چرخه حیات یک Package میتواند تغییر کند. اغلب سطح حفاظتی در طول توسعه یا به محض (deploy) استقرار Package تنظیم میشود.توجه: علاوه بر سطوح حفاظتی که توصیف شد، Packageها در بانک اطلاعاتی msdb ذخیره میشوند که همچنین میتوانند توسط نقشهای ثابت در سطح بانک اطلاعاتی (fixed database-level roles) حفاظت شوند. Integration Service شامل 3 نقش ثابت بانک اطلاعاتی برای نسبت دادن مجوزها به Package است که عبارتند از db_ssisadmin ،db_ssisltduser و db_ssisoperator
3-1- درک سطوح حفاظتی
در یک Package اطلاعات زیر به عنوان حساس تعریف میشوند:• بخش password در یک connection string. گرچه، اگر گزینه ای را که همه چیز را رمزگذاری کند، انتخاب کنید تمامی connection string حساس در نظر گرفته میشود.
• گرههای task-generated XML که برچسب (tagged) هایی حساس هستند.
• هر متغییری که به عنوان حساس نشان گذاری شود.
3-1-1- Do not save sensitive
هنگامی که Package ذخیره میشود از ذخیره مقادیر ویژگیهای حساس در Package جلوگیری میکند. این سطح حفاظتی رمزگذاری نمیکند اما در عوض از ذخیره شدن ویژگی هایی که حساس نشان گذاری شده اند به همراه Package جلوگیری میکند.3-1-2- Encrypt all with password
به منظور رمزگذاری تمامی Package از یک Password استفاده میشود. Package توسط Password ای رمزگذاری میشود که کاربر هنگامی که Package را ایجاد یا Export میکند، ارائه میدهد. به منظور باز کردن Package در SSIS Designer یا اجرای Package توسط ابزار خط فرمان dtexec کاربر بایست رمز Package را ارائه نماید. بدون رمز کاربر قادر به دستیابی و اجرای Package نیست.3-1-3- Encrypt all with user key
به منظور رمزگذاری تمامی Package از یک کلید که مبتنی بر Profile کاربر جاری میباشد، استفاده میشود. تنها کاربری که Package را ایجاد یا Export میکند، میتواند Package را در SSIS Designer باز کند و یا Package را توسط ابزار خط فرمان dtexec اجرا کند.3-1-4- Encrypt sensitive with password
به منظور رمزگذاری تنها مقادیر ویژگیهای حساس در Package از یک Password استفاده میشود. برای رمزگذاری از DPAPI استفاده میشود. دادههای حساس به عنوان بخشی از Package ذخیره میشوند اما آن دادهها با استفاده از Password رمزگذاری میشوند. به منظور باز نمودن Package در SSIS Designer کاربر باید رمز Package را ارائه دهد. اگر رمز ارائه نشود، Package بدون دادههای حساس باز میشود و کاربر باید مقادیر جدیدی برای دادههای حساس فراهم کند. اگر کاربر سعی نماید Package را بدون ارائه رمز اجرا کند، اجرای Package با خطا مواجه میشود.3-1-5- Encrypt sensitive with user key
به منظور رمزگذاری تنها مقادیر ویژگیهای حساس در Package از یک کلید که مبتنی بر Profile کاربر جاری میباشد، استفاده میشود. تنها کاربری که از همان Profile استفاده میکند، Package را میتواند بارگذاری (load) کند. اگر کاربر متفاوتی Package را باز نماید، اطلاعات حساس با مقادیر پوچی جایگزین میشود و کاربر باید مقادیر جدیدی برای دادههای حساس فراهم کند. اگر کاربر سعی نماید Package را بدون ارائه رمز اجرا کند، اجرای Package با خطا مواجه میشود. برای رمزگذاری از DPAPI استفاده میشود.3-1-6- (Rely on server storage for encryption (ServerStorage
با استفاده از نقشهای بانک اطلاعاتی، SQL Server تمامی Package را حفاظت میکند. این گزینه تنها زمانی پشتیبانی میشود که Package در بانک اطلاعاتی msdb ذخیره شده است.4- استفاده از نقشهای Integration Service
برای کنترل کردن دستیابی به Package، SSIS شامل 3 نقش ثابت در سطح بانک اطلاعاتی است. نقشها میتوانند تنها روی Package هایی که در بانک اطلاعاتی msdb ذخیره شده اند، بکار روند. با استفاده از SSMS میتوانید نقشها را به Packageها نسبت دهید، این انتساب نقشها در بانک اطلاعاتی msdb ذخیره میشود.Write action | Read action | Role |
Import packages Delete own packages Delete all packages Change own package roles Change all package roles * به نکته رجوع شود | Enumerate own packages Enumerate all packages View own packages View all packages Execute own packages Execute all packages Export own packages Export all packages Execute all packages in SQL Server Agent | db_ssisadmin or sysadmin |
Import packages Delete own packages Change own package roles | Enumerate own packages Enumerate all packages View own packages Execute own packages Export own packages | db_ssisltduser |
None | Enumerate all packages View all packages Execute all packages Export all packages Execute all packages in SQL Server Agent | db_ssisoperator |
Stop all currently running packages | View execution details of all running packages | Windows administrators |
همچنین جدول sysssispackages در بانک اطلاعاتی msdb شامل Package هایی است که در SQL Server ذخیره میشوند. این جدول شامل ستون هایی که اطلاعاتی درباره نقش هایی که به Packageها نسبت داده شده است، میباشد.
به صورت پیش فرض، مجوزهای نقشهای ثابت بانک اطلاعاتی db_ssisadmin و db_ssisoperator و شناسه منحصر به فرد کاربری (unique security identifier) که Package را ایجاد کرده برای خواندن Package بکار میرود، و مجوزهای نقش db_ssisadmin و شناسه منحصر به فرد کاربری که Package را ایجاد کرده برای نوشتن Package به کار میرود. یک User باید عضو نقش db_ssisadmin و db_ssisltduser یا db_ssisoperator برای داشتن دسترسی خواندن Package باشد. یک User باید عضو نقش db_ssisadmin برای داشتن دسترسی نوشتن Package باشد.
5- اتصال به صورت Remote به Integration Service
زمانی که یک کاربر بدون داشتن دسترسی کافی تلاش کند به یک Integration Service به صورت Remote متصل شود، با پیغام خطای "Access is denied" مواجه میشود. برای اجتناب از این پیغام خطا میتوان تضمین کرد که کاربر مجوز مورد نیاز DCOM را دارد.به منظور پیکربندی کردن دسترسی کاربر به صورت Remote به سرویس Integration مراحل زیر را دنبال کنید:
- Component Service را باز نمایید ( در Run عبارت dcomcnfg را تایپ کنید).مجوز دسترسی Lunch به منظور شروع و خاتمه سرویس، اعطا یا رد میشود و مجوز دسترسیActivation به منظور متصل شدن به سرویس، اعطا (grant) یا رد (deny) میشود.
- گره Component Service را باز کنید، گره Computer و سپس My Computer را باز نمایید و روی DCOM Config کلیک نمایید.
- گره DCOM Config را باز کنید و از لیست برنامه هایی که میتوانند پیکربندی شوند MsDtsServer را انتخاب کنید.
- روی Properties برنامه MsDtsServer رفته و قسمت Security را انتخاب کنید.
- در قسمت Lunch and Activation Permissions، مورد Customize را انتخاب و سپس روی Edit کلیک نمایید تا پنجره Lunch Permission باز شود.
- در پنجره Lunch Permission، کاربران را اضافه و یا حذف کنید و مجوزهای مناسب را به کاربران یا گروههای مناسب نسبت دهید. مجوزهای موجود عبارتند از Local Lunch، Remote Lunch، Local Activation و Remote Activation .
- در قسمت Access Permission مراحل فوق را به منظور نسبت دادن مجوزهای مناسب به کاربران یا گروههای مناسب انجام دهید.
- سرویس Integration را Restart کنید.
6- پیاده سازی
در ابتدا به ایجاد یک CLR Stored Procedures پرداخته میشود نام اسمبلی ساخته شده به این نام RunningPackage.dll میباشد و حاوی کد زیر است:Partial Public Class StoredProcedures '------------------------------------------------ 'exec dbo.Spc_NtDtexec 'Package','ssis','ssis@ssis','1234512345' '------------------------------------------------ <Microsoft.SqlServer.Server.SqlProcedure()> _ Public Shared Sub Spc_NtDtexec(ByVal PackageName As String, _ ByVal UserName As String, _ ByVal Password As String, _ ByVal Decrypt As String) Dim p As New System.Diagnostics.Process() p.StartInfo.FileName = "C:\Program Files\Microsoft SQL Server\100\DTS\Binn\DTExec.exe" p.StartInfo.RedirectStandardOutput = True p.StartInfo.Arguments = "/sql " & PackageName & " /User " & UserName & " /Password " & Password & " /De " & Decrypt p.StartInfo.UseShellExecute = False p.Start() p.WaitForExit() Dim output As String output = p.StandardOutput.ReadToEnd() Microsoft.SqlServer.Server.SqlContext.Pipe.Send(output) End Sub End Class
در قدم بعدی نیاز به Register کردن dll ساخته شده در سطح بانک اطلاعاتی SQL Server است، این گامها پس از اتصال به SQL Server Management Studio به شرح زیر است:
1- فعال کردن CLR در سرویس SQL Server
SP_CONFIGURE 'clr enabled',1 GO RECONFIGURE
2- فعال کردن ویژگی TRUSTWORTHY در بانک اطلاعاتی مورد نظر
ALTER DATABASE <Database Name> SET TRUSTWORTHY ON GO RECONFIGURE
3- ایجاد Assembly و Stored Procedure در بانک اطلاعاتی مورد نظر
Assembly ساخته شده با نام RunningPacakge.dll در ریشه :C کپی شود. بعد از ثبت نمودن این Assembly لزومی به وجود آن نمیباشد.
USE <Database Name> GO CREATE ASSEMBLY [RunningPackage] AUTHORIZATION [dbo] FROM 'C:\RunningPackage.dll' WITH PERMISSION_SET = UNSAFE Go CREATE PROCEDURE [dbo].[Spc_NtDtexec] @PackageName [nvarchar](50), @UserName [nvarchar](50), @Password [nvarchar](50), @Decrypt [nvarchar](50) WITH EXECUTE AS CALLER AS EXTERNAL NAME [RunningPackage].[RunningPackage.StoredProcedures].[Spc_NtDtexec] GO
در برنامه کاربردی تان کافی است متدی به شکل زیر ایجاد و با توجه به نیازتان در برنامه به فراخوانی آن و اجرای Package بپردازید.
Private Sub ExecutePackage() Dim oSqlConnection As SqlClient.SqlConnection Dim oSqlCommand As SqlClient.SqlCommand Dim strCnt As String = String.Empty strCnt = "Data Source=" & txtServer.Text & ";User ID=" & txtUsername.Text & ";Password=" & txtPassword.Text & ";Initial Catalog=" & cmbDatabaseName.SelectedValue.ToString() & ";" Try oSqlConnection = New SqlClient.SqlConnection(strCnt) oSqlCommand = New SqlClient.SqlCommand With oSqlCommand .Connection = oSqlConnection .CommandType = System.Data.CommandType.StoredProcedure .CommandText = "dbo.Spc_NtDtexec" .Parameters.Clear() .Parameters.Add("@PackageName", System.Data.SqlDbType.VarChar, 50) .Parameters.Add("@UserName", System.Data.SqlDbType.VarChar, 50) .Parameters.Add("@Password", System.Data.SqlDbType.VarChar, 50) .Parameters.Add("@Decrypt", System.Data.SqlDbType.VarChar, 50) .Parameters("@PackageName").Value = txtPackageName.Text.Trim() .Parameters("@UserName").Value = txtUsername.Text.Trim() .Parameters("@Password").Value = txtPassword.Text.Trim() .Parameters("@Decrypt").Value = txtDecrypt.Text.Trim() End With If (oSqlCommand.Connection.State <> System.Data.ConnectionState.Open) Then oSqlCommand.Connection.Open() oSqlCommand.ExecuteNonQuery() System.Windows.Forms.MessageBox.Show("Success") End If If (oSqlCommand.Connection.State = System.Data.ConnectionState.Open) Then oSqlCommand.Connection.Close() End If Catch ex As Exception MessageBox.Show(ex.Message, "Error") End Try End Sub 'ExecutePackage
Column Store Index یکی از ویژگیهای جدید SQL Server 2012 می باشد، که کارایی Query های قایل اجرا روی دیتابیسهای با حجم داده ای بسیار بالا را (که اصطلاحا به آنها Data Warehouse یا انبار داده گویند)، چندین برابر بهبود بخشیده است.
قبل از توضیح در مورد Column Store مختصری در مورد نحوه ذخیره سازی دادهها در SQL Server می پردازیم. میتوان گفت در SQL Server دو روش ذخیره سازی وجود دارد،یکی بصورت ردیفی که اصطلاحا به آن Row Storeیا Row-Wise گویند، و دیگری بصورت ستونی که اصطلاحا به آن Column Store گویند.
در روش ذخیره سازی Row Store، مقادیر ستونها در یک سطر بصورت متوالی ذخیره میشوند، در این روش ذخیره سازی از ساختار B-Tree یا Heap استفاده میشود.
یادآوری: در ساختار B-Tree، یک گره Root وجود دارد، و گره بعد از Root گره ای است که آدرس گره راست بعدی و آدرس گره چپ بعدی را در خود نگه میدارد.
شکل زیر نمای یک درخت B-Tree میباشد:
جهت کسب اطلاعات بیشتر درمورد ساختار B-Tree
یادآوری: وقتی در یک جدول، ایندکسی از نوع Clustered ایجاد نماییم، SQL Server، در ابتدا یک کپی از جدول ایجاد و دادههای جدول را از نو مرتب مینماید، و ساختار صفحه ریشه و دیگر صفحات را ایجاد میکند و سپس جدول اصلی را حذف مینماید. به جدولی که Clustered Index ندارد، اصطلاحا Heap گویند.
برخلاف ذخیره سازی Row Store، در ذخیره سازی Column Store، دادهها بصورت ستونی ذخیره میشوند،در این روش داده ها، فشرده سازی میشوند و اینکار باعث میشود،در زمان درخواست یک Query، نیاز به Disk I/o به حداقل برسد، در نتیجه، زمان و سرعت پاسخگویی به پرس و جوها بسیار افزایش مییابد.
شکل زیر نحوه ذخیره سازی داده ها،بصورت Row Store را نمایش میدهد:
شکل بالا ذخیره سازی داده ها، در ساختار B-Tree یا Heap را نمایش میدهد، در شکل فوق یک جدول چهار ستونی با N سطر (Row) در نظر گرفته شده است.بطوریکه ستونهای هر Row بطور متوالی در یک صفحه (Page) یکسان ذخیره میشوند.
شکل زیر نحوه ذخیره سازی داده ها،بصورت Column Store را نمایش میدهد:
مطابق شکل،ستونهای مربوط به هر Row،همگی در یک صفحه (Page) یکسان ذخیره شده اند. به عنوان مثال ستون C1 که مربوط به سطر اول (Row1) میباشد، با ستون C1 که مربوط به سطر دوم (Row2) میباشد، در یک ستون و در یک صفحه (Page1) ذخیره شده اند، و الی آخر ...
سئوال: یکبار دیگر به هردو شکل با دقت نگاهی بیاندازید، عمده تفاوت آنها در چیست؟
جواب: درست حدس زدید، تفاوت بارز بین دو روش Column Store و Row Store در نحوه ذخیره سازی دادهها میباشد. بطور مثال، فرض کنید،در روش ذخیره سازی Row Store، به دنبال مقادیری از ستون C2 میباشید، SQL Server میبایست کل رکوردهای جدول (منظور همه Rowها در همه Page ها)را Scan نماید، تا مقادیر مربوط به ستون C2 را بدست آورد.درحالیکه در روش ذخیره سازی Column Store، جهت یافتن مقادیر ستون C2، نیازی به Scan نمودن کل جدول نیست،بلکه SQL Server فقط به Scan نمودن ستون دوم (C2) یا Page2 بسنده مینماید.همین امر باعث افزایش چندین برابری، زمان پاسخگویی به هر Query میشود.
سئوال: در روش ذخیره سازی Column Store، چگونه مصرف حافظه بهینه میشود؟
جواب: واضح است، که در روش SQL Server، Row Store مجبور است، برای بدست آوردن دادههای مورد نظرتان،کل اطلاعات جدول را وارد حافظه نماید(اطلاعات اضافه ای که به هیچ وجه بدرد، نتیجه پرس و جوی شما نمیخورد)، و شروع به Scan دادههای مد نظر شما مینماید.بطوریکه در روش SQL Server، Column Store، فقط ستون دادههای مورد پرس و جو را در حافظه قرار میدهد.(در واقع فقط داده هایی را در حافظه قرار میدهد، که شما به آن نیاز دارید)،بنابراین،طبیعی است که در روش Column Store مقدار حافظه کمتری نسبت به روش Row Store در هنگام اجرای Query استفاده میشود. به عبارت دیگر میتوان گفت که در روش Column Store به دلیل، به حداقل رساندن استفاده از Disk I/o سرعت و زمان پاسخگویی به پرس و جوها چندین برابر میشود.
برای درک بیشتر Row Store و Column Store مثالی میزنیم:
فرض کنید،قصد بدست آوردن ستونهای C1 و C2 از جدول A را داریم، بنابراین خواهیم داشت:
Select C1, C2 from A
روش Row Store:
در این روش همه صفحات دیسک (مربوط به جدول A) درون حافظه قرار داده میشود، یعنی علاوه بر ستونهای C1 و C2، اطلاعات مربوط به ستونهای C3 و C4 نیز درون حافظه قرار میگیرد،بطوریکه مقادیر ستونهای C3 و C4 به هیچ وجه مورد قبول ما نیست، و در خروجی پرس و جوی ما تاثیری ندارد، و فقط بی جهت حافظه اشغال مینماید.
روش Column Store:
در این روش فقط صفحات مروبط به ستون C1 و C2 در حافظه قرار میگیرد.(منظور Page1 و Page2 میباشد) بنابراین فقط اطلاعات مورد نیاز در خروجی، در حافظه قرار میگیرد.
- از دیگر مزایای استفاده از روش Column Store، فشرده سازی داده میباشد،برای درک بیشتر توضیح میدهم:
چه موقع میتوانیم از Column Store استفاده نماییم:
در تعریف Column Store گفته بودم، روش فوق، جهت بهبود بخشیدن به زمان و سرعت پاسخگویی به Queryهای اجرا شده روی دیتابیسهای با حجم داده ای بسیار بالا(Data Warehouse ) میباشد، به بیان سادهتر Column Store را روی دیتابیسهای offline یا دیتابیسهایی که صرفا جهت گزارش گیری مورد استفاده قرار میگیرند، تنظیم مینمایند.در واقع با تنظیم Column Store Index روی Databaseهای بزرگ مانند Databaseهای بانکها که حجم داده ای میلیونی در جداول آنها وجود دارد، سرعت پاسخگویی Query ها، چندین برابر افزایش مییابد.
- در یک جدول میتوانید، هم Column Store Index داشته باشید و هم یک Row Store Index (منظور یک Clustered Index می باشد)
- Syntax برای ایجاد Column Store Index به شرح ذیل میباشد:
CREATE [ NONCLUSTERED ] COLUMNSTORE INDEX index_name ON <object> ( column [ ,...n ] ) [ WITH ( <column_index_option> [ ,...n ] ) ] [ ON { { partition_scheme_name ( column_name ) } | filegroup_name | "default" } ] [ ; ] <object> ::= { [database_name. [schema_name ] . | schema_name . ] table_name { <column_index_option> ::= { DROP_EXISTING = { ON | OFF } | MAXDOP = max_degree_of_parallelism }
- یک Column Store Index میبایست از نوع NONCLUSTERED باشد.
CREATE NONCLUSTERED COLUMNSTORE INDEX [IX_MyFirstName_ColumnStore] ON [Test] (Firstname)
- زمانی که در یک جدول، یک Column Store Index ایجاد نماییم، جدول ما در حالت Read-only قرار میگیرد، بطوریکه از آن پس اختیار Delete،Update و Insert روی جدول فوق را نخواهیم داشت. برای اینکه بتوانید عملیات Insert، Update یا Delete را انجام دهید، میبایست Column Store Index جدول مربوطه را Disable نمایید، و برای فعال نمودن Column Store Index، میبایست آن را Rebuild نمایید، با کلیک راست روی ایندکس ایجاد شده در SQL Server2012 موارد Disable و Rebuild قابل مشاهده میباشد.
ALTER INDEX [IX_MyFirstName_ColumnStore] ON [Test] DISABLE ALTER INDEX [IX_MyFirstName_ColumnStore] ON [Test] Rebuild
- بیشتر از یک Column Store Index نمیتوانید روی یک جدول ایجاد نمایید.
- در صورتی که تمایل داشته باشید بوسیله Alter ، نوع فیلدی (Type)، را که Column Store Index روی آنها اعمال گردیده است، تغییر دهید، در ابتدا میبایست Column Store Index، خود را Drop یا حذف نمایید، سپس عملیات Alter را اعمال کنید، در غیر اینصورت با خطای SQL Server مواجه میشوید.
- یک Column Store Index میتواند روی 1024 ستون در یک جدول اعمال گردد.
- یک Column Store Index نمی توانند، Unique باشد و نمیتوان از آن به عنوان Primary Key یا Foreign Key استفاده نمود.
sudo apt-get update -y sudo apt-get upgrade -y
sudo java -version
sudo add-apt-repository -y ppa:webupd8team/java
gpg: keyring `/tmp/tmpkjrm4mnm/secring.gpg' created gpg: keyring `/tmp/tmpkjrm4mnm/pubring.gpg' created gpg: requesting key EEA14886 from hkp server keyserver.ubuntu.com gpg: /tmp/tmpkjrm4mnm/trustdb.gpg: trustdb created gpg: key EEA14886: public key "Launchpad VLC" imported gpg: no ultimately trusted keys found gpg: Total number processed: 1 gpg: imported: 1 (RSA: 1) OK
sudo apt-get update
sudo apt-get install oracle-java8-installer -y
sudo java -version
java version "1.8.0_66" Java(TM) SE Runtime Environment (build 1.8.0_66-b17) Java HotSpot(TM) 64-Bit Server VM (build 25.66-b17, mixed mode)
پس از دریافت Apache Kafka، از طریق یک terminal جدید وارد پوشه Downloads شوید. سپس فایل مورد نظر را از حالت فشرده خارج کنید و وارد پوشه اصلی آن شوید. برای این کار از دستورات زیر استفاده کنید:
tar -xzf kafka_2.11-1.0.0.tgz cd kafka_2.11-1.0.0
سپس با استفاده از دستور "ls " لیست تمامی فایلها و فولدرهای موجود در فولدر kafka_2.11-1.0.0 و را نمایش دهید:
در لیست نمایش داده شده دو فولد اصلی bin و config وجود دارند که به ترتیب فایلهای اجرایی و کانفیگهای پیشفرض و مورد نیاز، در آنها قرار دارند.
اجرای Apache ZooKeeper:
همانطور که در بخش قبل توضیح داده شد، Apache Kafka هیچ Stateی را در خود ذخیره و مدیریت نمیکند و اصطلاحا Stateless میباشد و مدیریت تمامی این Stateها را بر عهده Apache ZooKeeper قرار میدهد. بنابراین قبل از اینکه بخواهیم Kafka را اجرا کنیم، ابتدا باید Apache ZooKeeper را اجرا کنیم. برای اجرای Apache ZooKeeper در terminalی که قبلا باز کردهاید، دستور زیر را اجرا کنید:
bin/zookeeper-server-start.sh config/zookeeper.properties
با اجرای فایل zookeeper-server-start.sh و ارسال کانفیگ پیشفرضش در فولدر config با نام zookeeper.properties، قسمت مدیریت کننده Stateهای Kafka اجرا میشود.
اجرای Apache Kafka:
پس از اجرای Apache ZooKeeper، باید یک terminal جدید را باز کنید و با استفاده از دستور زیر kafka-server را با استفاده از کانفیگ پیشفرضش اجرا کنید:
bin/kafka-server-start.sh config/server.properties
به همین راحتی Kafka Server اجرا شده و آمادهی استفاده است. برای ادامه و نمایش دادن سایر قابلیتهای Kafka باید یک Topic جدید را ایجاد کنیم.
ایجاد Topic:
همانطور که میدانید تمامی پیامهای شما در Partitionهای Topic ذخیره میشوند؛ پس قبل از اینکه بخواهیم توسط یک Producer پیامی را ارسال کنیم یا اینکه بخواهیم توسط Consumer پیامی را دریافت کنیم، ابتدا باید Topic مربوطه را ایجاد کنیم. بهترین و عمومیترین مثال برای نمایش قابلیتهای Publish-Subscribe در Kafka، مثال چت بین کاربران است که در آن یک کاربر به عنوان Producer عمل میکند و پیامهایی را برای سایر کاربران که نقش Consumer را دارند ارسال میکند. kafka-topics
.sh
در Kafka ابزاریست برای مدیریت Topic ها که با استفاده از آن میتوانید Topicهای مورد نیاز را تعریف، ویرایش و یا حذف کنید.
یک terminal جدید را آغاز و با استفاده از دستور زیر یک Topic را با نام userChat ایجاد کنید:
bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic userChat
با این دستور یک Topic در Kafka با نام userChat ایجاد میشود و مشخصات آن (منظور stateهای مرتبط با آن است)، در zookeeper نیز ثبت میشود.
توضیحات Optionها و آرگومانهایی که در دستور ایجاد Topic استفاده شدند به شرح زیر است:
create-- :
مشخص کننده این است که شما میخواهید یک Topic جدید را ایجاد کنید.
zookeeper localhost:2181--:
مشخص کننده آدرس سرور zookeeper است (سرور zookeeper بصورت پیشفرض از پورت 2181 استفاده میکند).
replication-factor 1--:
مشخص کننده این است که تنها یک کپی از این Topic در سرور ایجاد شود. البته در این مثال به دلیل اینکه تنها یک سرور از Kafka را اجرا کردهایم در صورتیکه این مقدار را بیش از عدد 1 در نظر بگیریم، با خطا مواجه میشویم.
partitions 1--
تعداد Partitionهای این Topic را مشخص میکند. مقدار Partition در حالتیکه حتی یک سرور را نیز اجرا کردهایم، میتواند بیش از 1 باشد؛ اما در این حالت Primary Broker برای تمام Partitionهای همین سرور در نظر گرفته میشود.
topic userChat--
نام Topic را مشخص میکند.
پس از اینکه Topic مورد نظر ایجاد شد، با استفاده از دستور زیر میتوانیم لیست Topicهای ایجاد شده را مشاهده کنیم:
bin/kafka-topics.sh --list --zookeeper localhost:2181
توضیحات Optionها و آرگومانهایی که در دستور نمایش لیست Topicها استفاده شدند به شرح زیر است:
list--:
شما میخواهید لیست topicها را مشاهده کنید.
zookeeper localhost:2181--:
همانند دستور ایجاد، مشخص کننده سرور zookeeper میباشد.
تا اینجا برای ارسال و دریافت پیام در بین Applicationهای مختلف همه چیز آمادهاست. البته همانطور که در بخشهای بعدی این سری مقالات میبینیم، در عمل Producerها و Consumerها زیرسیستمهایی هستند که خود ما با استفاده از هر زبان برنامه نویسی پیاده سازی میکنیم. اما در این قسمت برای نمایش و تکمیل مثالمان، از ابزارهایی که خود Kafka در اختیار ما قرار داده استفاده میکنیم.
اجرای یک Producer و ارسال پیام به Kafka:
kafka-console-producer.sh ابزاریست که با استفاده از آن میتوانید به راحتی یک Producer را ایجاد کنید. در terminalی که قبلا برای مدیریت Topicها باز کردهاید با استفاده از دستور زیر، Producer را اجرا میکنیم:
bin/kafka-console-producer.sh --broker-list localhost:9092 --topic userChat
توضیحات Optionها و آرگومانهایی که در دستور ایجاد Producer استفاده شدند به شرح زیر است:
broker-list localhost:9092--:
نشان دهنده لیست Brokerها میباشد و در صورتیکه تعداد آنها بیش از یک باشد، میتوان آنها را با "," از هم جدا کرد (پورت پیشفرض Kafka server برای دریافت دستورات، 9092 میباشد).
topic userChat--:
Topicی از Kafka server را مشخص میکند که این Producer میخواهد پیامهای خود را برایش ارسال کند.
پس از اجرای دستور فوق، با تایپ کردن و زدن کلید Enter، پیام مورد نظر به Broker موردنظر یعنی localhost:9092 ارسال و در تنها Partition تاپیک userChat ذخیره میشود.
اجرای یک Consumer و دریافت پیامهای موجود در Topic مورد نظر:
kafka-console-consumer.sh ابزاریست که خود Kafka در اختیار ما قرار داده است و با استفاده از آن میتوانیم به راحتی یک Consumer برای userChat ،Topic ایجاد کنیم.
در یک terminal جدید با استفاده از دستور زیر یک Consumer را ایجاد کنید که userChat ،Topic را Subscribe میکند:
bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic userChat --from-beginning
توضیحات Optionها و آرگومانهایی که در دستور ایجاد یک Consumer استفاده شدند به شرح زیر است:
bootstrap-server localhost:9092-- :
مشخص کننده Kafka serverی است که میخواهیم از طریق آن به تمامی اعضای Cluster دسترسی داشته باشیم. در صورتیکه بیش از یک سرور را بخواهیم وارد کنیم، باید آنها را با "," از هم جدا کنیم.
topic userChat --:
مشخص کننده Topicی است که این Consumer میخواهد روی آن Subscribe کند.
from-beginning--:
مشخص کننده این است که Consumer ایجاد شده میخواهد تمامی پیامهای موجود در userChat ، Topic را از اولین offset تا آخرین offset دریافت کند.
پس از اجرای کد فوق مشاهده میکنید که پیامهایی که قبلا Producer در تاپیک userChat ثبت کردهاست، برای این Consumer ارسال میشوند.
از اینجا به بعد هر لحظه که در terminal ارسال کننده یا Producer پیامی تایپ کنید و کلید Enter را بزنید، بلافاصله پیام مورد نظر برای Consumer ارسال میشود و در terminal آن نمایش داده میشود. حتی میتوانیم چندین Consumer را در terminal های مختلفی اجرا کنیم. در این صورت با ارسال هر پیام از طرف Producer، تمامی Consumerها آن را نمایش میدهند.
با استفاده از سادگی راه اندازی و قابلیتهای بسیار زیادی که Apache Kafka در مدیریت جریان دادهای بین سیستمهایمان به ما میدهد، میتوانیم به سادگی در سیستمهایمان قابلیتهای مقیاس پذیری افقی، تحمل در برابر خطا، در دسترس بودن، کارآیی بالا و سادگی مدیریت ارتباطات بخشهای مختلف را اضافه کنیم. در بخشهای بعدی به نحوه ایجاد یک Cluster و اینکه چگونه میتوان از این بستر در Net. استفاده کرد، میپردازیم.
مثال 1: یافتن زمانهای شروع رزرو کردن امکانات مختلف، توسط یک کاربر مشخص.
چگونه میتوان زمانهای شروع رزروهای کاربری به نام «David Farrell» را یافت؟
همانطور که در دیاگرام فوق مشاهده میکنید، به ازای هر ID کاربری در جدول کاربران، به دنبال ردیفهایی در جدول Bookings هستیم که این ID در آنها درج شدهاست. اما ... در EF-Core برخلاف SQL نویسی معمولی، ما کاری به ذکر قسمت اتصالی ON [Bookings].[MemId] = [Members].[MemId] نداریم. همینقدر که در کوئری نوشته شده به یک سر دیگر رابطه و خاصیت راهبری (navigation property) دیگری اشاره شود، خود EF-Core جوینی را به صورت خودکار تشکیل خواهد داد و شرط یاد شده را نیز برقرار میکند.
در قسمت اول این سری، در حین طراحی موجودیت کاربر، برای تشکیل سر دیگر رابطهی one-to-many آن، به جدول Bookings، خاصیت Member را نیز که بیانگر کلید خارجی به جدول کاربران است، اضافه کردیم:
namespace EFCorePgExercises.Entities { public class Booking { // ... public int MemId { set; get; } public virtual Member Member { set; get; } // ... } }
var startTimes = context.Bookings .Where(booking => booking.Member.FirstName == "David" && booking.Member.Surname == "Farrell") .Select(booking => new { booking.StartTime }) .ToList();
مثال 2: یافتن زمانهای شروع به رزرو شدن یک امکان خاص در مجموعه.
لیست زمانهای شروع به رزرو شدن زمین(های) تنیس را برای روز 2012-09-21 تولید کنید. خروجی آن باید به همراه ستونهای StartTime, FacilityName باشد.
طراحی موجودیت Booking، به همراه یک کلید خارجی به Facility نیز هست:
namespace EFCorePgExercises.Entities { public class Booking { // ... public int FacId { set; get; } public virtual Facility Facility { set; get; } // ... } }
int[] tennisCourts = { 0, 1 }; var date1 = new DateTime(2012, 09, 21); var date2 = new DateTime(2012, 09, 22); var startTimes = context.Bookings .Where(booking => tennisCourts.Contains(booking.Facility.FacId) && booking.StartTime >= date1 && booking.StartTime < date2) .Select(booking => new { booking.StartTime, booking.Facility.Name }) .ToList();
- در قسمت Where این کوئری چون booking.Facility ذکر شده، سبب ایجاد جوین خودکاری به جدول Facilities خواهد شد.
- علت استفادهی از دو تاریخ در اینجا برای یافتن اطلاعات تنها یک روز، ثبت زمان، به همراه تاریخ رزرو است. ستون تاریخ شروع، به صورت «2012-09-21 18:00:00.0000000» مقدار دهی شدهاست و نه به صورت «2012-09-21». البته در EF-Core راه دیگری هم برای حل این مساله وجود دارد. هر خاصیت از نوع DateTime، به همراه خاصیت Date نیز هست. برای مثال اگر بجای booking.StartTime نوشته شود booking.StartTime.Date (به خاصیت Date اضافه شده دقت کنید)، کد SQL حاصل، به همراه «CONVERT(date, [b].[StartTime])» خواهد بود که سبب حذف خودکار قسمت زمان این ستون میشود.
مثال 3: تولید لیست کاربرانی که کاربر دیگری را توصیه کردهاند.
چگونه میتوان لیست کاربرانی را یافت که کاربر دیگری را توصیه کردهاند؟ این لیست نباید به همراه ردیفهای تکراری باشد و همچنین باید بر اساس surname, firstname مرتب شود.
در اینجا به مفهوم جوین کردن یک جدول با خودش رسیدهایم. جدول کاربران، یک جدول خود ارجاع دهندهاست:
namespace EFCorePgExercises.Entities { public class Member { // ... public virtual ICollection<Member> Children { get; set; } public virtual Member Recommender { set; get; } public int? RecommendedBy { set; get; } // ... } }
var members = context.Members .Where(member => member.Recommender != null) .Select(member => new { member.Recommender.FirstName, member.Recommender.Surname }) .Distinct() .OrderBy(member => member.Surname).ThenBy(member => member.FirstName) .ToList();
مثال 4: تولید لیست کاربران به همراه توصیه کنندهی آنها.
چگونه میتوان لیست کاربران را به همراه توصیه کنندهی آنها تولید کرد؟ این لیست باید بر اساس surname, firstname مرتب شود.
var members = context.Members .Select(member => new { memFName = member.FirstName, memSName = member.Surname, recFName = member.Recommender.FirstName ?? "", recSName = member.Recommender.Surname ?? "" }) .OrderBy(member => member.memSName).ThenBy(member => member.memFName) .ToList();
همانطور که ملاحظه میکنید، نوع جوین خودکار تشکیل شده، Left join است و دیگر مانند جوینهای مثالهای ابتدای بحث، inner join نیست. در inner join، جدول سمت راست و چپ بر اساس شرط ON آنها با هم مقایسه شده و ردیفهای کاملا تطابق یافتهای بازگشت داده میشوند. کار Left join نیز مشابه است، با این تفاوت که در اینجا ممکن است برای جدول سمت چپ، هیچ ردیف تطابق یافتهای در جدول سمت راست وجود نداشته باشد (نوع آن بر اساس نال پذیری خاصیت RecommendedBy تشخیص داده شدهاست)؛ برای مثال یک کاربر ممکن است توسط کاربر دیگری توصیه نشده باشد (و RecommendedBy او نال باشد)، اما علاقمندیم که نام او در لیست نهایی حضور داشته باشد و حذف نشود.
یک نکته: در SQL Server تفاوتی بین left join و left outer join وجود ندارد و ذکر واژهی کلیدی outer کاملا اختیاری است. جدول موارد مشابهی در SQL Server که به یک معنا هستند، صورت زیر است:
A LEFT JOIN B A LEFT OUTER JOIN B A RIGHT JOIN B A RIGHT OUTER JOIN B A FULL JOIN B A FULL OUTER JOIN B A INNER JOIN B A JOIN B
مثال 5: تولید لیست کاربرانی که از زمین تنیس استفاده کردهاند.
چگونه میتوان لیست کاربرانی را تولید کرد که از زمین(های) تنیس استفاده کردهاند؟ خروجی این گزارش باید به همراه یک ستون جمع نام و نام خانوادگی و ستون نام زمین باشد. این گزارش نباید دارای ردیفهای تکراری باشد و همچنین باید بر اساس حاصل جمع نام و نام خانوادگی، مرتب شده باشد.
جدول Bookings به همراه دو کلید خارجی به جداول Facilities و Members است:
namespace EFCorePgExercises.Entities { public class Booking { // ... public int FacId { set; get; } public virtual Facility Facility { set; get; } public int MemId { set; get; } public virtual Member Member { set; get; } // ... } }
namespace EFCorePgExercises.Entities { public class Member { // ... public virtual ICollection<Booking> Bookings { set; get; } } }
int[] tennisCourts = { 0, 1 }; var members = context.Members .SelectMany(x => x.Bookings) .Where(booking => tennisCourts.Contains(booking.Facility.FacId)) .Select(booking => new { Member = booking.Member.FirstName + " " + booking.Member.Surname, Facility = booking.Facility.Name }) .Distinct() .OrderBy(x => x.Member) .ToList();
پس از آن برای حذف ردیفهای تکراری حاصل از گزارش، از متد Distinct استفاده شده و OrderBy نیز بر اساس خاصیت جدید Member، قابل تعریف است:
مثال 6: تولید لیست رزروهای گران قیمت
لیست رزروهای روز 2012-09-14 را تولید کنید که هزینهی آنها بیشتر از 30 دلار باشد. باید بخاطر داشت که هزینههای کاربران با مهمانها متفاوت است و هزینهها بر اساس Slotهای نیم ساعته محاسبه میشوند و ID کاربر مهمان همیشه صفر است. خروجی این گزارش باید به همراه نام کامل کاربر، نام امکانات مورد استفاده و هزینهی نهایی باشد. همچنین باید بر اساس هزینههای نهایی به صورت نزولی مرتب شود.
var date1 = new DateTime(2012, 09, 14); var date2 = new DateTime(2012, 09, 15); var items = context.Members .SelectMany(x => x.Bookings) .Where(booking => booking.StartTime >= date1 && booking.StartTime < date2 && ( (((booking.Slots * booking.Facility.GuestCost) > 30) && (booking.MemId == 0)) || (((booking.Slots * booking.Facility.MemberCost) > 30) && (booking.MemId != 0)) )) .Select(booking => new { Member = booking.Member.FirstName + " " + booking.Member.Surname, Facility = booking.Facility.Name, Cost = booking.MemId == 0 ? booking.Slots * booking.Facility.GuestCost : booking.Slots * booking.Facility.MemberCost }) .Distinct() .OrderByDescending(x => x.Cost) .ToList();
سپس بر اساس صفر بودن یا نبودن booking.MemId (کاربر مهمان بودن یا خیر)، شرط هزینهی بیشتر از 30 دلار اعمال شدهاست.
در آخر Select گزارش مورد نیاز، به همراه جمع نام و نام خانوادگی، نام امکانات استفاده شده و خاصیت محاسباتی Cost است که بر اساس مهمان بودن یا نبودن کاربر، متفاوت است.
متد Distinct ردیفهای تکراری حاصل از این گزارش را حذف میکند (محل درج آن مهم است) و متد OrderByDescending، مرتب سازی نزولی بر اساس خاصیت محاسباتی Cost را انجام میدهد.
مثال 7: تولید لیست کاربران به همراه توصیه کنندهی آنها، بدون استفاده از جوین.
در اینجا میخواهیم همان مثال 4 را بدون استفاده از جوین بررسی کنیم. بدون استفاده از جوین در اینجا به معنای استفاده از sub-query است (نوشتن یک کوئری داخل کوئری اصلی).
var members = context.Members .Select(member => new { Member = member.FirstName + " " + member.Surname, Recommender = context.Members .Where(recommender => recommender.MemId == member.RecommendedBy) .Select(recommender => recommender.FirstName + " " + recommender.Surname) .FirstOrDefault() ?? "" }) .Distinct() .OrderBy(member => member.Member) .ToList();
مثال 8: تولید لیست رزروهای گران قیمت با استفاده از یک sub-query.
هدف از این مثال، ارائهی روش حل دیگری برای مثال 6، به نحو تمیزتری است. در مثال 6، هزینهی رزرو را دوبار، یکبار در متد Where و یکبار در متد Select محاسبه کردیم. اینبار میخواهیم با استفاده از sub-queryها این محاسبه را یکبار انجام دهیم.
var date1 = new DateTime(2012, 09, 14); var date2 = new DateTime(2012, 09, 15); var items = context.Members .SelectMany(x => x.Bookings) .Where(booking => booking.StartTime >= date1 && booking.StartTime < date2) .Select(booking => new { Member = booking.Member.FirstName + " " + booking.Member.Surname, Facility = booking.Facility.Name, Cost = booking.MemId == 0 ? booking.Slots * booking.Facility.GuestCost : booking.Slots * booking.Facility.MemberCost }) .Where(x => x.Cost > 30) .Distinct() .OrderByDescending(x => x.Cost) .ToList();
هرچند کوئری SQL نهایی تولید شدهی توسط EF-Core آن، تفاوتی چندانی با نگارش قبلی ندارد:
کدهای کامل این قسمت را در اینجا میتوانید مشاهده کنید.