しもむブログ

技術メモ。といっても高度なことは書けないので…初心者がハマったところや、ググってもすぐには出てこないようなものを書くつもり。

忘れた頃に埋め込んでしまうバグ 2024年冬(SQLServerの空白文字列に注意)

  • これはInfocom Advent Calendar 2024 23日目の記事です。
  • 下記のリンク、知識としては前々から知っていたのですが、プロジェクトが移ろいゆくなかで埋め込まれてしまったバグが見つかったので記事にします。

qiita.com

バグの現象

  • 社員を登録しようとすると、社員番号が既に登録されており、登録できない(画像はイメージです)。

  • 社員番号はユニークなのでバリデーションをかけているが、意図せぬ結果らしい。
  • よくよく調べてもらうと、どうやら[00001]と[00001 ](末尾に空白あり)という社員番号で起こるとのこと。
  • ここまで聞いてピンと来た、この仕様のしわざだと(再掲)。

qiita.com

もしChatGPTが実装したら

  • しくみで解決する術があるかはさておき、この仕様を知らないかぎりなかなか注意もできないと思う。
  • というわけで、ChatGPTだったらどうなるか試してみた。

SQLServerで社員マスタを作成します。テーブルを設計してください。

  • 社員番号がなかったので、

社員番号を追加してください。

列名 データ型 制約 説明
社員ID INT PRIMARY KEY, IDENTITY(1,1) 一意な社員識別子(自動採番)
社員番号 NVARCHAR(20) NOT NULL, UNIQUE 社員の一意な識別番号
氏名 NVARCHAR(100) NOT NULL 社員の氏名
フリガナ NVARCHAR(100) NULL 社員のフリガナ(任意)
生年月日 DATE NULL 社員の生年月日
性別 CHAR(1) NULL 性別(例: 'M'=男性, 'F'=女性)
入社日 DATE NOT NULL 入社日
退社日 DATE NULL 退社日(在籍中の場合はNULL)
部署ID INT NULL, FOREIGN KEY 所属部署(部署マスタ参照)
メールアドレス NVARCHAR(255) NULL 社員のメールアドレス
電話番号 NVARCHAR(15) NULL 社員の電話番号
郵便番号 NVARCHAR(10) NULL 社員の住所(郵便番号)
住所 NVARCHAR(255) NULL 社員の住所
作成日時 DATETIME DEFAULT GETDATE() レコード作成日時
更新日時 DATETIME DEFAULT GETDATE() レコード更新日時
削除フラグ BIT DEFAULT 0 削除済みフラグ(0=有効, 1=削除)
CREATE TABLE 社員マスタ (
    社員ID INT PRIMARY KEY IDENTITY(1,1),
    社員番号 NVARCHAR(20) NOT NULL UNIQUE,
    氏名 NVARCHAR(100) NOT NULL,
    フリガナ NVARCHAR(100),
    生年月日 DATE,
    性別 CHAR(1),
    入社日 DATE NOT NULL,
    退社日 DATE,
    部署ID INT,
    メールアドレス NVARCHAR(255),
    電話番号 NVARCHAR(15),
    郵便番号 NVARCHAR(10),
    住所 NVARCHAR(255),
    作成日時 DATETIME DEFAULT GETDATE(),
    更新日時 DATETIME DEFAULT GETDATE(),
    削除フラグ BIT DEFAULT 0,
    FOREIGN KEY (部署ID) REFERENCES 部署マスタ(部署ID)
);
  • よさげなテーブルができた。社員番号だけど文字列型なのもありがたい。
  • ここからが本題。

社員番号は一意であるため、webフォームでの入力時にはバリデーションして画面に登録できないようメッセージを表示したいと思います。 C#でバリデーションロジックをコーディングしてください。

以下は、C#で社員番号の一意性を確認するバリデーションロジックの例です。
Webフォームの登録時に、既存の社員番号と重複がないか確認し、エラーメッセージを表示する方法を示します。

使用する技術スタック
ASP.NET MVC または ASP.NET Core を想定
データベース接続には Entity Framework を使用

バリデーションのコードだけ抜粋

// 社員番号の一意性をチェック
public bool IsEmployeeNumberUnique(string employeeNumber)
{
    return !_context.社員マスタ.Any(e => e.社員番号 == employeeNumber);
}
  • ここで実行されるSQLはこちら。
    • SQLServerの仕様は考慮されていないので、同様の現象が発生する。
SELECT CASE
    WHEN EXISTS (
        SELECT 1
        FROM 社員マスタ
        WHERE 社員番号 = @employeeNumber
    ) THEN CAST(1 AS BIT)
    ELSE CAST(0 AS BIT)
END;

さいごに

  • ChatGPTが生成してくれたコードは真っ当だったが、SQLServerの細かい仕様まではあらかじめ考慮されていなかった
  • EntityFrameworkのAny関数で発行されるSQLをまじまじと見たことなかったので、今回の件で勉強になった