مقایسه هوشمند دادهها: معرفی عملگر SQL IS NOT DISTINCT FROM و کاربردهای آن
در توسعه و بهینهسازی کوئریهای SQL، یکی از چالشهای همیشگی، مدیریت مقادیر NULL است. عملگرهای مقایسه استاندارد مانند `=` یا “ در SQL با مقادیر NULL به شیوهای متفاوت عمل میکنند، زیرا NULL به معنای “نامعلوم” است و نمیتواند برابر یا نابرابر با هیچ مقدار دیگری، حتی خود NULL، باشد. این موضوع اغلب باعث پیچیدگی در منطق کوئریها میشود، بهویژه هنگام فیلتر کردن دادهها یا انجام JOIN.
اینجاست که عملگر **`IS NOT DISTINCT FROM`** به کمک میآید. این عملگر که به استانداردهای SQL:1999 اضافه شده است، راه حلی ساده و کارآمد برای مقایسه دو عبارت ارائه میدهد که در آن NULLها نیز بهدرستی مقایسه میشوند. به عبارت دیگر، دو مقدار را یکسان (یا غیرمتمایز) در نظر میگیرد اگر هر دو برابر باشند یا هر دو NULL باشند. این ویژگی به طور قابل توجهی خوانایی و سادگی کوئریهای SQL را افزایش میدهد.
برای درک بهتر، بیایید چند مثال را بررسی کنیم.
فرض کنید میخواهیم دو مقدار NULL را مقایسه کنیم.
همانطور که میدانید، نتیجه مقایسه `NULL = NULL` نامعلوم است و TRUE باز نمیگرداند.
SELECT NULL = NULL;
این کوئری خروجی `NULL` را تولید میکند.
به همین ترتیب، مقایسه `NULL NULL` نیز `NULL` را برمیگرداند.
SELECT NULL NULL;
برای مقایسه NULLها در گذشته از `IS NULL` استفاده میشد، اما این روش فقط برای یک مقدار کار میکند.
SELECT NULL IS NULL;
این کوئری خروجی `TRUE` را تولید میکند.
برای حل مشکل مقایسه دو ستون یا دو عبارت که میتوانند NULL باشند، اغلب از توابعی مانند `NULLIF` یا `COALESCE` استفاده میشد.
مثلاً `NULLIF(NULL, NULL)` خروجی `NULL` را میدهد.
SELECT NULLIF(NULL,NULL);
و `COALESCE(NULL, NULL)` نیز خروجی `NULL` را میدهد.
SELECT COALESCE(NULL,NULL);
اما با عملگر `IS NOT DISTINCT FROM`، مقایسه سادهتر میشود. این عملگر دقیقاً برعکس `IS DISTINCT FROM` عمل میکند.
یک عبارت را در نظر بگیرید:
A IS NOT DISTINCT FROM B
این عبارت زمانی `TRUE` برمیگرداند که A و B هر دو NULL باشند، یا A و B هر دو غیر NULL و برابر باشند.
در غیر این صورت، `FALSE` برمیگرداند (یعنی اگر یکی NULL و دیگری غیر NULL باشد، یا هر دو غیر NULL و نابرابر باشند).
بیایید نتایج مقایسههای مختلف با `IS NOT DISTINCT FROM` را بررسی کنیم:
SELECT NULL IS NOT DISTINCT FROM NULL;
این کوئری خروجی `TRUE` را میدهد، زیرا هر دو مقدار NULL هستند.
SELECT 1 IS NOT DISTINCT FROM 1;
این کوئری نیز خروجی `TRUE` را میدهد، زیرا هر دو مقدار برابر و غیر NULL هستند.
SELECT 1 IS NOT DISTINCT FROM NULL;
و این کوئری خروجی `FALSE` را میدهد، زیرا یکی NULL و دیگری غیر NULL است.
SELECT NULL IS NOT DISTINCT FROM 1;
این کوئری هم خروجی `FALSE` را میدهد، به همان دلیل قبلی.
**کاربرد در عبارت WHERE**
فرض کنید جدولی به نام `TestNulls` داریم که شامل دو ستون `Val1` و `Val2` است و میخواهیم ردیفهایی را انتخاب کنیم که این دو ستون با هم “یکسان” هستند (با در نظر گرفتن NULLها).
ابتدا جدول را ایجاد و دادهها را وارد میکنیم:
CREATE TABLE TestNulls (
ID INT IDENTITY(1,1) PRIMARY KEY,
Val1 INT,
Val2 INT
);
INSERT INTO TestNulls (Val1, Val2) VALUES
(1,1),
(2,NULL),
(NULL,2),
(NULL,NULL),
(3,3);
اگر از عبارت `WHERE Val1 = Val2` استفاده کنیم، ردیفهایی که شامل NULL هستند (مثل `(2,NULL)`, `(NULL,2)`, `(NULL,NULL)`) نادیده گرفته میشوند، حتی اگر منطقاً بخواهیم `(NULL,NULL)` یکسان در نظر گرفته شود.
SELECT * FROM TestNulls WHERE Val1 = Val2;
برای حل این مشکل بدون استفاده از `IS NOT DISTINCT FROM`، باید از منطق پیچیدهتری استفاده میکردیم:
SELECT * FROM TestNulls WHERE (Val1 = Val2 OR (Val1 IS NULL AND Val2 IS NULL));
این کوئری منطق را پیچیده میکند. اما با `IS NOT DISTINCT FROM`، همان نتیجه را با کد سادهتر و خواناتر به دست میآوریم:
SELECT * FROM TestNulls WHERE Val1 IS NOT DISTINCT FROM Val2;
این روش به طور قابل توجهی کوئری شما را ساده و خواناتر میکند و احتمال خطا را کاهش میدهد.
**کاربرد در عملیات JOIN**
هنگام استفاده از JOIN در SQL، بهخصوص زمانی که کلیدهای JOIN ممکن است مقادیر NULL داشته باشند، `IS NOT DISTINCT FROM` میتواند بسیار مفید باشد. فرض کنید دو جدول `TableA` و `TableB` داریم و میخواهیم بر اساس ستون `Val` آنها را JOIN کنیم، به طوری که ردیفهایی که `Val` آنها NULL است نیز در نظر گرفته شوند.
ایجاد جداول نمونه و درج داده:
CREATE TABLE TableA (ID INT, Name VARCHAR(50), Val INT);
INSERT INTO TableA VALUES (1,'Apple',10),(2,'Banana',NULL),(3,'Cherry',20),(4,'Date',NULL);
CREATE TABLE TableB (ID INT, Name VARCHAR(50), Val INT);
INSERT INTO TableB VALUES (1,'Grape',10),(2,'Kiwi',30),(3,'Lemon',NULL),(4,'Mango',NULL);
روش سنتی JOIN با مدیریت NULL:
SELECT A.ID, A.Name, A.Val, B.ID, B.Name, B.Val
FROM TableA A
JOIN TableB B ON (A.Val = B.Val OR (A.Val IS NULL AND B.Val IS NULL));
این کوئری کار میکند، اما طولانی و مستعد خطا است. با `IS NOT DISTINCT FROM`، همان JOIN را میتوانیم به سادگی بنویسیم:
SELECT A.ID, A.Name, A.Val, B.ID, B.Name, B.Val
FROM TableA A
JOIN TableB B ON A.Val IS NOT DISTINCT FROM B.Val;
این روش نه تنها کوئری را خواناتر میکند، بلکه میتواند منجر به طرحهای اجرای (Execution Plan) بهینهتری شود، چرا که موتور پایگاه داده میتواند این منطق خاص را بهینهتر پردازش کند.
**کاربرد در عبارت CASE**
`IS NOT DISTINCT FROM` همچنین میتواند در عبارت `CASE` برای تعریف منطقهای شرطی واضحتر، بهخصوص در مواجهه با مقادیر NULL، مورد استفاده قرار گیرد.
یک جدول `Sales` را در نظر بگیرید:
CREATE TABLE Sales (
OrderID INT,
Product VARCHAR(50),
Price DECIMAL(10,2),
Discount DECIMAL(5,2)
);
INSERT INTO Sales VALUES
(1,'Laptop',1200.00,0.10),
(2,'Mouse',25.00,NULL),
(3,'Keyboard',75.00,0.15),
(4,'Monitor',300.00,NULL);
فرض کنید میخواهیم وضعیت تخفیف محصولات را بر اساس ستون `Discount` تعیین کنیم. اگر `Discount` برابر با NULL باشد، “No Discount”، اگر 0.10 باشد، “Standard Discount” و در غیر این صورت “Other Discount” باشد.
روش سنتی با بررسی NULL:
SELECT
OrderID,
Product,
Price,
Discount,
CASE
WHEN Discount IS NULL THEN 'No Discount'
WHEN Discount = 0.10 THEN 'Standard Discount'
ELSE 'Other Discount'
END AS DiscountStatus
FROM Sales;
با `IS NOT DISTINCT FROM`، میتوانیم همان منطق را به شکل یکپارچهتری بنویسیم:
SELECT
OrderID,
Product,
Price,
Discount,
CASE
WHEN Discount IS NOT DISTINCT FROM NULL THEN 'No Discount'
WHEN Discount IS NOT DISTINCT FROM 0.10 THEN 'Standard Discount'
ELSE 'Other Discount'
END AS DiscountStatus
FROM Sales;
این استفاده، باعث میشود که تمامی شرایط مقایسه، اعم از NULL و غیر NULL، با یک عملگر ثابت و یکسان در `CASE` مدیریت شوند که خوانایی کد را افزایش میدهد.