【SQLite講座】数値や文字列をゼロ埋めする方法|LPADが使えないSQLiteでの工夫とは?

データベース・SQL

データベースで数値を「001」「000123」のようにゼロ埋め(ゼロパディング)して表示したいケース、よくありますよね?

MySQLやPostgreSQLではLPAD()関数を使えますが、SQLiteにはこの関数が標準で搭載されていません。

この記事では、SQLiteでゼロ埋めを実現するための具体的なテクニックを、基礎から応用まで分かりやすく紹介します。

スポンサーリンク

ゼロ埋め(ゼロパディング)の基礎知識

ゼロ埋めとは何か?

ゼロ埋め(Zero Padding)とは、数値や文字列の桁数を指定した長さに揃えるため、足りない部分に「0」を追加する処理のことです。

具体例

元の数値: 7
3桁ゼロ埋め: 007

元の数値: 123
5桁ゼロ埋め: 00123

元の数値: 45
4桁ゼロ埋め: 0045

ゼロ埋めが必要になる場面

データ管理での活用

  • 社員番号:EMP001、EMP002、EMP003…
  • 商品コード:PROD00001、PROD00002…
  • 注文番号:ORDER000001、ORDER000002…

日時表示の整形

  • 時刻表示:09:05:03(9:5:3ではなく)
  • 日付表示:2024/01/05(2024/1/5ではなく)

レポートやCSV出力

  • 桁数を揃えて見やすくする
  • ソート順序を正しく保つ
  • システム間でのデータ連携

ファイル名の管理

  • image001.jpg、image002.jpg、image003.jpg
  • 連番ファイルの整理

他のデータベースとの比較

MySQL・PostgreSQLの場合

-- MySQL・PostgreSQLで使える関数
SELECT LPAD('7', 3, '0');        -- 結果: '007'
SELECT LPAD('123', 5, '0');      -- 結果: '00123'
SELECT RPAD('ABC', 6, '0');      -- 結果: 'ABC000'

SQLiteでの課題

  • LPAD()関数がない:標準では提供されていない
  • RPAD()関数もない:右側への文字追加も標準では不可
  • 独自の工夫が必要:他の関数を組み合わせて実現

SQLiteでゼロ埋めを実現する方法

方法1:printf()関数を使う(推奨)

printf()関数とは SQLiteには、C言語スタイルの書式指定ができるprintf()関数が用意されています。これを使うのが最も簡単で確実な方法です。

基本的な構文

SELECT printf(書式指定, 値) AS 結果カラム名;

ゼロ埋めの書式指定

-- 5桁のゼロ埋め
SELECT printf('%05d', 123) AS zero_padded;
-- 結果: 00123

-- 3桁のゼロ埋め  
SELECT printf('%03d', 7) AS zero_padded;
-- 結果: 007

-- 8桁のゼロ埋め
SELECT printf('%08d', 12345) AS zero_padded;
-- 結果: 00012345

書式指定の説明

  • %:書式指定の開始
  • 0:埋める文字(ゼロ)
  • 5:全体の桁数
  • d:10進数の整数

実際のテーブルでの使用例

-- サンプルテーブルの作成
CREATE TABLE employees (
    id INTEGER,
    name TEXT
);

-- サンプルデータの挿入
INSERT INTO employees (id, name) VALUES 
(1, '田中太郎'),
(25, '佐藤花子'),
(156, '山田次郎');

-- ゼロ埋めした社員番号で表示
SELECT 
    printf('EMP%04d', id) AS employee_code,
    name
FROM employees;

-- 結果:
-- EMP0001  田中太郎
-- EMP0025  佐藤花子  
-- EMP0156  山田次郎

方法2:文字列連結とsubstr()を使う

基本的な考え方

  1. 十分な数のゼロを用意
  2. 対象の数値と連結
  3. 右端から必要な桁数を切り取り

構文例

SELECT substr('0000000000' || 数値, -桁数, 桁数) AS 結果;

具体的な使用例

-- 5桁のゼロ埋め
SELECT substr('00000' || 123, -5, 5) AS zero_padded;
-- 結果: 00123

-- 処理の流れを詳しく見る
-- Step1: '00000' || 123 → '00000123'
-- Step2: substr('00000123', -5, 5) → '00123'

-- 3桁のゼロ埋め
SELECT substr('000' || 7, -3, 3) AS zero_padded;
-- 結果: 007

substr()関数の説明

  • 第1引数:対象文字列
  • 第2引数:開始位置(-5は右端から5番目)
  • 第3引数:取得する文字数

テーブルでの実用例

-- 商品テーブルのサンプル
CREATE TABLE products (
    product_id INTEGER,
    product_name TEXT,
    price INTEGER
);

INSERT INTO products VALUES 
(5, 'ノートPC', 89800),
(23, 'マウス', 2980),
(147, 'キーボード', 5980);

-- 商品コードの生成
SELECT 
    'PROD' || substr('0000' || product_id, -4, 4) AS product_code,
    product_name,
    price
FROM products;

-- 結果:
-- PROD0005  ノートPC    89800
-- PROD0023  マウス      2980
-- PROD0147  キーボード   5980

方法3:CASE文を使った条件分岐

桁数に応じて個別処理

SELECT 
    CASE 
        WHEN id < 10 THEN '00' || id
        WHEN id < 100 THEN '0' || id
        ELSE CAST(id AS TEXT)
    END AS zero_padded_id
FROM employees;

メリットとデメリット

  • メリット:処理が明確で理解しやすい
  • デメリット:桁数が増えると記述が煩雑

方法4:独自関数の作成(上級者向け)

拡張関数の実装 SQLiteでは、プログラミング言語(Python、C、JavaScriptなど)で独自の関数を作成できます。

Python例(sqlite3拡張)

import sqlite3

def lpad(text, length, pad_char='0'):
    """左側を指定文字で埋める関数"""
    text_str = str(text)
    return text_str.rjust(length, pad_char)

# データベース接続
conn = sqlite3.connect('database.db')
conn.create_function("lpad", 3, lpad)

# 使用例
cursor = conn.execute("SELECT lpad(id, 5, '0') FROM employees")

各方法の比較と選び方

性能とメンテナンス性の比較

方法記述の簡潔さ処理速度メンテナンス性推奨度
printf()★★★★★★★★★最高
substr()連結★★★★★★普通
CASE文限定的
独自関数★★★上級者のみ

状況別の推奨方法

一般的な用途

  • printf()を推奨:最も簡潔で高性能

複雑な書式設定

  • printf()のフォーマット指定を活用

既存コードの修正

  • substr()連結で段階的移行

大量データ処理

  • printf()でパフォーマンス重視

実践的な活用例

社員管理システム

テーブル設計

CREATE TABLE employees (
    employee_id INTEGER PRIMARY KEY,
    first_name TEXT NOT NULL,
    last_name TEXT NOT NULL,
    department_id INTEGER,
    hire_date DATE
);

-- サンプルデータ
INSERT INTO employees VALUES 
(1, '太郎', '田中', 10, '2023-04-01'),
(15, '花子', '佐藤', 20, '2023-05-15'),
(127, '次郎', '山田', 10, '2023-06-01');

社員番号の表示

-- 基本的な社員リスト
SELECT 
    printf('EMP-%04d', employee_id) AS 社員番号,
    last_name || ' ' || first_name AS 氏名,
    printf('DEPT-%02d', department_id) AS 部署コード,
    hire_date AS 入社日
FROM employees
ORDER BY employee_id;

-- 結果:
-- EMP-0001  田中 太郎   DEPT-10  2023-04-01
-- EMP-0015  佐藤 花子   DEPT-20  2023-05-15  
-- EMP-0127  山田 次郎   DEPT-10  2023-06-01

注文管理システム

注文番号の自動生成

CREATE TABLE orders (
    order_id INTEGER PRIMARY KEY AUTOINCREMENT,
    customer_id INTEGER,
    order_date DATE,
    total_amount INTEGER
);

-- 注文番号の表示(年月+連番)
SELECT 
    strftime('%Y%m', order_date) || '-' || printf('%05d', order_id) AS 注文番号,
    printf('CUST-%04d', customer_id) AS 顧客番号,
    order_date AS 注文日,
    printf('¥%,d', total_amount) AS 合計金額
FROM orders;

-- 結果例:
-- 202401-00001  CUST-0123  2024-01-15  ¥15,800
-- 202401-00002  CUST-0456  2024-01-16  ¥32,400

日時データの整形

時刻表示の統一

CREATE TABLE schedules (
    schedule_id INTEGER,
    hour INTEGER,
    minute INTEGER,
    second INTEGER,
    title TEXT
);

INSERT INTO schedules VALUES 
(1, 9, 5, 0, '朝礼'),
(2, 13, 30, 0, '午後会議'),
(3, 17, 0, 0, '終業');

-- 時刻の見やすい表示
SELECT 
    printf('%02d:%02d:%02d', hour, minute, second) AS 時刻,
    title AS 予定
FROM schedules
ORDER BY hour, minute, second;

-- 結果:
-- 09:05:00  朝礼
-- 13:30:00  午後会議
-- 17:00:00  終業

ファイル管理システム

連番ファイル名の生成

CREATE TABLE uploaded_files (
    file_id INTEGER PRIMARY KEY AUTOINCREMENT,
    original_name TEXT,
    upload_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- ファイル名の統一命名
SELECT 
    printf('IMG_%08d.jpg', file_id) AS 保存ファイル名,
    original_name AS 元ファイル名,
    datetime(upload_date, 'localtime') AS アップロード時刻
FROM uploaded_files;

-- 結果例:
-- IMG_00000001.jpg  photo1.jpg      2024-01-15 10:30:25
-- IMG_00000002.jpg  vacation.jpg    2024-01-15 11:45:12
-- IMG_00000003.jpg  family.jpg      2024-01-15 14:20:33

高度なゼロ埋めテクニック

動的な桁数指定

桁数を変数で指定

-- 最大値に応じて桁数を決定
WITH max_id AS (
    SELECT MAX(LENGTH(CAST(employee_id AS TEXT))) AS max_digits
    FROM employees
)
SELECT 
    printf('%0' || (max_digits.max_digits + 2) || 'd', employee_id) AS パディング済みID,
    first_name || ' ' || last_name AS 氏名
FROM employees, max_id;

複合的な書式設定

複数のパターンを組み合わせ

-- 部署コード + 社員番号 + 入社年
SELECT 
    printf('D%02d-E%04d-%04d', 
        department_id, 
        employee_id, 
        CAST(strftime('%Y', hire_date) AS INTEGER)
    ) AS 統合コード,
    last_name || ' ' || first_name AS 氏名
FROM employees;

-- 結果例:
-- D10-E0001-2023  田中 太郎
-- D20-E0015-2023  佐藤 花子
-- D10-E0127-2023  山田 次郎

条件付きゼロ埋め

特定条件での書式変更

SELECT 
    employee_id,
    CASE 
        WHEN department_id = 10 THEN printf('MGR-%04d', employee_id)
        WHEN department_id = 20 THEN printf('ENG-%04d', employee_id)  
        ELSE printf('EMP-%04d', employee_id)
    END AS 職種別コード,
    first_name || ' ' || last_name AS 氏名
FROM employees;

パフォーマンス最適化

大量データでの処理

インデックスの活用

-- 検索用のインデックス作成
CREATE INDEX idx_employee_id ON employees(employee_id);

-- ゼロ埋めした値での検索
SELECT * FROM employees 
WHERE printf('%04d', employee_id) = '0127';

計算列の事前生成

-- 計算済みの列を追加
ALTER TABLE employees ADD COLUMN employee_code TEXT;

-- 既存データの更新
UPDATE employees 
SET employee_code = printf('EMP-%04d', employee_id);

-- 新規データ挿入時のトリガー
CREATE TRIGGER update_employee_code 
AFTER INSERT ON employees
BEGIN
    UPDATE employees 
    SET employee_code = printf('EMP-%04d', NEW.employee_id)
    WHERE employee_id = NEW.employee_id;
END;

メモリ使用量の最適化

必要な時のみ計算

-- ビューを使用して必要時のみ計算
CREATE VIEW employee_view AS
SELECT 
    employee_id,
    printf('EMP-%04d', employee_id) AS employee_code,
    first_name,
    last_name
FROM employees;

-- 使用時
SELECT * FROM employee_view WHERE employee_id < 100;

エラー対処とデバッグ

よくあるエラーと解決法

数値以外の値でのエラー

-- エラーになる例
SELECT printf('%05d', 'ABC123');  -- エラー: not a number

-- 対処法: 事前チェック
SELECT 
    CASE 
        WHEN CAST(value AS INTEGER) = value 
        THEN printf('%05d', CAST(value AS INTEGER))
        ELSE value 
    END AS safe_padding
FROM test_table;

NULL値の処理

-- NULL値の安全な処理
SELECT 
    COALESCE(printf('%05d', employee_id), 'N/A') AS employee_code
FROM employees;

桁数オーバーの処理

-- 指定桁数を超える場合の処理
SELECT 
    CASE 
        WHEN LENGTH(CAST(employee_id AS TEXT)) > 4 
        THEN CAST(employee_id AS TEXT)
        ELSE printf('%04d', employee_id)
    END AS flexible_padding
FROM employees;

デバッグのコツ

段階的な確認

-- Step by step での確認
SELECT 
    employee_id,                                    -- 元の値
    CAST(employee_id AS TEXT) AS text_value,       -- 文字列変換
    LENGTH(CAST(employee_id AS TEXT)) AS length,   -- 桁数確認
    printf('%04d', employee_id) AS padded          -- ゼロ埋め結果
FROM employees;

他データベースとの互換性

移植性を考慮したコード

データベース判定での分岐

-- 他DBでも動作するような書き方
SELECT 
    /* SQLiteの場合 */
    printf('%05d', id) AS padded_id,
    
    /* MySQLの場合は以下をコメントアウト */
    -- LPAD(id, 5, '0') AS padded_id,
    
    name
FROM employees;

共通関数の作成

アプリケーション側での統一

def universal_lpad(value, length, pad_char='0'):
    """データベースに依存しないLPAD関数"""
    return str(value).rjust(length, pad_char)

# SQLiteで使用
def create_lpad_function(conn):
    conn.create_function("lpad", 3, universal_lpad)

まとめ

最適な方法の選択指針

推奨順位

  1. printf()関数:最も簡潔で高性能
  2. substr()連結:printf()が使えない場合
  3. CASE文:複雑な条件分岐が必要な場合
  4. 独自関数:アプリケーション側で統一したい場合

実装時のポイント

設計段階での考慮

  • 必要な桁数の事前決定
  • NULL値や異常値の処理方針
  • パフォーマンス要件の確認

運用での注意点

  • データ量の増加に対する対応
  • 他システムとの連携仕様
  • バックアップ・リストア時の整合性

コメント

タイトルとURLをコピーしました