db上での半角全角変換
半角カナでdbにデータを入れたい、DB上の全角カナを半角カナに変更して出力したい。
ということはよくあることかと思います。
特に金融や医療関係のシステムだと要件に入ってくることも多い。
また、古い外部の決済システムや、販売代行会社などを使っていると、顧客名簿や商品名が半角カナのcsvで送られてくることもあります。
これらのデータを半角↔全角をあまり意識しないようにシステムを組みたい。
ただ、この処理をアプリケーション側で定義すると下記のような問題が置きます。
デメリット
- 金融や医療関係のシステムの場合は複数のベンダーが入ってくることが多く、それぞれ別のアプリケーションを作っている場合が多いため、アプリケーションごとにそれぞれ別の半角カナ変換ロジックを管理することになり大変。
- アプリケーション側で実装すると速度が遅い。
半角カナの処理は業務上、それなりの頻度で実行する処理かバッチ処理に組み込まれることが多いので、早くしておきたいというのもあります。
またプログラムを書くにあたって、合字対策や反転処理をなど、知識というよりシンプルにプログラミングスキルが試される処理になるので、あまり外に投げないほうがいい処理かと思います。
どうするか?
上のデメリットはロジックをストアドに寄せて、それをアプリケーションから参照するようにすると、システム全体で半角カナのロジックは一つ管理するだけで良くなり、簡単になる。
もし今後ハングル文字など他の文字の全角半角サポートが増えても、ここに処理を追加するだけで機能修正は終わりです。
Userテーブルのカラムに使う場合は生成カラムからストアドを呼び出すと、全角カナのデータ一つで半角カナを表現できるため、管理も楽になる。既存のシステムが全部ormだとしても、ormとの相性もそこまで悪くない。
ソースコード
postgresqlの例を下に示す。
他のRDBMSも同じ要領です。
function にimmutableは必ずつけてください。
これがないとDBの変更を起こす副作用があるストアドなのかどうか、
DB側で判別できないため、生成カラムが使えなかったり、その他DBの処理に大きな影響を与えます。
これは他のRDBMSでも同じです。
hankakukana2zenkaku_string -> 半角文字列を全角文字列に変更するストアド
zenkaku2hankakukana_string -> 全角文字列を半角文字列に変更するストアド
CREATE FUNCTION zenkaku2hankakukana (c CHAR)
RETURNS VARCHAR(2) AS $$
DECLARE
ret VARCHAR(2);
BEGIN
CASE c
-- ア行
WHEN 'ア' THEN
ret := 'ア';
WHEN 'イ' THEN
ret := 'イ';
WHEN 'ウ' THEN
ret := 'ウ';
WHEN 'エ' THEN
ret := 'エ';
WHEN 'オ' THEN
ret := 'オ';
-- カ行
WHEN 'カ' THEN
ret := 'カ';
WHEN 'キ' THEN
ret := 'キ';
WHEN 'ク' THEN
ret := 'ク';
WHEN 'ケ' THEN
ret := 'ケ';
WHEN 'コ' THEN
ret := 'コ';
-- サ行
WHEN 'サ' THEN
ret := 'サ';
WHEN 'シ' THEN
ret := 'シ';
WHEN 'ス' THEN
ret := 'ス';
WHEN 'セ' THEN
ret := 'セ';
WHEN 'ソ' THEN
ret := 'ソ';
-- タ行
WHEN 'タ' THEN
ret := 'タ';
WHEN 'チ' THEN
ret := 'チ';
WHEN 'ツ' THEN
ret := 'ツ';
WHEN 'テ' THEN
ret := 'テ';
WHEN 'ト' THEN
ret := 'ト';
-- ナ行
WHEN 'ナ' THEN
ret := 'ナ';
WHEN 'ニ' THEN
ret := 'ニ';
WHEN 'ヌ' THEN
ret := 'ヌ';
WHEN 'ネ' THEN
ret := 'ネ';
WHEN 'ノ' THEN
ret := 'ノ';
-- ハ行
WHEN 'ハ' THEN
ret := 'ハ';
WHEN 'ヒ' THEN
ret := 'ヒ';
WHEN 'フ' THEN
ret := 'フ';
WHEN 'ヘ' THEN
ret := 'ヘ';
WHEN 'ホ' THEN
ret := 'ホ';
-- マ行
WHEN 'マ' THEN
ret := 'マ';
WHEN 'ミ' THEN
ret := 'ミ';
WHEN 'ム' THEN
ret := 'ム';
WHEN 'メ' THEN
ret := 'メ';
WHEN 'モ' THEN
ret := 'モ';
-- ヤ行
WHEN 'ヤ' THEN
ret := 'ヤ';
WHEN 'ユ' THEN
ret := 'ユ';
WHEN 'ヨ' THEN
ret := 'ヨ';
-- ワ行
WHEN 'ワ' THEN
ret := 'ワ';
WHEN 'ヲ' THEN
ret := 'ヲ';
WHEN 'ン' THEN
ret := 'ン';
-- ガ行
WHEN 'ガ' THEN
ret := 'ガ';
WHEN 'ギ' THEN
ret := 'ギ';
WHEN 'グ' THEN
ret := 'グ';
WHEN 'ゲ' THEN
ret := 'ゲ';
WHEN 'ゴ' THEN
ret := 'ゴ';
-- ザ行
WHEN 'ザ' THEN
ret := 'ザ';
WHEN 'ジ' THEN
ret := 'ジ';
WHEN 'ズ' THEN
ret := 'ズ';
WHEN 'ゼ' THEN
ret := 'ゼ';
WHEN 'ゾ' THEN
ret := 'ゾ';
-- ダ行
WHEN 'ダ' THEN
ret := 'ダ';
WHEN 'ヂ' THEN
ret := 'ヂ';
WHEN 'ヅ' THEN
ret := 'ヅ';
WHEN 'デ' THEN
ret := 'デ';
WHEN 'ド' THEN
ret := 'ド';
-- バ行
WHEN 'バ' THEN
ret := 'バ';
WHEN 'ビ' THEN
ret := 'ビ';
WHEN 'ブ' THEN
ret := 'ブ';
WHEN 'ベ' THEN
ret := 'ベ';
WHEN 'ボ' THEN
ret := 'ボ';
-- パ行
WHEN 'パ' THEN
ret := 'パ';
WHEN 'ピ' THEN
ret := 'ピ';
WHEN 'プ' THEN
ret := 'プ';
WHEN 'ペ' THEN
ret := 'ペ';
WHEN 'ポ' THEN
ret := 'ポ';
-- 小文字
WHEN 'ァ' THEN
ret := 'ァ';
WHEN 'ィ' THEN
ret := 'ィ';
WHEN 'ゥ' THEN
ret := 'ゥ';
WHEN 'ェ' THEN
ret := 'ェ';
WHEN 'ォ' THEN
ret := 'ォ';
WHEN 'ッ' THEN
ret := 'ッ';
WHEN 'ャ' THEN
ret := 'ャ';
WHEN 'ュ' THEN
ret := 'ュ';
WHEN 'ョ' THEN
ret := 'ョ';
-- その他 伸ばし棒ー(マイナスとは違う)
WHEN 'ー' THEN
ret := 'ー';
-- アルファベット
-- 小文字
WHEN 'a' THEN
ret := 'a';
WHEN 'b' THEN
ret := 'b';
WHEN 'c' THEN
ret := 'c';
WHEN 'd' THEN
ret := 'd';
WHEN 'e' THEN
ret := 'e';
WHEN 'f' THEN
ret := 'f';
WHEN 'g' THEN
ret := 'g';
WHEN 'h' THEN
ret := 'h';
WHEN 'i' THEN
ret := 'i';
WHEN 'j' THEN
ret := 'j';
WHEN 'k' THEN
ret := 'k';
WHEN 'l' THEN
ret := 'l';
WHEN 'm' THEN
ret := 'm';
WHEN 'n' THEN
ret := 'n';
WHEN 'o' THEN
ret := 'o';
WHEN 'p' THEN
ret := 'p';
WHEN 'q' THEN
ret := 'q';
WHEN 'r' THEN
ret := 'r';
WHEN 's' THEN
ret := 's';
WHEN 't' THEN
ret := 't';
WHEN 'u' THEN
ret := 'u';
WHEN 'v' THEN
ret := 'v';
WHEN 'w' THEN
ret := 'w';
WHEN 'x' THEN
ret := 'x';
WHEN 'y' THEN
ret := 'y';
WHEN 'z' THEN
ret := 'z';
-- 大文字
WHEN 'A' THEN
ret := 'A';
WHEN 'B' THEN
ret := 'B';
WHEN 'C' THEN
ret := 'C';
WHEN 'D' THEN
ret := 'D';
WHEN 'E' THEN
ret := 'E';
WHEN 'F' THEN
ret := 'F';
WHEN 'G' THEN
ret := 'G';
WHEN 'H' THEN
ret := 'H';
WHEN 'I' THEN
ret := 'I';
WHEN 'J' THEN
ret := 'J';
WHEN 'K' THEN
ret := 'K';
WHEN 'L' THEN
ret := 'L';
WHEN 'M' THEN
ret := 'M';
WHEN 'N' THEN
ret := 'N';
WHEN 'O' THEN
ret := 'O';
WHEN 'P' THEN
ret := 'P';
WHEN 'Q' THEN
ret := 'Q';
WHEN 'R' THEN
ret := 'R';
WHEN 'S' THEN
ret := 'S';
WHEN 'T' THEN
ret := 'T';
WHEN 'U' THEN
ret := 'U';
WHEN 'V' THEN
ret := 'V';
WHEN 'W' THEN
ret := 'W';
WHEN 'X' THEN
ret := 'X';
WHEN 'Y' THEN
ret := 'Y';
WHEN 'Z' THEN
ret := 'Z';
-- 数字
WHEN '0' THEN
ret := '0';
WHEN '1' THEN
ret := '1';
WHEN '2' THEN
ret := '2';
WHEN '3' THEN
ret := '3';
WHEN '4' THEN
ret := '4';
WHEN '5' THEN
ret := '5';
WHEN '6' THEN
ret := '6';
WHEN '7' THEN
ret := '7';
WHEN '8' THEN
ret := '8';
WHEN '9' THEN
ret := '9';
ELSE
-- 定義されていないものはそのまま返す
ret := c;
END CASE;
RETURN ret;
END;
$$ LANGUAGE plpgsql IMMUTABLE;
-- 全角文字列を半角文字列に変更する関数。
CREATE FUNCTION zenkaku2hankakukana_string (str TEXT)
RETURNS TEXT AS $$
DECLARE
ret TEXT;
i INTEGER := 1;
-- 何文字取るか
cap INTEGER := 1;
BEGIN
WHILE i <= char_length(str)
LOOP
ret := concat(ret, zenkaku2hankakukana(SUBSTRING(str, i, cap)));
-- 取った数だけ進める
i := i + cap;
END LOOP;
RETURN ret;
END;
$$ LANGUAGE plpgsql IMMUTABLE;
-- 半角を全角に変更する
CREATE FUNCTION hankakukana2zenkaku (c VARCHAR(2))
RETURNS CHAR(1) AS $$
DECLARE
ret CHAR(1);
BEGIN
CASE c
-- ア行
WHEN 'ア' THEN
ret := 'ア';
WHEN 'イ' THEN
ret := 'イ';
WHEN 'ウ' THEN
ret := 'ウ';
WHEN 'エ' THEN
ret := 'エ';
WHEN 'オ' THEN
ret := 'オ';
-- カ行
WHEN 'カ' THEN
ret := 'カ';
WHEN 'キ' THEN
ret := 'キ';
WHEN 'ク' THEN
ret := 'ク';
WHEN 'ケ' THEN
ret := 'ケ';
WHEN 'コ' THEN
ret := 'コ';
-- サ行
WHEN 'サ' THEN
ret := 'サ';
WHEN 'シ' THEN
ret := 'シ';
WHEN 'ス' THEN
ret := 'ス';
WHEN 'セ' THEN
ret := 'セ';
WHEN 'ソ' THEN
ret := 'ソ';
-- タ行
WHEN 'タ' THEN
ret := 'タ';
WHEN 'チ' THEN
ret := 'チ';
WHEN 'ツ' THEN
ret := 'ツ';
WHEN 'テ' THEN
ret := 'テ';
WHEN 'ト' THEN
ret := 'ト';
-- ナ行
WHEN 'ナ' THEN
ret := 'ナ';
WHEN 'ニ' THEN
ret := 'ニ';
WHEN 'ヌ' THEN
ret := 'ヌ';
WHEN 'ネ' THEN
ret := 'ネ';
WHEN 'ノ' THEN
ret := 'ノ';
-- ハ行
WHEN 'ハ' THEN
ret := 'ハ';
WHEN 'ヒ' THEN
ret := 'ヒ';
WHEN 'フ' THEN
ret := 'フ';
WHEN 'ヘ' THEN
ret := 'ヘ';
WHEN 'ホ' THEN
ret := 'ホ';
-- マ行
WHEN 'マ' THEN
ret := 'マ';
WHEN 'ミ' THEN
ret := 'ミ';
WHEN 'ム' THEN
ret := 'ム';
WHEN 'メ' THEN
ret := 'メ';
WHEN 'モ' THEN
ret := 'モ';
-- ヤ行
WHEN 'ヤ' THEN
ret := 'ヤ';
WHEN 'ユ' THEN
ret := 'ユ';
WHEN 'ヨ' THEN
ret := 'ヨ';
-- ワ行
WHEN 'ワ' THEN
ret := 'ワ';
WHEN 'ヲ' THEN
ret := 'ヲ';
WHEN 'ン' THEN
ret := 'ン';
-- ガ行
WHEN 'ガ' THEN
ret := 'ガ';
WHEN 'ギ' THEN
ret := 'ギ';
WHEN 'グ' THEN
ret := 'グ';
WHEN 'ゲ' THEN
ret := 'ゲ';
WHEN 'ゴ' THEN
ret := 'ゴ';
-- ザ行
WHEN 'ザ' THEN
ret := 'ザ';
WHEN 'ジ' THEN
ret := 'ジ';
WHEN 'ズ' THEN
ret := 'ズ';
WHEN 'ゼ' THEN
ret := 'ゼ';
WHEN 'ゾ' THEN
ret := 'ゾ';
-- ダ行
WHEN 'ダ' THEN
ret := 'ダ';
WHEN 'ヂ' THEN
ret := 'ヂ';
WHEN 'ヅ' THEN
ret := 'ヅ';
WHEN 'デ' THEN
ret := 'デ';
WHEN 'ド' THEN
ret := 'ド';
-- バ行
WHEN 'バ' THEN
ret := 'バ';
WHEN 'ビ' THEN
ret := 'ビ';
WHEN 'ブ' THEN
ret := 'ブ';
WHEN 'ベ' THEN
ret := 'ベ';
WHEN 'ボ' THEN
ret := 'ボ';
-- パ行
WHEN 'パ' THEN
ret := 'パ';
WHEN 'ピ' THEN
ret := 'ピ';
WHEN 'プ' THEN
ret := 'プ';
WHEN 'ペ' THEN
ret := 'ペ';
WHEN 'ポ' THEN
ret := 'ポ';
-- 小文字
WHEN 'ァ' THEN
ret := 'ァ';
WHEN 'ィ' THEN
ret := 'ィ';
WHEN 'ゥ' THEN
ret := 'ゥ';
WHEN 'ェ' THEN
ret := 'ェ';
WHEN 'ォ' THEN
ret := 'ォ';
WHEN 'ッ' THEN
ret := 'ッ';
WHEN 'ャ' THEN
ret := 'ャ';
WHEN 'ュ' THEN
ret := 'ュ';
WHEN 'ョ' THEN
ret := 'ョ';
-- その他 伸ばし棒-(マイナス)とは違う
WHEN 'ー' THEN
ret := 'ー';
-- アルファベット
-- 小文字
WHEN 'a' THEN
ret := 'a';
WHEN 'b' THEN
ret := 'b';
WHEN 'c' THEN
ret := 'c';
WHEN 'd' THEN
ret := 'd';
WHEN 'e' THEN
ret := 'e';
WHEN 'f' THEN
ret := 'f';
WHEN 'g' THEN
ret := 'g';
WHEN 'h' THEN
ret := 'h';
WHEN 'i' THEN
ret := 'i';
WHEN 'j' THEN
ret := 'j';
WHEN 'k' THEN
ret := 'k';
WHEN 'l' THEN
ret := 'l';
WHEN 'm' THEN
ret := 'm';
WHEN 'n' THEN
ret := 'n';
WHEN 'o' THEN
ret := 'o';
WHEN 'p' THEN
ret := 'p';
WHEN 'q' THEN
ret := 'q';
WHEN 'r' THEN
ret := 'r';
WHEN 's' THEN
ret := 's';
WHEN 't' THEN
ret := 't';
WHEN 'u' THEN
ret := 'u';
WHEN 'v' THEN
ret := 'v';
WHEN 'w' THEN
ret := 'w';
WHEN 'x' THEN
ret := 'x';
WHEN 'y' THEN
ret := 'y';
WHEN 'z' THEN
ret := 'z';
-- 大文字
WHEN 'A' THEN
ret := 'A';
WHEN 'B' THEN
ret := 'B';
WHEN 'C' THEN
ret := 'C';
WHEN 'D' THEN
ret := 'D';
WHEN 'E' THEN
ret := 'E';
WHEN 'F' THEN
ret := 'F';
WHEN 'G' THEN
ret := 'G';
WHEN 'H' THEN
ret := 'H';
WHEN 'I' THEN
ret := 'I';
WHEN 'J' THEN
ret := 'J';
WHEN 'K' THEN
ret := 'K';
WHEN 'L' THEN
ret := 'L';
WHEN 'M' THEN
ret := 'M';
WHEN 'N' THEN
ret := 'N';
WHEN 'O' THEN
ret := 'O';
WHEN 'P' THEN
ret := 'P';
WHEN 'Q' THEN
ret := 'Q';
WHEN 'R' THEN
ret := 'R';
WHEN 'S' THEN
ret := 'S';
WHEN 'T' THEN
ret := 'T';
WHEN 'U' THEN
ret := 'U';
WHEN 'V' THEN
ret := 'V';
WHEN 'W' THEN
ret := 'W';
WHEN 'X' THEN
ret := 'X';
WHEN 'Y' THEN
ret := 'Y';
WHEN 'Z' THEN
ret := 'Z';
-- 数字
WHEN '0' THEN
ret := '0';
WHEN '1' THEN
ret := '1';
WHEN '2' THEN
ret := '2';
WHEN '3' THEN
ret := '3';
WHEN '4' THEN
ret := '4';
WHEN '5' THEN
ret := '5';
WHEN '6' THEN
ret := '6';
WHEN '7' THEN
ret := '7';
WHEN '8' THEN
ret := '8';
WHEN '9' THEN
ret := '9';
ELSE
-- 定義されていないものはそのまま返す
ret := c;
END CASE;
RETURN ret;
END;
$$ LANGUAGE plpgsql IMMUTABLE;
-- 合字(二つで一つの文字か判定)
CREATE FUNCTION ligature (c VARCHAR(2))
RETURNS BOOLEAN AS $$
DECLARE
ret BOOLEAN := FALSE;
BEGIN
-- 2文字の時のみ
IF char_length(c) = 2 THEN
-- 濁音、 半濁音は他の音があって初めてなので
-- 実際あるか同時は他の関数に委ねる。漫画などにある表現として「あ」の濁点とかがあるので。
IF Substring(c, 2, 1) IN ('゙', '゚') THEN
ret := TRUE;
END IF;
-- 2文字以上の合字が出たら足して行く。
-- 3文字以上の合字が出たらELSE IF で追加して行く。
END IF;
RETURN ret;
END;
$$ LANGUAGE plpgsql IMMUTABLE;
-- 半角文字列を全角文字列に変更する関数。
CREATE FUNCTION hankakukana2zenkaku_string (str TEXT)
RETURNS TEXT AS $$
DECLARE
ret TEXT;
i INTEGER := 1;
-- 何文字取るか
cap INTEGER := 1;
BEGIN
WHILE i <= char_length(str)
LOOP
-- 合字かどうか。
-- 指定の位置から最大二文字取る。
IF ligature(SUBSTRING(str, i, 2)) THEN
-- 2文字で一つの文字なので二文字取る。
cap := 2;
ELSE
cap := 1;
END IF;
ret := concat(ret, hankakukana2zenkaku(SUBSTRING(str, i, cap)));
-- 取った数だけ進める
i := i + cap;
END LOOP;
RETURN ret;
END;
$$ LANGUAGE plpgsql IMMUTABLE;
使い方
first_name, secound_nameに全角文字列が入っていると、
半角文字列がちゃんと出力されます。
SELECT
secound_name
,zenkaku2hankakukana_string(secound_name) AS secound_name_kana
,first_name
,zenkaku2hankakukana_string(first_name) AS first_name_kana
FROM users
下のように生成カラムに組み込むこともできます。
CREATE TABLE users (
id SERIAL NOT NULL,
secound_name varchar(20),
secound_name_kana varchar(20) GENERATED ALWAYS AS (zenkaku2hankakukana_string(secound_name)) STORED
first_name varchar(20),
first_name_kana varchar(20) GENERATED ALWAYS AS (zenkaku2hankakukana_string(first_name)) STORED
);
最新のソースコード
githubにあります。
github
Discussion