Schema และ Table เป็นโครงสร้างพื้นฐานที่สำคัญที่สุดในระบบฐานข้อมูล PostgreSQL ก่อนที่จะเริ่มเก็บข้อมูลใด ๆ ได้ จำเป็นต้องออกแบบและสร้าง Schema เพื่อจัดกลุ่มตาราง แล้วจึงสร้างตารางพร้อมกำหนดคอลัมน์ ชนิดข้อมูล และ Constraints ให้เหมาะสม
บทความนี้จะอธิบายการสร้างและจัดการ Schema การสร้างตารางพร้อมตัวอย่างจริง การเลือกชนิดข้อมูลที่เหมาะสม การใช้ Constraints ต่าง ๆ รวมถึงเทคนิคขั้นสูงอย่าง Table Inheritance และ Partitioning
ทำความเข้าใจ Schema
Schema คือ Namespace ที่ใช้จัดกลุ่มวัตถุต่าง ๆ ในฐานข้อมูล เช่น ตาราง View Function และ Sequence ทุกฐานข้อมูลจะมี Schema เริ่มต้นชื่อ public ซึ่งเป็นที่เก็บวัตถุทั้งหมดหากไม่ได้ระบุ Schema เฉพาะ
การใช้ Schema แยกกันช่วยจัดระเบียบฐานข้อมูลได้ดีขึ้น เช่น แยกตามทีม แยกตามโมดูลของแอปพลิเคชัน หรือแยกข้อมูลระหว่างผู้เช่า (Multi-Tenant) ทำให้จัดการสิทธิ์และดูแลรักษาได้สะดวกขึ้น
การสร้างและจัดการ Schema
สร้าง Schema
-- สร้าง schema พื้นฐาน
CREATE SCHEMA inventory;
-- สร้าง schema พร้อมกำหนดเจ้าของ
CREATE SCHEMA sales AUTHORIZATION app_user;
-- สร้าง schema ถ้ายังไม่มี (ป้องกัน error)
CREATE SCHEMA IF NOT EXISTS analytics;
-- ดู schema ทั้งหมดใน database
SELECT schema_name FROM information_schema.schemata;
ตั้งค่า Search Path
Search Path กำหนดลำดับการค้นหา Schema เมื่อเรียกชื่อวัตถุโดยไม่ระบุ Schema นำหน้า
-- ดู search_path ปัจจุบัน
SHOW search_path;
-- ตั้ง search_path สำหรับ session ปัจจุบัน
SET search_path TO inventory, public;
-- ตั้ง search_path ถาวรสำหรับ role
ALTER ROLE app_user SET search_path = inventory, sales, public;
-- ตั้ง search_path ถาวรสำหรับ database
ALTER DATABASE mydb SET search_path = inventory, public;
แก้ไขและลบ Schema
-- เปลี่ยนชื่อ schema
ALTER SCHEMA old_name RENAME TO new_name;
-- เปลี่ยนเจ้าของ schema
ALTER SCHEMA inventory OWNER TO admin_user;
-- ลบ schema (ต้องว่างเปล่า)
DROP SCHEMA temp_schema;
-- ลบ schema พร้อมวัตถุทั้งหมดข้างใน
DROP SCHEMA temp_schema CASCADE;
ชนิดข้อมูล (Data Types) ที่ใช้บ่อย
การเลือกชนิดข้อมูลที่เหมาะสมมีผลต่อทั้งประสิทธิภาพและความถูกต้องของข้อมูล ด้านล่างเป็นชนิดข้อมูลหลักที่ควรรู้จัก
ตัวเลข (Numeric Types)
-- จำนวนเต็ม
SMALLINT -- -32,768 ถึง 32,767 (2 bytes)
INTEGER -- -2 พันล้าน ถึง 2 พันล้าน (4 bytes)
BIGINT -- -9 ล้านล้าน ถึง 9 ล้านล้าน (8 bytes)
-- Auto-increment
SMALLSERIAL -- SMALLINT + auto increment
SERIAL -- INTEGER + auto increment
BIGSERIAL -- BIGINT + auto increment
-- ทศนิยม
NUMERIC(10, 2) -- ทศนิยมแม่นยำ (เหมาะกับเงิน)
REAL -- ทศนิยม 6 หลัก (4 bytes)
DOUBLE PRECISION -- ทศนิยม 15 หลัก (8 bytes)
ข้อความ (Text Types)
CHAR(n) -- ความยาวคงที่ เติมช่องว่างให้ครบ
VARCHAR(n) -- ความยาวจำกัด ไม่เติมช่องว่าง
TEXT -- ความยาวไม่จำกัด (แนะนำใช้แทน VARCHAR)
วันที่และเวลา (Date/Time Types)
DATE -- วันที่อย่างเดียว (2026-04-07)
TIME -- เวลาอย่างเดียว (14:30:00)
TIMESTAMP -- วันที่ + เวลา (ไม่มี timezone)
TIMESTAMPTZ -- วันที่ + เวลา + timezone (แนะนำ)
INTERVAL -- ช่วงเวลา ('1 year 2 months 3 days')
ชนิดข้อมูลพิเศษ
BOOLEAN -- true / false / null
UUID -- Universally Unique Identifier
JSONB -- JSON แบบ binary (ค้นหาเร็ว แนะนำ)
JSON -- JSON แบบ text (เก็บตามต้นฉบับ)
INET -- IP address (IPv4/IPv6)
CIDR -- IP network
MACADDR -- MAC address
BYTEA -- ข้อมูล binary
ARRAY -- อาร์เรย์ เช่น INTEGER[], TEXT[]
MONEY -- สกุลเงิน (ไม่แนะนำ ใช้ NUMERIC แทน)
การสร้างตาราง (CREATE TABLE)
โครงสร้างพื้นฐาน
CREATE TABLE inventory.products (
id SERIAL PRIMARY KEY,
sku VARCHAR(50) NOT NULL UNIQUE,
name VARCHAR(200) NOT NULL,
description TEXT,
price NUMERIC(10, 2) NOT NULL CHECK (price >= 0),
quantity INTEGER NOT NULL DEFAULT 0 CHECK (quantity >= 0),
category_id INTEGER,
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
ตัวอย่างข้างต้นสร้างตาราง products ใน schema inventory โดยมี Primary Key แบบ auto-increment, Constraints หลายตัว และค่า Default สำหรับบางคอลัมน์
ตารางที่มี Foreign Key
-- สร้างตาราง categories ก่อน
CREATE TABLE inventory.categories (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL UNIQUE,
slug VARCHAR(100) NOT NULL UNIQUE
);
-- สร้างตาราง orders ที่อ้างอิง customers
CREATE TABLE sales.orders (
id BIGSERIAL PRIMARY KEY,
customer_id INTEGER NOT NULL REFERENCES sales.customers(id),
order_date TIMESTAMPTZ NOT NULL DEFAULT NOW(),
status VARCHAR(20) NOT NULL DEFAULT 'pending'
CHECK (status IN ('pending', 'confirmed', 'shipped', 'delivered', 'cancelled')),
total_amount NUMERIC(12, 2) NOT NULL CHECK (total_amount >= 0)
);
-- สร้างตาราง order_items ที่อ้างอิงทั้ง orders และ products
CREATE TABLE sales.order_items (
id BIGSERIAL PRIMARY KEY,
order_id BIGINT NOT NULL REFERENCES sales.orders(id) ON DELETE CASCADE,
product_id INTEGER NOT NULL REFERENCES inventory.products(id) ON DELETE RESTRICT,
quantity INTEGER NOT NULL CHECK (quantity > 0),
unit_price NUMERIC(10, 2) NOT NULL CHECK (unit_price >= 0),
UNIQUE (order_id, product_id)
);
Foreign Key กำหนดความสัมพันธ์ระหว่างตาราง โดย ON DELETE CASCADE หมายถึงเมื่อลบแถวหลักจะลบแถวที่อ้างอิงด้วย ส่วน ON DELETE RESTRICT จะป้องกันไม่ให้ลบแถวหลักหากยังมีแถวอ้างอิงอยู่
Constraints ที่ควรรู้
PRIMARY KEY
-- Primary Key คอลัมน์เดียว
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) NOT NULL
);
-- Composite Primary Key (หลายคอลัมน์)
CREATE TABLE user_roles (
user_id INTEGER NOT NULL REFERENCES users(id),
role_id INTEGER NOT NULL REFERENCES roles(id),
PRIMARY KEY (user_id, role_id)
);
UNIQUE
-- Unique คอลัมน์เดียว
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE
);
-- Unique หลายคอลัมน์ (composite unique)
CREATE TABLE subscriptions (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL,
plan_id INTEGER NOT NULL,
UNIQUE (user_id, plan_id)
);
-- Partial Unique (unique เฉพาะเงื่อนไข)
CREATE UNIQUE INDEX idx_active_email
ON users (email) WHERE is_active = true;
CHECK
CREATE TABLE employees (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
age INTEGER CHECK (age >= 18 AND age <= 100),
salary NUMERIC(10, 2) CHECK (salary > 0),
email VARCHAR(255) CHECK (email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$'),
CONSTRAINT valid_hire_date CHECK (hire_date <= CURRENT_DATE)
);
NOT NULL และ DEFAULT
CREATE TABLE articles (
id SERIAL PRIMARY KEY,
title VARCHAR(200) NOT NULL,
content TEXT NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'draft',
view_count INTEGER NOT NULL DEFAULT 0,
published_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
การใช้ Generated Columns
Generated Column คือคอลัมน์ที่คำนวณค่าอัตโนมัติจากคอลัมน์อื่นในตารางเดียวกัน ไม่ต้องคำนวณเองทุกครั้งที่ query
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name VARCHAR(200) NOT NULL,
price NUMERIC(10, 2) NOT NULL,
tax_rate NUMERIC(4, 2) NOT NULL DEFAULT 0.07,
-- Stored generated column
price_with_tax NUMERIC(10, 2) GENERATED ALWAYS AS (price * (1 + tax_rate)) STORED
);
-- ตัวอย่าง: full_name จากชื่อ + นามสกุล
CREATE TABLE contacts (
id SERIAL PRIMARY KEY,
first_name VARCHAR(100) NOT NULL,
last_name VARCHAR(100) NOT NULL,
full_name TEXT GENERATED ALWAYS AS (first_name || ' ' || last_name) STORED
);
การแก้ไขตาราง (ALTER TABLE)
-- เพิ่มคอลัมน์
ALTER TABLE inventory.products ADD COLUMN weight NUMERIC(8, 2);
-- ลบคอลัมน์
ALTER TABLE inventory.products DROP COLUMN weight;
-- เปลี่ยนชนิดข้อมูล
ALTER TABLE inventory.products ALTER COLUMN sku TYPE VARCHAR(100);
-- เปลี่ยน default value
ALTER TABLE inventory.products ALTER COLUMN quantity SET DEFAULT 10;
-- เพิ่ม NOT NULL
ALTER TABLE inventory.products ALTER COLUMN description SET NOT NULL;
-- ลบ NOT NULL
ALTER TABLE inventory.products ALTER COLUMN description DROP NOT NULL;
-- เพิ่ม CHECK constraint
ALTER TABLE inventory.products ADD CONSTRAINT check_sku_format
CHECK (sku ~ '^[A-Z]{2,4}-[0-9]{4,8}$');
-- ลบ constraint
ALTER TABLE inventory.products DROP CONSTRAINT check_sku_format;
-- เปลี่ยนชื่อคอลัมน์
ALTER TABLE inventory.products RENAME COLUMN name TO product_name;
-- เปลี่ยนชื่อตาราง
ALTER TABLE inventory.products RENAME TO items;
-- ย้ายตารางไป schema อื่น
ALTER TABLE inventory.items SET SCHEMA sales;
Index สำหรับเพิ่มประสิทธิภาพ
Index ช่วยเร่งความเร็วในการค้นหาข้อมูล แต่ใช้พื้นที่เพิ่มและทำให้การเขียนข้อมูลช้าลงเล็กน้อย ควรสร้าง Index เฉพาะคอลัมน์ที่ใช้ค้นหาบ่อย
-- B-tree Index (ค่าเริ่มต้น เหมาะกับ =, <, >, BETWEEN)
CREATE INDEX idx_products_category ON inventory.products (category_id);
-- Unique Index
CREATE UNIQUE INDEX idx_products_sku ON inventory.products (sku);
-- Composite Index (หลายคอลัมน์)
CREATE INDEX idx_orders_customer_date
ON sales.orders (customer_id, order_date DESC);
-- Partial Index (เฉพาะเงื่อนไข)
CREATE INDEX idx_active_products
ON inventory.products (name) WHERE is_active = true;
-- GIN Index สำหรับ JSONB
CREATE INDEX idx_products_metadata
ON inventory.products USING GIN (metadata);
-- GIN Index สำหรับ Full-Text Search
CREATE INDEX idx_products_search
ON inventory.products USING GIN (to_tsvector('english', name || ' ' || description));
-- Expression Index
CREATE INDEX idx_products_lower_name
ON inventory.products (LOWER(name));
Table Partitioning
Partitioning แบ่งตารางขนาดใหญ่ออกเป็นส่วนย่อย ช่วยเพิ่มประสิทธิภาพการ query และจัดการข้อมูลเก่าได้สะดวก
Range Partitioning
-- สร้างตารางหลักแบบ partitioned
CREATE TABLE sales.transactions (
id BIGSERIAL,
customer_id INTEGER NOT NULL,
amount NUMERIC(12, 2) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
) PARTITION BY RANGE (created_at);
-- สร้าง partition สำหรับแต่ละเดือน
CREATE TABLE sales.transactions_2026_01
PARTITION OF sales.transactions
FOR VALUES FROM ('2026-01-01') TO ('2026-02-01');
CREATE TABLE sales.transactions_2026_02
PARTITION OF sales.transactions
FOR VALUES FROM ('2026-02-01') TO ('2026-03-01');
CREATE TABLE sales.transactions_2026_03
PARTITION OF sales.transactions
FOR VALUES FROM ('2026-03-01') TO ('2026-04-01');
-- สร้าง default partition (จับข้อมูลที่ไม่ตรงเงื่อนไขใด)
CREATE TABLE sales.transactions_default
PARTITION OF sales.transactions DEFAULT;
List Partitioning
-- แบ่งตามค่าที่กำหนด
CREATE TABLE sales.orders_by_region (
id BIGSERIAL,
region VARCHAR(20) NOT NULL,
total NUMERIC(12, 2) NOT NULL,
order_date TIMESTAMPTZ NOT NULL DEFAULT NOW()
) PARTITION BY LIST (region);
CREATE TABLE sales.orders_north
PARTITION OF sales.orders_by_region
FOR VALUES IN ('north', 'northeast');
CREATE TABLE sales.orders_central
PARTITION OF sales.orders_by_region
FOR VALUES IN ('central', 'east', 'west');
CREATE TABLE sales.orders_south
PARTITION OF sales.orders_by_region
FOR VALUES IN ('south');
Temporary Tables
Temporary Table เป็นตารางชั่วคราวที่อยู่เฉพาะใน session หรือ transaction ปัจจุบัน เหมาะสำหรับเก็บผลลัพธ์ระหว่างการประมวลผล
-- Temporary table (หายเมื่อจบ session)
CREATE TEMPORARY TABLE temp_results (
id INTEGER,
score NUMERIC(5, 2),
status VARCHAR(20)
);
-- Temporary table ที่หายเมื่อจบ transaction
CREATE TEMPORARY TABLE temp_batch (
batch_id INTEGER,
data JSONB
) ON COMMIT DROP;
-- Temporary table ที่ลบข้อมูลเมื่อจบ transaction แต่โครงสร้างยังอยู่
CREATE TEMPORARY TABLE temp_staging (
row_id INTEGER,
value TEXT
) ON COMMIT DELETE ROWS;
ตัวอย่างการออกแบบฐานข้อมูลจริง
ด้านล่างเป็นตัวอย่างการสร้าง Schema และตารางสำหรับระบบ E-Commerce แบบครบวงจร
-- ============================================
-- E-Commerce Database Schema
-- ============================================
-- สร้าง schemas
CREATE SCHEMA IF NOT EXISTS store;
CREATE SCHEMA IF NOT EXISTS payment;
-- ตาราง customers
CREATE TABLE store.customers (
id BIGSERIAL PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
first_name VARCHAR(100) NOT NULL,
last_name VARCHAR(100) NOT NULL,
phone VARCHAR(20),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- ตาราง addresses
CREATE TABLE store.addresses (
id BIGSERIAL PRIMARY KEY,
customer_id BIGINT NOT NULL REFERENCES store.customers(id) ON DELETE CASCADE,
address TEXT NOT NULL,
city VARCHAR(100) NOT NULL,
province VARCHAR(100) NOT NULL,
postal_code VARCHAR(10) NOT NULL,
is_default BOOLEAN NOT NULL DEFAULT false
);
-- ตาราง categories
CREATE TABLE store.categories (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
slug VARCHAR(100) NOT NULL UNIQUE,
parent_id INTEGER REFERENCES store.categories(id)
);
-- ตาราง products
CREATE TABLE store.products (
id BIGSERIAL PRIMARY KEY,
sku VARCHAR(50) NOT NULL UNIQUE,
name VARCHAR(200) NOT NULL,
description TEXT,
price NUMERIC(10, 2) NOT NULL CHECK (price >= 0),
stock INTEGER NOT NULL DEFAULT 0 CHECK (stock >= 0),
category_id INTEGER REFERENCES store.categories(id),
metadata JSONB DEFAULT '{}',
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- ตาราง orders
CREATE TABLE store.orders (
id BIGSERIAL PRIMARY KEY,
customer_id BIGINT NOT NULL REFERENCES store.customers(id),
address_id BIGINT NOT NULL REFERENCES store.addresses(id),
status VARCHAR(20) NOT NULL DEFAULT 'pending'
CHECK (status IN ('pending','confirmed','shipped','delivered','cancelled')),
total NUMERIC(12, 2) NOT NULL DEFAULT 0,
note TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- ตาราง order_items
CREATE TABLE store.order_items (
id BIGSERIAL PRIMARY KEY,
order_id BIGINT NOT NULL REFERENCES store.orders(id) ON DELETE CASCADE,
product_id BIGINT NOT NULL REFERENCES store.products(id),
quantity INTEGER NOT NULL CHECK (quantity > 0),
unit_price NUMERIC(10, 2) NOT NULL,
UNIQUE (order_id, product_id)
);
-- ตาราง payments
CREATE TABLE payment.transactions (
id BIGSERIAL PRIMARY KEY,
order_id BIGINT NOT NULL REFERENCES store.orders(id),
method VARCHAR(30) NOT NULL CHECK (method IN ('credit_card','bank_transfer','promptpay')),
amount NUMERIC(12, 2) NOT NULL CHECK (amount > 0),
status VARCHAR(20) NOT NULL DEFAULT 'pending',
paid_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- สร้าง Indexes
CREATE INDEX idx_products_category ON store.products (category_id);
CREATE INDEX idx_products_active ON store.products (is_active) WHERE is_active = true;
CREATE INDEX idx_orders_customer ON store.orders (customer_id);
CREATE INDEX idx_orders_status ON store.orders (status);
CREATE INDEX idx_order_items_order ON store.order_items (order_id);
CREATE INDEX idx_payments_order ON payment.transactions (order_id);
CREATE INDEX idx_products_metadata ON store.products USING GIN (metadata);
สรุป
การสร้าง Schema และ Table ใน PostgreSQL เป็นพื้นฐานที่สำคัญของการออกแบบฐานข้อมูล Schema ช่วยจัดกลุ่มวัตถุให้เป็นระเบียบ ขณะที่ตารางต้องออกแบบให้มีชนิดข้อมูลและ Constraints ที่เหมาะสมตั้งแต่แรก การใช้ Index เร่งประสิทธิภาพ Partitioning จัดการข้อมูลขนาดใหญ่ และ Generated Columns ลดการคำนวณซ้ำ ล้วนเป็นเครื่องมือที่ช่วยให้ฐานข้อมูลทำงานได้อย่างมีประสิทธิภาพและดูแลรักษาง่าย
แนะนำบริการ DE
การออกแบบและสร้างฐานข้อมูลต้องอาศัยเซิร์ฟเวอร์ที่ให้คุณควบคุมการติดตั้งและตั้งค่าได้อย่างอิสระ Cloud VPS ของ DE รองรับการติดตั้ง PostgreSQL ทุกเวอร์ชัน พร้อม root access เต็มรูปแบบ สามารถปรับแต่ง Schema, Partitioning, Index และ Extension ต่าง ๆ ได้ตามต้องการ เหมาะสำหรับงาน Production ที่ต้องการประสิทธิภาพสูง
สำหรับผู้ที่ต้องการโฮสต์เว็บไซต์หรือแอปพลิเคชันที่ใช้ฐานข้อมูลโดยไม่ต้องจัดการเซิร์ฟเวอร์เอง Cloud Hosting ของ DE มีระบบจัดการฐานข้อมูลผ่าน Control Panel ที่ใช้งานง่าย พร้อมรองรับเว็บไซต์ได้ทันที

