رفتن به محتوا
دیتابیس و SQLSQL از صفردرس ۲ از ۱۰

SELECT و فیلتر

داده را دقیق بخوان: انتخاب ستون و اسم مستعار با AS، فیلتر با WHERE و عملگرهای = و <> و BETWEEN و IN و LIKE و ILIKE، ترکیب با AND و OR و پرانتز، NULL و IS NULL و COALESCE، مرتب‌سازی با ORDER BY، صفحه‌بندی با LIMIT و OFFSET، DISTINCT و ستون‌های محاسبه‌ای.

  • حدود ۱۷ دقیقه مطالعه
  • ۹ مثال اجراشده
  • ۳ تمرین با جواب
  • مقدماتی تا متوسط
  • به‌روزرسانی: ۲۹ مهر ۱۴۰۵

توی این درس یاد می‌گیری از یک جدول دقیقاً همان چیزی را بیرون بکشی که لازم داری. با SELECT ستون‌ها را انتخاب می‌کنی و با AS اسم خوانا می‌دهی، با WHERE سطرها را فیلتر می‌کنی (برابری، بازه با BETWEEN، فهرست با IN، الگوی متن با LIKE)، شرط‌ها را با AND و OR ترکیب می‌کنی، با مقدار مرموز NULL درست کار می‌کنی، نتیجه را با ORDER BY مرتب و با LIMIT محدود می‌کنی، و ستون‌های جدید حساب می‌کنی (ارزش موجودی، قیمت بعد از تخفیف). این درس پایه‌ی همه‌ی درس‌های بعدی است.

مسئله: جدول هزار سطری و یک سؤال ساده

Section titled “مسئله: جدول هزار سطری و یک سؤال ساده”

SELECT * FROM products برای ۱۲ محصول خوب است، ولی فروشگاه واقعی ده هزار محصول دارد و سؤال‌ها مشخص‌اند: «کالاهای شبکه‌ی تمام‌شده»، «۱۰ کالای گران‌تر از یک میلیون که تخفیف دارند»، «محصول‌هایی که از اول مهر اضافه شده‌اند». کشیدن کل جدول به برنامه و فیلتر کردن با پایتون هم کند است و هم بی‌دلیل؛ دیتابیس دقیقاً برای جواب دادن به همین سؤال‌ها بهینه شده.

SELECT مثل کار کردن با یک سینی میوه است: FROM می‌گوید کدام سینی، WHERE صافی است که فقط میوه‌های مورد نظر (سیب‌های قرمز بزرگ‌تر از فلان) رد می‌شوند، SELECT می‌گوید از هر میوه چه چیزی را نشان بده (اسم و وزن، نه همه‌چیز)، ORDER BY آن‌ها را ردیف می‌کند و LIMIT می‌گوید «فقط سه تای اول را بیاور».

دیتابیس یک SELECT را به این ترتیب منطقی اجرا می‌کند، نه به ترتیبی که نوشته می‌شود؛ برای همین اسم مستعار SELECT در WHERE شناخته نمی‌شود ولی در ORDER BY چرا.

یک جدول محصول کامل‌تر لازم داریم. این را در فایل ~/products.sql (با nano) بنویس:

products.sql
DROP TABLE IF EXISTS products;
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
category TEXT NOT NULL,
price INTEGER NOT NULL CHECK (price >= 0),
stock INTEGER NOT NULL DEFAULT 0,
discount INTEGER,
added_on DATE NOT NULL DEFAULT CURRENT_DATE
);
INSERT INTO products (name, category, price, stock, discount, added_on) VALUES
('کتاب پایتون', 'کتاب', 320000, 12, 10, '2026-03-10'),
('کتاب لینوکس', 'کتاب', 280000, 0, NULL, '2026-01-22'),
('کتاب داکر', 'کتاب', 350000, 5, NULL, '2026-08-01'),
('ماوس بی‌سیم', 'لوازم جانبی', 450000, 30, 5, '2026-02-14'),
('کیبورد مکانیکی', 'لوازم جانبی', 1890000, 8, NULL, '2026-05-03'),
('هدفون', 'لوازم جانبی', 1250000, 0, 15, '2026-06-18'),
('هارد اکسترنال', 'ذخیره‌سازی', 3200000, 4, NULL, '2026-04-09'),
('فلش 64 گیگ', 'ذخیره‌سازی', 290000, 50, NULL, '2026-07-30'),
('SSD 512 گیگ', 'ذخیره‌سازی', 2750000, 7, 10, '2026-09-12'),
('کابل شبکه', 'شبکه', 95000, 120, NULL, '2026-01-05'),
('روتر وای‌فای', 'شبکه', 2400000, 3, NULL, '2026-08-25'),
('سوئیچ 8 پورت', 'شبکه', 1650000, 0, 20, '2026-10-02');

و اجرایش کن:

Terminal window
psql shop -f ~/products.sql
خروجی
DROP TABLE
CREATE TABLE
INSERT 0 12

-f یک فایل SQL را اجرا می‌کند (داخل psql: \i ~/products.sql). DROP TABLE IF EXISTS جدول قبلی درس اول را پاک می‌کند تا از نو بسازیم؛ روی داده‌ی واقعی با این دستور بسیار محتاط باش. ستون discount (درصد تخفیف) عمداً برای بعضی کالاها خالی (NULL) است.

از اینجا همه‌چیز داخل psql shop.

SELECT * FROM products;
خروجی
id | name | category | price | stock | discount | added_on
----+----------------+-------------+---------+-------+----------+------------
1 | کتاب پایتون | کتاب | 320000 | 12 | 10 | 2026-03-10
2 | کتاب لینوکس | کتاب | 280000 | 0 | | 2026-01-22
3 | کتاب داکر | کتاب | 350000 | 5 | | 2026-08-01
4 | ماوس بی‌سیم | لوازم جانبی | 450000 | 30 | 5 | 2026-02-14
5 | کیبورد مکانیکی | لوازم جانبی | 1890000 | 8 | | 2026-05-03
6 | هدفون | لوازم جانبی | 1250000 | 0 | 15 | 2026-06-18
7 | هارد اکسترنال | ذخیره‌سازی | 3200000 | 4 | | 2026-04-09
8 | فلش 64 گیگ | ذخیره‌سازی | 290000 | 50 | | 2026-07-30
9 | SSD 512 گیگ | ذخیره‌سازی | 2750000 | 7 | 10 | 2026-09-12
10 | کابل شبکه | شبکه | 95000 | 120 | | 2026-01-05
11 | روتر وای‌فای | شبکه | 2400000 | 3 | | 2026-08-25
12 | سوئیچ 8 پورت | شبکه | 1650000 | 0 | 20 | 2026-10-02
(12 rows)

خانه‌های خالی ستون discount همان NULLاند. (کج شدن یکی دو ستون در اسم‌هایی که نیم‌فاصله دارند، فقط به نمایش psql برمی‌گردد.) در برنامه‌ها SELECT * را کم استفاده کن: ستون‌هایی را بخواه که لازم داری (مثال بعد).

مثال ۲: فیلتر برابری و مرتب‌سازی

Section titled “مثال ۲: فیلتر برابری و مرتب‌سازی”
SELECT name, price FROM products WHERE category = 'کتاب' ORDER BY price DESC;
خروجی
name | price
-------------+--------
کتاب داکر | 350000
کتاب پایتون | 320000
کتاب لینوکس | 280000
(3 rows)
  • WHERE category = 'کتاب': فقط سطرهایی که این شرط برایشان درست است. در SQL مقایسه با یک = است (برخلاف == پایتون) و نابرابری <> (یا !=).
  • ORDER BY price DESC: از گران به ارزان (DESC)؛ پیش‌فرض ASC (صعودی) است. بدون ORDER BY، ترتیب سطرها تضمین‌شده نیست؛ اگر ترتیب برایت مهم است، همیشه بنویسش.

مثال ۳: بازه و فهرست، BETWEEN و IN

Section titled “مثال ۳: بازه و فهرست، BETWEEN و IN”
SELECT name, price FROM products WHERE price BETWEEN 300000 AND 1500000 ORDER BY price;
SELECT name, category FROM products WHERE category IN ('کتاب', 'شبکه') AND stock = 0;
خروجی
name | price
-------------+---------
کتاب پایتون | 320000
کتاب داکر | 350000
ماوس بی‌سیم | 450000
هدفون | 1250000
(4 rows)
name | category
--------------+----------
کتاب لینوکس | کتاب
سوئیچ 8 پورت | شبکه
(2 rows)

BETWEEN a AND b هر دو سر را شامل می‌شود (یعنی price >= a AND price <= b). IN (...) کوتاه‌شده‌ی چند OR است: category = 'کتاب' OR category = 'شبکه'. سؤال دوم همان سؤال مدیر فروش است: کتاب‌ها و کالاهای شبکه‌ی تمام‌شده.

مثال ۴: الگوی متن، LIKE و ILIKE

Section titled “مثال ۴: الگوی متن، LIKE و ILIKE”
SELECT name FROM products WHERE name LIKE 'کتاب%';
SELECT name FROM products WHERE name ILIKE '%ssd%';
خروجی
name
-------------
کتاب پایتون
کتاب لینوکس
کتاب داکر
(3 rows)
name
-------------
SSD 512 گیگ
(1 row)

در LIKE، % یعنی «هر تعداد حرف (حتی صفر)» و _ یعنی «دقیقاً یک حرف». 'کتاب%' یعنی «با کتاب شروع شود» و '%ssd%' یعنی «جایی ssd داشته باشد». ILIKE (مخصوص PostgreSQL) به بزرگی و کوچکی حروف حساس نیست؛ برای همین ssd کوچک، SSD را پیدا کرد. (شبیه wildcardهای * و ? در shell، با علامت‌های دیگر.)

⚡ بررسی سریع

کدام الگو اسم‌هایی را پیدا می‌کند که به «گیگ» ختم می‌شوند؟

می‌خواهی «کتاب‌ها یا کالاهای شبکه، که گران‌تر از یک میلیون باشند». بدون پرانتز:

SELECT name FROM products WHERE category = 'کتاب' OR category = 'شبکه' AND price > 1000000;
خروجی
name
--------------
کتاب پایتون
کتاب لینوکس
کتاب داکر
روتر وای‌فای
سوئیچ 8 پورت
(5 rows)

کتاب‌های ارزان هم آمدند! چون AND قبل از OR حساب می‌شود (مثل ضرب قبل از جمع)، و شرط این‌طور خوانده شد: «کتاب باشد، یا (شبکه باشد و گران)». با پرانتز:

SELECT name FROM products WHERE (category = 'کتاب' OR category = 'شبکه') AND price > 1000000;
خروجی
name
--------------
روتر وای‌فای
سوئیچ 8 پورت
(2 rows)

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

مثال ۶: NULL، مقدار نامعلوم

Section titled “مثال ۶: NULL، مقدار نامعلوم”
SELECT name, discount FROM products WHERE discount = NULL;
SELECT name, discount FROM products WHERE discount IS NULL LIMIT 3;
خروجی
name | discount
------+----------
(0 rows)
name | discount
----------------+----------
کتاب لینوکس |
کتاب داکر |
کیبورد مکانیکی |
(3 rows)

اولی هیچ سطری نداد، با اینکه هفت کالا تخفیف ندارند. NULL یعنی «نامعلوم»، و «آیا نامعلوم برابر نامعلوم است؟» جوابش هم نامعلوم است، نه «درست». برای همین هر مقایسه‌ای با NULL (=، <>، <) هرگز درست نمی‌شود. همیشه IS NULL و IS NOT NULL. برای جایگزین کردن NULL با یک مقدار پیش‌فرض، COALESCE (مثال ۸).

مثال ۷: ستون محاسبه‌ای و AS

Section titled “مثال ۷: ستون محاسبه‌ای و AS”
SELECT name, price, stock, price * stock AS stock_value
FROM products
ORDER BY stock_value DESC
LIMIT 3;
خروجی
name | price | stock | stock_value
----------------+---------+-------+-------------
SSD 512 گیگ | 2750000 | 7 | 19250000
کیبورد مکانیکی | 1890000 | 8 | 15120000
فلش 64 گیگ | 290000 | 50 | 14500000
(3 rows)

SELECT می‌تواند حساب کند: price * stock ارزش موجودی هر کالاست. AS به نتیجه اسم می‌دهد (اسم مستعار یا alias) که در سرستون می‌آید و در ORDER BY هم قابل‌استفاده است. LIMIT 3 فقط سه سطر اول را می‌دهد: «سه کالایی که بیشترین پول در انبار خوابانده‌اند».

مثال ۸: COALESCE و قیمت نهایی

Section titled “مثال ۸: COALESCE و قیمت نهایی”
SELECT name, price,
COALESCE(discount, 0) AS discount,
price * (100 - COALESCE(discount, 0)) / 100 AS final_price
FROM products
WHERE category = 'لوازم جانبی';
خروجی
name | price | discount | final_price
----------------+---------+----------+-------------
ماوس بی‌سیم | 450000 | 5 | 427500
کیبورد مکانیکی | 1890000 | 0 | 1890000
هدفون | 1250000 | 15 | 1062500
(3 rows)

COALESCE(a, b) اولین مقدار غیر NULL را برمی‌گرداند؛ اینجا یعنی «اگر تخفیف نیست، صفر». بدون آن، هر حسابی با NULL (مثل 100 - NULL) خودش NULL می‌شد و قیمت نهایی کیبورد خالی می‌ماند.

مثال ۹: تاریخ، DISTINCT و صفحه‌بندی

Section titled “مثال ۹: تاریخ، DISTINCT و صفحه‌بندی”
SELECT name, added_on FROM products WHERE added_on >= '2026-08-01' ORDER BY added_on;
SELECT DISTINCT category FROM products ORDER BY category;
SELECT name, price FROM products ORDER BY price DESC LIMIT 3 OFFSET 3;
خروجی
name | added_on
--------------+------------
کتاب داکر | 2026-08-01
روتر وای‌فای | 2026-08-25
SSD 512 گیگ | 2026-09-12
سوئیچ 8 پورت | 2026-10-02
(4 rows)
category
-------------
ذخیره‌سازی
شبکه
لوازم جانبی
کتاب
(4 rows)
name | price
----------------+---------
کیبورد مکانیکی | 1890000
سوئیچ 8 پورت | 1650000
هدفون | 1250000
(3 rows)
  • تاریخ‌ها با قالب 'YYYY-MM-DD' و با همان عملگرهای مقایسه کار می‌کنند.
  • DISTINCT تکراری‌ها را حذف می‌کند: فهرست دسته‌های متفاوت.
  • LIMIT 3 OFFSET 3 یعنی «سه سطر اول را رد کن و سه تای بعدی را بده»؛ همان «صفحه‌ی دوم» یک فهرست در سایت. (برای صفحه‌بندی همیشه ORDER BY لازم است، وگرنه صفحه‌ها ممکن است هم‌پوشانی داشته باشند.)

پشت پرده: SQL «چه» می‌گوید، نه «چطور»

Section titled “پشت پرده: SQL «چه» می‌گوید، نه «چطور»”

در پایتون حلقه می‌نوشتی: «برای هر سطر، اگر شرط درست بود، به لیست اضافه کن». در SQL فقط می‌گویی چه می‌خواهی («کتاب‌های گران‌تر از …، مرتب‌شده بر اساس قیمت») و دیتابیس خودش تصمیم می‌گیرد چطور پیدایشان کند: کل جدول را بخواند، از یک ایندکس استفاده کند، یا اول مرتب کند و بعد فیلتر. به این زبان اعلانی (declarative) می‌گویند. بخشی از PostgreSQL به اسم برنامه‌ریز (planner) بهترین راه را انتخاب می‌کند؛ در درس ایندکس با EXPLAIN نقشه‌ای را که کشیده می‌بینی.

ترتیب منطقی اجرا هم با ترتیب نوشتن فرق دارد (شکل بالای درس): اول FROM و WHERE، بعد SELECT، بعد ORDER BY و LIMIT. برای همین اسم مستعاری که در SELECT ساخته‌ای (stock_value) در ORDER BY قابل‌استفاده است ولی در WHERE نه (اشتباهات رایج ۵).

عملگر معنی مثال
= / <> برابر / نابرابر category = 'کتاب'
< > <= >= مقایسه price > 1000000
BETWEEN a AND b در بازه (با دو سر) price BETWEEN 1 AND 9
IN (...) یکی از فهرست id IN (1, 3, 5)
LIKE / ILIKE الگو (% هر چیز، _ یک حرف) / بدون حساسیت به حروف name ILIKE '%ssd%'
IS NULL / IS NOT NULL خالی بودن discount IS NULL
AND / OR / NOT ترکیب (AND اول؛ پرانتز بگذار) (a OR b) AND c
بخش کار
SELECT a, b AS x ستون‌ها و اسم مستعار
DISTINCT حذف تکراری
ORDER BY a DESC, b مرتب‌سازی (چند ستونه)
LIMIT n OFFSET m فقط n سطر بعد از m سطر
COALESCE(x, 0) جایگزین NULL

مثال ۶: صفر سطر، بدون هیچ خطایی. خطرناک است چون ساکت است. راه‌حل: IS NULL.

۲) فراموش کردن پرانتز بین AND و OR

Section titled “۲) فراموش کردن پرانتز بین AND و OR”

مثال ۵. راه‌حل: همیشه پرانتز.

۳) جا انداختن ویرگول بین ستون‌ها

Section titled “۳) جا انداختن ویرگول بین ستون‌ها”
SELECT name price FROM products LIMIT 2;
خروجی
price
-------------
کتاب پایتون
کتاب لینوکس
(2 rows)

هیچ خطایی نیامد؛ ولی سرستون می‌گوید price و داخلش اسم‌هاست! بدون ویرگول، price اسم مستعار ستون name شد (AS اختیاری است). راه‌حل: SELECT name, price.

۴) نگه داشتن عدد به شکل متن

Section titled “۴) نگه داشتن عدد به شکل متن”
SELECT '9' > '10' AS text_compare, 9 > 10 AS number_compare;
خروجی
text_compare | number_compare
--------------+----------------
t | f
(1 row)

متن‌ها حرف به حرف مقایسه می‌شوند و «۹» از «۱» بزرگ‌تر است، پس '9' > '10' درست است (t). اگر قیمت را در ستون TEXT نگه داری، مرتب‌سازی و مقایسه‌ات خراب می‌شود. راه‌حل: نوع درست برای هر ستون (INTEGER، NUMERIC، DATE).

۵) استفاده از اسم مستعار در WHERE

Section titled “۵) استفاده از اسم مستعار در WHERE”

SELECT price * stock AS v FROM products WHERE v > 10000000; خطای column "v" does not exist می‌دهد، چون WHERE قبل از SELECT اجرا می‌شود. راه‌حل: خود عبارت را تکرار کن: WHERE price * stock > 10000000.

۶) کوتیشن دوتایی برای متن

Section titled “۶) کوتیشن دوتایی برای متن”
خروجی
ERROR: column "هدفون" does not exist
LINE 1: SELECT name FROM products WHERE name = "هدفون";
^

راه‌حل: 'هدفون' (درس قبل).

تمرینآسان

اسم و موجودی همه‌ی کالاهایی را بده که موجودی‌شان صفر نیست و قیمتشان زیر ۵۰۰ هزار تومان است، از ارزان به گران.

دیدن جواب
SELECT name, price, stock FROM products WHERE stock > 0 AND price < 500000 ORDER BY price;
خروجی
name | price | stock
-------------+--------+-------
کابل شبکه | 95000 | 120
فلش 64 گیگ | 290000 | 50
کتاب پایتون | 320000 | 12
کتاب داکر | 350000 | 5
ماوس بی‌سیم | 450000 | 30
(5 rows)
تمرینمتوسط

تمرین اصلی: فهرست کالاهایی را بده که تخفیف دارند، با اسم، دسته و درصد تخفیف، از بیشترین تخفیف به کمترین.

دیدن جواب
SELECT name, category, discount
FROM products
WHERE discount IS NOT NULL
ORDER BY discount DESC;
خروجی
name | category | discount
--------------+-------------+----------
سوئیچ 8 پورت | شبکه | 20
هدفون | لوازم جانبی | 15
کتاب پایتون | کتاب | 10
SSD 512 گیگ | ذخیره‌سازی | 10
ماوس بی‌سیم | لوازم جانبی | 5
(5 rows)

اگر WHERE discount > 0 می‌نوشتی هم همین جواب می‌آمد، چون مقایسه با NULL هرگز درست نیست؛ ولی IS NOT NULL نیتت را صریح‌تر می‌گوید.

تمرینسخت

کالاهایی را پیدا کن که در سه ماه اول سال ۲۰۲۶ (ژانویه تا مارس) اضافه شده‌اند، و برای هر کدام «قیمت نهایی» بعد از تخفیف را هم با اسم مستعار final_price نشان بده؛ مرتب بر اساس تاریخ.

دیدن جواب
SELECT name, added_on, price,
price * (100 - COALESCE(discount, 0)) / 100 AS final_price
FROM products
WHERE added_on BETWEEN '2026-01-01' AND '2026-03-31'
ORDER BY added_on;
خروجی
name | added_on | price | final_price
-------------+------------+--------+-------------
کابل شبکه | 2026-01-05 | 95000 | 95000
کتاب لینوکس | 2026-01-22 | 280000 | 280000
ماوس بی‌سیم | 2026-02-14 | 450000 | 427500
کتاب پایتون | 2026-03-10 | 320000 | 288000
(4 rows)

BETWEEN روی تاریخ هم دو سر را شامل می‌شود. (برای ستون‌های TIMESTAMP که ساعت هم دارند، < '2026-04-01' امن‌تر است، چون '2026-03-31' یعنی نیمه‌شب آغاز آن روز.)

آزمونک
  1. کدام شرط کالاهای بدون تخفیف را پیدا می‌کند؟

  2. در LIKE، الگوی 'a_c' با کدام جور است؟

  3. a OR b AND c چطور خوانده می‌شود؟

  4. بدون ORDER BY ترتیب سطرهای SELECT…

  5. LIMIT 10 OFFSET 20 چه سطرهایی می‌دهد؟

  6. COALESCE(discount, 0) چه می‌کند؟

  • SELECT ستون‌ها FROM جدول WHERE شرط ORDER BY ... LIMIT ...؛ ستون‌های لازم را بخواه، نه *.
  • فیلترها: =، <>، مقایسه‌ها، BETWEEN (با دو سر)، IN، LIKE/ILIKE با % و _.
  • AND قبل از OR حساب می‌شود؛ همیشه پرانتز.
  • NULL یعنی نامعلوم: IS NULL / IS NOT NULL، و COALESCE برای مقدار پیش‌فرض.
  • SELECT حساب می‌کند (price * stock AS v)؛ اسم مستعار در ORDER BY کار می‌کند، در WHERE نه.
  • DISTINCT برای تکراری‌ها، LIMIT ... OFFSET ... برای صفحه‌بندی (همیشه با ORDER BY).
برگه‌ی تقلب این درس
دستورکاری که می‌کند
SELECT name, price FROM products WHERE category = 'کتاب';فیلتر ساده
ORDER BY price DESC LIMIT 5پنج گران‌ترین
WHERE price BETWEEN 1 AND 9بازه
WHERE category IN ('a', 'b')فهرست
WHERE name ILIKE '%ssd%'جستجوی متن بدون حساسیت به حروف
WHERE discount IS NULLخالی‌ها
COALESCE(discount, 0)جایگزین NULL
SELECT price * stock AS valueستون محاسبه‌ای
SELECT DISTINCT categoryمقدارهای یکتا
psql shop -f file.sqlاجرای فایل SQL