PL/SQL 中字符類型的選擇直接影響內存佔用、比較語義、性能表現與數據完整性。CHAR 與 VARCHAR2 的固定/變長機制、CHARACTER 作為 CHAR 子類型的繼承關係,構成了 PL/SQL 字符處理的基石。本文通過底層內存分配演示、比較語義對比、隱式轉換陷阱實測,徹底釐清類型邊界。
DECLARE
c CHAR(32767) := ' '; -- 宣告最大長度
v VARCHAR2(32767) := ' '; -- 宣告最大長度
BEGIN
DBMS_OUTPUT.PUT_LINE('c 初始長度: [' || LENGTH(c) || ']'); -- 32767
DBMS_OUTPUT.PUT_LINE('v 初始長度: [' || LENGTH(v) || ']'); -- 1
v := v || ' '; -- 拼接一個空格
DBMS_OUTPUT.PUT_LINE('v 拼接後: [' || LENGTH(v) || ']'); -- 2
END;
/
輸出:
c 初始長度: [32767]
v 初始長度: [1]
v 拼接後: [2]
結論:
CHAR(n):宣告即分配 n 字節內存 (填充空格),與實際賦值無關VARCHAR2(n):動態分配,僅佔用實際字符長度 + 1-2 字節長度頭DECLARE
var1 CHAR := ' '; -- 隱式 CHAR(1)
var2 CHAR(1) := ' '; -- 顯式 CHAR(1)
var3 CHAR(1 BYTE) := ' '; -- 字節語義
var4 CHAR(1 CHAR) := ' '; -- 字符語義 (多字節字符集下不同)
BEGIN
DBMS_OUTPUT.PUT_LINE('var1: ' || LENGTH(var1)); -- 1
DBMS_OUTPUT.PUT_LINE('var2: ' || LENGTH(var2)); -- 1
DBMS_OUTPUT.PUT_LINE('var3: ' || LENGTH(var3)); -- 1
DBMS_OUTPUT.PUT_LINE('var4: ' || LENGTH(var4)); -- 1 (若字符集 AL32UTF8,1 CHAR 可能佔 3 字節)
END;
/
CREATE TABLE tt (a1 CHAR(2), a2 VARCHAR2(2));
INSERT INTO tt VALUES ('A', 'A');
INSERT INTO tt VALUES ('A', 'A '); -- 含尾部空格
COMMIT;
-- CHAR 比較:右側填充空格後比較
SELECT * FROM tt WHERE a1 = 'A'; -- 返回 2 行 ('A' 填充為 'A ' 匹配)
SELECT * FROM tt WHERE a1 = 'A '; -- 返回 2 行
-- VARCHAR2 比較:精確匹配,不填充
SELECT * FROM tt WHERE a2 = 'A'; -- 返回 1 行
SELECT * FROM tt WHERE a2 = 'A '; -- 返回 1 行 (第二行)
-- CHAR vs VARCHAR2 跨類型比較:不填充,直接比較
SELECT * FROM tt WHERE a1 = a2; -- 返回 1 行 (第一行 'A'='A')
-- DECODE: 將參數視為 VARCHAR2 處理 (不填充空格)
SELECT DECODE(a1, 'A', 'AAAA', 'BBBB') FROM tt;
-- 結果: BBBB, BBBB (因為 'A' 不填充,不等於 'A ')
SELECT DECODE(a1, 'A ', 'AAAA', 'BBBB') FROM tt; -- 兩個空格
-- 結果: BBBB, BBBB (DECODE 仍按 VARCHAR2 比較,'A ' != 'A ')
-- CASE: 遵循 CHAR 比較規則 (填充空格)
SELECT CASE a1 WHEN 'A' THEN 'AAA' ELSE 'BBB' END FROM tt;
-- 結果: AAA, AAA (CHAR 比較:'A' 填充為 'A ' 匹配)
SELECT CASE a1 WHEN 'A ' THEN 'AAA' ELSE 'BBB' END FROM tt;
-- 結果: AAA, AAA
根因:DECODE 是函數,參數類型解析為 VARCHAR2;CASE 是表達式,保持操作數原類型語義。
CREATE TABLE t1 (name CHAR(10));
CREATE TABLE t2 (name CHAR(20));
INSERT INTO t1 SELECT TO_CHAR(LEVEL) FROM DUAL CONNECT BY LEVEL <= 5;
INSERT INTO t2 SELECT TO_CHAR(LEVEL) FROM DUAL CONNECT BY LEVEL <= 5;
COMMIT;
-- 若按 CHAR 比較規則 (填充空格),t1 '1________' (10字節) 應等於 t2 '1__________________' (20字節)
-- 實際結果:
SELECT name FROM t1 MINUS SELECT name FROM t2; -- 返回 5 行 (1,2,3,4,5)
SELECT name FROM t1 INTERSECT SELECT name FROM t2; -- 無返回
-- 結論:集合操作按「實際存儲字節」比較,不遵循 CHAR 填充語義
-- 子類型定義語法
SUBTYPE subtype_name IS base_type[(maximum_size [BYTE | CHAR])] [NOT NULL];
-- 實例:定義業務代碼類型
DECLARE
SUBTYPE code IS CHAR(1 CHAR); -- 基於 CHAR(1 CHAR)
c CHAR(1 CHAR) := 'A';
d CODE;
BEGIN
d := c; -- 隱式轉換,同類型賦值
DBMS_OUTPUT.PUT_LINE('Assigned: ' || d);
END;
/
特性:
CHARACTER 是 CHAR 的標準 SQL 別名 (ANSI 兼容)CHARACTER 即 CHAR 的子類型,大小、語義完全一致CHAR,避免混用造成認知負擔| 場景 | 推薦類型 | 理由 |
|---|---|---|
| 固定長度代碼 (國標、貨幣、狀態碼) | CHAR(n) 或 CHAR(n CHAR) |
長度固定、比較語義清晰、索引效率高 |
| 變長業務字段 (姓名、地址、描述) | VARCHAR2(n) 或 VARCHAR2(n CHAR) |
節省空間、無填充開銷、動態分配 |
| 頻繁 UPDATE 長度變化的列 | CHAR(n) |
避免行遷移,以空間換穩定性 |
| 主鍵/唯一索引列 (定長) | CHAR(n) |
索引條目固定長度,分裂可預測 |
| PL/SQL 變量接收表列值 | table_col%TYPE |
錨定類型,DDL 變更自動適配 |
CHAR = 宣告即佔滿,比較填空格;VARCHAR2 = 用多少佔多少,比較不填充。
DECODE 視 CHAR 為 VARCHAR2;CASE 尊重 CHAR 本質。
集合操作看實際字節,不看類型語義。
業務代碼用 CHAR,業務文本用 VARCHAR2,變量用 %TYPE。
掌握類型的內存模型、比較語義、函數/表達式處理差異,能讓您避開 90% 的字符處理邏輯 Bug,編寫高效、可預測的 PL/SQL 代碼。