PostgreSQL รองรับ Data Types หลากหลายประเภท ตั้งแต่ตัวเลข ข้อความ วันเวลา ไปจนถึงชนิดพิเศษอย่าง JSON, Array และ Network Address การเลือกใช้ชนิดข้อมูลที่เหมาะสมช่วยให้ฐานข้อมูลทำงานได้เร็วขึ้น ใช้พื้นที่จัดเก็บน้อยลง และป้องกันข้อมูลผิดพลาดตั้งแต่ระดับโครงสร้าง
บทความนี้จะอธิบาย Data Types ทุกกลุ่มใน PostgreSQL อย่างละเอียด พร้อมตัวอย่างการใช้งานจริง เปรียบเทียบข้อดีข้อเสียของแต่ละชนิด และแนวทางเลือกใช้ให้เหมาะกับสถานการณ์ต่าง ๆ
Numeric Types — ชนิดข้อมูลตัวเลข
PostgreSQL แบ่งชนิดข้อมูลตัวเลขออกเป็น 3 กลุ่มหลัก ได้แก่ Integer (จำนวนเต็ม), Floating-Point (ทศนิยมแบบประมาณค่า) และ Exact Numeric (ทศนิยมแบบแม่นยำ) แต่ละกลุ่มเหมาะกับงานที่ต่างกัน
Integer Types
ชนิดจำนวนเต็มมี 3 ขนาดให้เลือก ขึ้นอยู่กับช่วงค่าที่ต้องการ
| ชนิด | ขนาด | ช่วงค่า | เหมาะกับ |
|---|---|---|---|
| SMALLINT | 2 bytes | -32,768 ถึง 32,767 | สถานะ, rating, อายุ |
| INTEGER | 4 bytes | -2.1 พันล้าน ถึง 2.1 พันล้าน | ใช้ทั่วไป, Primary Key |
| BIGINT | 8 bytes | -9.2 ล้านล้านล้าน ถึง 9.2 ล้านล้านล้าน | ตารางขนาดใหญ่, timestamp |
-- ตัวอย่างการเลือก Integer Type
CREATE TABLE products (
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
quantity SMALLINT DEFAULT 0,
view_count BIGINT DEFAULT 0
);
GENERATED ALWAYS AS IDENTITY เป็นวิธีสร้าง Auto-increment ตามมาตรฐาน SQL แทนที่ SERIAL ซึ่งเป็นแบบเก่าของ PostgreSQL โดย IDENTITY column ป้องกันการ insert ค่าด้วยมือได้ดีกว่า
Serial Types (Auto-Increment)
Serial เป็น shorthand สำหรับสร้าง Integer column ที่มี Sequence อัตโนมัติ มี 3 ขนาดคือ SMALLSERIAL (2 bytes), SERIAL (4 bytes) และ BIGSERIAL (8 bytes) อย่างไรก็ตาม ตั้งแต่ PostgreSQL 10 เป็นต้นมา แนะนำให้ใช้ IDENTITY column แทน เพราะเป็นมาตรฐาน SQL และควบคุม permission ได้ดีกว่า
-- แบบเก่า (Serial)
CREATE TABLE logs_old (
id SERIAL PRIMARY KEY,
message TEXT
);
-- แบบใหม่ (Identity) — แนะนำ
CREATE TABLE logs_new (
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
message TEXT
);
Floating-Point Types
ชนิดทศนิยมแบบประมาณค่า ใช้สำหรับข้อมูลที่ยอมรับความคลาดเคลื่อนเล็กน้อยได้ เช่น พิกัดภูมิศาสตร์ คะแนน หรือข้อมูลทางวิทยาศาสตร์
| ชนิด | ขนาด | ความแม่นยำ |
|---|---|---|
| REAL (FLOAT4) | 4 bytes | 6 ตำแหน่งทศนิยม |
| DOUBLE PRECISION (FLOAT8) | 8 bytes | 15 ตำแหน่งทศนิยม |
-- ตัวอย่าง Floating-Point
CREATE TABLE locations (
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name TEXT NOT NULL,
latitude DOUBLE PRECISION,
longitude DOUBLE PRECISION
);
INSERT INTO locations (name, latitude, longitude)
VALUES ('Bangkok', 13.7563, 100.5018);
ข้อควรระวัง: ห้ามใช้ REAL หรือ DOUBLE PRECISION กับข้อมูลการเงิน เพราะอาจเกิดปัญหา rounding error เช่น 0.1 + 0.2 อาจไม่เท่ากับ 0.3 พอดี ให้ใช้ NUMERIC แทน
NUMERIC / DECIMAL — ทศนิยมแม่นยำ
NUMERIC (หรือ DECIMAL ซึ่งเหมือนกัน) เก็บค่าทศนิยมแบบแม่นยำ 100% เหมาะสำหรับข้อมูลการเงิน ราคาสินค้า หรือการคำนวณที่ต้องการความถูกต้องสูง
-- NUMERIC(precision, scale)
-- precision = จำนวนหลักทั้งหมด
-- scale = จำนวนหลักหลังจุดทศนิยม
CREATE TABLE transactions (
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
amount NUMERIC(12, 2) NOT NULL, -- สูงสุด 9,999,999,999.99
tax NUMERIC(5, 4), -- เช่น 0.0700 (7%)
total NUMERIC(12, 2)
);
-- NUMERIC ไม่มี rounding error
SELECT 0.1::NUMERIC + 0.2::NUMERIC = 0.3::NUMERIC;
-- ผลลัพธ์: true
NUMERIC ใช้พื้นที่จัดเก็บมากกว่าและคำนวณช้ากว่า Integer หรือ Floating-Point ดังนั้นควรใช้เฉพาะเมื่อต้องการความแม่นยำจริง ๆ เท่านั้น
Character Types — ชนิดข้อมูลข้อความ
ชนิดข้อความใน PostgreSQL มี 3 แบบหลัก แต่ละแบบมีพฤติกรรมและข้อจำกัดต่างกัน
| ชนิด | ความยาว | พฤติกรรม | เหมาะกับ |
|---|---|---|---|
| CHAR(n) | คงที่ n ตัวอักษร | เติมช่องว่างให้ครบ n | รหัสที่ยาวคงที่ เช่น ISO code |
| VARCHAR(n) | ไม่เกิน n ตัวอักษร | เก็บตามจริง + ตรวจสอบขนาด | ข้อมูลที่มีขีดจำกัด เช่น email, ชื่อ |
| TEXT | ไม่จำกัด | เก็บตามจริง | เนื้อหายาว เช่น บทความ, description |
CREATE TABLE users (
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
email VARCHAR(255) NOT NULL,
country_code CHAR(2), -- เช่น 'TH', 'US'
bio TEXT -- ไม่จำกัดความยาว
);
คำแนะนำ: ใน PostgreSQL นั้น TEXT กับ VARCHAR ที่ไม่ระบุ (n) มีประสิทธิภาพเท่ากัน ไม่มีความแตกต่างด้าน performance CHAR(n) ไม่ค่อยแนะนำเพราะเติมช่องว่างโดยไม่จำเป็น ยกเว้นข้อมูลที่ยาวคงที่จริง ๆ เช่น รหัสประเทศ 2 ตัวอักษร
Date/Time Types — ชนิดข้อมูลวันเวลา
PostgreSQL มีชนิดวันเวลาที่ครอบคลุมทั้งวันที่ เวลา เวลาพร้อม timezone และช่วงระยะเวลา
| ชนิด | ขนาด | ตัวอย่างค่า | เหมาะกับ |
|---|---|---|---|
| DATE | 4 bytes | 2026-04-07 | วันเกิด, วันที่เอกสาร |
| TIME | 8 bytes | 14:30:00 | เวลาโดยไม่สนใจ timezone |
| TIMESTAMP | 8 bytes | 2026-04-07 14:30:00 | วันเวลาภายใน timezone เดียว |
| TIMESTAMPTZ | 8 bytes | 2026-04-07 14:30:00+07 | วันเวลาข้าม timezone (แนะนำ) |
| INTERVAL | 16 bytes | 1 year 2 months 3 days | ช่วงระยะเวลา, อายุสมาชิก |
CREATE TABLE events (
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
title TEXT NOT NULL,
event_date DATE NOT NULL,
start_time TIMESTAMPTZ NOT NULL,
duration INTERVAL
);
INSERT INTO events (title, event_date, start_time, duration)
VALUES (
'Database Workshop',
'2026-05-15',
'2026-05-15 09:00:00+07',
'3 hours 30 minutes'
);
-- คำนวณเวลาสิ้นสุด
SELECT title,
start_time,
start_time + duration AS end_time
FROM events;
แนะนำให้ใช้ TIMESTAMPTZ แทน TIMESTAMP เสมอ เพราะ TIMESTAMPTZ เก็บเวลาเป็น UTC ภายใน และแปลงเป็น timezone ของ session อัตโนมัติ ช่วยป้องกันปัญหาเมื่อระบบให้บริการข้ามประเทศ ส่วน TIMESTAMP ไม่เก็บข้อมูล timezone ทำให้อาจเกิดความสับสนได้
ฟังก์ชันวันเวลาที่ใช้บ่อย
-- วันเวลาปัจจุบัน
SELECT NOW(); -- TIMESTAMPTZ ปัจจุบัน
SELECT CURRENT_DATE; -- วันที่ปัจจุบัน
SELECT CURRENT_TIME; -- เวลาปัจจุบัน
-- แยกส่วนประกอบ
SELECT EXTRACT(YEAR FROM NOW()); -- ปี
SELECT EXTRACT(DOW FROM NOW()); -- วันในสัปดาห์ (0=อาทิตย์)
-- คำนวณอายุ
SELECT AGE(TIMESTAMP '1990-06-15'); -- ผลลัพธ์: 35 years 9 mons 23 days
-- Truncate เวลา
SELECT DATE_TRUNC('month', NOW()); -- ตัดให้เหลือวันที่ 1 ของเดือน
-- จัดรูปแบบ
SELECT TO_CHAR(NOW(), 'DD/MM/YYYY HH24:MI'); -- 07/04/2026 14:30
Boolean Type — ชนิดข้อมูลตรรกะ
BOOLEAN เก็บค่า TRUE, FALSE หรือ NULL ใช้พื้นที่ 1 byte เหมาะกับ flag หรือสถานะเปิด-ปิด
CREATE TABLE users (
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
username VARCHAR(50) NOT NULL,
is_active BOOLEAN DEFAULT TRUE,
is_admin BOOLEAN DEFAULT FALSE
);
-- ค่าที่ PostgreSQL ยอมรับเป็น TRUE
-- true, 't', 'yes', 'y', 'on', '1'
-- ค่าที่ PostgreSQL ยอมรับเป็น FALSE
-- false, 'f', 'no', 'n', 'off', '0'
-- ใช้ใน WHERE ได้โดยตรง (ไม่ต้องเขียน = TRUE)
SELECT * FROM users WHERE is_active AND NOT is_admin;
UUID Type — ตัวระบุเฉพาะสากล
UUID (Universally Unique Identifier) เป็นค่า 128-bit ที่ไม่ซ้ำกัน เหมาะสำหรับใช้เป็น Primary Key ในระบบ distributed หรือ API ที่ไม่ต้องการเปิดเผย sequential ID
-- เปิดใช้ extension สำหรับสร้าง UUID
CREATE EXTENSION IF NOT EXISTS pgcrypto;
CREATE TABLE api_tokens (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id INTEGER NOT NULL,
token TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
INSERT INTO api_tokens (user_id, token)
VALUES (1, 'secret-token-abc');
-- id จะถูกสร้างอัตโนมัติ เช่น 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'
ข้อดีของ UUID: ไม่ซ้ำกันแม้สร้างจากคนละเซิร์ฟเวอร์ ไม่เปิดเผยลำดับข้อมูล ข้อเสีย: ใช้พื้นที่ 16 bytes (vs 4 bytes ของ INTEGER) และ Index ช้ากว่าเพราะค่าสุ่มทำให้เกิด random I/O มากขึ้น ใน PostgreSQL 17+ สามารถใช้ UUIDv7 ที่เรียงตามเวลาได้ ช่วยลดปัญหา Index performance
JSON และ JSONB — ข้อมูลแบบ Semi-Structured
PostgreSQL รองรับการเก็บข้อมูล JSON ใน 2 รูปแบบ คือ JSON (เก็บเป็น text) และ JSONB (เก็บเป็น binary ที่ parse แล้ว) แนะนำให้ใช้ JSONB เป็นหลักเพราะค้นหาเร็วกว่าและรองรับ Index ได้
| คุณสมบัติ | JSON | JSONB |
|---|---|---|
| การจัดเก็บ | Text ตามที่ใส่ | Binary (parse แล้ว) |
| ลำดับ key | คงตามที่ใส่ | ไม่รักษาลำดับ |
| Duplicate key | เก็บทั้งหมด | เก็บ key สุดท้าย |
| ความเร็วในการเขียน | เร็วกว่าเล็กน้อย | ช้ากว่าเล็กน้อย (ต้อง parse) |
| ความเร็วในการอ่าน/ค้นหา | ช้า (ต้อง parse ทุกครั้ง) | เร็วมาก |
| รองรับ Index | ไม่รองรับ | รองรับ GIN Index |
CREATE TABLE products (
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name TEXT NOT NULL,
metadata JSONB DEFAULT '{}'::JSONB
);
INSERT INTO products (name, metadata)
VALUES (
'Laptop',
'{"brand": "Dell", "specs": {"ram": 16, "ssd": 512}, "tags": ["computer", "portable"]}'
);
-- เข้าถึงข้อมูลด้วย Operator
SELECT name,
metadata->>'brand' AS brand, -- ดึง text
metadata->'specs'->>'ram' AS ram, -- ดึง nested text
metadata->'specs'->'ram' AS ram_json, -- ดึง JSON value
metadata#>>'{specs,ssd}' AS ssd -- ดึง nested path เป็น text
FROM products;
-- ค้นหาด้วย containment operator @>
SELECT * FROM products
WHERE metadata @> '{"brand": "Dell"}';
-- ตรวจสอบว่ามี key หรือไม่
SELECT * FROM products
WHERE metadata ? 'brand';
-- ค้นหาใน array
SELECT * FROM products
WHERE metadata->'tags' ? 'portable';
-- สร้าง GIN Index สำหรับค้นหาเร็วขึ้น
CREATE INDEX idx_products_metadata ON products USING GIN (metadata);
แก้ไขข้อมูล JSONB
-- เพิ่ม/แก้ไข key
UPDATE products
SET metadata = metadata || '{"color": "silver"}'
WHERE name = 'Laptop';
-- ลบ key
UPDATE products
SET metadata = metadata - 'color'
WHERE name = 'Laptop';
-- แก้ไข nested value ด้วย jsonb_set
UPDATE products
SET metadata = jsonb_set(metadata, '{specs,ram}', '32')
WHERE name = 'Laptop';
-- เพิ่มสมาชิกใน array
UPDATE products
SET metadata = jsonb_set(
metadata,
'{tags}',
(metadata->'tags') || '"gaming"'::JSONB
)
WHERE name = 'Laptop';
Array Type — เก็บข้อมูลหลายค่าในคอลัมน์เดียว
PostgreSQL รองรับ Array สำหรับทุกชนิดข้อมูล ช่วยให้เก็บรายการค่าหลายค่าในคอลัมน์เดียวได้โดยไม่ต้องสร้างตารางแยก เหมาะกับข้อมูลอย่าง tags, หมายเลขโทรศัพท์ หรือ permissions
CREATE TABLE articles (
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
title TEXT NOT NULL,
tags TEXT[] DEFAULT '{}' -- Array ของ TEXT
);
INSERT INTO articles (title, tags)
VALUES
('PostgreSQL Guide', ARRAY['database', 'postgresql', 'tutorial']),
('Docker Basics', '{"docker", "container", "devops"}'); -- อีกวิธีเขียน
-- เข้าถึงสมาชิก (Index เริ่มจาก 1)
SELECT title, tags[1] AS first_tag FROM articles;
-- ค้นหาด้วย ANY
SELECT * FROM articles WHERE 'postgresql' = ANY(tags);
-- ค้นหาด้วย contains operator @>
SELECT * FROM articles WHERE tags @> ARRAY['database'];
-- ค้นหาด้วย overlap operator &&
SELECT * FROM articles WHERE tags && ARRAY['docker', 'postgresql'];
-- เพิ่มสมาชิก
UPDATE articles
SET tags = array_append(tags, 'beginner')
WHERE title = 'PostgreSQL Guide';
-- ลบสมาชิก
UPDATE articles
SET tags = array_remove(tags, 'tutorial')
WHERE title = 'PostgreSQL Guide';
-- นับจำนวนสมาชิก
SELECT title, array_length(tags, 1) AS tag_count FROM articles;
-- แปลง Array เป็นแถว (unnest)
SELECT title, unnest(tags) AS tag FROM articles;
-- สร้าง GIN Index สำหรับค้นหาเร็วขึ้น
CREATE INDEX idx_articles_tags ON articles USING GIN (tags);
ข้อควรระวัง: Array เหมาะกับข้อมูลง่าย ๆ ที่ไม่ต้อง JOIN หรือ Aggregate บ่อย หากต้องการ query ซับซ้อน ควรใช้ตารางแยก (many-to-many) แทน
Network Address Types — ชนิดข้อมูลเครือข่าย
PostgreSQL มีชนิดข้อมูลเฉพาะสำหรับจัดเก็บ IP address และ MAC address พร้อมฟังก์ชันตรวจสอบและเปรียบเทียบในตัว ซึ่งมีประสิทธิภาพมากกว่าเก็บเป็น TEXT ธรรมดา
| ชนิด | ขนาด | ตัวอย่างค่า | เหมาะกับ |
|---|---|---|---|
| INET | 7 หรือ 19 bytes | 192.168.1.1/24 | IP address (host + subnet) |
| CIDR | 7 หรือ 19 bytes | 192.168.1.0/24 | Network address (subnet เท่านั้น) |
| MACADDR | 6 bytes | 08:00:2b:01:02:03 | MAC address ของอุปกรณ์ |
CREATE TABLE access_logs (
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
client_ip INET NOT NULL,
server_net CIDR,
device_mac MACADDR,
logged_at TIMESTAMPTZ DEFAULT NOW()
);
INSERT INTO access_logs (client_ip, server_net, device_mac)
VALUES ('192.168.1.100', '192.168.1.0/24', '08:00:2b:01:02:03');
-- ตรวจสอบว่า IP อยู่ในวง subnet หรือไม่
SELECT * FROM access_logs
WHERE client_ip << '192.168.0.0/16'::CIDR; -- << หมายถึง "contained in"
-- ดึงเฉพาะ host จาก INET
SELECT client_ip, host(client_ip) AS ip_only FROM access_logs;
-- ดึง network จาก INET
SELECT client_ip, network(client_ip) AS net FROM access_logs;
Enumerated Type (ENUM) — ชนิดข้อมูลแจกแจง
ENUM เป็นชนิดข้อมูลที่กำหนดค่าที่เป็นไปได้ไว้ล่วงหน้า เช่น สถานะออเดอร์ หรือระดับสมาชิก ช่วยป้องกันข้อมูลผิดพลาดโดยไม่ต้องสร้าง lookup table แยก
-- สร้าง ENUM type
CREATE TYPE order_status AS ENUM (
'pending', 'processing', 'shipped', 'delivered', 'cancelled'
);
CREATE TABLE orders (
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
status order_status DEFAULT 'pending'
);
INSERT INTO orders (status) VALUES ('processing');
INSERT INTO orders (status) VALUES ('invalid'); -- ERROR: ค่าไม่ถูกต้อง
-- เพิ่มค่าใหม่ใน ENUM (ทำได้ แต่ลบไม่ได้)
ALTER TYPE order_status ADD VALUE 'returned' AFTER 'delivered';
-- ENUM เปรียบเทียบตามลำดับที่กำหนด
SELECT * FROM orders WHERE status > 'processing'; -- shipped, delivered, cancelled
ข้อจำกัด: ENUM ลบค่าออกไม่ได้ (ต้อง recreate type) และเปลี่ยนลำดับไม่ได้ หากต้องการความยืดหยุ่นมากกว่า ควรใช้ lookup table แทน หรือใช้ CHECK constraint กับ VARCHAR
Composite Type — ชนิดข้อมูลผสม
Composite Type คือชนิดข้อมูลที่ประกอบด้วยหลาย field รวมกัน คล้าย struct ในภาษาโปรแกรม เหมาะกับการจัดกลุ่มข้อมูลที่เกี่ยวข้องกันเข้าด้วยกัน
-- สร้าง Composite Type
CREATE TYPE address AS (
street TEXT,
city TEXT,
state TEXT,
zipcode VARCHAR(10)
);
CREATE TABLE companies (
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name TEXT NOT NULL,
hq address,
branch address
);
-- Insert ด้วย ROW constructor
INSERT INTO companies (name, hq, branch)
VALUES (
'TechCorp',
ROW('123 Main St', 'Bangkok', 'BKK', '10110'),
ROW('456 Side Rd', 'Chiang Mai', 'CM', '50000')
);
-- เข้าถึง field ด้วย dot notation (ต้องใส่วงเล็บ)
SELECT name, (hq).city, (branch).city FROM companies;
Range Types — ชนิดข้อมูลช่วง
Range Types เก็บข้อมูลแบบช่วง เช่น ช่วงวันที่จองห้อง ช่วงราคา หรือช่วงเวลาทำงาน PostgreSQL มี Range Types ในตัวหลายแบบ พร้อม operator สำหรับตรวจสอบการทับซ้อน
| ชนิด | ข้อมูลที่เก็บ | ตัวอย่าง |
|---|---|---|
| INT4RANGE | ช่วง INTEGER | [1, 10) |
| INT8RANGE | ช่วง BIGINT | [1, 1000000) |
| NUMRANGE | ช่วง NUMERIC | [0.5, 9.99] |
| TSRANGE | ช่วง TIMESTAMP | [2026-01-01, 2026-12-31) |
| TSTZRANGE | ช่วง TIMESTAMPTZ | [2026-01-01+07, 2026-12-31+07) |
| DATERANGE | ช่วง DATE | [2026-01-01, 2026-01-31) |
CREATE TABLE reservations (
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
room TEXT NOT NULL,
period DATERANGE NOT NULL,
-- ป้องกันจองซ้อนกัน
EXCLUDE USING GIST (room WITH =, period WITH &&)
);
INSERT INTO reservations (room, period)
VALUES ('Room A', '[2026-05-01, 2026-05-05)');
-- จองซ้อนจะ ERROR อัตโนมัติ
INSERT INTO reservations (room, period)
VALUES ('Room A', '[2026-05-03, 2026-05-07)');
-- ERROR: conflicting key value violates exclusion constraint
-- Operator ที่ใช้บ่อย
SELECT * FROM reservations WHERE period @> '2026-05-02'::DATE; -- ค่าอยู่ในช่วง
SELECT * FROM reservations WHERE period && '[2026-05-04, 2026-05-10)'; -- ทับซ้อน
SELECT * FROM reservations WHERE period << '[2026-06-01, 2026-06-30)'; -- อยู่ก่อน
-- ดึงขอบเขต
SELECT room, lower(period) AS checkin, upper(period) AS checkout
FROM reservations;
เครื่องหมาย [ หมายถึง inclusive (รวมค่านี้) และ ) หมายถึง exclusive (ไม่รวมค่านี้) เช่น [2026-05-01, 2026-05-05) หมายถึงวันที่ 1-4 พ.ค. ไม่รวมวันที่ 5
BYTEA — ข้อมูลไบนารี
BYTEA (Byte Array) ใช้เก็บข้อมูลไบนารี เช่น ไฟล์ขนาดเล็ก รูปภาพ thumbnail หรือข้อมูลที่เข้ารหัสแล้ว
CREATE TABLE files (
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
filename TEXT NOT NULL,
content BYTEA,
checksum BYTEA
);
-- Insert ด้วย hex format
INSERT INTO files (filename, content)
VALUES ('test.bin', '\x48656c6c6f');
-- Insert ด้วย escape format
INSERT INTO files (filename, content)
VALUES ('test2.bin', E'\\x48656c6c6f');
-- ดึงขนาด
SELECT filename, octet_length(content) AS size_bytes FROM files;
-- สร้าง hash
SELECT filename, sha256(content) AS hash FROM files;
คำแนะนำ: ไม่ควรเก็บไฟล์ขนาดใหญ่ใน BYTEA เพราะจะทำให้ backup ช้าและใช้หน่วยความจำมาก ควรเก็บเฉพาะ path หรือ URL ของไฟล์แทน แล้วเก็บไฟล์จริงใน Object Storage
Special Types อื่น ๆ
Geometric Types
PostgreSQL มีชนิดข้อมูลเรขาคณิตในตัว เช่น POINT, LINE, LSEG, BOX, PATH, POLYGON และ CIRCLE สำหรับงาน 2D geometry พื้นฐาน สำหรับงาน GIS จริงจัง ควรใช้ PostGIS extension แทน
CREATE TABLE stores (
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name TEXT NOT NULL,
location POINT -- (x, y) หรือ (longitude, latitude)
);
INSERT INTO stores (name, location)
VALUES ('Store A', POINT(100.5018, 13.7563));
-- คำนวณระยะทาง
SELECT a.name, b.name,
a.location <-> b.location AS distance
FROM stores a, stores b
WHERE a.id != b.id;
XML Type
XML type เก็บข้อมูล XML พร้อม validate โครงสร้างอัตโนมัติ รองรับ XPath query แต่ในปัจจุบัน JSONB ได้รับความนิยมมากกว่า XML type จึงเหมาะกับระบบที่ต้องรองรับ legacy XML data
CREATE TABLE xml_docs (
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
data XML
);
INSERT INTO xml_docs (data)
VALUES ('<book><title>PostgreSQL Guide</title><year>2026</year></book>');
-- XPath query
SELECT (xpath('//title/text()', data))[1]::TEXT AS title
FROM xml_docs;
Bit String Types
BIT(n) เก็บ bit string ขนาดคงที่ และ BIT VARYING(n) เก็บแบบไม่คงที่ เหมาะกับการเก็บ flag, permission mask หรือข้อมูลระดับ bit
CREATE TABLE permissions (
user_id INTEGER PRIMARY KEY,
flags BIT(8) DEFAULT B'00000000'
-- bit 0 = read, bit 1 = write, bit 2 = execute, ...
);
INSERT INTO permissions VALUES (1, B'00000111'); -- read + write + execute
-- ตรวจสอบด้วย bitwise AND
SELECT * FROM permissions WHERE flags & B'00000001' = B'00000001'; -- มีสิทธิ์ read
Domain — สร้างชนิดข้อมูลที่กำหนดเอง
Domain เป็นการสร้างชนิดข้อมูลใหม่จากชนิดที่มีอยู่ โดยเพิ่ม constraint เข้าไป ช่วยให้บังคับ business rule ได้จากระดับ type
-- สร้าง Domain สำหรับ Email
CREATE DOMAIN email_address AS VARCHAR(255)
CHECK (VALUE ~* '^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$');
-- สร้าง Domain สำหรับ เบอร์โทรไทย
CREATE DOMAIN thai_phone AS VARCHAR(10)
CHECK (VALUE ~ '^0[0-9]{8,9}$');
-- สร้าง Domain สำหรับ จำนวนบวก
CREATE DOMAIN positive_int AS INTEGER
CHECK (VALUE > 0);
CREATE TABLE contacts (
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
email email_address NOT NULL,
phone thai_phone,
age positive_int
);
INSERT INTO contacts (email, phone, age)
VALUES ('[email protected]', '0812345678', 25); -- สำเร็จ
INSERT INTO contacts (email, phone, age)
VALUES ('invalid-email', '0812345678', 25); -- ERROR: ไม่ผ่าน email check
Type Casting — การแปลงชนิดข้อมูล
เมื่อต้องแปลงข้อมูลจากชนิดหนึ่งไปอีกชนิด สามารถใช้ได้ 3 วิธี
-- วิธีที่ 1: CAST (มาตรฐาน SQL)
SELECT CAST('42' AS INTEGER);
SELECT CAST('2026-04-07' AS DATE);
-- วิธีที่ 2: :: (PostgreSQL shorthand — ใช้บ่อยที่สุด)
SELECT '42'::INTEGER;
SELECT '2026-04-07'::DATE;
SELECT '{"key": "value"}'::JSONB;
-- วิธีที่ 3: Type function
SELECT INTEGER '42';
-- ตัวอย่างการใช้งานจริง
SELECT
'3.14'::NUMERIC * 2 AS calc,
NOW()::DATE AS today,
42::TEXT || ' items' AS label,
'192.168.1.1'::INET AS ip;
แนวทางเลือกใช้ชนิดข้อมูลให้เหมาะสม
การเลือกชนิดข้อมูลที่ถูกต้องตั้งแต่แรกช่วยลดปัญหาในระยะยาวได้มาก แนวทางต่อไปนี้จะช่วยตัดสินใจได้เร็วขึ้น
สำหรับ Primary Key: ใช้ INTEGER + IDENTITY สำหรับตารางทั่วไป ใช้ BIGINT + IDENTITY สำหรับตารางที่คาดว่าจะมีข้อมูลมากกว่า 2 พันล้านแถว ใช้ UUID สำหรับระบบ distributed หรือ API ที่ไม่ต้องการเปิดเผย sequential ID
สำหรับข้อความ: ใช้ TEXT เป็นค่า default สำหรับข้อความทั่วไป ใช้ VARCHAR(n) เมื่อต้องการจำกัดความยาว (เช่น email, username) หลีกเลี่ยง CHAR(n) ยกเว้นรหัสที่ยาวคงที่จริง ๆ
สำหรับตัวเลข: ใช้ INTEGER สำหรับงานทั่วไป ใช้ NUMERIC สำหรับเงินและข้อมูลที่ต้องการความแม่นยำ ใช้ DOUBLE PRECISION สำหรับข้อมูลวิทยาศาสตร์หรือพิกัด
สำหรับวันเวลา: ใช้ TIMESTAMPTZ เป็นค่า default (ไม่ใช่ TIMESTAMP) ใช้ DATE เมื่อไม่ต้องการเวลา ใช้ INTERVAL สำหรับช่วงระยะเวลา
สำหรับข้อมูลซับซ้อน: ใช้ JSONB สำหรับ semi-structured data ที่ต้องค้นหา ใช้ Array สำหรับรายการง่าย ๆ ที่ไม่ซับซ้อน ใช้ ENUM สำหรับค่าที่เปลี่ยนแปลงน้อย ใช้ Range Types สำหรับข้อมูลแบบช่วง
สรุป
PostgreSQL มีระบบชนิดข้อมูลที่หลากหลายและทรงพลังมากกว่าฐานข้อมูลอื่นส่วนใหญ่ ตั้งแต่ชนิดพื้นฐานอย่าง Integer, Text และ Date ไปจนถึงชนิดพิเศษอย่าง JSONB, Array, Range Types และ Network Address ที่ช่วยให้จัดการข้อมูลซับซ้อนได้ง่ายขึ้น นอกจากนี้ยังสามารถสร้างชนิดข้อมูลเองผ่าน Domain, ENUM และ Composite Type เพื่อบังคับ business rule ตั้งแต่ระดับฐานข้อมูล
หลักการสำคัญคือเลือกชนิดข้อมูลให้เหมาะกับลักษณะข้อมูลจริง ใช้ NUMERIC แทน FLOAT สำหรับเงิน ใช้ TIMESTAMPTZ แทน TIMESTAMP ใช้ JSONB แทน TEXT สำหรับข้อมูล JSON การเลือกถูกตั้งแต่แรกช่วยลดปัญหา performance และ data integrity ในระยะยาวได้มาก
แนะนำบริการ DE
การทดลองใช้งานชนิดข้อมูลต่าง ๆ ของ PostgreSQL ต้องการเซิร์ฟเวอร์ที่สามารถติดตั้งและ configure ฐานข้อมูลได้อย่างอิสระ Cloud VPS ของ DE ให้คุณควบคุมเซิร์ฟเวอร์ได้เต็มที่ ติดตั้ง PostgreSQL เวอร์ชันล่าสุดพร้อม extension ต่าง ๆ เช่น PostGIS, pgcrypto หรือ pgAudit ได้ตามต้องการ
สำหรับผู้ที่ต้องการโฮสต์เว็บแอปพลิเคชันที่เชื่อมต่อกับ PostgreSQL โดยไม่ต้องจัดการเซิร์ฟเวอร์เอง Cloud Hosting ของ DE ก็เป็นทางเลือกที่สะดวก รองรับการเชื่อมต่อฐานข้อมูลภายนอกได้ ช่วยให้คุณโฟกัสกับการพัฒนาแอปพลิเคชันมากกว่าดูแลโครงสร้างพื้นฐาน

