Tech Blog

การต่อสู้กับปัญหา Encoding ของ PostgreSQL บน Windows + Docker

by Tech Writer
PostgreSQL Docker Windows Encoding

บทนำ

เมื่อรัน PostgreSQL ด้วย Docker บน Windows บางครั้งมีปัญหาในการจัดการข้อมูลภาษาญี่ปุ่น

“อักขระเพี้ยน”, “encoding errors”, “Illegal multibyte sequence”

ผมเจอ error เหล่านี้และใช้เวลาหลายชั่วโมงก่อนจะแก้ได้
บทความนี้คือบันทึกจุดเจ็บปวดเหล่านั้นและวิธีแก้ไข


ปัญหาที่เจอ

ปัญหาที่ 1: อักขระเพี้ยนเมื่อ Import CSV

ระหว่างงาน Japanese localization สำหรับ dvdrental ผมสร้าง CSV ที่แปลงชื่อภาพยนตร์เป็นภาษาญี่ปุ่นและพยายาม import เข้า PostgreSQL

COPY film(title) FROM '/tmp/titles_ja.csv' WITH (FORMAT CSV, HEADER TRUE, ENCODING 'UTF8');

Error:

ERROR:  invalid byte sequence for encoding "UTF8": 0x83

สาเหตุคือ CSV ที่บันทึกด้วย Excel บน Windows เป็น “Shift-JIS (CP932)“

ปัญหาที่ 2: เครื่องหมายคำถามเมื่อ Execute SQL จาก PowerShell

$sql = "UPDATE film SET title = '進撃の巨人' WHERE film_id = 1;"
docker exec -i postgres psql -U postgres -d dvdrental -c $sql

ผลลัพธ์: title กลายเป็น ???

Default encoding ของ PowerShell ไม่ใช่ UTF-8 และการแปลง encoding เกิดขึ้นเมื่อส่งผ่าน pipe ไปยัง PostgreSQL


วิธีแก้ไข

วิธีแก้ไขที่ 1: บันทึก CSV เป็น UTF-8 (ไม่มี BOM)

แม้จะบันทึกจาก Excel เป็น “CSV UTF-8” บน Windows อาจกลายเป็น UTF-8 ที่มี BOM (UTF-8 with BOM)
COPY command ของ PostgreSQL อาจไม่จัดการ BOM อย่างถูกต้อง

วิธี A: เปิด CSV ใน VS Code และบันทึกเป็น “UTF-8” (ไม่มี BOM)

  • File → Save As → เลือก encoding “UTF-8”

วิธี B: แปลงด้วย PowerShell

# แปลงจาก BOM มีไปเป็น BOM ไม่มี
$content = Get-Content -Path "titles_ja_bom.csv" -Encoding UTF8
$content | Set-Content -Path "titles_ja_nobom.csv" -Encoding UTF8NoBOM

วิธีแก้ไขที่ 2: คัดลอกไปยัง PostgreSQL Container แล้ว Import

# คัดลอกไฟล์ไปยัง container
docker cp .\sql\titles_ja.csv postgres:/tmp/titles_ja.csv

# Execute COPY ภายใน container (มีแนวโน้มเกิดปัญหา encoding น้อยกว่า)
docker exec postgres psql -U postgres -d dvdrental -c `
    "COPY film(title) FROM '/tmp/titles_ja.csv' WITH (FORMAT CSV, HEADER TRUE, ENCODING 'UTF8')"

เนื่องจากไม่ผ่าน local filesystem จึงไม่มีการแปลง encoding ของ OS

วิธีแก้ไขที่ 3: การตั้งค่า Encoding ของ PowerShell

# เพิ่มที่ต้น script
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
$PSDefaultParameterValues['Out-File:Encoding'] = 'utf8NoBOM'

# หรือตั้งค่าเป็น environment variable
$env:PYTHONUTF8 = "1"  # เมื่อใช้ Python scripts

PowerShell 7.x (pwsh) default เป็น UTF-8 แต่ Windows PowerShell 5.1 default เป็น Shift-JIS
วิธีตรวจสอบ:

[System.Text.Encoding]::Default

ถ้าแสดง Shift-JIS หรือ Code page 932 จำเป็นต้องเปลี่ยนการตั้งค่า

วิธีแก้ไขที่ 4: ตั้งค่า Locale อย่างชัดเจนใน Docker Compose

# compose.yml
services:
  postgres:
    image: postgres:16
    environment:
      POSTGRES_DB: dvdrental
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      LANG: ja_JP.UTF-8
      LC_ALL: ja_JP.UTF-8
    volumes:
      - pgdata:/var/lib/postgresql/data

Default locale เมื่อสร้าง PostgreSQL database จะเป็น UTF-8


การตรวจสอบ Encoding ของ PostgreSQL

-- ตรวจสอบ encoding ของ DB ที่เชื่อมต่ออยู่
SELECT pg_encoding_to_char(encoding), datcollate FROM pg_database WHERE datname = current_database();

-- ตรวจสอบ encoding ทั้ง server
SHOW server_encoding;
SHOW client_encoding;

ยืนยันว่า server_encoding เป็น UTF8


วิธีตรวจสอบ Encoding ของไฟล์ CSV

# ตรวจสอบ bytes แรกของไฟล์ด้วย PowerShell
$bytes = [System.IO.File]::ReadAllBytes(".\titles_ja.csv") | Select-Object -First 4
$bytes | ForEach-Object { "0x{0:X2}" -f $_ }

# UTF-8 ที่มี BOM: 0xEF 0xBB 0xBF ...
# UTF-8 ไม่มี BOM: Data bytes มาตรงจากต้น

การแก้ไขข้อมูลที่เพี้ยนแล้ว

สำหรับการแก้ไขข้อมูลที่เพี้ยนไปแล้ว:

-- ตรวจสอบข้อมูลที่เพี้ยน
SELECT film_id, title FROM film WHERE title ~ '[^\x00-\x7F]' IS FALSE;

-- เขียนทับด้วยข้อมูลญี่ปุ่นที่ถูกต้อง (re-import จาก CSV)
TRUNCATE TABLE film_title_ja;  -- ถ้าใช้ working table

-- หรือแก้ไขตรงๆ ด้วย UPDATE
UPDATE film SET title = 'ชื่อภาษาญี่ปุ่นที่ถูกต้อง' WHERE film_id = 1;

สรุป

ปัญหาสาเหตุวิธีแก้ไข
Import CSV ล้มเหลวShift-JIS หรือ UTF-8 ที่มี BOMบันทึกใหม่เป็น UTF-8 (ไม่มี BOM)
PowerShell อักขระเพี้ยนConsole encoding เป็น CP932[Console]::OutputEncoding = UTF8
ส่งตรงไปยัง container ไม่ได้PowerShell pipe แปลง encodingใช้ docker cp คัดลอกไฟล์ก่อน
DB default ไม่ใช่ UTF-8ไม่ได้ตั้ง LANG ใน Docker Composeตั้งค่า LANG: ja_JP.UTF-8 อย่างชัดเจน

ในสภาพแวดล้อม development บน Windows มีหลายสถานการณ์ที่สมมติฐาน “UTF-8 เป็นเรื่องปกติ” ใช้ไม่ได้
การพัฒนานิสัยในการตรวจสอบ encoding ทุกครั้งที่จัดการ CSV ช่วยลดเวลาที่เสียไปได้อย่างมาก


แผนที่บทความของ Series นี้

การสร้างแอพ DVD Rental สำหรับผู้ใช้ควบคู่กับหน้าจัดการ — ภาพรวมโครงสร้าง Vue 3 + Spring Boot

ส่งข้อความได้ตามสบาย

กรุณาส่งข้อความ หากมีคำปรึกษาด้านเทคนิค ความคิดเห็น หรือคำถาม