فصل ۲: SQL و انواع داده‌ی پستگرس

شناسه‌ها و انواع ویژه: identity، uuid، enum، array، range و jsonb

ابزارهایی که MySQL ندارد

identity در برابر serial

serial روش قدیمی است: یک sequence جدا می‌سازد و DEFAULT ستون را روی nextval می‌گذارد. GENERATED ... AS IDENTITY روش استاندارد SQL و توصیه‌ی امروز است: sequence به ستون گره خورده، مجوزها ساده‌ترند و با ALWAYS نمی‌شود تصادفاً مقدار دستی درج کرد.

CREATE TABLE looms (
  id        int GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  code      text UNIQUE NOT NULL,
  reed      smallint NOT NULL,            -- شانه: 700، 1000، 1200
  density   smallint NOT NULL             -- تراکم: 2550، 3000
);
-- درج مقدار دستی (مثلاً هنگام مهاجرت داده):
INSERT INTO looms (id, code, reed, density) OVERRIDING SYSTEM VALUE VALUES (100, 'L-100', 1200, 3600);
SELECT setval(pg_get_serial_sequence('looms', 'id'), (SELECT max(id) FROM looms));

uuid

gen_random_uuid() از نسخه‌ی 13 داخلی است و uuid نسخه‌ی 4 (کاملاً تصادفی) می‌سازد. uuid برای شناسه‌ای که در URL یا بین سیستم‌ها جابه‌جا می‌شود عالی است، اما تصادفی بودن آن درج در ایندکس B-tree را پراکنده می‌کند. نسخه‌ی 18 تابع uuidv7() را اضافه کرده که بر اساس زمان مرتب است و این مشکل را ندارد.

enum

CREATE TYPE order_status AS ENUM ('draft', 'confirmed', 'weaving', 'finishing', 'delivered', 'cancelled');
ALTER TYPE order_status ADD VALUE 'qc' BEFORE 'finishing';

enum چهار بایت جا می‌گیرد و به ترتیب تعریف مرتب می‌شود. اما حذف یا تغییر نام مقدار دردسر دارد؛ برای فهرست‌هایی که کاربر مدیریت می‌کند جدول مرجع با کلید خارجی بهتر است.

array

CREATE TABLE products (id int PRIMARY KEY, name text, tags text[] NOT NULL DEFAULT '{}');
INSERT INTO products VALUES (1, 'فرش ۱۲۰۰ شانه افشان', ARRAY['ماشینی', 'اکریلیک', 'کلاسیک']);
SELECT name FROM products WHERE 'کلاسیک' = ANY (tags);
SELECT name FROM products WHERE tags @> ARRAY['ماشینی', 'اکریلیک'];   -- شامل هر دو

range

بازه‌ها (int4range، numrange، daterange، tstzrange) یک نوع داده‌اند با عملگرهای هم‌پوشانی (&&) و شمول (@>). پیش‌فرض کران‌ها [) است: ابتدا شامل، انتها نه.

SELECT tstzrange('2026-09-28 08:00+03:30', '2026-09-28 16:00+03:30') &&
       tstzrange('2026-09-28 15:00+03:30', '2026-09-28 20:00+03:30');   -- true
SELECT daterange('2026-09-23', '2026-10-23') @> current_date;

jsonb

json متن خام را نگه می‌دارد؛ jsonb ساختار باینری تجزیه‌شده است: کلیدهای تکراری حذف و ترتیب کلیدها عوض می‌شود، اما قابل ایندکس و بسیار سریع‌تر در جست‌وجوست. تقریباً همیشه jsonb می‌خواهید؛ عملگرها و ایندکس آن در فصل‌های ۴ و ۵.

نکته‌هایی که کمتر کسی می‌داند

  • sequenceها تراکنشی نیستند: INSERT ناموفق یا ROLLBACK‌شده هم یک عدد مصرف می‌کند. شماره‌ی فاکتور بدون حفره را با sequence نسازید؛ یک جدول شمارنده با UPDATE ... RETURNING داخل همان تراکنش لازم است.
  • آرایه‌ها از ۱ شروع می‌شوند و array_length('{}'::int[], 1) مقدار NULL برمی‌گرداند نه صفر؛ برای شمارش از cardinality() استفاده کنید.
  • ALTER TYPE ... ADD VALUE داخل تراکنش مجاز است، اما مقدار جدید تا commit همان تراکنش قابل استفاده نیست؛ migrationی که مقدار را اضافه و بلافاصله استفاده می‌کند شکست می‌خورد.
  • از نسخه‌ی 14، multirange هم داریم: range_agg(during) چند بازه‌ی هم‌پوشان را به یک مجموعه‌ی ادغام‌شده تبدیل می‌کند؛ برای محاسبه‌ی «ساعت‌های کاری واقعی یک دستگاه» عالی است.
  • در jsonb عدد با دقت numeric ذخیره می‌شود، اما در JavaScript بیشتر از 2^53 دقت از دست می‌رود؛ شناسه‌های bigint را در API به‌صورت رشته بفرستید.

برای ذخیره‌ی پیشرفت و شرکت در آزمون، وارد شوید — رایگان است.