Skip to main content
🐘

PostgreSQL FTS

kham-pg เป็น extension ค้นหาข้อความแบบ Full-Text สำหรับ PostgreSQL แต่ละ token ภาษาไทยขยายเป็น lexeme สูงสุด 6 ตัวในตำแหน่งเดียวกัน: คำเอง, รหัส soundex lk82, RTGS, รูป ASCII (สำหรับเลขไทย) และ tag ชนิดคำ (POS) คำ stopword จะถูกกรองออกอัตโนมัติ

1

Docker Hub (เริ่มใช้งานเร็วที่สุด)

ดึง image พร้อมใช้ — ไม่ต้องติดตั้ง Rust, ไม่ต้อง build เอง รองรับ amd64 + arm64

# PostgreSQL 17 พร้อม kham_pg ติดตั้งไว้แล้ว (latest = PG 17)
docker run --rm -e POSTGRES_PASSWORD=secret \
  -p 5432:5432 nickmsft/kham-pg:latest

# ระบุ PostgreSQL version — tags ที่มี:
#   v0.8.2-pg14  v0.8.2-pg16  v0.8.2-pg17  v0.8.2-pg18
docker run --rm -e POSTGRES_PASSWORD=secret \
  -p 5432:5432 nickmsft/kham-pg:v0.8.2-pg17

# เชื่อมต่อและใช้งานได้เลย
psql -h localhost -U postgres -c "
  CREATE EXTENSION kham_pg;
  SELECT to_tsvector('kham', 'กินข้าวกับปลา');
"
2

สิ่งที่ต้องมีก่อน

PostgreSQL 14 ขึ้นไป, Rust toolchain (1.85+) และ pg_config ใน PATH

pg_config --version   # PostgreSQL 14 หรือใหม่กว่า
3

Build และติดตั้ง

cargo build -p kham-pg --release
make -C kham-pg install
4

Load และใช้งาน

extension มี configuration kham พร้อมใช้ทันที คำ stopword ภาษาไทยถูกกรองออกอัตโนมัติโดยไม่ต้องตั้งค่าเพิ่ม

-- โหลด extension
CREATE EXTENSION kham_pg;

-- ค้นหาข้อความแบบ full-text ด้วย configuration kham ในตัว
SELECT to_tsvector('kham', 'กินข้าวกับปลา') @@ plainto_tsquery('kham', 'ปลา');
-- t  (กับ เป็น stopword จึงถูกกรองออก; ปลา ถูก index ไว้)
5

Index เอกสาร

CREATE TABLE articles (
  id    SERIAL PRIMARY KEY,
  title TEXT,
  body  TEXT,
  tsv   TSVECTOR
);

UPDATE articles
SET tsv = to_tsvector('kham', coalesce(title, '') || ' ' || coalesce(body, ''));

CREATE INDEX articles_tsv_idx ON articles USING GIN(tsv);

CREATE TRIGGER articles_tsv_update
  BEFORE INSERT OR UPDATE ON articles
  FOR EACH ROW EXECUTE FUNCTION
    tsvector_update_trigger(tsv, 'public.kham', title, body);
6

ค้นหาด้วย ts_headline

SELECT id,
  ts_headline('kham', body, query,
    'StartSel=<mark>, StopSel=</mark>') AS snippet
FROM articles,
     to_tsquery('kham', 'ข้าว & ปลา') AS query
WHERE tsv @@ query
ORDER BY ts_rank(tsv, query) DESC;
7

ค้นหาแบบ phonetic และ romanization

token ภาษาไทยทุกตัวจะขยายด้วยรหัส soundex lk82 และ RTGS romanization — คำพ้องเสียงและ query ภาษาอังกฤษจึงเจอเอกสารภาษาไทยได้ทันที

-- query ภาษาอังกฤษ (RTGS) ค้นหาเอกสารภาษาไทย
SELECT to_tsvector('kham', 'กินข้าวกับปลา') @@ plainto_tsquery('kham', 'pla');
-- t  (ปลา → 'pla' ถูก index ไว้ในตำแหน่งเดียวกัน)

-- ค้นหาด้วยรหัส lk82 ของ ปลา (4800)
SELECT title FROM articles WHERE tsv @@ to_tsquery('kham', '4800');
8

การแปลงตัวเลขไทย

ตัวเลขไทย (๑๒๓) ถูก index พร้อมกับรูป ASCII (123) ในตำแหน่งเดียวกัน

-- ค้นหา ๑๒๓ ด้วยตัวเลข ASCII
SELECT to_tsvector('kham', '๑๒๓') @@ plainto_tsquery('kham', '123') AS found;
-- t
9

การกรองด้วย POS lexeme

token ที่รู้ชนิดคำจะมี pos_<tag> เป็น lexeme เพิ่ม ใช้ ::tsquery cast เพื่อให้ underscore ไม่ถูกตัดแยก

-- หาเอกสารที่มีคำกริยา
SELECT title FROM articles WHERE tsv @@ 'pos_verb'::tsquery;

-- ดู POS tags ที่ถูก index สำหรับคำนั้น
SELECT lexeme FROM unnest(to_tsvector('kham', 'ปลา'))
WHERE lexeme LIKE 'pos_%';
-- pos_noun
10

Dictionary soundex variants

มี dictionary variants สองแบบสำหรับการจับเสียงที่ละเอียดกว่า

-- udom83 soundex
CREATE TEXT SEARCH CONFIGURATION kham_udom83 (PARSER = kham);
ALTER TEXT SEARCH CONFIGURATION kham_udom83
    ADD MAPPING FOR thai, named WITH kham_fts_dict_udom83;
ALTER TEXT SEARCH CONFIGURATION kham_udom83
    ADD MAPPING FOR latin, number, unknown WITH kham_dict;

-- MetaSound
CREATE TEXT SEARCH CONFIGURATION kham_metasound (PARSER = kham);
ALTER TEXT SEARCH CONFIGURATION kham_metasound
    ADD MAPPING FOR thai, named WITH kham_fts_dict_metasound;
ALTER TEXT SEARCH CONFIGURATION kham_metasound
    ADD MAPPING FOR latin, number, unknown WITH kham_dict;
11

การจัดอันดับและ field boosting

ใช้ ts_rank() หรือ ts_rank_cd() เรียงผลตามความเกี่ยวข้อง และใช้ setweight() เพิ่มน้ำหนักแต่ละ field — น้ำหนัก A (1.0) > B (0.4) > C (0.2) > D (0.1)

-- จัดอันดับพื้นฐาน
SELECT id, ts_rank(tsv, query) AS rank
FROM articles, to_tsquery('kham', 'ข้าว') query
WHERE tsv @@ query
ORDER BY rank DESC;

-- Field boosting: token น้ำหนัก A มีค่า ~5× เทียบกับน้ำหนัก C
UPDATE articles
SET tsv =
  setweight(to_tsvector('kham', coalesce(title, '')), 'A') ||
  setweight(to_tsvector('kham', coalesce(body,  '')), 'C');