やる気がストロングZERO

やる気のストロングスタイル

【テーブル設計】フルネームで保持しているカラムを後から「名字・名前」に分けたい時

フルネームで保持しているカラムを後から「名字・名前」に分けたい時、どうすればいいか考えてみた。
出来たと思うけど、脳内シミュレーションしただけで実際にシステムに対して行ったことがあるわけではないので、もしかしたら穴があるかも。

サンプルケース

既存実装では「名字・名前」を分けてなかった

ユーザー情報を保持するuserテーブルがあって、名前を保持するnameカラムが定義されている。

userテーブル

id name
1 山田 太郎
2 岡田次郎

データベースからデータを読み出して、Userクラスとして使っている

// Userクラス
class User {
    id int
    name string

    construct(id int, name string){
        user_id = id
        name = name
    }

    function getName(){
        return name
    }
}


// dbから取得したデータをセット
user = new User(1, "山田 太郎")

// 名前取得
user.getName()  // "山田 太郎"が取得できる

今後は「名字・名前」を分けて保持してほしいという要件が発生した

ある時、名字と名前を使い分けたいという要望が発生した為、名字と名前を分けて保持することにした。
ただし、既に登録されているユーザーの名前を変換することができない。
(空白部分で「名字・名前」を分けてデータをコンバートすればいいという案が出たが、「岡田次郎」のように空白を入力していないユーザーもいたため不可能だった)
どうすればいいか?

名前保持用のテーブルに分割する

旧名前用データテーブル(フルネームしか持ってない)と新名前用データテーブル(「名字・名前」データで持っている)を用意して使い分ける。

userテーブル

id
1
2
3

user_nameテーブル(旧名前用データテーブル。userテーブルからnameを移動させる)

user_id name
1 山田 太郎
2 岡田次郎

user_separate_nameテーブル(新名前用データテーブル。以降はこのテーブルに名前データを入れていく)

user_id first_name last_name
3 三郎 川上

データ取得時はjoinして取る

select 
    user.id, 
    user_name.name, 
    user_separate_name.first_name, 
    user_separate_name.last_name 
from user
left outer join user_name on user_name.user_id = user.id
left outer join user_separate_name on user_separate_name.user_id = user.id

こんな感じで取れる

id name first_name last_name
1 山田 太郎 null null
2 岡田次郎 null null
3 null 三郎 川上

プログラム側で扱うデータ構造はこんな感じ

// インターフェース
interfacd IName{
    getFullName() string
    getFirstName() string
    getLastName() string
}

// 旧名前データ用クラス。INameインターフェースを実装する
class UserName < IName {
    user_id int
    name string

    construct(id int, name string){
        user_id = id
        name = name
    }

    function getFullName(){
        return name
    }

    function getFirstName(){
        return null
    }

    function getLastName(){
        return null
    }
}

// 新名前データ用クラス。INameインターフェースを実装する
class UserSeparateName < IName {
    user_id int
    first_name string
    last_name string

    construct(id int, first_name string, last_name string){
        user_id = id
        first_name= first_name
        last_name = last_name
    }

    function getFullName(){
        return last_name + " " + first_name
    }

    function getFirstName(){
        return first_name
    }

    function getLastName(){
        return last_name
    }
}

// Userクラス。INameインターフェースを実装したクラス(旧 or 新名前用クラス)を内部に持って、名前情報取得処理を委譲する。
class User{
    id int
    name IName
    
    construct(id int, name IName){
        user_id = id
        name= name
    }

    // getFullNameにメソッド名変更してもいいかも。
    function getName(){
        return name.getFullName()
    }

    function getFirstName(){
        return name.first_name
    }

    function getLastName(){
        return name.last_name
    }
}

// 使う時こんな感じ

// user_nameテーブルのデータを読み込む
user_name = new UserName(1, "山田 太郎")

// userテーブルデータを読み込む
user = new User(1, user_name)

// 使う
user.getName() // 山田 太郎
user.getFirstName() // null  (旧データなのでfirst_nameデータがない)
user.getLastName() // null  (旧データなのでlast_nameデータがない)


// user_separate_nameテーブルデータを読み込む
user_separate_name = new UserSeparateName(3, "三郎", "川上")

// userテーブルデータを読み込む
user = new User(3, user_separate_name)

// 使う
user.getName() // 川上 三郎
user.getFirstName() // 三郎(新データなのでfirst_nameデータがある)
user.getLastName() // 川上(新データなのでlast_nameデータがある)

(他案)userテーブルにfirst_nameとlast_nameカラムを追加するだけでいいのでは?=> 良くない

テーブル分割してややこしくしなくても、first_name, last_nameカラムをuserテーブルに追加すれば良い気がしたりするけど、個人的にはNG。

↓こんな感じ?

id name first_name last_name
1 山田 太郎 null null
2 岡田次郎 null null
3 川上 三郎 三郎 川上

※1,2が旧データ、3が新データ

なぜNGか?

データを冗長に保持している感じが嫌。
こんなデータを入れられてしまう可能性がある。

id name first_name last_name
4 岡本 太郎 武蔵 宮本

この場合、フルネームを取得すると「岡本 太郎」が取得できるけど、名字を取得すると「宮本」となる。
データ不整合が発生する可能性のあるデータ構造は、アプリケーション側やオペレーター側に整合性担保の責任を押し付けてくるので良くないと思ってる。