پاسخ به بازخوردهای پروژهها
بازخوردهای دوره
متدهای توکار استفاده از نوع دادهای XML - قسمت دوم
چنین چیزی به صورت توکار پشتیبانی نمیشود. فقط میتوانید مستقیما کوئریهای SQL را نوشته و اجرا کنید.
از SQL server 2005 به بعد، پشتیبانی کاملی از XML توسط این محصول صورت میگیرد. در ادامه مروری خواهیم داشت بر نحوهی به روز رسانی مقادیر فیلدهایی از نوع XML در SQL Server .
در ابتدا جدول موقتی زیر را که شامل یک رکورد از نوع XML است، در نظر بگیرید:
DECLARE @tblTest AS TABLE (xmlField XML)
INSERT INTO @tblTest
(
xmlField
)
VALUES
(
'<Sample>
<Node1>Value1</Node1>
<Node2>Value2</Node2>
<Node3>OldValue</Node3>
</Sample>'
)
سعی اول:
DECLARE @newValue VARCHAR(50)
SELECT @newValue = 'NewValue'
UPDATE @tblTest
SET xmlField.modify('replace value of (/Sample/Node3)[1] with ' + @newValue)
The argument 1 of the XML data type method "modify" must be a string literal.
سعی دوم:
DECLARE @newValue VARCHAR(50)
SELECT @newValue = 'NewValue'
UPDATE @tblTest
SET xmlField.modify(
'replace value of (/Sample/Node3)[1] with sql:variable("@newValue")'
)
XQuery [@tblTest.xmlField.modify()]: The target of 'replace value of' must be a non-metadata attribute or an element with simple typed content, found 'element(NodeThree,xdt:untyped) ?'
سعی سوم:
DECLARE @newValue VARCHAR(50)
SELECT @newValue = 'NewValue'
UPDATE @tblTest
SET xmlField.modify(
'replace value of (/Sample/Node3/text())[1]
with sql:variable("@newValue")'
)
SELECT xmlField.value('(/Sample/Node3)[1]','varchar(50)') FROM @tblTest
و بله. کار میکنه!
XML ایی را که در ابتدا استفاده کردیم از نوع un-typed XML محسوب شده و هیچ schema ایی را برای آن در نظر نگرفتهایم، به همین جهت باید دقیقا مشخص کنیم که قصد داریم text این node را ویرایش نمائیم.
مشکل بعدی!
در ابتدا مثال زیر را در نظر بگیرید:
DECLARE @tblTest AS TABLE (xmlField XML)
INSERT INTO @tblTest
(
xmlField
)
VALUES
(
'<Sample>
<Node1>Value1</Node1>
<Node2>Value2</Node2>
<Node3></Node3>
</Sample>'
)
DECLARE @newValue VARCHAR(50)
SELECT @newValue = 'NewValue'
UPDATE @tblTest
SET xmlField.modify(
'replace value of (/Sample/Node3/text())[1]
with sql:variable("@newValue")'
)
SELECT xmlField.value('(/Sample/Node3)[1]','varchar(50)') FROM @tblTest
این عبارات T-SQL ، خلاصه بحث ما تا به اینجا هستند اما با یک تفاوت. نود 3 در اینجا خالی است.
اگر اسکریپت را اجرا کنید، هیچ تغییری را مشاهده نخواهید کرد. به عبارت دیگر به روز رسانی صورت نمیگیرد. در اینجا چون text این نود خالی است ، فرض SQL Server بر این خواهد بود که وجود ندارد، بنابراین این نود را به روز رسانی نخواهد کرد. به همین منظور باید برای به روز رسانی این نود، عبارت جدید را در جایی که text ندارد insert کرد (و نه replace).
DECLARE @newValue VARCHAR(50)
SELECT @newValue = 'NewValue'
UPDATE @tblTest
SET xmlField.modify(
'replace value of (/Sample/Node3/text())[1]
with sql:variable("@newValue")'
)
UPDATE @tblTest
SET xmlField.modify(
'insert text{sql:variable("@newValue")} into
(/Sample/Node3)[1] [not(text())]'
)
SELECT xmlField.value('(/Sample/Node3)[1]','varchar(50)') FROM @tblTest
مقدمه
تکنولوژی CTE از نسخه SQL Server 2005 رسمیت یافته است و شامل یک result set موقتی[1] است که دارای نام مشخص بوده و میتوان از آن در دستورات SELECT, INSERT, UPDATE, DELETEاستفاده کرد. همچنین از CTE میتوان در دستور CREATE VIEW و دستور SELECT مربوط به آن استفاده کرد. در نسخه SQL Server 2008 نیز امکان استفاده از CTE در دستور MERGE فراهم شده است.
در SQL Serverاز دو نوع CTE بازگشتی[2] و غیر بازگشتی[3] پشتیبانی میشود. در این مقاله سعی شده است نحوه تعریف و استفاده از هر دو نوع آن آموزش داده شود.
انواع روشهای ایجاد جداول موقت
برای استفاده از جداول موقتی در سرور اسکیوال، سه راه زیر وجود دارد.
روش اول: استفاده از دستوری مانند زیر است که سبب ایجاد جدول موقتی در بانک سیستمی tempdb میشود. زمانیکه شما ارتباط خود را با سرور SQL قطع میکنید به صورت اتوماتیک جداول موقت شما از بانک tempdb حذف میشوند. این روش در برنامه نویسی پیشنهاد نمیشود و فقط در کارهای موقتی و آزمایشی مناسب است.
روش دوم: استفاده از متغیر نوع Table است، که نمونه آن در مثال زیر دیده میشود. زمانیکه از محدوده[4] جاری کد[5] خودتان خارج شوید آن متغیر نیز از حافظه پاک میشود. از این روش، عموما در کدهای Stored Procedureها و UserDefined Functionها استفاده میشود.
روش سوم: استفاده از CTE است که مزیتهایی نسبت به دو روش قبلی دارد و در بخش بعدی به نحوه تعریف و استفاده از آن خواهیم پرداخت.
کار با CTE
ساده ترین شکل تعریف یک CTE به صورت زیر است:
با کلمه WITH شروع شده و یک نام اختیاری به آن داده میشود. سپس فهرست فیلدهای جدول موقت را درون زوج پرانتز، مشخص میکنید. تعریف این فیلدها اختیاری است و اگر حذف شود، فیلدهای جدول موقت، مانند فیلدهای کوئری مربوطه خواهد بود.
your query شامل دستوری است که سبب تولید یک result set میشود. قواعد تعریف این کوئری مشابه قواعد تعریف کوئری است که در دستور CREATE VIEW کاربرد دارد.
همانطور که از این تصویر مشخص است میتوان چندین بلوک از این ساختار را به دنبال هم تعریف نمود که با کاما از هم جدا میشوند. در واقع یکی از کاربردهای CTE ایجاد قطعات کوچکی است که امکان استفاده مجدد را به شما داده و میتواند سبب خواناتر شدن کدهای پیچیده شود.
یکی دیگر از کاربردهای CTE آنجایی است که شما نمیخواهید یک شی Viewی عمومی تعریف کنید و در عین حال میخواهید از مزایای Viewها بهرمند شوید.
و همچنین از کاربردهای دیگر CTE تعریف جدول موقت و استفاده از آن جدول به صورت همزمان در یک دستور است.
بعد از آنکه CTE یا CTEهای خودتان را تعریف کردید آنگاه میتوانید مانند جداول معمولی از آنها استفاده کنید. استفاده از این جداول توسط دستوری خواهد بود که دقیقا بعد از تعریف CTE نوشته میشود.
ایجاد یک CTE غیر بازگشتی[6]
مثال اول، یک CTE غیر بازگشتی ساده را نشان میدهد.
مثال دوم نمونهای دیگر از یک CTE غیر بازگشتی است.
هدف این کوئری، محاسبه کل میزان فروش کالاها، به ازای هر کشور میباشد. ابتدا از جدول Order Details مجموع فروش هر سفارش محاسبه شده و نتیجه آن در یک CTE به نام orderSales قرار میگیرد و از JOIN این جدول موقت با جدول Orders محاسبه نهایی انجام شده و نتیجهای مانند این تصویر حاصل میشود.
مثال سوم استفاده از دو CTE را به صورت همزمان نشان میدهد:
مثال چهارم استفاده مجدد از یک CTE را نشان میدهد. فرض کنید جدولی به نام digits داریم که فقط یک فیلد digit دارد و دارای 10 رکورد با مقادیر 0 تا 9 است. مانند تصویر زیر
حال میخواهیم از طریق CROSS JOIN اعداد 1 تا 100 را با استفاده از مقادیر این جدول تولید کنیم. کد زیر آنرا نشان میدهد:
در این کد یک CTE تعریف شده و دو بار مورد استفاده قرار گرفته است. مثلا اگر بخواهید اعداد 1 تا 1000 را تولید کنید میتوانید سه بار از آن استفاده کنید. حاصل این دستور result setی مانند زیر است.
حتی میتوان از یک CTE در کوئری CTE بعدی مانند کد زیر استفاده کرد.
ایجاد یک CTE بازگشتی[7]
از CTE بازگشتی برای پیمایش جداولی استفاده میشود که رکوردهای آن دارای رابطه سلسله مراتبی یا درختی است. نمونه این جداول، جدول کارمندان است که مدیر هر کارمند نیز مشخص شده است یا جدولی که ساختار سازمانی را نشان میدهد یا جدولی که موضوعات درختی را در خود ذخیره کرده است. یکی از مزایای استفاده از CTE بازگشتی، سرعت کار آن در مقایسه با روشهای پردازشی دیگر است.
ساختار کلی یک دستور CTE بازگشتی به صورت زیر است.
در بدنه CTE حداقل دو عضو[8] (کوئری) وجود دارد که بایستی با یکی از عبارتهای زیر به هم متصل شوند.
UNION
UNION ALL
INTERSECT
EXCEPT
query1 شامل دستوری است که اولین سری از رکوردهای result set نهایی را تولید میکند. اصطلاحا به این کوئری anchor memberمیگویند.
بعد از دستور query1، حتما بایستی از UNION ALL و امثال آنها استفاده شود.
سپس query2 ذکر میشود. اصطلاحا به این کوئری recursive member گفته میشود. این کوئری شامل دستوری است که سطوح بعدی درخت را تولید خواهد کرد. این کوئری دارای شرایط زیر است.
بدنه CTE میتواند حاوی چندین anchor member و چندین recursive member باشد ولی فقط recursive memberها هستند که به CTE اشاره میکنند.
برای آنکه نکات فوق روشن شود به مثالهای زیر توجه کنید.
فرض کنید جدولی از کارمندان و مدیران آنها داریم که به صورت زیر تعریف و مقداردهی اولیه شده است.
مثال اول: میخواهیم فهرست کارمندان را به همراه نام مدیر آنها و شماره سطح درخت نمایش دهیم. کوئری زیر نمونهای از یک کوئری بر اساس CTE بازگشتی میباشد.
کوئری اول در بدنه CTE رکورد مدیری را میدهد که ریشه درخت بوده و بالاسری ندارد و شماره سطح این رکورد را 1 در نظر میگیرد.
کوئری دوم در بدنه CTE از یک JOIN بین Employees و cteReports استفاده کرده و کارمندان زیر دست هر کارمند قبلی (فرزندان) را بدست آورده و مقدار شماره سطح آنرا به صورت Level+1 تنظیم میکند.
در نهایت با استفاده از CTE و یک subquery جهت بدست آوردن نام مدیر هر کارمند، نتیجه نهایی تولید میشود.
مثال دوم: میخواهیم شناسه یک کارمند را بدهیم و نام او و نام مدیران وی را به عنوان جواب در خروجی بگیریم.
اگر دقت کنید اولین تفاوت در خط اول مشاهده میشود. در اینجا مشخص میکند که اولین سری از رکوردها چگونه انتخاب شود. مثلا کارمندی را میخواهیم که شناسه آن 110 باشد.
دومین تفاوت اصلی این کوئری با مثال قبلی، در قسمت دوم دیده میشود. شما میخواهید مدیر (پدر) کارمندی که در آخرین پردازش در جدول موقت قرار گرفته است را استخراج کنید.
[1] a temporary named result set
[2] recursive
[3] nonrecursive
[4] Scope
[5]مثلا محدوده کدهای یک روال یا یک تابع
[6] nonrecursive
[7] recursive
[8] member
[9] Infinite loop
تکنولوژی CTE از نسخه SQL Server 2005 رسمیت یافته است و شامل یک result set موقتی[1] است که دارای نام مشخص بوده و میتوان از آن در دستورات SELECT, INSERT, UPDATE, DELETEاستفاده کرد. همچنین از CTE میتوان در دستور CREATE VIEW و دستور SELECT مربوط به آن استفاده کرد. در نسخه SQL Server 2008 نیز امکان استفاده از CTE در دستور MERGE فراهم شده است.
در SQL Serverاز دو نوع CTE بازگشتی[2] و غیر بازگشتی[3] پشتیبانی میشود. در این مقاله سعی شده است نحوه تعریف و استفاده از هر دو نوع آن آموزش داده شود.
انواع روشهای ایجاد جداول موقت
برای استفاده از جداول موقتی در سرور اسکیوال، سه راه زیر وجود دارد.
روش اول: استفاده از دستوری مانند زیر است که سبب ایجاد جدول موقتی در بانک سیستمی tempdb میشود. زمانیکه شما ارتباط خود را با سرور SQL قطع میکنید به صورت اتوماتیک جداول موقت شما از بانک tempdb حذف میشوند. این روش در برنامه نویسی پیشنهاد نمیشود و فقط در کارهای موقتی و آزمایشی مناسب است.
SELECT * INTO #temptable FROM [Northwind].[dbo].[Products] UPDATE #temptable SET [UnitPrice] = [UnitPrice] + 10
روش دوم: استفاده از متغیر نوع Table است، که نمونه آن در مثال زیر دیده میشود. زمانیکه از محدوده[4] جاری کد[5] خودتان خارج شوید آن متغیر نیز از حافظه پاک میشود. از این روش، عموما در کدهای Stored Procedureها و UserDefined Functionها استفاده میشود.
DECLARE @tempTable TABLE ( [ProductID] [int] NOT NULL, [ProductName] [nvarchar](40) NOT NULL, [UnitPrice] [money] NULL ) INSERT INTO @tempTable SELECT [ProductID], [ProductName], [UnitPrice] FROM [Northwind].[dbo].[Products] UPDATE @temptable SET [UnitPrice] = [UnitPrice] + 10
روش سوم: استفاده از CTE است که مزیتهایی نسبت به دو روش قبلی دارد و در بخش بعدی به نحوه تعریف و استفاده از آن خواهیم پرداخت.
کار با CTE
ساده ترین شکل تعریف یک CTE به صورت زیر است:
WITH yourName [(Column1, Column2, ...)] AS ( your query )
your query شامل دستوری است که سبب تولید یک result set میشود. قواعد تعریف این کوئری مشابه قواعد تعریف کوئری است که در دستور CREATE VIEW کاربرد دارد.
شکل کلی دستور
همانطور که از این تصویر مشخص است میتوان چندین بلوک از این ساختار را به دنبال هم تعریف نمود که با کاما از هم جدا میشوند. در واقع یکی از کاربردهای CTE ایجاد قطعات کوچکی است که امکان استفاده مجدد را به شما داده و میتواند سبب خواناتر شدن کدهای پیچیده شود.
یکی دیگر از کاربردهای CTE آنجایی است که شما نمیخواهید یک شی Viewی عمومی تعریف کنید و در عین حال میخواهید از مزایای Viewها بهرمند شوید.
و همچنین از کاربردهای دیگر CTE تعریف جدول موقت و استفاده از آن جدول به صورت همزمان در یک دستور است.
بعد از آنکه CTE یا CTEهای خودتان را تعریف کردید آنگاه میتوانید مانند جداول معمولی از آنها استفاده کنید. استفاده از این جداول توسط دستوری خواهد بود که دقیقا بعد از تعریف CTE نوشته میشود.
ایجاد یک CTE غیر بازگشتی[6]
مثال اول، یک CTE غیر بازگشتی ساده را نشان میدهد.
WITH temp AS ( SELECT [ProductName], [UnitPrice] FROM [Northwind].[dbo].[Products] ) SELECT * FROM temp ORDER BY [UnitPrice] DESC
مثال دوم نمونهای دیگر از یک CTE غیر بازگشتی است.
WITH orderSales (OrderID, Total) AS ( SELECT [OrderID], SUM([UnitPrice]*[Quantity]) AS Total FROM [Northwind].[dbo].[Order Details] GROUP BY [OrderID] ) SELECT O.[ShipCountry], SUM(OS.[Total]) AS TotalSales FROM [Northwind].[dbo].[Orders] AS O INNER JOIN [orderSales] AS OS ON O.[OrderID] = OS.[OrderID] GROUP BY O.[ShipCountry] ORDER BY TotalSales DESC
نتیجه خروجی
مثال سوم استفاده از دو CTE را به صورت همزمان نشان میدهد:
WITH customerList AS ( SELECT [CustomerID], [ContactName] FROM [Northwind].[dbo].[Customers] WHERE [Country] ='UK' ) ,orderList AS ( SELECT [CustomerID], [OrderDate] FROM [Northwind].[dbo].[Orders] WHERE YEAR([OrderDate])< 2000 ) SELECT cl.[ContactName], YEAR(ol.[OrderDate]) AS SalesYear FROM customerList AS cl JOIN orderList AS ol ON cl.[CustomerID] = ol.[CustomerID]
مثال چهارم استفاده مجدد از یک CTE را نشان میدهد. فرض کنید جدولی به نام digits داریم که فقط یک فیلد digit دارد و دارای 10 رکورد با مقادیر 0 تا 9 است. مانند تصویر زیر
نتیجه خروجی
حال میخواهیم از طریق CROSS JOIN اعداد 1 تا 100 را با استفاده از مقادیر این جدول تولید کنیم. کد زیر آنرا نشان میدهد:
WITH digitList AS ( SELECT [digit] from [digits] ) SELECT a.[digit] * 10 + b.[digit] + 1 AS [Digit] FROM [digitList] AS a CROSSJOIN [digitList] AS b
نتیجه
نتیجه
حتی میتوان از یک CTE در کوئری CTE بعدی مانند کد زیر استفاده کرد.
WITH CTE_1 AS ( .... ), CTE_2 AS ( SELECT ... FROM CTE_1 JOIN ... ) SELECT * FROM FOO LEFTJOIN CTE_1 LEFTJOIN CTE_2
ایجاد یک CTE بازگشتی[7]
از CTE بازگشتی برای پیمایش جداولی استفاده میشود که رکوردهای آن دارای رابطه سلسله مراتبی یا درختی است. نمونه این جداول، جدول کارمندان است که مدیر هر کارمند نیز مشخص شده است یا جدولی که ساختار سازمانی را نشان میدهد یا جدولی که موضوعات درختی را در خود ذخیره کرده است. یکی از مزایای استفاده از CTE بازگشتی، سرعت کار آن در مقایسه با روشهای پردازشی دیگر است.
ساختار کلی یک دستور CTE بازگشتی به صورت زیر است.
WITH cteName AS ( query1 UNION ALL query2 )
UNION
UNION ALL
INTERSECT
EXCEPT
query1 شامل دستوری است که اولین سری از رکوردهای result set نهایی را تولید میکند. اصطلاحا به این کوئری anchor memberمیگویند.
بعد از دستور query1، حتما بایستی از UNION ALL و امثال آنها استفاده شود.
سپس query2 ذکر میشود. اصطلاحا به این کوئری recursive member گفته میشود. این کوئری شامل دستوری است که سطوح بعدی درخت را تولید خواهد کرد. این کوئری دارای شرایط زیر است.
- حتما بایستی به CTE که همان cteName است اشاره کرده و در جایی از آن استفاده شده باشد. به عبارت دیگر از رکوردهای موجود در جدول موقت استفاده کند تا بتواند رکوردهای بعدی را تشخیص دهد.
- حتما بایستی مطمئن شوید که شرایط کافی برای پایان حلقه پیمایش رکوردها را داشته باشد در غیر این صورت سبب تولید حلقه بی پایان[9] خواهد شد.
بدنه CTE میتواند حاوی چندین anchor member و چندین recursive member باشد ولی فقط recursive memberها هستند که به CTE اشاره میکنند.
برای آنکه نکات فوق روشن شود به مثالهای زیر توجه کنید.
فرض کنید جدولی از کارمندان و مدیران آنها داریم که به صورت زیر تعریف و مقداردهی اولیه شده است.
IFOBJECT_ID('Employees','U')ISNOTNULL DROPTABLE dbo.Employees GO CREATETABLE dbo.Employees ( EmployeeID intNOTNULLPRIMARYKEY, FirstName varchar(50)NOTNULL, LastName varchar(50)NOTNULL, ManagerID intNULL ) GO INSERTINTO Employees VALUES (101,'Alireza','Nematollahi',NULL) INSERTINTO Employees VALUES (102,'Ahmad','Mofarrahzadeh', 101) INSERTINTO Employees VALUES (103,'Mohammad','BozorgGhommi', 102) INSERTINTO Employees VALUES (104,'Masoud','Narimani', 103) INSERTINTO Employees VALUES (105,'Mohsen','Hashemi', 103) INSERTINTO Employees VALUES (106,'Aref','Partovi', 102) INSERTINTO Employees VALUES (107,'Hosain','Mahmoudi', 106) INSERTINTO Employees VALUES (108,'Naser','Pourali', 106) INSERTINTO Employees VALUES (109,'Reza','Bagheri', 102) INSERTINTO Employees VALUES (110,'Abbas','Najafian', 102)
مثال اول: میخواهیم فهرست کارمندان را به همراه نام مدیر آنها و شماره سطح درخت نمایش دهیم. کوئری زیر نمونهای از یک کوئری بر اساس CTE بازگشتی میباشد.
WITHcteReports(EmpID, FirstName, LastName, MgrID, EmpLevel) AS ( SELECT EmployeeID, FirstName, LastName, ManagerID, 1 FROM Employees WHERE ManagerID ISNULL UNIONALL SELECT e.EmployeeID, e.FirstName, e.LastName, e.ManagerID,r.EmpLevel + 1 FROM Employees e INNERJOINcteReports r ON e.ManagerID = r.EmpID ) SELECT FirstName +' '+ LastName AS FullName, EmpLevel, (SELECT FirstName +' '+ LastName FROM Employees WHERE EmployeeID = cteReports.MgrID)AS Manager FROMcteReports ORDERBY EmpLevel, MgrID
کوئری دوم در بدنه CTE از یک JOIN بین Employees و cteReports استفاده کرده و کارمندان زیر دست هر کارمند قبلی (فرزندان) را بدست آورده و مقدار شماره سطح آنرا به صورت Level+1 تنظیم میکند.
در نهایت با استفاده از CTE و یک subquery جهت بدست آوردن نام مدیر هر کارمند، نتیجه نهایی تولید میشود.
مثال دوم: میخواهیم شناسه یک کارمند را بدهیم و نام او و نام مدیران وی را به عنوان جواب در خروجی بگیریم.
WITHcteReports(EmpID, FirstName, LastName, MgrID, EmpLevel) AS ( SELECT EmployeeID, FirstName, LastName, ManagerID, 1 FROM Employees WHERE EmployeeID = 110 UNIONALL SELECTe.EmployeeID, e.FirstName, e.LastName, e.ManagerID,r.EmpLevel + 1 FROM Employees e INNERJOINcteReports r ON e.EmployeeID = r.MgrID ) SELECT FirstName +' '+ LastName AS FullName, EmpLevel FROMcteReports ORDERBY EmpLevel
دومین تفاوت اصلی این کوئری با مثال قبلی، در قسمت دوم دیده میشود. شما میخواهید مدیر (پدر) کارمندی که در آخرین پردازش در جدول موقت قرار گرفته است را استخراج کنید.
[1] a temporary named result set
[2] recursive
[3] nonrecursive
[4] Scope
[5]مثلا محدوده کدهای یک روال یا یک تابع
[6] nonrecursive
[7] recursive
[8] member
[9] Infinite loop
تا اینجا نحوهی اجرای برنامهها را داخل کانتینرها بررسی کردیم؛ اما هنوز در مورد دادههای آنها بحث نکردهایم. اگر بانکهای اطلاعاتی را به درون کانتینرها منتقل کنیم، چه بر سر دادههای آنها میآید؟
بررسی روش اجرای MS SQL Server Express درون یک Container
اگر مخازن Imageهای رسمی مایکروسافت را در داکرهاب بررسی کنیم، به مخازنی مانند mssql-server-windows-express ، mssql-server و یا mssql-server-linux نیز خواهیم رسید. در اینجا آخرین نگارش Image مربوط به SQL Server Express آن، حدود 7GB حجم دارد. برای دریافت آن ابتدا به Windows Containers سوئیچ کنید و سپس دستور زیر را صادر نمائید:
پس از دریافت آن، اگر به مستندات رسمی آن در داکر هاب مراجعه کنیم، دستوری را به صورت زیر برای اجرای آن عنوان کردهاست:
در این دستور:
- سوئیچ d سبب میشود تا پس از اجرای این دستور، بلافاصله به command prompt بازگشت داده شویم و SQL Server Express در background اجرا شود.
- سپس پورت 1433 میزبان به پورت 1433 کانتینر، نگاشت شدهاست که پورت استاندارد SQL Server است.
- سوئیچ e، امکان تنظیم متغیرهای محیطی را میسر میکند؛ برای مثال ورود کلمهی عبور کاربر SA و یا پذیرش مجوز آن. برای نمونه، این کلمهی عبور را مساوی password وارد کنید؛ هرچند کار نخواهد کرد، اما بررسی خطاهای به همراه آن مفید است.
- و در آخر نام image مرتبط ذکر شدهاست.
پس از اجرای این دستور، کانتینر SQL Server Express، در پس زمینه شروع به کار خواهد کرد و بلافاصله به خط فرمان بازگشت داده میشویم. در اینجا ممکن است آغاز SQL Server اندکی طول بکشد. برای اینکه دریابیم در این لحظه وضعیت پروسهی آن به چه صورتی است، دستور docker logs id را صادر کنید. پس از آن خطایی مانند password validation failed را مشاهده خواهیم کرد. عنوان میکند که پیچیدگی کلمهی عبور وارد شده کافی نیست.
یک نکته: زمانیکه دستور docker run را اجرا میکنیم، یک هش طولانی را نمایش میدهد و پس از آن به خط فرمان بازگشت داده میشویم. این هش طولانی، همان id کانتینر در حال اجرا است. برای مثال در دستور docker logs id میتوان 3 حرف ابتدای این هش را بجای id وارد کرد. البته این id را توسط دستور docker ps نیز میتوان بدست آورد.
بنابراین با توجه به اینکه دستور docker logs id، خطایی را گزارش کردهاست، توسط دستور docker stop id، این کانتینر را متوقف کرده و آنرا مجددا با کلمهی عبوری مانند pass!w0rd1 اجرا میکنیم:
اینبار نیز مجددا دستور docker logs id را بر اساس id جدید این کانتینر اجرا میکنیم که پیام Started SQL Server را نمایش میدهد. بنابراین تا به اینجا موفق شدیم پروسهی SQL Server Express را بدون مشکلی آغاز کنیم.
همانطور که در قسمت سوم نیز عنوان شد، اگر این کانتینر را بر روی ویندوز سرور، در حالت Windows Containers اجرا کنیم (و نه در حالت Hyper-V)، پروسههای اجرای شدهی داخل یک Container را میتوان با Job Object Idهای یکسانی که دارند، در Task Manager ویندوز، در کنار سایر پروسههای سیستم، شناسایی کرد.
اتصال به SQL Server Express اجرا شدهی داخل یک Container توسط SQL Server Management Studio
پس از اجرای SQL Server Express دخل کانتینر، مطابق تنظیمات آن، چه در سیستم میزبان و چه در داخل کانتینر، به پورت 1433 گوش فرا داده میشود. به همین منظور نیاز است IP این کانتینر را نیز بدست آوریم. برای اینکار دستور ipconfig را در سیستم میزبان صادر کنید تا بر اساس مشخصات کارت شبکهی مجازی آن، بتوان IP آنرا بدست آورد (دستور docker inspect id نیز چنین اطلاعاتی را به همراه دارد). اکنون میتوان از داخل سیستم راه دور دیگری که SQL Server Management Studio بر روی آن نصب است، توسط این IP و پورت، به SQL Server Express متصل شد.
البته در اینجا نیازی به ذکر پورت نیست؛ چون پورت 1433، شماره پورت پیشفرض است. بعد از اتصال، میتوان کارهای متداولی مانند ایجاد یک بانک اطلاعاتی جدید را انجام داد.
برای آزمایش، یکبار دستور docker ps را صادر کنید تا id این کانتینر مشخص شود. سپس دستور docker stop id را صادر کنید تا پروسه SQL Server Express خاتمه یابد. اکنون اگر در SQL Server Management Studio قصد کار با آنرا داشته باشیم، پیام عدم اتصال مشاهده میشود. اکنون برای اجرای مجدد کانتینر، دستور docker start id را صادر کنید.
بررسی روش اجرای MySQL داخل یک Container
برای اجرای MySQL نیاز است به Linux Containers سوئیچ کنیم. حجم tag ویژهی latest آن نیز حدود 138MB است که نسبت به SQL Server Express هفت گیگابایتی، بسیار کمتر است!
در همان صفحهی مستندات آن در داکرهاب، دستور اجرایی آن نیز ذکر شدهاست:
در اینجا نیز توسط سوئیچ e که مخفف environment است، یکسری از متغیرهای محیطی MySQL، مانند کلمهی عبور آن قابل تنظیم هستند. همچنین سوئیچ d نیز برای اجرای آن در پس زمینه، ذکر شدهاست. همین دستور را به همین شکل، صرفا با حذف tag آن، جهت اشارهی به آخرین نگارش موجود این image، اجرا میکنیم:
با اجرای این دستور، در ابتدا MySQL از داکرهاب دریافت شده و سپس در پس زمینه اجرا خواهد شد. پیش از بازگشت به command prompt، یک هش طولانی نیز نمایش داده میشود که همان id کانتینر در حال اجرای آن است. برای اینکه بتوانیم ریز جزئیات رخ داده را بهتر مشاهده کنیم، میتوان از دستور docker logs id استفاده کرد.
یک نکته: میتوان یک command prompt جدید را باز کرد و سپس دستور docker logs -f id را در آن صادر کرد. به این صورت لاگهای لحظهای یک کانتینر را نیز میتوان مشاهده کرد (f در اینجا به معنای follow است).
اکنون میخواهیم MySQL Client موجود در همین Container در حال اجرا را، اجرا کنیم (اجرای پروسهای درون یک کانتینر در حال اجرا). برای اینکار از دستور docker exec استفاده میشود:
ابتدا توسط دستور docker ps مقدار id این کانتینر را بدست میآوریم و سپس در دستور بعدی، از آن استفاده خواهیم کرد.
در اینجا توسط دستور docker exec ابتدا یک interactive shell را درخواست کردهایم (اجرای foreground یک برنامهی شل). سپس id این کانتینر باید ذکر شود. پس از آن نام فایل اجرایی MySQL Client قید شده و در پایان، نام کاربری و کلمهی عبور اتصال به آن که در دستور docker run تنظیم شدهاند، ذکر میشوند.
با اجرای این دستور، به خط فرمان MySQL Client داخل این کانتینر دسترسی پیدا میکنیم. در اینجا میتوان دستورات مختلفی را برای کار با پروسهی mysql اجرا کرد؛ مانند اجرای دستور show databases که لیست بانکهای اطلاعاتی موجود را نمایش میدهد:
روش مدیریت دادههای بانکهای اطلاعاتی توسط Docker
در قسمت قبل دریافتیم که لایهی رویی یک container، دارای قابلیت read/write است و برای مثال میتوان فایلهای یک وب سایت استاتیک را در آنجا کپی و سپس هاست کرد. اما این لایه، لایهی مناسبی برای ذخیره سازی دادههای یک بانک اطلاعاتی نیست. در اینجا برای مدیریت بهتر این نوع دادهها، از مفهومی به نام volume استفاده میشود.
برای درک روش مدیریت دادهها توسط داکر، دستور docker volume ls را اجرا کنید. مشاهده خواهید کرد که docker یک volume پیشفرض را نیز ایجاد کردهاست. البته با volumes پیشتر در قسمت چهارم، در بخش «روش به اشتراک گذاری فایل سیستم میزبان با کانتینرها» نیز آشنا شدهایم. این volume پیشفرض، کار ذخیره سازی اطلاعات را حتی اگر کانتینری در حال اجرا نباشد نیز انجام میدهد. وجود یک چنین قابلیتی جهت از دست نرفتن اطلاعات ارزشمند ذخیره شدهی در بانکهای اطلاعاتی بسیار ضروری است.
البته لازم به ذکر است، این volume ای را که در اینجا مشاهده میکنید، توسط Dockerfile خود mysql به صورت خودکار ایجاد میشود. برای مثال در داکرهاب، در قسمت full description این image، در ابتدای توضیحات قسمتی است به نام supported tags and respective dockerfile links. در اینجا هر tag نامبرده شده، در حقیقت لینکی است به یک Dockerfile. اگر یکی از آنها را باز کنید، چنین سطری را در آن مشاهده خواهید کرد:
این دستور سبب میشود چنین مسیری (مسیر پیشفرض ثبت اطلاعات mysql) به صورت یک volume جدید، خارج از فایل سیستم کانتینر، بر روی سیستم میزبان، ایجاد شود. سپس این مسیر و volume جدید، توسط داکر به صورت خودکار به این کانتینر mount خواهد شد و برای این موارد نیازی نیست کار خاصی توسط ما انجام شود.
اینکار نه فقط برای بالابردن کارآیی اعمال read/write انجام شدهی توسط container انجام میشود، بلکه حتی اگر این کانتینر را توسط دستور docker rm id حذف کنیم، دستور docker volume ls، هنوز همان volume ای را که در حین نصب mysql به صورت خودکار ایجاد شده بود، نمایش میدهد. علت اینجا است که طول عمر این volume، وابستهی به طول عمر کانتینر آن نیست. به این ترتیب حذف تصادفی یک کانتینر، سبب از دست رفتن اطلاعات ارزشمند داخل بانک اطلاعاتی آن نمیشود.
روش تعیین صریح یک volume برای یک کانتینر بانک اطلاعاتی، توسط volumeهای نامدار
دستور docker run ای را که برای اجرای mysql صادر کردیم، یک volume خودکار را ایجاد کردهاست و اگر آنرا با دستور docker volume ls بررسی کنیم، دارای یک نام هش مانند است که به آن anonymous volume هم گفته میشود. در ادامه قصد داریم یک volume نامدار را ایجاد کنیم و سپس از آن جهت ذخیره سازی اطلاعات چندین وهله از کانتینر mysql استفاده نمائیم.
پیش از ادامه بحث، ابتدا توسط دستور docker rm id، کانتینر mysql ای را که پیشتر ایجاد کردیم حذف کنید؛ هرچند این دستور، volume متناظر با آنرا حذف نمیکند.
سپس برای اینکه یک کانتینر جدید mysql را با ذکر صریح volume آن ایجاد و اجرا کنیم، میتوان از دستور زیر استفاده کرد:
در اینجا از سوئیچ v برای ایجاد یک volume نامدار استفاده شدهاست و در آن بجای ذکر قسمت مسیر پوشهای در سمت میزبان، صرفا یک نام، مانند db، پیش از ذکر : قید شدهاست. پس از :، مسیری که این volume قرار است در آن کانتینر به آن نگاشت شود، ذکر شدهاست.
اکنون اگر دستور docker volume ls را صادر کنیم، در لیست خروجی آن، نام db قابل مشاهدهاست.
و تغییراتی را به صورت زیر اعمال میکنیم:
در اینجا بانک اطلاعاتی جدید pets ایجاد شدهاست.
اکنون در ابتدا این کانتینر را متوقف کرده و سپس آنرا حذف میکنیم:
هرچند اگر دستور حذف را با سوئیچ f- نیز اجرا کنیم (به معنای force)، کار stop را به صورت خودکار انجام میدهد.
در ادامه مجددا همان دستور قبلی را که توسط آن volume نامداری، ایجاد کردیم، اجرا میکنیم:
اینبار اگر دستور docker volume ls را مجددا صادر کنیم، مشاهده خواهیم کرد این کانتینر جدید، بجای ایجاد یک volume جدید، از همان volume موجود db که آنرا پیشتر ایجاد کردیم، استفاده میکند؛ هرچند کانتینری که آنرا ایجاد کردهاست، دیگر وجود خارجی ندارد. در این حالت اگر MySQL Client این کانتینر را اجرا نمائیم:
و سپس دستور نمایش بانکهای اطلاعاتی آنرا صادر کنیم:
در خروجی آن هنوز بانک اطلاعاتی pets که پیشتر ایجاد شده بود، قابل مشاهدهاست. بنابراین حذف و یا ایجاد کانتینرها، تاثیری را بر روی volumeهای ایجاد شده، نخواهند داشت.
روش حذف volumes اضافی
با توجه به اینکه volumeها، طول عمر متفاوتی را نسبت به کانتینرها دارند، ممکن است پس از مدتی فضای دیسک سخت شما را پر کنند. برای مثال به ازای هربار اجرای دستور docker run مربوط با MYSQL با نامی متفاوت، یک volume جدید نیز ایجاد میشود.
خروجی دستور docker inspect id به همراه قسمتی است به نام mounts که خاصیت name آن، دقیقا مساوی نام volume متناظر با کانتینر بررسی شدهاست. همچنین خاصیت source آن، محل دقیق ذخیره سازی این volume را بر روی فایل سیستم میزبان مشخص میکند.
برای حذف آنها، ابتدا نیاز است کانتینرها را متوقف کرد. دستور زیر تمام کانتینرهای در حال اجرا را متوقف میکند. در اینجا دستور docker ps -q، لیست id تمام کانتینرهای در حال اجرا را باز میگرداند (در این دستورات، افزودن پارامتر q، سبب بازگشت صرفا idها میشود):
اگر میخواهید تمام کانتینرهای موجود را حذف کنید:
و یا دستور زیر ابتدا تمام کانتینرهای موجود را متوقف کرده و سپس آنها را حذف میکند:
دستور زیر تمام volumes موجود را حذف میکند:
دستور زیر یک کانتینر با id مشخص شده را به همراه volume نامگذاری نشدهی مرتبط با آن، متوقف و سپس حذف میکند:
دستور زیر، لیست تمام volumes غیراستفاده شدهی توسط کانتینرهای موجود را نمایش میدهد (به یک چنین volumeهای در اینجا dangling گفته میشود؛ volume ای که کانتینر آن حذف شدهاست):
که میتواند لیست مناسبی برای حذف باشند:
بررسی روش اجرای MS SQL Server Express درون یک Container
اگر مخازن Imageهای رسمی مایکروسافت را در داکرهاب بررسی کنیم، به مخازنی مانند mssql-server-windows-express ، mssql-server و یا mssql-server-linux نیز خواهیم رسید. در اینجا آخرین نگارش Image مربوط به SQL Server Express آن، حدود 7GB حجم دارد. برای دریافت آن ابتدا به Windows Containers سوئیچ کنید و سپس دستور زیر را صادر نمائید:
docker pull microsoft/mssql-server-windows-express
docker run -d -p 1433:1433 -e sa_password=<SA_PASSWORD> -e ACCEPT_EULA=Y microsoft/mssql-server-windows-express
- سوئیچ d سبب میشود تا پس از اجرای این دستور، بلافاصله به command prompt بازگشت داده شویم و SQL Server Express در background اجرا شود.
- سپس پورت 1433 میزبان به پورت 1433 کانتینر، نگاشت شدهاست که پورت استاندارد SQL Server است.
- سوئیچ e، امکان تنظیم متغیرهای محیطی را میسر میکند؛ برای مثال ورود کلمهی عبور کاربر SA و یا پذیرش مجوز آن. برای نمونه، این کلمهی عبور را مساوی password وارد کنید؛ هرچند کار نخواهد کرد، اما بررسی خطاهای به همراه آن مفید است.
- و در آخر نام image مرتبط ذکر شدهاست.
پس از اجرای این دستور، کانتینر SQL Server Express، در پس زمینه شروع به کار خواهد کرد و بلافاصله به خط فرمان بازگشت داده میشویم. در اینجا ممکن است آغاز SQL Server اندکی طول بکشد. برای اینکه دریابیم در این لحظه وضعیت پروسهی آن به چه صورتی است، دستور docker logs id را صادر کنید. پس از آن خطایی مانند password validation failed را مشاهده خواهیم کرد. عنوان میکند که پیچیدگی کلمهی عبور وارد شده کافی نیست.
یک نکته: زمانیکه دستور docker run را اجرا میکنیم، یک هش طولانی را نمایش میدهد و پس از آن به خط فرمان بازگشت داده میشویم. این هش طولانی، همان id کانتینر در حال اجرا است. برای مثال در دستور docker logs id میتوان 3 حرف ابتدای این هش را بجای id وارد کرد. البته این id را توسط دستور docker ps نیز میتوان بدست آورد.
بنابراین با توجه به اینکه دستور docker logs id، خطایی را گزارش کردهاست، توسط دستور docker stop id، این کانتینر را متوقف کرده و آنرا مجددا با کلمهی عبوری مانند pass!w0rd1 اجرا میکنیم:
docker run -d -p 1433:1433 -e sa_password=pass!w0rd1 -e ACCEPT_EULA=Y microsoft/mssql-server-windows-express
همانطور که در قسمت سوم نیز عنوان شد، اگر این کانتینر را بر روی ویندوز سرور، در حالت Windows Containers اجرا کنیم (و نه در حالت Hyper-V)، پروسههای اجرای شدهی داخل یک Container را میتوان با Job Object Idهای یکسانی که دارند، در Task Manager ویندوز، در کنار سایر پروسههای سیستم، شناسایی کرد.
اتصال به SQL Server Express اجرا شدهی داخل یک Container توسط SQL Server Management Studio
پس از اجرای SQL Server Express دخل کانتینر، مطابق تنظیمات آن، چه در سیستم میزبان و چه در داخل کانتینر، به پورت 1433 گوش فرا داده میشود. به همین منظور نیاز است IP این کانتینر را نیز بدست آوریم. برای اینکار دستور ipconfig را در سیستم میزبان صادر کنید تا بر اساس مشخصات کارت شبکهی مجازی آن، بتوان IP آنرا بدست آورد (دستور docker inspect id نیز چنین اطلاعاتی را به همراه دارد). اکنون میتوان از داخل سیستم راه دور دیگری که SQL Server Management Studio بر روی آن نصب است، توسط این IP و پورت، به SQL Server Express متصل شد.
البته در اینجا نیازی به ذکر پورت نیست؛ چون پورت 1433، شماره پورت پیشفرض است. بعد از اتصال، میتوان کارهای متداولی مانند ایجاد یک بانک اطلاعاتی جدید را انجام داد.
برای آزمایش، یکبار دستور docker ps را صادر کنید تا id این کانتینر مشخص شود. سپس دستور docker stop id را صادر کنید تا پروسه SQL Server Express خاتمه یابد. اکنون اگر در SQL Server Management Studio قصد کار با آنرا داشته باشیم، پیام عدم اتصال مشاهده میشود. اکنون برای اجرای مجدد کانتینر، دستور docker start id را صادر کنید.
بررسی روش اجرای MySQL داخل یک Container
برای اجرای MySQL نیاز است به Linux Containers سوئیچ کنیم. حجم tag ویژهی latest آن نیز حدود 138MB است که نسبت به SQL Server Express هفت گیگابایتی، بسیار کمتر است!
در همان صفحهی مستندات آن در داکرهاب، دستور اجرایی آن نیز ذکر شدهاست:
docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag
docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql
یک نکته: میتوان یک command prompt جدید را باز کرد و سپس دستور docker logs -f id را در آن صادر کرد. به این صورت لاگهای لحظهای یک کانتینر را نیز میتوان مشاهده کرد (f در اینجا به معنای follow است).
اکنون میخواهیم MySQL Client موجود در همین Container در حال اجرا را، اجرا کنیم (اجرای پروسهای درون یک کانتینر در حال اجرا). برای اینکار از دستور docker exec استفاده میشود:
docker ps docker exec -it id mysql --user=root --password=my-secret-pw
در اینجا توسط دستور docker exec ابتدا یک interactive shell را درخواست کردهایم (اجرای foreground یک برنامهی شل). سپس id این کانتینر باید ذکر شود. پس از آن نام فایل اجرایی MySQL Client قید شده و در پایان، نام کاربری و کلمهی عبور اتصال به آن که در دستور docker run تنظیم شدهاند، ذکر میشوند.
با اجرای این دستور، به خط فرمان MySQL Client داخل این کانتینر دسترسی پیدا میکنیم. در اینجا میتوان دستورات مختلفی را برای کار با پروسهی mysql اجرا کرد؛ مانند اجرای دستور show databases که لیست بانکهای اطلاعاتی موجود را نمایش میدهد:
mysql> show databases; use mysql; show tables; select * from user; exit;
روش مدیریت دادههای بانکهای اطلاعاتی توسط Docker
در قسمت قبل دریافتیم که لایهی رویی یک container، دارای قابلیت read/write است و برای مثال میتوان فایلهای یک وب سایت استاتیک را در آنجا کپی و سپس هاست کرد. اما این لایه، لایهی مناسبی برای ذخیره سازی دادههای یک بانک اطلاعاتی نیست. در اینجا برای مدیریت بهتر این نوع دادهها، از مفهومی به نام volume استفاده میشود.
برای درک روش مدیریت دادهها توسط داکر، دستور docker volume ls را اجرا کنید. مشاهده خواهید کرد که docker یک volume پیشفرض را نیز ایجاد کردهاست. البته با volumes پیشتر در قسمت چهارم، در بخش «روش به اشتراک گذاری فایل سیستم میزبان با کانتینرها» نیز آشنا شدهایم. این volume پیشفرض، کار ذخیره سازی اطلاعات را حتی اگر کانتینری در حال اجرا نباشد نیز انجام میدهد. وجود یک چنین قابلیتی جهت از دست نرفتن اطلاعات ارزشمند ذخیره شدهی در بانکهای اطلاعاتی بسیار ضروری است.
البته لازم به ذکر است، این volume ای را که در اینجا مشاهده میکنید، توسط Dockerfile خود mysql به صورت خودکار ایجاد میشود. برای مثال در داکرهاب، در قسمت full description این image، در ابتدای توضیحات قسمتی است به نام supported tags and respective dockerfile links. در اینجا هر tag نامبرده شده، در حقیقت لینکی است به یک Dockerfile. اگر یکی از آنها را باز کنید، چنین سطری را در آن مشاهده خواهید کرد:
VOLUME /var/lib/mysql
اینکار نه فقط برای بالابردن کارآیی اعمال read/write انجام شدهی توسط container انجام میشود، بلکه حتی اگر این کانتینر را توسط دستور docker rm id حذف کنیم، دستور docker volume ls، هنوز همان volume ای را که در حین نصب mysql به صورت خودکار ایجاد شده بود، نمایش میدهد. علت اینجا است که طول عمر این volume، وابستهی به طول عمر کانتینر آن نیست. به این ترتیب حذف تصادفی یک کانتینر، سبب از دست رفتن اطلاعات ارزشمند داخل بانک اطلاعاتی آن نمیشود.
روش تعیین صریح یک volume برای یک کانتینر بانک اطلاعاتی، توسط volumeهای نامدار
دستور docker run ای را که برای اجرای mysql صادر کردیم، یک volume خودکار را ایجاد کردهاست و اگر آنرا با دستور docker volume ls بررسی کنیم، دارای یک نام هش مانند است که به آن anonymous volume هم گفته میشود. در ادامه قصد داریم یک volume نامدار را ایجاد کنیم و سپس از آن جهت ذخیره سازی اطلاعات چندین وهله از کانتینر mysql استفاده نمائیم.
پیش از ادامه بحث، ابتدا توسط دستور docker rm id، کانتینر mysql ای را که پیشتر ایجاد کردیم حذف کنید؛ هرچند این دستور، volume متناظر با آنرا حذف نمیکند.
سپس برای اینکه یک کانتینر جدید mysql را با ذکر صریح volume آن ایجاد و اجرا کنیم، میتوان از دستور زیر استفاده کرد:
docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d -v db:/var/lib/mysql mysql
اکنون اگر دستور docker volume ls را صادر کنیم، در لیست خروجی آن، نام db قابل مشاهدهاست.
در ادامه پروسهی MySQL Client داخل این کانتینر را اجرا کرده:
docker exec -it some-mysql mysql --user=root --password=my-secret-pw
mysql> show databases; create database pets; show databases; exit;
اکنون در ابتدا این کانتینر را متوقف کرده و سپس آنرا حذف میکنیم:
docker ps docker stop id docker rm id
در ادامه مجددا همان دستور قبلی را که توسط آن volume نامداری، ایجاد کردیم، اجرا میکنیم:
docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d -v db:/var/lib/mysql mysql
docker exec -it some-mysql mysql --user=root --password=my-secret-pw
mysql> show databases;
روش حذف volumes اضافی
با توجه به اینکه volumeها، طول عمر متفاوتی را نسبت به کانتینرها دارند، ممکن است پس از مدتی فضای دیسک سخت شما را پر کنند. برای مثال به ازای هربار اجرای دستور docker run مربوط با MYSQL با نامی متفاوت، یک volume جدید نیز ایجاد میشود.
خروجی دستور docker inspect id به همراه قسمتی است به نام mounts که خاصیت name آن، دقیقا مساوی نام volume متناظر با کانتینر بررسی شدهاست. همچنین خاصیت source آن، محل دقیق ذخیره سازی این volume را بر روی فایل سیستم میزبان مشخص میکند.
برای حذف آنها، ابتدا نیاز است کانتینرها را متوقف کرد. دستور زیر تمام کانتینرهای در حال اجرا را متوقف میکند. در اینجا دستور docker ps -q، لیست id تمام کانتینرهای در حال اجرا را باز میگرداند (در این دستورات، افزودن پارامتر q، سبب بازگشت صرفا idها میشود):
docker stop $(docker ps -q)
docker rm $(docker ps -aq)
docker rm -f $(docker ps -aq)
docker volume rm $(docker volume ls -q)
docker rm -fv id
docker volume ls -f dangling=true
docker volume rm $(docker volume ls -qf dangling=true)
در این بخش به دو Function از Analytic Functionها (توابع تحلیلی)، یعنی Lead Function و LAG Function می پردازیم.
قبل از اینکه به توابع ذکرشده بپردازیم، باید عرض کنم، شرح عملکرد اینگونه توابع کمی مشکل میباشد، بنابراین با ذکر مثال و توضیح آنها،سعی میکنیم،قابلیت هریک را بررسی و درک نماییم.
- Lead Function:
این فانکشن در SQL Server 2012 ارائه شده است، و امکان دسترسی، به Dataهای سطر بعدی نسبت به سطر جاری را در نتیجه یک پرس و جو (Query)، ارائه میدهد. بدون آنکه از Self-join استفاده نمایید،
Syntax تابع فوق بصورت زیر است:
LEAD ( scalar_expression [ ,offset ] , [ default ] ) OVER ( [ partition_by_clause ] order_by_clause )
شرح Syntax:
- Scalar_expression: در Scalar_expression، نام یک فیلد یا ستون درج میشود، و مقدار برگشتی فیلد مورد نظر، به مقدار تعیین شده offset نیز بستگی دارد. خروجی Scalar_expression فقط یک مقدار است.
- offset: منظور از Offset در این Syntax همانند عملکرد Offset در Syntax مربوط به Over میباشد. یعنی هر عددی برای offset در نظر گرفته شود، بیانگر نقطه آغازین سطر بعدی یا قبلی نسبت به سطر جاری است. به بیان دیگر، عدد تعیین شده در Offset به Sql server میفهماند چه تعداد سطر را در محاسبه در نظر نگیرد.
- Default: زمانی که برای Offset مقداری را تعیین مینمایید، SQL Server به تعداد تعیین شده در Offset، سطرها را در نظر نمیگیرد، بنابراین مقدار خروجی Scalar_expression بطور پیش فرض Null در نظر گرفته میشود، چنانچه بخواهید، مقداری غیر از Null درج نمایید، میتوانید مقدار دلخواه را در قسمت Default وارد کنید.
- (OVER ( [ partition_by_clause ] order_by_clause : در بخش اول بطور کامل توضیح داده شده است.
برای درک بهتر Lead Function چند مثال را بررسی مینماییم:
ابتدا Script زیر را اجرا مینماییم، که شامل ایجاد یک جدول و درج 18 رکورد در آن:
Create Table TestLead_LAG (SalesOrderID int not null, SalesOrderDetailID int not null , OrderQty smallint not null); GO Insert Into TestLead_LAG Values (43662,49,1),(43662,50,3),(43662,51,1), (43663,52,1),(43664,53,1),(43664,54,1), (43667,77,3),(43667,78,1),(43667,79,1), (43667,80,1),(43668,81,3),(43669,110,1), (43670,111,1),(43670,112,2),(43670,113,2), (43670,114,1),(43671,115,1),(43671,116,2)
مثال:قصد داریم در هر سطر مقدار بعدی فیلد SalesOrderDetailID در فیلد دیگری به نام LeadValue نمایش دهیم، بنابراین Script زیر را ایجاد میکنیم:SELECT s.SalesOrderID,s.SalesOrderDetailID,s.OrderQty, LEAD(SalesOrderDetailID) OVER (ORDER BY SalesOrderDetailID) LeadValue FROM TestLead_LAG s WHERE SalesOrderID IN (43670, 43669, 43667, 43663) ORDER BY s.SalesOrderID,s.SalesOrderDetailID,s.OrderQtyخروجی بصورت زیر خواهد بود:
مطابق شکل، براحتی واضح است، که در هر سطر مقدار بعدی فیلد SalesOrderDetailID در فیلد LeadValue درج و نمایش داده میشود. فقط در سطر 10، چون مقدار بعدی برای فیلد SalesOrderDetailID وجود ندارد، SQL Server مقدار فیلد LeadValue را، Null در نظر میگیرد.در این مثال فقط از آرگومان Scalar_expression، استفاده کردیم، و Offset و Default را مقدار دهی ننمودیم، بنابراین SQL Server بطور پیش فرض هیچ سطری را حذف نمیکند و مقدار Default را Null در نظر میگیرد.مثال دوم: قصد داریم در هر سطر مقدار دو سطر بعدی فیلد SalesOrderDetailID را در فیلد LeadValue نمایش دهیم، و در صورت وجود نداشتن مقدار فیلد SalesOrderDetailID، مقدار پیش فرض صفر ،در فیلد LeadValue قرار دهیم،بنابراین Script آن بصورت زیر خواهد شد:SELECT s.SalesOrderID,s.SalesOrderDetailID,s.OrderQty, LEAD(SalesOrderDetailID,2,0) OVER (ORDER BY SalesOrderDetailID) LeadValue FROM TestLead_LAG s WHERE SalesOrderID IN (43670, 43669, 43667, 43663) ORDER BY s.SalesOrderID,s.SalesOrderDetailID,s.OrderQtyخروجی:
در صورت مسئله بیان کرده بودیم، در هر سطر،مقدار فیلد SalesOrderDetailID دو سطر بعدی، را نمایش دهیم، بنابراین مقداری که برای Offset در نظر میگیریم، برابر دو خواهد بود، سپس گفته بودیم، چنانچه در هر سطر مقدار فیلد SalesOrderDetailID وجود نداشت،بجای مقدار پیش فرض Null،از مقدار صفر استفاده شود، بنابراین به Default مقدار صفر را نسبت دادیم.LEAD(SalesOrderDetailID,2,0)در شکل، مطابق صورت مسئله، مقدار فیلد LeadValue سطر اول برابر است با 78،به بیان سادهتر برای بدست آوردن مقدار فیلد LaedValue هر سطر، میبایست هر سطر را به علاوه 2 (Offset) نماییم، تا سطر بعدی بدست آید، سپس مقدار SalesOrderDetailID را در فیلد LeadValue قرار میدهیم.به سطر 9 و 10 توجه نمایید، که مقدار فیلد LeadValue آنها برابر با صفر است، واضح است، سطر 10 + 2 برابر است با 12( 10+2=12 )، چنین سطری در خروجی نداریم، بنابراین بطور پیش فرض مقدار LeadVaule توسط Sql Server برابر Null در نظر گرفته میشود، اما نمیخواستیم، که این مقدار Null باشد، بنابراین به آرگومان Default مقدار صفر را نسبت دادیم، تا SQL Server ، به جای استفاده از Null، مقدار در نظر گرفته شده صفر را استفاده نماید.اگر چنین فانکشنی وجود نداشت، برای شبیه سازی آن میبایست از Join روی خود جدول استفاده مینمودیم، و یکسری محاسابت دیگر، که کار را سخت مینمود، مثال دوم را با Script زیر میتوان شبیه سازی نمود:WITH cteLead AS ( SELECT SalesOrderID,SalesOrderDetailID,OrderQty, ROW_NUMBER() OVER (ORDER BY SalesOrderDetailID) AS sn FROM TestLead_LAG WHERE SalesOrderID IN (43670, 43669, 43667, 43663) ) SELECT m.SalesOrderID, m.SalesOrderDetailID, m.OrderQty, case when sLead.SalesOrderDetailID is null Then 0 Else sLead.SalesOrderDetailID END as leadvalue FROM cteLead AS m LEFT OUTER JOIN cteLead AS sLead ON sLead.sn = m.sn+2 ORDER BY m.SalesOrderID, m.SalesOrderDetailID, m.OrderQtyجدول موقتی ایجاد نمودیم، که ROW_Number را در آن اضافه کردیم، سپس جدول ایجاد شده را با خود Join کردیم، و گفتیم، که مقدار فیلدLeadValue هر سطر برابر است با مقدار فیلد SalesOrderDetailID دو سطر بعد از آن. و با Case نیز مقدار پیش فرض را صفر در نظر گرفتیم.
- LAG Function:
این فانکشن نیز در SQL Server 2012 ارائه شده است، و امکان دسترسی، به Dataهای سطر قبلی نسبت به سطر جاری را در نتیجه یک پرس و جو (Query)، ارائه میدهد. بدون آنکه از Self-join استفاده نمایید،Syntax آن شبیه به فانکشن Lead میباشد و بصورت زیر است:LAG (scalar_expression [,offset] [,default]) OVER ( [ partition_by_clause ] order_by_clause )Syntax مربوط به فانکشن LAG را شرح نمیدهم، بدلیل آنکه شبیه به فانکشن Lead میباشد، فقط تفاوت آن در Offset است، Offset در فانکشن LAG روی سطرهای ماقبل سطر جاری اعمال میگردد.مثال دوم را برای حالت LAG Function شبیه سازی مینماییم:SELECT s.SalesOrderID,s.SalesOrderDetailID,s.OrderQty, LAG(SalesOrderDetailID,2,0) OVER (ORDER BY SalesOrderDetailID) LAGValue FROM TestLead_LAG s WHERE SalesOrderID IN (43670, 43669, 43667, 43663) ORDER BY s.SalesOrderID,s.SalesOrderDetailID,s.OrderQty goخروجی :
همانطور که گفتیم، LAG Function عکس LEAD Function میباشد. یعنی مقدار فیلد LAGValue سطر جاری برابر است با مقدار SalesOrderDetailID دو سطر ما قبل خود.مقدار فیلد LAGValue دو سطر اول و دوم نیز برابر صفر است، چون دو سطر ماقبل آنها وجود ندارد، و مقدار صفر نیز بدلیل این است که Default را برابر صفر در نظر گرفته بودیم.مثال: در این مثال از Laed Function و LAG Function بطور همزمان استفاده میکنیم، با این تفاوت، که از گروه بندی نیز استفاده شده است:Script زیر را اجرا نمایید:SELECT s.SalesOrderID,s.SalesOrderDetailID,s.OrderQty, Lead(SalesOrderDetailID) OVER (PARTITION BY SalesOrderID ORDER BY SalesOrderDetailID) LeadValue, LAG(SalesOrderDetailID) OVER (PARTITION BY SalesOrderID ORDER BY SalesOrderDetailID) LAGValue FROM TestLead_LAG s WHERE SalesOrderID IN (43670, 43669, 43667, 43663) ORDER BY s.SalesOrderID,s.SalesOrderDetailID,s.OrderQty goخروجی:با بررسی هایی که در مثالهای قبل نمودیم،خروجی زیر را میتوان براحتی تشخیص داد، و توضیح بیشتری نمیدهم.موفق باشید.
مقدمه و شرح مساله
توسط ویژگیهای جدیدی که در نسخه 2012 به بحث window افزوده شد میتوانیم مسالهای running total و running average را به شکل بهینه ای حل کنیم.
ابتدا این دو مساله را بدون بکارگیری ویژگیهای جدید، حل نموده و سپس سراغ توابع جدید خواهم رفت.
قبل از هر چیزی لازم است جدول زیر ساخته شود و دادههای نمونه در آن درج شود:
مساله running total بسیار ساده است، یعنی جمع مقدار سطر جاری با مقادیر سطرهای قبلی (بر اساس یک ترتیب معین)
running average هم مشابه به running total هست با این تفاوت که میانگین مقادیر سطرجاری وسطرهای قبلی محاسبه میشود.
و نتیجه به صورت نمودار:
راه حل در SQL Server 2000
توسط دو correlated scalar subquery در ماده SELECT میتوانیم مقادیر دو ستون مورد نظر با محاسبه کنیم:
اگر به نقشه اجرای این query نگاه کنید گره(عملگر) inner join دو بار بکار رفته است (به وجود دو subquery)، که این عدد در روش توابع تجمعی window به صفر کاهش پیدا خواهد کرد
راه حل در SQL Server 2005
توسط cross apply به سادگی میتوانیم دو subquery که در روش قبل بود را به یکی کاهش دهیم:
این بار تنها یک عملگر inner join در نقشه اجرای query مشاهده میشود:
راه حل در SQL Server 2012
با اضافه شدن برخی از ویژگیهای استاندارد به ماده OVER مثل rows و range شاهد بهبودی در عملکرد queryها هستیم.
یکی از کاربردهای توابع تجمعی window حل مساله running total و running average است.
به تصویر زیر توجه کنید، همانطور که در قبل توضیح دادم ما به سطرجاری و سطرهای پیشین نیاز داریم تا اعمال تجمعی (جمع و میانگین) را روی مقادیر بدست آمده انجام دهیم. در تصویر زیر سطرجاری و سطرهای قبلی به ازای هر سطری به وضوح قابل مشاهده است، مثلا هنگامی که سطر جاری برابر با روز 30 است ما خود سطر جاری (current row) و تمام سطرهای پیشین و قبلی (unbounded preceding) را نیاز داریم.
و اکنون query مورد نظر
در نقشه اجرای این query دیگر خبری از عملگر inner join نخواهد بود که به معنای عملکرد بهتر query است.
توسط ویژگیهای جدیدی که در نسخه 2012 به بحث window افزوده شد میتوانیم مسالهای running total و running average را به شکل بهینه ای حل کنیم.
ابتدا این دو مساله را بدون بکارگیری ویژگیهای جدید، حل نموده و سپس سراغ توابع جدید خواهم رفت.
قبل از هر چیزی لازم است جدول زیر ساخته شود و دادههای نمونه در آن درج شود:
create table testTable ( day_nbr integer not null primary key clustered, value integer not null check (value > 0) ); insert into testTable values (10, 7), (20, 15), (30, 3), (40, 9), (50, 17), (60, 25), (70, 10);
مساله running total بسیار ساده است، یعنی جمع مقدار سطر جاری با مقادیر سطرهای قبلی (بر اساس یک ترتیب معین)
running average هم مشابه به running total هست با این تفاوت که میانگین مقادیر سطرجاری وسطرهای قبلی محاسبه میشود.
و نتیجه به صورت نمودار:
راه حل در SQL Server 2000
توسط دو correlated scalar subquery در ماده SELECT میتوانیم مقادیر دو ستون مورد نظر با محاسبه کنیم:
select *, runningTotal = (select sum(value) from testTable where day_nbr <= t.day_nbr), runningAverage = (select avg(value) from testTable where day_nbr <= t.day_nbr) from testTable t;
اگر به نقشه اجرای این query نگاه کنید گره(عملگر) inner join دو بار بکار رفته است (به وجود دو subquery)، که این عدد در روش توابع تجمعی window به صفر کاهش پیدا خواهد کرد
راه حل در SQL Server 2005
توسط cross apply به سادگی میتوانیم دو subquery که در روش قبل بود را به یکی کاهش دهیم:
select * from testTable t cross apply (select sum(value) as runningTotal, avg(value) as runningAverage from testTable where day_nbr <= t.day_nbr)d;
این بار تنها یک عملگر inner join در نقشه اجرای query مشاهده میشود:
راه حل در SQL Server 2012
با اضافه شدن برخی از ویژگیهای استاندارد به ماده OVER مثل rows و range شاهد بهبودی در عملکرد queryها هستیم.
یکی از کاربردهای توابع تجمعی window حل مساله running total و running average است.
به تصویر زیر توجه کنید، همانطور که در قبل توضیح دادم ما به سطرجاری و سطرهای پیشین نیاز داریم تا اعمال تجمعی (جمع و میانگین) را روی مقادیر بدست آمده انجام دهیم. در تصویر زیر سطرجاری و سطرهای قبلی به ازای هر سطری به وضوح قابل مشاهده است، مثلا هنگامی که سطر جاری برابر با روز 30 است ما خود سطر جاری (current row) و تمام سطرهای پیشین و قبلی (unbounded preceding) را نیاز داریم.
و اکنون query مورد نظر
select *, sum(value) over(order by day_nbr rows between unbounded preceding and current row) as runningTotal, avg(value) over(order by day_nbr rows between unbounded preceding and current row) as runningAverage from testTable
در نقشه اجرای این query دیگر خبری از عملگر inner join نخواهد بود که به معنای عملکرد بهتر query است.