Data Types ใน PostgreSQL

PostgreSQL รองรับ Data Types หลากหลายประเภท ตั้งแต่ตัวเลข ข้อความ วันเวลา ไปจนถึงชนิดพิเศษอย่าง JSON, Array และ Network Address การเลือกใช้ชนิดข้อมูลที่เหมาะสมช่วยให้ฐานข้อมูลทำงานได้เร็วขึ้น ใช้พื้นที่จัดเก็บน้อยลง และป้องกันข้อมูลผิดพลาดตั้งแต่ระดับโครงสร้าง

บทความนี้จะอธิบาย Data Types ทุกกลุ่มใน PostgreSQL อย่างละเอียด พร้อมตัวอย่างการใช้งานจริง เปรียบเทียบข้อดีข้อเสียของแต่ละชนิด และแนวทางเลือกใช้ให้เหมาะกับสถานการณ์ต่าง ๆ

Numeric Types — ชนิดข้อมูลตัวเลข

PostgreSQL แบ่งชนิดข้อมูลตัวเลขออกเป็น 3 กลุ่มหลัก ได้แก่ Integer (จำนวนเต็ม), Floating-Point (ทศนิยมแบบประมาณค่า) และ Exact Numeric (ทศนิยมแบบแม่นยำ) แต่ละกลุ่มเหมาะกับงานที่ต่างกัน

Integer Types

ชนิดจำนวนเต็มมี 3 ขนาดให้เลือก ขึ้นอยู่กับช่วงค่าที่ต้องการ

ชนิดขนาดช่วงค่าเหมาะกับ
SMALLINT2 bytes-32,768 ถึง 32,767สถานะ, rating, อายุ
INTEGER4 bytes-2.1 พันล้าน ถึง 2.1 พันล้านใช้ทั่วไป, Primary Key
BIGINT8 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 bytes6 ตำแหน่งทศนิยม
DOUBLE PRECISION (FLOAT8)8 bytes15 ตำแหน่งทศนิยม
-- ตัวอย่าง 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 และช่วงระยะเวลา

ชนิดขนาดตัวอย่างค่าเหมาะกับ
DATE4 bytes2026-04-07วันเกิด, วันที่เอกสาร
TIME8 bytes14:30:00เวลาโดยไม่สนใจ timezone
TIMESTAMP8 bytes2026-04-07 14:30:00วันเวลาภายใน timezone เดียว
TIMESTAMPTZ8 bytes2026-04-07 14:30:00+07วันเวลาข้าม timezone (แนะนำ)
INTERVAL16 bytes1 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 ได้

คุณสมบัติJSONJSONB
การจัดเก็บ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 ธรรมดา

ชนิดขนาดตัวอย่างค่าเหมาะกับ
INET7 หรือ 19 bytes192.168.1.1/24IP address (host + subnet)
CIDR7 หรือ 19 bytes192.168.1.0/24Network address (subnet เท่านั้น)
MACADDR6 bytes08:00:2b:01:02:03MAC 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 ก็เป็นทางเลือกที่สะดวก รองรับการเชื่อมต่อฐานข้อมูลภายนอกได้ ช่วยให้คุณโฟกัสกับการพัฒนาแอปพลิเคชันมากกว่าดูแลโครงสร้างพื้นฐาน