خانه/ مبانی SQL/ WHERE

WHERE — فیلتر کردن ردیف‌ها بر اساس شرط

مبتدی ۱۴ دقیقه مطالعه

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

ساختار پایه

sql
SELECT full_name, city
FROM students
WHERE city = N'تهران';

این پرس‌وجو فقط دانش‌آموزانی را برمی‌گرداند که مقدار ستون cityشان دقیقاً برابر 'تهران' باشد. توجه کن که در SQL Server، برای مقادیر متنی، پیشوند N قبل از رشته گذاشته می‌شود تا مطمئن شویم متن فارسی به‌درستی مقایسه می‌شود — دقیقاً همان نکته‌ای که در درس انواع داده دیدیم.

عملگرهای مقایسه‌ای

عملگرمعنا
=برابر است با
<> یا !=برابر نیست با
>بزرگ‌تر از
<کوچک‌تر از
>=بزرگ‌تر یا مساوی
<=کوچک‌تر یا مساوی
sql
SELECT full_name, grade
FROM students
WHERE grade >= 10;

ترکیب چند شرط با AND و OR

می‌توان چند شرط را با هم ترکیب کرد:

  • AND: ردیف فقط زمانی نگه داشته می‌شود که همه‌ی شرط‌ها درست باشند.
  • OR: ردیف نگه داشته می‌شود اگر حداقل یکی از شرط‌ها درست باشد.
sql
-- دانش‌آموزان تهرانی با پایه‌ی ۱۰ یا بالاتر
SELECT full_name, city, grade
FROM students
WHERE city = N'تهران' AND grade >= 10;

-- دانش‌آموزان تهرانی یا شیرازی، فارغ از پایه
SELECT full_name, city
FROM students
WHERE city = N'تهران' OR city = N'شیراز';

بیا این دو ایده را روی داده‌ی واقعی امتحان کنیم:

امتحانش کن — ترکیب AND و OR

مراقب اولویت AND و OR باش

یکی از رایج‌ترین اشتباهات مبتدیان همین‌جاست: AND همیشه قبل از OR محاسبه می‌شود — دقیقاً مثل ضرب که در ریاضی قبل از جمع محاسبه می‌شود. این پرس‌وجو را ببین:

sql
SELECT full_name, city, grade
FROM students
WHERE city = N'تهران' OR city = N'شیراز' AND grade = 11;

در نگاه اول ممکن است فکر کنی این پرس‌وجو یعنی «شهر تهران یا شیراز، با پایه‌ی ۱۱». اما SQL Server اول AND را محاسبه می‌کند، پس این پرس‌وجو در واقع این‌طور خوانده می‌شود:

sql
WHERE city = N'تهران' OR (city = N'شیراز' AND grade = 11)

یعنی: «هر دانش‌آموز تهرانی (با هر پایه‌ای)، یا هر دانش‌آموز شیرازی که پایه‌اش ۱۱ باشد» — چیزی کاملاً متفاوت از آنچه شاید در ذهن داشتی. راه‌حل ساده است: هروقت AND و OR را با هم ترکیب می‌کنی، منظور واقعی‌ات را با پرانتز صریح بنویس:

sql
SELECT full_name, city, grade
FROM students
WHERE (city = N'تهران' OR city = N'شیراز') AND grade = 11;
قانون کاربردی: هر وقت بیش از یک نوع عملگر منطقی (AND و OR) در یک شرط دارید، حتماً با پرانتز مشخص کنید کدام قسمت اول محاسبه شود — حتی اگر مطمئنید اولویت پیش‌فرض همان چیزی است که می‌خواهید. این کار، کد را هم برای خودتان بعداً و هم برای هم‌تیمی‌ها خیلی خواناتر می‌کند.

منفی کردن یک شرط با NOT

sql
SELECT full_name, city
FROM students
WHERE NOT city = N'تهران';
-- دقیقاً معادل: WHERE city <> N'تهران'

فیلتر کردن یک بازه با BETWEEN

برای این‌که مقداری بین دو عدد (یا دو تاریخ) باشد، به‌جای نوشتن دو شرط با AND، می‌توان از BETWEEN استفاده کرد:

sql
SELECT full_name, grade
FROM students
WHERE grade BETWEEN 10 AND 11;
-- دقیقاً معادل: WHERE grade >= 10 AND grade <= 11
BETWEEN هر دو سر بازه را هم شامل می‌شود (inclusive)؛ یعنی خود ۱۰ و خود ۱۱ هم در نتیجه هستند.

بررسی عضویت در یک لیست با IN

وقتی می‌خواهی چند مقدار مشخص را چک کنی، به‌جای زنجیره‌ای طولانی از OR، از IN استفاده کن:

sql
SELECT full_name, city
FROM students
WHERE city IN (N'تهران', N'شیراز', N'مشهد');
-- دقیقاً معادل: WHERE city = N'تهران' OR city = N'شیراز' OR city = N'مشهد'

برعکسش هم با NOT IN ممکن است: WHERE city NOT IN (N'تهران', N'شیراز') یعنی هر شهری غیر از این دو.

بیا BETWEEN و IN را هم امتحان کنیم:

امتحانش کن — BETWEEN و IN

جستجوی الگو در متن با LIKE

وقتی مقدار دقیق را نمی‌دانی و فقط یک الگو در ذهن داری (مثلاً «نامی که با علی شروع می‌شود»)، از LIKE همراه با دو نویسه‌ی خاص استفاده می‌شود:

نویسهمعنا
%هر تعداد نویسه (حتی صفر نویسه)
_دقیقاً یک نویسه
sql
-- نام‌هایی که با «علی» شروع می‌شوند
SELECT full_name FROM students WHERE full_name LIKE N'علی%';

-- نام‌هایی که به «محمدی» ختم می‌شوند
SELECT full_name FROM students WHERE full_name LIKE N'%محمدی';

-- نام‌هایی که در هر جایی «حسین» دارند
SELECT full_name FROM students WHERE full_name LIKE N'%حسین%';
نکته‌ی کارایی: الگوهایی که با % شروع می‌شوند (مثل '%حسین%') نمی‌توانند از ایندکس معمولی استفاده کنند و روی جدول‌های بزرگ می‌توانند کند باشند. در فصل ۱۰ (ایندکس‌گذاری) بیشتر درباره‌ی این موضوع صحبت می‌کنیم.

حالا نوبت توست — یکی از این سه الگو را امتحان کن:

امتحانش کن — الگوی متنی با LIKE

یک نکته‌ی مهم درباره‌ی NULL

ممکن است وسوسه شوی برای پیدا کردن ردیف‌های خالی بنویسی WHERE city = NULL. این کار هیچ‌وقت جواب درست نمی‌دهد؛ چون NULL به‌معنای «نامشخص» است، نه یک مقدار قابل‌مقایسه. برای این کار باید از IS NULL استفاده کرد — که با جزئیات کامل، موضوع درس بعدی است.

چرا WHERE معمولاً نمی‌تواند نام مستعار SELECT را ببیند؟

در درس قبل با AS نام مستعار ساختیم. در بسیاری از پایگاه‌داده‌های رابطه‌ای — از جمله SQL Server — یک قانون سخت‌گیرانه وجود دارد: نمی‌توان همان نام مستعار را داخل WHERE همان پرس‌وجو استفاده کرد. علتش به «ترتیب پردازش منطقی» SQL برمی‌گردد که در درس SELECT اشاره کردیم: WHERE پیش از SELECT پردازش می‌شود، پس وقتی نوبت به بررسی شرط WHERE می‌رسد، نام مستعاری که قرار است در SELECT ساخته شود، هنوز اصلاً وجود ندارد. در SQL Server، پرس‌وجوی زیر با خطا متوقف می‌شود:

sql
SELECT grade + 1 AS NextYearGrade
FROM Students
WHERE NextYearGrade > 10;
-- خطا: نام ستون «NextYearGrade» شناخته‌شده نیست

حالا بیا همین پرس‌وجو را در Playground این سایت (که روی SQLite اجرا می‌شود) امتحان کنیم — نتیجه‌اش شاید تعجب‌آور باشد:

همین پرس‌وجو را این‌جا امتحان کن
نکته‌ی مهم: برخلاف SQL Server، این پرس‌وجو در SQLite (موتور Playground این سایت) بدون خطا اجرا می‌شود و درست هم فیلتر می‌کند — SQLite به‌عنوان یک ویژگی اضافه‌ی مخصوص به خودش، اجازه می‌دهد نام مستعار SELECT در WHERE همان پرس‌وجو هم استفاده شود. این دقیقاً یکی از همان تفاوت‌های بین موتورهاست که باید به آن‌ها عادت کنی: کدی که این‌جا کار می‌کند، ممکن است روی SQL Server واقعی با خطا متوقف شود. برای این‌که کدت همه‌جا کار کند، امن‌ترین راه این است که عبارت را در WHERE تکرار کنی: WHERE grade + 1 > 10.

جمع‌بندی این درس

  • WHERE فقط ردیف‌هایی را نگه می‌دارد که شرط داده‌شده برایشان درست باشد.
  • عملگرهای مقایسه‌ای (=, <>, >, ...) و عملگرهای منطقی (AND, OR, NOT) پایه‌ی هر شرط هستند.
  • AND همیشه قبل از OR محاسبه می‌شود — برای وضوح، همیشه از پرانتز استفاده کن.
  • BETWEEN برای بازه، IN برای لیستی از مقادیر، و LIKE برای الگوی متنی به‌کار می‌روند.
  • city = NULL هرگز کار نمی‌کند؛ به‌جایش باید از IS NULL استفاده کرد.
  • در SQL Server، نام مستعار ساخته‌شده در SELECT داخل WHERE همان پرس‌وجو در دسترس نیست (SQLite در این مورد استثنا و مسامحه‌کارتر عمل می‌کند).

در درس بعدی، کامل با مفهوم NULL آشنا می‌شویم: چرا یک مقدار «نامشخص» با هیچ مقداری — حتی خودش — برابر نیست، و چطور درست با آن کار کنیم.