Site logo
Tác giả
  • avatar Nguyễn Đức Xinh
    Name
    Nguyễn Đức Xinh
    Twitter
Ngày xuất bản
Ngày xuất bản

Viết Table/Column COMMENT Cho Khách Hàng Nhật Và Cách Verify Bằng SQL

Giới Thiệu

Làm dự án offshore cho khách hàng Nhật, sớm hay muộn bạn sẽ nhận một ticket kiểu này:

テーブル・カラムに論理名のコメントを設定してください。
(Hãy đặt COMMENT tên logic cho các table và column.)

Nghe thì đơn giản: chạy vài câu ALTER TABLE ... COMMENT. Nhưng khi làm thật, bạn sẽ gặp những câu hỏi như:

  • Tên logic lấy từ đâu? Màn hình, tài liệu cũ hay tự dịch?
  • JANコード hay JANコード? Khách Nhật để ý chuyện này.
  • Sửa COMMENT trên DB rồi, nhưng chạy lại migration thì mất hết?
  • Làm sao chứng minh cho khách là đã xong 100%, không sót cột nào?

Bài này đi qua toàn bộ quy trình, dựa trên một task thật: đặt COMMENT cho 12 bảng của hệ thống quản lý sản phẩm, deploy lên hai môi trường dev và demo, rồi báo cáo kèm SQL để khách tự kiểm tra. Bạn sẽ học được:

  • Vì sao khách Nhật coi COMMENT và テーブル定義書 (tài liệu định nghĩa bảng) là deliverable chính thức
  • Quy tắc viết 論理名 (tên logic): nguồn lấy tên, half-width/full-width, thuật ngữ thống nhất
  • Cách sửa COMMENT an toàn trong MySQL/MariaDB và giữ đồng bộ với Laravel migration
  • Bộ SQL verify trên INFORMATION_SCHEMA: phát hiện cột trống, ký tự full-width, thuật ngữ không thống nhất, lệch giữa các môi trường
  • Cách viết báo cáo hoàn thành song ngữ Việt/Nhật mà khách tự verify được

Yêu Cầu

  • MySQL 8.0+ hoặc MariaDB 10.5+, database dùng utf8mb4
  • Biết ALTER TABLE và truy vấn INFORMATION_SCHEMA cơ bản (xem bài Information Schema)
  • Không cần biết tiếng Nhật, nhưng cần phân biệt được hiragana, katakana, kanji và ký tự full-width

Vì Sao Khách Nhật Quan Tâm Đến COMMENT?

物理名 Và 論理名

Trong văn hóa phát triển phần mềm ở Nhật, mỗi table và column có hai tên:

Khái niệm Tiếng Nhật Ví dụ Ai dùng
Tên vật lý 物理名 (butsuri-mei) product_sku / jan_cd Developer, trong code và SQL
Tên logic 論理名 (ronri-mei) SKU情報 / JANコード Khách hàng, BA, QA, người vận hành

Khách hàng (thường là phòng IT của doanh nghiệp, 情報システム部) không đọc tên vật lý. Họ đọc テーブル定義書, một file Excel liệt kê từng bảng: số thứ tự, 論理名, 物理名, kiểu dữ liệu, NOT NULL, default, khóa, ghi chú. Tài liệu này được dùng để:

  • Nghiệm thu (検収) khi bàn giao hệ thống
  • Vận hành và bảo trì sau này, khi team offshore đã rời dự án
  • Viết query báo cáo, đối chiếu dữ liệu, điều tra sự cố
  • Làm đầu vào cho dự án migration hoặc renewal tiếp theo

Vì Sao Lưu Trong COMMENT Thay Vì Chỉ Trong Excel?

Excel viết tay chắc chắn sẽ lệch với DB thật sau vài sprint. Khi tên logic nằm trong COMMENT của DB:

  • DB là single source of truth. テーブル定義書 được sinh ra từ INFORMATION_SCHEMA, không phải viết tay
  • Các tool như DBeaver, A5:SQL Mk-2 (rất phổ biến ở Nhật), MySQL Workbench hiển thị COMMENT ngay cạnh tên cột
  • Verify được bằng SQL, không cần so sánh bằng mắt

A5:SQL Mk-2 thậm chí có tùy chọn hiển thị 論理名 thay cho 物理名 trong ER diagram. Nhiều khách Nhật dùng tính năng này hằng ngày, và COMMENT trống sẽ hiện ra rất rõ.

Bước 1: Xác Định Nguồn Của Tên Logic

Sai lầm lớn nhất là tự dịch tên cột sang tiếng Nhật. Bạn dịch delivery_date thành 配達日, nhưng nghiệp vụ của khách gọi là 納品日. Hai từ đều đúng tiếng Nhật, nhưng chỉ một từ đúng với khách.

Thứ tự ưu tiên khi lấy tên logic:

Ưu tiên Nguồn Ghi chú
1 Danh sách khách ghi trong ticket Dùng nguyên văn, không sửa dù bạn nghĩ có từ hay hơn
2 Label trên màn hình (UI) của hệ thống Đây là từ người dùng cuối thấy hằng ngày
3 テーブル定義書 của hệ thống cũ (legacy) Cho các cột không hiển thị trên màn hình
4 Spec, thiết kế màn hình, tài liệu nghiệp vụ Tìm từ khóa tiếng Nhật đi cùng cột đó
5 Tự đặt Phải liệt kê riêng trong báo cáo để khách confirm

Trong task mẫu, khách liệt kê sẵn table comment cho 12 bảng trong ticket, ví dụ products → 商品情報. Column comment lấy từ label màn hình. Các cột không có trên màn hình (cột kỹ thuật, cờ, khóa ngoại) lấy theo COMMENTS trong テーブル定義書 của hệ thống legacy.

Mẹo: nếu hệ thống đang được viết lại (renewal) từ một hệ thống cũ, hãy tìm テーブル定義書 cũ trước tiên. Dùng lại đúng từ khách đã quen là cách nhanh nhất để được nghiệm thu.

Lập Bảng Thuật Ngữ (用語集)

Cùng một khái niệm phải có cùng một tên logic ở mọi bảng. Trước khi viết COMMENT, hãy lập bảng thuật ngữ cho các cột lặp lại:

Tên cột Tên logic Ghi chú
version バージョン番号 Mọi bảng dùng optimistic lock
created_at 登録日時 Không dùng 作成日時 lẫn lộn
updated_at 更新日時
created_by 登録者
deleted_at 削除日時
product_cd 商品コード
jan_cd JANコード Chữ Latin half-width

Sau này ta sẽ dùng SQL để kiểm tra bảng thuật ngữ này được áp dụng đúng (Bước 5).

Bước 2: Quy Tắc Viết Tên Logic

Half-width Và Full-width

Tiếng Nhật có hai dạng cho cùng một chữ Latin/số:

Loại Ví dụ Mã Unicode Dùng trong COMMENT?
Half-width (半角) chữ/số JAN, 123 U+0020–U+007E Có
Full-width (全角) chữ/số JAN, 123 U+FF10–U+FF5A Không
Full-width katakana コード U+30A0–U+30FF Có
Half-width katakana コード U+FF66–U+FF9F Không
Full-width space   U+3000 Không

Quy ước phổ biến (và đúng với hầu hết khách): chữ Latin và số dùng half-width, katakana dùng full-width. Vì vậy JANコード là đúng, còn JANコード hay JANコード đều sai.

Lỗi này rất hay xảy ra khi copy từ Excel cũ hoặc gõ bằng IME tiếng Nhật ở chế độ full-width. Bằng mắt thường gần như không phân biệt được, nên phải kiểm tra bằng SQL.

Các Quy Tắc Khác

  • Không trộn tiếng Anh và tiếng Nhật. COMMENT tiếng Anh cũ kiểu 'SKU code for warehouse' phải được thay bằng tên logic tiếng Nhật
  • Ngoại lệ cho từ viết tắt chuẩn: SKU, ID, JAN có thể đứng một mình nếu khách cũng dùng như vậy. Hãy liệt kê rõ các ngoại lệ (ta sẽ dùng danh sách này trong SQL verify)
  • COMMENT là tên logic, không phải mô tả dài. Viết 納品日, không viết 商品が倉庫に届く予定の日付です. Nếu cần giải thích thêm, đưa vào cột "備考" của テーブル定義書
  • Cờ và mã nên ghi giá trị nếu khách yêu cầu, ví dụ 削除フラグ(0:有効 1:削除). Hãy thống nhất format với khách trước
  • Giới hạn độ dài: column COMMENT tối đa 1024 ký tự, table COMMENT tối đa 2048 ký tự (MySQL 5.7+). Tên logic thì không bao giờ chạm tới giới hạn này

Bước 3: Sửa COMMENT An Toàn Trong MySQL/MariaDB

Table COMMENT

Đơn giản, không ảnh hưởng dữ liệu:

ALTER TABLE products COMMENT = '商品情報';

Column COMMENT: Cái Bẫy Của MODIFY

MySQL không có cú pháp chỉ đổi COMMENT của column. Bạn phải dùng MODIFY COLUMN (hoặc CHANGE COLUMN), và phải viết lại toàn bộ định nghĩa cột:

-- SAI: mất NOT NULL và DEFAULT, cột trở thành NULL DEFAULT NULL
ALTER TABLE product_variants
  MODIFY COLUMN jan_cd VARCHAR(13) COMMENT 'JANコード';

-- ĐÚNG: giữ nguyên mọi thuộc tính, chỉ thêm/đổi COMMENT
ALTER TABLE product_variants
  MODIFY COLUMN jan_cd VARCHAR(13) NOT NULL DEFAULT '' COMMENT 'JANコード';

Giải thích: MODIFY thay thế định nghĩa cột bằng đúng những gì bạn viết. Thuộc tính nào bạn không viết (NOT NULL, DEFAULT, CHARACTER SET, COLLATE, AUTO_INCREMENT, ON UPDATE CURRENT_TIMESTAMP) sẽ bị mất. Đây là lỗi nguy hiểm vì nó im lặng: không báo lỗi, chỉ đến khi có dữ liệu NULL lọt vào mới phát hiện.

Cách an toàn nhất là sinh câu ALTER từ định nghĩa hiện tại:

SELECT CONCAT(
  'ALTER TABLE `', TABLE_NAME, '` MODIFY COLUMN `', COLUMN_NAME, '` ',
  COLUMN_TYPE,
  IF(CHARACTER_SET_NAME IS NOT NULL,
     CONCAT(' CHARACTER SET ', CHARACTER_SET_NAME, ' COLLATE ', COLLATION_NAME), ''),
  IF(IS_NULLABLE = 'NO', ' NOT NULL', ' NULL'),
  IF(COLUMN_DEFAULT IS NULL, '',
     IF(COLUMN_DEFAULT REGEXP '^(CURRENT_TIMESTAMP|current_timestamp)',
        CONCAT(' DEFAULT ', COLUMN_DEFAULT),
        CONCAT(' DEFAULT ', QUOTE(COLUMN_DEFAULT)))),
  IF(EXTRA <> '', CONCAT(' ', REPLACE(EXTRA, 'DEFAULT_GENERATED', '')), ''),
  ' COMMENT ', QUOTE('<<論理名>>'), ';'
) AS ddl
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
  AND TABLE_NAME = 'product_variants'
ORDER BY ORDINAL_POSITION;

Sau đó thay <<論理名>> bằng tên logic thật. Nhưng cách tốt nhất là lấy chính câu SHOW CREATE TABLE làm chuẩn rồi đối chiếu:

SHOW CREATE TABLE product_variants\G

Lưu ý với MariaDB: COLUMN_DEFAULT trong INFORMATION_SCHEMA của MariaDB 10.2.7+ trả về giá trị đã được quote ('abc', NULL), khác với MySQL. Câu sinh DDL ở trên cần chỉnh lại nếu chạy trên MariaDB. Luôn review DDL trước khi chạy.

Kiểm Tra Trước Và Sau Khi ALTER

Để chắc chắn ALTER chỉ đổi COMMENT, hãy chụp lại định nghĩa cột (trừ COMMENT) trước và sau:

-- Chạy trước và sau khi ALTER, kết quả phải giống hệt nhau
SELECT TABLE_NAME,
       MD5(GROUP_CONCAT(
         CONCAT_WS('|', COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE,
                   IFNULL(COLUMN_DEFAULT, '<NULL>'), EXTRA,
                   IFNULL(COLLATION_NAME, ''))
         ORDER BY ORDINAL_POSITION SEPARATOR '\n')) AS def_hash
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
  AND TABLE_NAME IN ('products', 'product_variants')
GROUP BY TABLE_NAME;

Nếu def_hash của bảng nào thay đổi, nghĩa là ALTER đã vô tình đổi thứ khác ngoài COMMENT.

Với bảng có nhiều cột, hãy tăng SET SESSION group_concat_max_len = 1000000; trước khi chạy, nếu không chuỗi sẽ bị cắt ở 1024 byte và hash vô nghĩa.

ALTER COMMENT Có Lock Bảng Không?

Với MySQL 8.0 và MariaDB 10.3+, đổi COMMENT là thao tác chỉ sửa metadata (ALGORITHM=INSTANT hoặc INPLACE không rebuild). Không copy bảng, chạy gần như tức thì kể cả với bảng hàng triệu dòng. Nhưng nó vẫn cần metadata lock, nên nếu có transaction dài đang mở trên bảng, câu ALTER sẽ phải chờ (và chặn các query đến sau). Hãy kiểm tra SHOW PROCESSLIST trước khi chạy trên môi trường có người dùng.

Bạn có thể yêu cầu rõ thuật toán để MySQL báo lỗi thay vì âm thầm rebuild bảng:

ALTER TABLE product_variants
  MODIFY COLUMN jan_cd VARCHAR(13) NOT NULL DEFAULT '' COMMENT 'JANコード',
  ALGORITHM = INSTANT;

Bước 4: Đồng Bộ Với Migration

Sửa trực tiếp trên DB là chưa đủ. Nếu migration không có COMMENT, lần tới ai đó tạo DB mới từ migration (môi trường mới, CI, máy dev mới), toàn bộ COMMENT sẽ biến mất.

Laravel

Schema::create('product_variants', function (Blueprint $table) {
    $table->comment('SKU情報');  // Table comment (Laravel 9+)

    $table->id()->comment('ID');
    $table->string('sku_cd', 10)->comment('SKUコード');
    $table->string('product_cd', 10)->comment('商品コード');
    $table->string('jan_cd', 13)->default('')->comment('JANコード');
    $table->unsignedInteger('version')->default(0)->comment('バージョン番号');
    $table->timestamp('created_at')->nullable()->comment('登録日時');
    $table->timestamp('updated_at')->nullable()->comment('更新日時');
});

Có hai chiến lược:

Chiến lược Cách làm Ưu Nhược
Sửa migration create_* gốc Thêm ->comment() vào file tạo bảng Migration đọc một chỗ là thấy đủ Không áp dụng cho DB đã chạy migration, phải ALTER riêng
Tạo migration mới Một migration chỉ để đổi COMMENT Chạy được trên mọi môi trường bằng migrate Định nghĩa bảng bị chia ra nhiều file

Trong task mẫu, team chọn cả hai: sửa create_*_table.php để DB tạo mới có đủ COMMENT, đồng thời có migration riêng chạy ALTER cho các DB đã tồn tại (dev, demo). Điều quan trọng là giá trị COMMENT ở hai nơi phải giống hệt nhau.

Nếu tạo migration ALTER bằng Laravel, cẩn thận với ->change(): từ Laravel 11, change() cũng thay thế toàn bộ định nghĩa cột giống MODIFY của MySQL. Bạn phải khai báo lại nullable(), default()... Cách an toàn hơn là dùng DB::statement() với DDL đã review:

public function up(): void
{
    DB::statement("ALTER TABLE products COMMENT = '商品情報'");
    DB::statement(
        "ALTER TABLE product_variants MODIFY COLUMN jan_cd VARCHAR(13) NOT NULL DEFAULT '' COMMENT 'JANコード'"
    );
}

Verify Migration Khớp Với DB

Cách chắc chắn nhất: tạo một DB trống, chạy migrate từ đầu, rồi so sánh COMMENT giữa DB mới và DB dev (xem Bước 6). Nếu không có điều kiện, ít nhất hãy grep migration so với kết quả SQL:

# Liệt kê mọi comment trong migration của bảng product_variants
grep -o "comment('[^']*')" database/migrations/*create_product_variants_table.php

Bước 5: Verify Bằng SQL Trên INFORMATION_SCHEMA

Đây là phần quan trọng nhất. Mục tiêu là mọi tiêu chí nghiệm thu đều có một câu SQL tương ứng, và câu SQL đó trả về kết quả rõ ràng: "12 dòng khớp danh sách" hoặc "0 dòng lỗi".

Để dễ tái sử dụng, khai báo danh sách bảng một lần:

SET @tables = 'products,product_variants,product_prices,product_images,categories,edit_locks,audit_logs';
-- Dùng: AND FIND_IN_SET(TABLE_NAME, @tables)

Trong báo cáo gửi khách, nên viết TABLE_NAME IN (...) tường minh thay vì biến. Khách có thể copy từng câu chạy riêng trong tool GUI, và biến session sẽ không còn.

Check 1: Table COMMENT Khớp Danh Sách

SELECT TABLE_NAME, TABLE_COMMENT
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = DATABASE()
  AND FIND_IN_SET(TABLE_NAME, @tables)
ORDER BY TABLE_NAME;

Kết quả mong đợi: số dòng bằng số bảng trong ticket, mỗi TABLE_COMMENT đúng nguyên văn. Nếu muốn tự động hóa hoàn toàn, đưa danh sách mong đợi vào câu SQL và chỉ in ra các dòng lệch:

SELECT e.table_name, e.expected, t.TABLE_COMMENT AS actual
FROM (
            SELECT 'products'         AS table_name, '商品情報'     AS expected
  UNION ALL SELECT 'product_variants',              'SKU情報'
  UNION ALL SELECT 'audit_logs',                    '操作履歴'
  -- ...
) e
LEFT JOIN INFORMATION_SCHEMA.TABLES t
  ON t.TABLE_SCHEMA = DATABASE() AND t.TABLE_NAME = e.table_name
WHERE t.TABLE_COMMENT IS NULL            -- bảng không tồn tại
   OR t.TABLE_COMMENT <> e.expected;     -- comment sai
-- Mong đợi: 0 dòng

Check 2: Column COMMENT Đầy Đủ Và Đúng Quy Tắc

Một câu SQL bắt tất cả các lỗi thường gặp. Kết quả mong đợi: 0 dòng.

SELECT TABLE_NAME, COLUMN_NAME, COLUMN_COMMENT
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
  AND FIND_IN_SET(TABLE_NAME, @tables)
  AND (
        -- (a) COMMENT trống
        COLUMN_COMMENT = ''
        -- (b) Không có kana/kanji nào (còn tiếng Anh), trừ từ viết tắt được phép
     OR (COLUMN_COMMENT NOT REGEXP '[ぁ-んァ-ヶ一-龯]'
         AND COLUMN_COMMENT NOT IN ('SKU', 'ID', 'URL'))
        -- (c) Có chữ/số full-width hoặc katakana half-width
     OR COLUMN_COMMENT REGEXP '[0-9A-Za-zヲ-゚]'
        -- (d) Có full-width space hoặc khoảng trắng thừa đầu/cuối
     OR COLUMN_COMMENT LIKE '% %'
     OR COLUMN_COMMENT <> TRIM(COLUMN_COMMENT)
        -- (e) Cột chung dùng sai thuật ngữ
     OR (COLUMN_NAME = 'version'    AND COLUMN_COMMENT <> 'バージョン番号')
     OR (COLUMN_NAME = 'created_at' AND COLUMN_COMMENT <> '登録日時')
     OR (COLUMN_NAME = 'updated_at' AND COLUMN_COMMENT <> '更新日時')
  )
ORDER BY TABLE_NAME, ORDINAL_POSITION;

Giải thích từng điều kiện:

  • (a) Cột bị sót. Đây là lỗi hay gặp nhất, đặc biệt với cột thêm vào sau qua migration khác
  • (b) [ぁ-んァ-ヶ一-龯] gồm hiragana, katakana và kanji thông dụng. COMMENT không chứa ký tự nào trong số đó nghĩa là vẫn là tiếng Anh hoặc chỉ có ký hiệu. Danh sách NOT IN là các ngoại lệ đã thống nhất với khách
  • (c) 0-9A-Za-z là chữ/số full-width, ヲ-゚ là katakana half-width. Có bất kỳ ký tự nào trong số này là vi phạm quy tắc 半角英数字
  • (d) Full-width space (U+3000) rất khó thấy bằng mắt, thường lọt vào khi copy từ Excel
  • (e) Áp dụng bảng thuật ngữ cho các cột lặp lại

Lưu ý về REGEXP với tiếng Nhật:

  • MySQL 8.0 dùng thư viện ICU nên regex hiểu Unicode, range [ぁ-ん] hoạt động đúng trên cột utf8mb4
  • MySQL 5.7 dùng thư viện Henry Spencer, làm việc theo byte. Range ký tự multi-byte không hoạt động đúng. Nếu còn dùng 5.7, hãy export ra và kiểm tra bằng script
  • MariaDB 10.0.5+ dùng PCRE, hỗ trợ Unicode, range ở trên hoạt động bình thường
  • 一-龯 không bao phủ toàn bộ kanji (một số kanji hiếm nằm ngoài), nhưng đủ cho tên logic thông thường

Check 3: Thuật Ngữ Thống Nhất Giữa Các Bảng

Check 2 chỉ kiểm tra các cột bạn đã liệt kê. Câu này tìm mọi cột cùng tên nhưng khác COMMENT, kể cả những cột bạn chưa nghĩ tới:

SELECT COLUMN_NAME,
       COUNT(DISTINCT COLUMN_COMMENT) AS variants,
       GROUP_CONCAT(DISTINCT CONCAT(COLUMN_COMMENT, ' (', TABLE_NAME, ')')
                    ORDER BY COLUMN_COMMENT SEPARATOR ' / ') AS detail
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
  AND FIND_IN_SET(TABLE_NAME, @tables)
GROUP BY COLUMN_NAME
HAVING variants > 1
ORDER BY COLUMN_NAME;

Ví dụ kết quả:

COLUMN_NAME  variants  detail
created_by   2         作成者 (edit_locks) / 登録者 (products) / 登録者 (product_variants)

Không phải dòng nào cũng là lỗi. Ví dụ cột no có thể mang nghĩa khác nhau ở hai bảng. Nhưng mỗi dòng đều cần một quyết định có chủ đích.

Check 4: Xuất Toàn Bộ Định Nghĩa Cột (テーブル定義書)

Câu này sinh ra đúng các cột của một テーブル定義書 chuẩn. Copy kết quả vào spreadsheet là có tài liệu đối chiếu:

SELECT c.TABLE_NAME                     AS 物理テーブル名,
       t.TABLE_COMMENT                  AS 論理テーブル名,
       c.ORDINAL_POSITION               AS No,
       c.COLUMN_COMMENT                 AS 論理名,
       c.COLUMN_NAME                    AS 物理名,
       c.COLUMN_TYPE                    AS データ型,
       IF(c.IS_NULLABLE = 'NO', '○', '') AS NOT_NULL,
       c.COLUMN_DEFAULT                 AS デフォルト,
       c.COLUMN_KEY                     AS キー,
       c.EXTRA                          AS 備考
FROM INFORMATION_SCHEMA.COLUMNS c
JOIN INFORMATION_SCHEMA.TABLES t
  ON t.TABLE_SCHEMA = c.TABLE_SCHEMA AND t.TABLE_NAME = c.TABLE_NAME
WHERE c.TABLE_SCHEMA = DATABASE()
  AND FIND_IN_SET(c.TABLE_NAME, @tables)
ORDER BY c.TABLE_NAME, c.ORDINAL_POSITION;

Giải thích:

  • Alias tiếng Nhật giúp copy thẳng vào sheet mà không phải đổi header. Nếu tool của bạn không hiển thị được alias tiếng Nhật, đổi sang alias tiếng Anh
  • ○ cho NOT NULL là quy ước quen thuộc trong tài liệu Nhật
  • COLUMN_KEY trả về PRI, UNI, MUL. Nếu khách muốn ghi rõ tên index, join thêm INFORMATION_SCHEMA.STATISTICS

Nếu dự án cần file Excel định dạng đẹp, viết một script nhỏ (Python + openpyxl, hoặc Laravel command) chạy câu SQL trên và ghi ra mỗi bảng một sheet. Quan trọng là tài liệu luôn được sinh lại từ DB, không sửa tay.

Bước 6: So Sánh Giữa Các Môi Trường

Task thường yêu cầu áp dụng trên nhiều môi trường (dev, staging/demo, production). Chạy các check trên từng môi trường là cần thiết, nhưng còn một rủi ro: hai môi trường đều "0 dòng lỗi" nhưng COMMENT khác nhau, ví dụ một bên sửa tay, một bên chạy migration.

Tạo "dấu vân tay" cho COMMENT của từng bảng:

SET SESSION group_concat_max_len = 1000000;

SELECT t.TABLE_NAME,
       MD5(CONCAT(
         t.TABLE_COMMENT, '\n',
         GROUP_CONCAT(CONCAT(c.COLUMN_NAME, '=', c.COLUMN_COMMENT)
                      ORDER BY c.ORDINAL_POSITION SEPARATOR '\n')
       )) AS comment_hash
FROM INFORMATION_SCHEMA.TABLES t
JOIN INFORMATION_SCHEMA.COLUMNS c
  ON c.TABLE_SCHEMA = t.TABLE_SCHEMA AND c.TABLE_NAME = t.TABLE_NAME
WHERE t.TABLE_SCHEMA = DATABASE()
  AND FIND_IN_SET(t.TABLE_NAME, @tables)
GROUP BY t.TABLE_NAME, t.TABLE_COMMENT
ORDER BY t.TABLE_NAME;

Chạy trên cả hai môi trường rồi diff:

for db in app_dev app_stg; do
  mysql -N "$db" < comment_hash.sql > "hash_$db.txt"
done
diff hash_app_dev.txt hash_app_stg.txt && echo "Khớp 100%"

Nếu hai DB nằm trên cùng một server, có thể so sánh trực tiếp bằng một câu SQL:

SELECT a.TABLE_NAME, a.COLUMN_NAME,
       a.COLUMN_COMMENT AS dev_comment, b.COLUMN_COMMENT AS stg_comment
FROM INFORMATION_SCHEMA.COLUMNS a
LEFT JOIN INFORMATION_SCHEMA.COLUMNS b
  ON b.TABLE_SCHEMA = 'app_stg'
 AND b.TABLE_NAME = a.TABLE_NAME AND b.COLUMN_NAME = a.COLUMN_NAME
WHERE a.TABLE_SCHEMA = 'app_dev'
  AND FIND_IN_SET(a.TABLE_NAME, @tables)
  AND NOT (b.COLUMN_COMMENT <=> a.COLUMN_COMMENT);
-- Mong đợi: 0 dòng

Toán tử <=> (NULL-safe equal) đảm bảo cột tồn tại ở dev nhưng thiếu ở stg cũng bị bắt.

Bước 7: Viết Báo Cáo Hoàn Thành Cho Khách

Với khách Nhật, một báo cáo "đã xong" mà không có bằng chứng sẽ dẫn tới một vòng hỏi lại. Báo cáo tốt cần trả lời được ba câu hỏi: đã làm gì, ở đâu, làm sao tự kiểm tra.

Cấu Trúc Khuyến Nghị

Phần Nội dung
Kết luận Một câu: 対応が完了しました
Kết quả (結果) Mỗi tiêu chí nghiệm thu là một bullet, ghi rõ môi trường áp dụng
Tài liệu (テーブル定義書) Link đến tài liệu đã sinh lại
Cách kiểm tra (確認方法) SQL copy-paste được, kèm kết quả mong đợi của từng câu
Bằng chứng Ngày giờ đã chạy check, kết quả thực tế trên từng môi trường
Source Link PR/commit

Mẫu Báo Cáo Song Ngữ

Trong team offshore Việt Nam, báo cáo thường viết song ngữ: phần tiếng Việt cho team nội bộ và comtor (biên dịch viên), phần tiếng Nhật cho khách. Mẫu rút gọn:

# 日本語

対応が完了しました。

**結果**(dev・demo の DB):
- 12テーブルのテーブルコメントは課題記載の文言どおり。
- 12テーブルの全カラムに日本語の論理名の COMMENT を設定済み(空のカラムなし)。
- `version` はすべて `バージョン番号`。
- 英字・数字は半角(例:`JANコード`)。英語の COMMENT 3列も日本語の論理名に置換済み。
- 画面に出ないカラムは Legacy のテーブル定義書の COMMENTS を使用。
- マイグレーションにも DB と同じ COMMENT を記載しているため、
  マイグレーションから作り直しても同じ COMMENT になります。

**テーブル定義書:** <link>

**確認方法:** 下記の SQL を dev または demo の DB で実行してください。
- (1) テーブルコメント:12行が課題の一覧と一致すること
- (2) カラムコメント:0行であること(空・かな漢字なし・全角英数字・version 誤り を検出)
- (3) カラム定義一覧:テーブル定義書との突合用

2026-10-16 確認済み:(1) 12/12 一致、(2) 0行(dev・demo とも)。

Những điểm làm báo cáo này hiệu quả:

  • Mỗi bullet là một tiêu chí kiểm tra được, không viết chung chung kiểu "đã cập nhật comment"
  • Ví dụ cụ thể (JANコード) cho quy tắc half-width, để khách hiểu ngay quy tắc đã áp dụng
  • Giải thích nguồn của tên logic ("画面に出ないカラムは Legacy のテーブル定義書"). Khách không phải hỏi "từ này ở đâu ra"
  • Nhắc tới migration. Khách có kinh nghiệm sẽ hỏi câu này, trả lời trước thì tốt hơn
  • Kết quả mong đợi đi kèm SQL: "12行", "0行". Khách chạy xong là biết ngay đạt hay không
  • Ngày đã kiểm tra và kết quả thực tế trên từng môi trường, là bằng chứng có thể đối chiếu sau này

Một số từ tiếng Nhật hay dùng trong loại báo cáo này:

Tiếng Nhật Đọc Nghĩa
対応完了 taiou kanryou Đã xử lý xong
論理名 / 物理名 ronri-mei / butsuri-mei Tên logic / tên vật lý
テーブル定義書 teeburu teigi-sho Tài liệu định nghĩa bảng
確認方法 kakunin houhou Cách kiểm tra
突合 tsukiawase Đối chiếu
半角 / 全角 hankaku / zenkaku Half-width / full-width
確認済み kakunin-zumi Đã kiểm tra
検収 kenshuu Nghiệm thu

Xử Lý Lỗi Thường Gặp

COMMENT hiển thị thành ??? hoặc ký tự lạ

Nguyên nhân thường là charset của connection, không phải của bảng. Kiểm tra:

SHOW VARIABLES LIKE 'character_set%';

character_set_client, character_set_connection, character_set_results phải là utf8mb4. Khi chạy file SQL bằng CLI, thêm --default-character-set=utf8mb4:

mysql --default-character-set=utf8mb4 app_dev < set_comments.sql

Nếu COMMENT đã bị lưu sai (lưu thành ? thật sự), phải chạy lại ALTER với connection đúng charset. Không có cách khôi phục từ ?.

REGEXP không bắt được ký tự full-width

  • Kiểm tra version: MySQL 5.7 không xử lý đúng range multi-byte
  • Collation của biểu thức có thể khiến so sánh không phân biệt full-width/half-width. Ép collation nhị phân khi cần chính xác tuyệt đối:
WHERE COLUMN_COMMENT COLLATE utf8mb4_bin REGEXP '[0-9A-Za-z]'

Sau khi ALTER, cột mất NOT NULL hoặc DEFAULT

Đây là bẫy MODIFY COLUMN ở Bước 3. Khôi phục bằng cách chạy lại MODIFY với định nghĩa đầy đủ (lấy từ SHOW CREATE TABLE trên môi trường khác hoặc từ migration). Từ lần sau, luôn chạy check def_hash trước và sau.

Chạy migration mới tạo ra COMMENT khác với DB

Migration create_* và migration ALTER có giá trị khác nhau. Dùng Check 3/Bước 6 trên một DB được tạo mới hoàn toàn từ migration để phát hiện.

Best Practices

  1. DB là nguồn sự thật, テーブル定義書 được sinh ra từ DB. Không bao giờ sửa tay tài liệu
  2. Dùng nguyên văn từ khách. Tên trong ticket, label trên màn hình, tài liệu legacy, theo thứ tự đó
  3. Lập bảng thuật ngữ trước khi viết, và verify nó bằng SQL (Check 3)
  4. Half-width cho chữ Latin/số, full-width cho katakana, không có full-width space
  5. Không bao giờ viết tay MODIFY COLUMN. Sinh DDL từ định nghĩa hiện tại và so sánh def_hash trước/sau
  6. COMMENT trong migration và trên DB phải giống hệt nhau
  7. Mỗi tiêu chí nghiệm thu là một câu SQL, có kết quả mong đợi rõ ràng (N dòng khớp, hoặc 0 dòng lỗi)
  8. Verify trên mọi môi trường đã áp dụng, và so sánh chéo giữa các môi trường
  9. Báo cáo kèm SQL, kết quả mong đợi, ngày kiểm tra và kết quả thực tế, để khách tự xác nhận được mà không cần hỏi lại

Tổng Kết

Việc cần làm Công cụ/Kỹ thuật
Xác định tên logic Ticket → UI → テーブル定義書 legacy → bảng thuật ngữ
Sửa table COMMENT ALTER TABLE t COMMENT = '...'
Sửa column COMMENT MODIFY COLUMN với định nghĩa đầy đủ, kiểm tra def_hash
Giữ bền vững ->comment() trong migration, giá trị giống DB
Verify đầy đủ/đúng quy tắc INFORMATION_SCHEMA.COLUMNS + REGEXP, mong đợi 0 dòng
Verify thuật ngữ GROUP BY COLUMN_NAME HAVING COUNT(DISTINCT COLUMN_COMMENT) > 1
Verify giữa môi trường Hash COMMENT theo bảng, diff hoặc join hai schema
Bàn giao テーブル定義書 sinh từ DB + báo cáo song ngữ kèm SQL

Đặt COMMENT nghe như một task nhỏ, nhưng với khách Nhật nó là một phần của chất lượng bàn giao. Làm đúng thì tài liệu luôn khớp với hệ thống, khách tự verify được trong vài phút, và team offshore xây dựng được niềm tin, thứ quan trọng hơn bất kỳ dòng code nào.

Tham Khảo