مطالب
کپی کردن ساختار و داده‌های یک جدول از یک دیتابیس به دیتابیسی دیگر
در مواقعی ممکن است نیاز داشته باشیم که جدول یا جدول‌هایی از یک پایگاه داده را به یک پایگاه داده دیگر انتقال دهیم. در این مقاله قصد داریم روند انجام این کار را هم به صورت کوئری و هم به صورت ویزارد(گرافیکی) انجام دهیم.

برای شروع کار ابتدا دو دیتابیس به اسم‌های databasefrm و databaseto می‌سازیم. دیتابیس databasefrm شامل یک جدول به اسم emp با سه فیلد ID,Name,Address می‌باشد. قصد داریم جدول tmp از دیتابیس databasefrm را به دیتابیس databaseto انتقال دهیم. برای انجام این کار، یکی از روش‌های زیر را استفاده خواهیم کرد:

روش 1 : استفاده از کوئری

ساختار کلی انجام این عمل به صورت زیر خواهد بود:
Select * into DestinationDB.dbo.tableName from SourceDB.dbo.SourceTable
مثال :
select * into databaseto.dbo.emp from databasefrm.dbo.Emp
با اجرای دستور فوق یک کپی از جدول emp به همراه تمامی داده‌های آن به دیتابیس databaseto منتقل و ایجاد می‌شوند. اگر بخواهیم تمامی ایندکس‌ها، تریگر‌ها و قید‌ها (Constraint) نیز منتقل شوند، برای اینکار نیاز به تولید یک اسکریپت خواهد بود (در ادامه).

حال اگر بخواهیم یک کپی از  جدول را در دیتابیس جاری ایجاد کنیم، ساختار آن به صورت زیر خواهد بود  :
select * into newtable from SourceTable
که نمونه ای از آن برای دیتابیس ما به صورت زیرخواهد بود :
 select * into  emp1 from emp

می‌توانیم فقط فیلدهایی مشخص را به جدول دیگر کپی کنیم. برای انجا این کار کافیست به جای *  اسم فیلد‌های مورد نیاز را نوشت که ساختار دستوری آن به صورت زیر است :
select col1, col2 into <destination_table> from <source_table>
که برای مثال ما به صورت زیر خواهد بود :
select Id,Name into databaseto.dbo.emp1 from databasefrm.dbo.Emp

بعد از اجرای کوئری فوق نتیجه به صورت زیر خواهد بود :

کد فوق باعث کپی کردن فیلد‌های Id,Name شده است.

اگر بخواهیم فقط ساختار جدول را کپی کنیم روند کار به صورت زیر خواهد بود :

select *into <destination_database.dbo.destination table> from _
<source_database.dbo.source table> where 1 = 2
که نمونه ای از آن برای مثال ما به صورت زیر خواهد بود :
select * into databaseto.dbo.emp from 
databasefrm.dbo.emp where 1 = 2
کاربرد where در دستور فوق برای این است که عنوان فیلد‌ها را بگیریم و در جدول دیگری ذخیره کنیم.

نکته: هر وقت نیاز بود که فقط فیلد‌های یک جدول را دریافت کنید، می‌تواند از کدی همانند فوق استفاده کنید؛ با یک شرط که همیشه false برگرداند. ولی راه بهتری که توصیه میکنم استفاده از Top در دستور  Select می‌باشد. نمونه‌ای از دستور فوق:
select top(0) * into databaseto.dbo.emp from 
databasefrm.dbo.emp
همانطور که مشاهده می‌کنید دیگر در دستور فوق خبری از where نیست.

روش 2: ویزارد

جهت تهیه کارهای فوق به صورت ویزارد، به صورت خلاصه فقط به روند انجام کار بسنده می‌کنیم:
1- SSMS را باز کنید.
2- بر روی دیتابیس مورد نظر کلیک راست کرده و از منوی ظاهر شده Task را انتخاب نموده و در کادر بازشو Export data را انتخاب کنید.
3- در پنجره‌ی ظاهر شده بر روی دکمه next کلیک کرده و در پنجره بعدی، نوع اعتبار سنجی را انتخاب کرده و دیتابیس مورد نظر را انتخاب نمایید (databasefrm).
4- همانند مرحله 3 است با این تفاوت که اینبار دیتابیس مقصد را انتخاب می‌کنیم (databaseto).
5- در پنجره‌ی بعدی گزینه اول را انتخاب کرده (copy data from ...) و بعد از کلیک بر روی next در پنجره ظاهر شده، جدول یا جداول مورد نظر را انتخاب کنید.

روش 3 : تولید اسکریپت

 
با استفاده از دو روش فوق فقط می‌توانستیم ساختار جداول و داده‌های آن را انتقال بدهیم. برای انتقال کامل جداول مثل تریگرها، قیدها و ... می‌بایست از جدول یا جداول اسکریپت تولید و در نهایست اسکریپت را اجرا نماییم.
Right click on datbase >>Task>>Generate script>>next

انتخاب دیتابیس مورد نظر و بعد انتخاب مواردی که قصد داریم از آنها اسکریپت ایجاد کنیم و در پایان اسکریپت مورد نظر را بر روی دیتابیس مقصد (databaseto) اجرا می‌کنیم.

و در پایان نهایت تشکر را از تمام عزیزان و دوستان نویسنده‌ی سایت دارم. امیدوارم در سال 94 شاهد موفقیت‌های خوبی در حوزه‌ی نرم افزار باشیم.
مطالب
آشنایی با Row_Number،Rank،Dense_Rank،NTILE
توابعی که در این بررسی عنوان می‌شود در زمان انتشار نسخه SQL Server2005 ارائه شده است.

قبل از بررسی توابع، Script  زیر را اجرا می‌نماییم، که شامل جدولی به نام Testو درج چند رکورد درون آن می‌باشد:

CREATE TABLE Test (ID INT, Product VARCHAR(100), Price INT, Color VARCHAR(100))
GO
INSERT INTO Test
SELECT 1, 'Toy', 100, 'Black'
UNION ALL
SELECT 2, 'Pen', 100, 'Black'
UNION ALL
SELECT 3, 'Pencil', 100, 'Blue'
UNION ALL
SELECT 4, 'Pencil', 100, 'Red'
UNION ALL
SELECT 5, 'Pencil', 200, 'Yellow'
UNION ALL
SELECT 6, 'Cup', 300, 'Orange'
UNION ALL
SELECT 7, 'Cup', 400, 'Brown'
GO
اولین تابعی را که بررسی می‌نماییم، Row_Number  می‌باشد و Syntax آن بصورت زیر است:

ROW_NUMBER () OVER ([<partition_by_clause>] <order_by_clause>)
بوسیله تابع Row_Number می‌توان اعداد توالی (ترتیبی) را به رکوردهای یک جدول نسبت داد. برای روشن‌تر شدن مطلب فوق مثالی را بررسی می‌نماییم.
در ابتدا Query زیر را اجرا نمایید:


Select *, ROW_NUMBER() OVER ( ORDER BY Price DESC) AS RN from Test
خروجی آن بصورت زیر می‌باشد:

همانطور که در شکل مشاهده می‌نمایید، یک عدد ترتیبی تولید شده و به هریک از رکوردهای جدول نسبت داده شده است، در مطلب بعدی یک مثال کاربردی از Row_Number خواهم زد.

  • لازم به یادآوری است که استفاده از  Order by در Syntax تابع Row_Number  الزامی میباشد.
اگر به Syntax تابع Row_Number توجه نماییم، با کلمه Partition مواجه می‌شویم،که جهت گروه بندی استفاده می‌شود،به عبارت دیگر ممکن است شما بخواهید، ابتدا جدول خود را براساس فیلد یا فیلدهایی Group by نمایید و سپس روی آنها Row_Number را اعمال کنید، که در این حالت از Partition استفاده می‌شود.
برای درک بیشتر Query زیر را اجرا نمایید:

Select *,ROW_NUMBER() OVER (PARTITION BY Product ORDER BY Price DESC) AS RN from Test
خروجی بصورت زیر خواهد بود:

همانطور که در شکل مشاهده می‌نمایید، در ابتدا، جدول براساس فیلد Product، دسته بندی (Group by) شده است و سپس اعداد ترتیبی روی هر Group by بصورت جداگانه اعمال شده است.

تابع ()RANK
از تابع فوق در جهت رتبه بندی نمودن فیلدهای یک جدول استفاده می‌شود و Syntax آن بصورت زیر میباشد:
RANK () OVER ([<partition_by_clause>] <order_by_clause>)
برای درک مطلب فوق نیز مثالی می‌زنیم:
ابتدا Query زیر را اجرا می‌نماییم:
Select *,RANK() over (ORDER BY Price ) AS RANK from Test
خروجی بصورت زیر خواهد بود:

یادآوری: زمانی که دورن Order by ترتیب صعودی یا نزولی بودن را تعیین نکنیم، Order by بصورت پیش فرض صعودی میباشد.

همانطور که در شکل مشاهده می‌نمایید،رتبه بندی انجام شده به ترتیب نمی‌باشد، و برای مقادیر تکراری فیلد Price از Rank یکسانی استفاده شده است. نکته دیگر این که بین اعداد مشاهده شده در فیلد Rank نیز gap ایجاد می‌شود. به عبارت دیگر عمده تفاوت تابع Rank با تابع Row_Number همین مواردی است که بیان شده است.

در Syntax تابع Rank نیز کلمه Partition هم وجود دارد، که در جهت Group by فیلد یا فیلدهای خاصی استفاده می‌شود، و رتبه بندی نیز در این حالت روی Group by انجام می‌گردد.

برای درک بهتر Query زیر را اجرا نمایی:

Select *,RANK() over (Partition by Product ORDER BY Price Desc) AS RANK from Test

خروجی بصورت زیر خواهد بود:

همانطور که در شکل مشاهده می‌نمایید، رتبه بندی روی هر Group by بصورت جداگانه اعمال شده است.

تابع Dense_Rank

این تابع نیز همانند تابع Rank عمل می‌کند، با این تفاوت که هیچ gap ی بین اعداد آن رخ نمی‌دهد.

با جرای Query زیر خواهیم داشت:

Select *,dense_RANK() over (ORDER BY Price ) AS dense_RANK from Test

خروجی بصورت زیر خواهد بود:

همانطور که ملاحظه می‌نماییدهیچ gap ی بین اعداد Rank ایجاد نشده است.

و برای استفاده از Partition، درتابع Dense_Rank همانند تابع‌های دیگر میباشد.

تابع NTILE:

این تابع نیز مانند توابع بالا در جهت رتبه بندی استفاده می‌شود، و بوسیله تابع فوق شما می‌توانید رکوردهای جدول خود را به تعداد گروه‌های دلخواه تقسیم نمایید.و Syntaxآن بصورت زیر میباشد:

NTILE (integer_expression) OVER ([<partition_by_clause>] <order_by_clause>)

برای درک مطلب فوق مثالی می‌زنیم:

Select * ,NTILE(4) over ( ORDER BY Price desc) from Test

خروجی بصورت زیر خواهد بود:

در Syntax تابع فوق اشاره به Integer_Expressionشده است.که یک مقدار عددی دریافت می‌کند و بیانگر تعداد گروه بندی دلخواه میباشد.

حال سئوال اینجاست که رتبه بندی جدول به چه صورت انجام شده است:

همانطور که مشاهده می‌نمایید، جدول فوق شامل 7 رکورد می‌باشد،و ما در مثال خود،تمایل داشتیم که رکوردهای جدول به چهار گروه تقسیم و سپس رتبه بندی شوند، بنابراین 7 تقسیم بر 4 شده است و باقی مانده آن می‌شود 3

پس خواهیم داشت7=3+1*4

در ابتدا چهار گروه ایجاد می‌شودو در هر خانه یک رکورد قرار می‌گیرد

سپس 3 رکورد باقی می‌ماند که از اولین گروه رو به پایین ، برای هر گروه فقط یک رکورد درج می‌شود، یعنی یک رکورد به گروه یک،یک رکورد به گروه 2 و هم چنین یک رکورد به گروه 3 بنابراین خواهیم داشت:

نکته مهم: اگر تعداد رکورد باقی مانده بعد از تقسیم بیش از یک عدد باشد، در زمان اختصاص دادن به گروه ها، به هر گروه از بالا به پایین فقط یک رکورد اختصاص داده می‌شود. 

مثالی دیگر:

Select *,NTILE(3) over ( ORDER BY Price desc) AS NTILE from Test                              

خروجی:

در این حالت 7=1+2*3

امیدوارم مطلب فوق مفید واقع شده باشد.

مطالب
روشی جهت یافتن فیلدهای استفاده شده درون Stored Procedure ، Function و View
گاهی اوقات بدلیل تغییرات در جداول و غیرو...، ممکن است لازم شود، از استفاده قرار گرفتن فیلدهای جداول درون Function ها،Stored Procedure‌ها و یا View‌ها ، مطلع شوید. در این زمان شما می‌توانید از روش زیر استفاده نماید.
قبل از هر چیز در ابتدا Script زیر را اجرا نمایید.که شامل یک جدول به نام FindField ، یک Stored Procedure به نام PROCEDURE_FindField و یک Function به نام FUNCTION_FindField می‌باشد.
Create Table FindField
(ID int,
Firstname varchar(255),
Lastname varchar(255))
Go
CREATE PROCEDURE PROCEDURE_FindField
AS
    SELECT Firstname FROM FindField;
GO
CREATE FUNCTION FUNCTION_FindField (@id int)
RETURNS int
WITH EXECUTE AS CALLER
AS
begin 
declare @id1 int;
   set @id1= (Select Max(Firstname) from dbo.FindField) ;
   RETURN (@id1);
END;
در ادامه Script زیر را اجرا نمایید:
SELECT OBJECT_NAME(object_id) as 'Procedure/Function/View Name',
definition
FROM sys.sql_modules
WHERE definition LIKE '%' + 'Firstname' + '%'
خروجی بصورت زیر خواهد بود:

همانطور که در شکل مشاهده می‌نمایید، فیلد Firstnameدر Function و Stored Procedure مورد استفاده واقع شده است.

جهت کسب اطلاعات بیشتر به آدرس زیر مراجعه نمایید:

http://msdn.microsoft.com/en-us/library/ms175081.aspx

مطالب
بررسی مقدار دهی اولیه متغیرها در T-SQL

یکی از موارد مشکل ساز حین استفاده از T-SQL ، مقدار دهی اولیه متغیرها به نال است و اگر اسکریپت تهیه شده کمی طولانی باشد، خطایابی مشکلات مرتبط با آن بسیار مشکل می‌شود. برای مثال:
Declare
@x int,
@y int

Set @x = 1
If (@x + @y = 1)
BEGIN
print 'yes!'
End

Set @y = (select sum(id) from Account)
If @x + @y = 1
BEGIN
print 'yes!'
End

کد فوق بدون هیچگونه خطایی اجرا می‌شود و هیچ وقت هم yes را چاپ نمی‌کند. مشکل هم همینجا است. خطایابی قسمت دوم این اسکریپت کمی مشکل‌تر از حالت قبل است. چون در اینجا به نظر متغیر y صریحا مقدار دهی شده است؛ اما در عمل ممکن است برای مثال به دلیل عدم وجود رکوردی در جدول Account، باز هم null به آن نسبت داده شود.

بنابراین سؤال این است که چگونه این نوع مشکلات را در یک پروژه با تعداد زیادی رویه ذخیره شده، تابع و غیره می‌توان تشخیص داد؟
پاسخ:
در این مورد قبلا مطلبی در این سایت منتشر شده [+] (البته اگر از نگارش کامل VS 2010 استفاده می‌کنید نیازی به نصب چیزی نخواهید داشت) و نکته‌ی آن بررسی SR0007 است.



مطالب
درک نمودار

سؤال: از نمودار زیر چه چیزی را برداشت می‌کنید؟!



منحنی که بالا رفته یعنی چی؟ یعنی بده، خوبه؟!
منحنی‌های پایین‌تر یعنی چی؟ این‌ها بهترند یا بالایی‌های آن‌ها؟
با بالا رفتن حجم فایل‌ها، کدام یک کارآیی بهتری دارد؟ بالایی‌ها یا پایینی‌ها؟


ماخذ این نمودار:(+). البته قبل از مراجعه به ماخذ و مطالعه آن‌، سعی کنید به سؤالات فوق پاسخ دهید.

مطالب
مزیت‌های استفاده از رویه‌های ذخیره شده؛ واقعیت یا توهم؟!

متن زیر یک سری نکات و یا شاید توهماتی را مطرح می‌کند که در مورد رویه‌های ذخیره شده در اس کیوال سرور رایج هستند.

1- رویه‌های ذخیره شده در مقابل SQL Injection مقاوم هستند. کوئری‌های Ad hoc همیشه این آسیب پذیری را به همراه دارند.
نادرست است! رویه‌های ذخیره شده‌ای که رشته‌ها را به صورت پارامتر دریافت کرده و آن‌ها را به صورت یک عبارت sql اجرا می‌کنند، آسیب پذیر هستند. اگر هنگام استفاده از کوئری‌های Ad hoc از پارامترها استفاده شود، در برابر حملات SQL Injection مصون خواهید بود.

2- execution plan رویه‌های ذخیره شده کش می‌شوند اما این Plan برای کوئری‌های Ad hoc هر بار محاسبه و تولید می‌گردد.
نادرست است! اس کیوال سرور تا این اندازه بی هوش نیست! اگر execution plan ایی موجود باشد حتما استفاده خواهد شد و برای موتور اس کیوال سرور اصلا اهمیتی ندارد که کوئری در حال اجرا از یک رویه ذخیره شده صادر شده است یا از یک کوئری Ad hoc . رویه‌های ذخیره شده پیش کامپایل شده نیستند و مانند تمامی کوئری‌های دیگر در زمان اجرا کامپایل می‌شوند.

3- زمانیکه از رویه ذخیره شده استفاده می‌کنید همه چیز را در یک مکان به صورت متمرکز و مجتمع خواهید داشت (مدیریت بهتر)
نادرست است! در یک مکان متمرکز در اختیار شما نیستند. برنامه جای خود را دارد و رویه‌های ذخیره شده در دیتابیس در جای دیگری قرار دارند و برای مثال اگر قرار باشد یک پارامتر را به رویه ذخیره شده خود اضافه کنید، کدهای شما نیز باید تغییر کنند.

4- می‌توان از یک رویه ذخیره شده استفاده مجدد کرد (در نقاط مختلف یک کد) و اعمال تغییرات تنها در یک مکان (دیتابیس) باید صورت گیرد.
هر چند این مورد درست است، اما باید دقت داشت که اگر چندین برنامه از این رویه ذخیره شده استفاده می‌کنند نباید تغییرات شما باعث از کار افتادن سایر برنامه‌ها شوند.

5- می‌توان رویه ذخیره شده را بدون نیاز به توزیع مجدد برنامه تغییر داد.
این مورد تا حدودی صحیح است. اگر تنها بحث بهینه سازی و امثال آن مطرح باشد صحیح است اما اگر واقعا نیاز به تغییر یک کوئری در رویه ذخیره شده وجود داشته باشد به احتمال زیاد برنامه نیز باید دستخوش تغییراتی گردد تا این دو با هم هماهنگ شوند.

نظر شما چیست؟


مطالب
مقایسه رکوردهای دو جدول

گاهی از اوقات یک سری از امکانات جدید در دسترس هستند اما فراموش می‌شوند. برای مثال روش یافتن رکوردهای غیر یکسان دو جدول یکسان. مثلا یک دیتابیس قدیمی دارید دقیقا مشابه دیتابیس کاری فعلی با همان ساختار (ری استور شده از یک بک آپ). اکنون می‌خواهید بدانید در طول این مدت چه رکوردهایی به دیتابیس کاری اضافه شده که در دیتابیس قدیمی ری استور شده موجود نیست و کلا کدام رکوردها با هم متفاوتند. چه باید کرد؟

مثال:
دو جدول موقتی یکسان زیر را در نظر بگیرید.

CREATE TABLE #tableA
(
column1 INT,
column2 INT
)

INSERT INTO #tableA
VALUES
(1,1)
,(1, 2)
,(1, 3)
,(2, 1)

SELECT column1,
column2
FROM #tableA

CREATE TABLE #tableB
(
column1 INT,
column2 INT
)

INSERT INTO #tableB
VALUES
(1,1)
,(1, 3)
,(2, 2)

SELECT column1,
column2
FROM #tableB

یک سری دیتای دلخواه به آن‌ها اضافه شده است. (از روش اضافه کردن چندین رکورد توسط یک عبارت insert که در اس کیوال سرور 2008 معرفی شده، استفاده گردیده است)
#tableA
column1 column2
----------- -----------
1 1
1 2
1 3
2 1


#tableB
column1 column2
----------- -----------
1 1
1 3
2 2

اکنون می‌خواهیم رکوردهایی از جدول A را که در جدول B نیستند، پیدا کنیم. روش متداول انجام این‌کار در اس کیوال سرور 2000 به صورت زیر است:

SELECT column1,
column2
FROM #tableA
WHERE NOT EXISTS (
SELECT *
FROM #tableB
WHERE #tableA.column1 = #tableB.column1
AND #tableA.column2 = #tableB.column2
)

column1   column2
----------- -----------
1 2
2 1
و یا روش زیباتر انجام این‌کار که از اس‌کیوال سرور 2005 به بعد معرفی شده، به صورت زیر می‌باشد:
SELECT column1, column2 FROM #tableA
EXCEPT
SELECT column1, column2 FROM #tableB

column1   column2
----------- -----------
1 2
2 1
Except رکوردهای منحصربفردی از کوئری سمت چپ را که در کوئری سمت راست وجود ندارند، بر می‌گرداند.
در این حالت تعداد ستون‌های در نظر گرفته شده برای مقایسه باید یکسان و یک نوع باشند.
همچنین اگر می‌خواهید رکوردهایی از جدول A را که در جدول B وجود دارند بیابید، می‌توان از intersect استفاده کرد.

مطالب
انتقال فایل‌های دیتابیس اس کیوال سرور 2008

روز قبل نیاز بود تا فایل‌های mdf و ldf دیتابیس‌ها جابجا شوند (یک هارد بزرگتر و از این مسایل).
برای جابجا کردن این فایل‌ها هم روش معمول detach و سپس attach است. ابتدا روی دیتابیس کلیک راست کرده و detach . حالا فایل‌ها را جابجا می‌کنید و سپس attach . یا می‌شود بک آپ کامل گرفت و بعد ری استور کرد.
عموما هم نمی‌توان دیتابیس در حال استفاده را detach‌ کرد. باید دیتابیس ابتدا single user شود و بعد می‌توان این‌کار را انجام داد.
تا اینجای کار متداول است. همه چیز به خوبی انجام شد. سپس در لحظه attach ، دیتابیس‌ها به صورت read only اتچ شدند با آیکونی سیاه رنگ در management studio . (و رنگ من هم بلافاصله به همین رنگ متمایل شد!)
بعد از مدتی جستجو مشخص شد که در اس کیوال سرور 2008 برای کاهش سطح حمله به سرور، از یک سری یوزر با دسترسی کم برای نصب اس کیوال سرور استفاده می‌شود (بسیار هم خوب) و اس کیوال سرور 2008 ، یک سری یوزر مخصوص را هم در حین نصب ایجاد می‌کند که به صورت خودکار بر روی پوشه دیتای شما دسترسی full control دارد برای اینکه بتواند کارش را انجام دهد.
حال اگر شما در جای دیگری پوشه‌ای درست کردید و این دیتابیس‌ها را منتقل نمودید، مجددا پیش از هر کاری باید این دسترسی را برقرار کنید و گرنه اس کیوال سرور مجوز write نخواهد داشت؛ به همین جهت دیتابیس به صورت read only در management studio با رنگ مشکی ظاهر می‌شود.
نام این کاربر مخصوص به صورت زیر است:

SQLServerMSSQLUser$ComputerName$MSSQLSERVER




پس از برقراری دسترسی هم مشکل برطرف نمی‌شود. باید دستور زیر را نیز اجرا نمود:
ALTER DATABASE myDB SET READ_WRITE
اجرای این دستور نیز، نیاز به حالت single user دارد.

پ.ن.
می‌توان دسترسی یوزر سرویس اس کیوال سرور 2008 را نیز مانند نگارش‌های قبلی به حالت local system تغییر داد (یا هر اکانت دیگری با دسترسی بالا) تا این مشکلات نباشد؛ ولی بدیهی است سطح حمله به سرور نیز به همین اندازه افزایش می‌یابد.

مطالب
مقایسه نتایج الگوریتم‌های هش کردن اطلاعات در اس کیوال سرور و دات نت

از اس کیوال سرور 2005 به بعد تابع HashBytes نیز به مجموعه توابع قابل استفاده در دستورات T-SQL اس کیوال سرور اضافه شده است که الگوریتم‌های MD2 | MD4 | MD5 | SHA | SHA1 را پشتیبانی می‌کند. برای مثال:
DECLARE @str1 VARCHAR(4),
@str2 NVARCHAR(4)

--متن یونیکد اینجا ناقص ذخیره می‌شود
SET @str1 = 'وحید'

SET @str2 = N'وحید'

SELECT hashbytes('md5', @str1) --C82A7D721AAE517AD76EF1B871BC33CE

SELECT hashbytes('md5', @str2) --7D883091B80F3CD20B872CADBFDDACDF

اگر این نتایج را بخواهیم با استفاده از فضای نام استاندارد System.Security.Cryptography تولید کنیم، باید به encoding رشته دریافتی حتما دقت داشت؛ در غیر اینصورت نتایج یکسان نخواهند بود.
مهم‌ترین encoding های پشتیبانی شده در دات نت در جدول زیر برشمرده شده‌اند:

Encoding تعداد بیت هر کاراکتر

ASCII
هر کاراکتر آن 7 بیت است

UTF7
هر کاراکتر آن 7 بیت است

UTF8
هر کاراکتر آن 8 بیت و یا یک بایت است

Unicode (UTF-16)
هر کاراکتر آن 16 بیت و یا دو بایت است

UTF32
هر کاراکتر آن 32 بیت و یا 4 بایت است


نوع nvarchar در اس کیوال سرور همانند حالت Encoding.Unicode‌ دات نت است و هر کاراکتر آن 2 بایت می‌باشد.
این نکته‌ هنگام استفاده از این توابع بسیار حائز اهمیت است. برای مثال اگر تابع HashBytes اس کیوال سرور را بخواهیم در دات نت پیاده سازی کنیم، به کلاس زیر خواهیم رسید:

using System.Text;
using System.Security.Cryptography;

class CHash
{
public static string GetMD5Hash(string input, Encoding encoding)
{
byte[] bytes = new MD5CryptoServiceProvider().ComputeHash(encoding.GetBytes(input));
StringBuilder chars = new StringBuilder();
foreach (byte chr in bytes)
{
chars.Append(chr.ToString("x2"));
}
return chars.ToString();
}
}
در اینجا تنها حالت زیر با هش تولید شده یک فیلد یا متغیر از نوع nvarchar توسط تابع HashBytes اس کیوال سرور معادل است:

string result = CHash.GetMD5Hash("وحید", Encoding.Unicode);

پ.ن.
احتمالا عده‌ای را دیده‌اید که هر چقدر تلاش می‌کنند با سی شارپ متون ایران سیستم تحت داس را به نمونه‌های ویندوزی تبدیل کنند، کمتر موفق می‌شوند؛ علت را با توجه به جدول encoding فوق و عدم اطلاع از آن بهتر می‌توان بررسی کرد.


مطالب
sp_send_dbmail و ارسال ایمیل فارسی

نکته‌ی کوچکی در مورد ارسال ایمیل فارسی توسط رویه ذخیره شده سیستمی sp_send_dbmail اس کیوال سرور وجود دارد که شبیه به insert داده‌های فارسی در دیتابیسی است که پس از ثبت، به صورت ؟؟؟ ذخیره می‌شوند. (این مورد با تنظیم collation تقریبا قابل حل است)
اگر هنگام ثبت، collation عربی یا فارسی (در اس کیوال سرور 2008) انتخاب شود، مشکلی در ثبت نخواهد بود.
اگر به collation اهمیت نمی‌دهید باید اس کیوال سرور را مجبور کرد که داده را یونیکد ذخیره کند و اینکار با اضافه کردن یک N به ابتدای رشته صورت می‌گیرد و همچنین انتخاب نوع داده‌های n دار مانند nvarchar و امثال آن (n در اینجا به معنای national و اجبار آن می‌باشد):

Insert into tblTest(f1,f2) values(1,N'متن فارسی')
دقیقا همین نکته هم درباره‌ی ارسال ایمیل از طریق اس کیوال سرور صادق است. اگر N به ابتدای رشته اضافه نشود، رشته ارسالی را با فرمت ANSI ارسال می‌کند و داده‌های یونیکد متن تخریب خواهند شد؛ مثلا چیزی شبیه به حالت زیر:

<div align="center"><table border="1" width="95%" dir="rtl" cellspacing="0" cellpadding="0" style="font-family: Tahoma; font-size: 8pt" bordercolor="#660066"><tr><td bgcolor="#FFF9FF"><blockquote><p align="justify"><br>????? ????? ?<br>???? ???? ? ????? ?????? ??? ?? ????? ????? ?????. ???? ??? ???? ???? ????? ????? ????? ????? ????? ??? ??? ???? ???? ?????? ? ???? ?? ????? ???? ???? ??? ????? ??????.<br>???? ??? ????? ???? ?? ?????? ???? ????? ?????? ????? ? ?? ???? ???????? ????? ???? ???? ???? ?????? ???? ???? ??? ?? ????? ????.</blockquote></td></tr></table></div>
این مشکل به صورت زیر قابل حل است:

DECLARE @msg NVARCHAR(max)
SET @msg=N'متن فارسی'
برای ردگیری وضعیت ایمیل‌های ارسالی هم می‌توان از کوئری‌های زیر استفاده نمود:

SELECT * from sysmail_allitems
SELECT * from sysmail_faileditems
SELECT * from sysmail_event_log