SQLiteで全レコードを削除する方法|DELETEとTRUNCATEの違いも解説!

データベース・SQL

SQLiteを使っていて、「テーブルの中身を全部削除したい!」という場面は意外と多いですよね。

たとえば、テストデータをリセットしたいとき、古いデータを一括で消したいときなど。

よくある削除の場面

  • テストデータのリセット
  • 開発環境の初期化
  • ログデータの定期的なクリア
  • データの移行前の準備
  • 不要になった古いデータの削除

この記事では、SQLiteで全レコードを削除する方法を、注意点とともにわかりやすく解説します。

スポンサーリンク

SQLiteとは(基本知識)

まず、SQLiteについて簡単に説明します。

SQLiteの特徴

  • ファイルベースの軽量なデータベース
  • インストール不要で使える
  • アプリケーションに組み込みやすい
  • 多くのプログラミング言語でサポート

レコードとは テーブルに保存されている1行分のデータのことです。例えば、ユーザー情報テーブルの場合、1人分の情報が1レコードになります。

DELETE文で全レコード削除

基本構文

最も基本的な全レコード削除の方法は、DELETE文を使うことです。

DELETE FROM テーブル名;

具体例

DELETE FROM users;

このSQL文は、users テーブルのすべての行データを削除します。ただし、テーブル構造(列や型)は残ります

削除前の確認方法

削除する前に、どのくらいのデータがあるか確認しましょう:

-- レコード数を確認
SELECT COUNT(*) FROM users;

-- データの内容を確認(最初の10件)
SELECT * FROM users LIMIT 10;

-- テーブル構造を確認
.schema users

実行例(詳細)

-- 削除前の状態確認
SELECT COUNT(*) AS 'レコード数' FROM users;
-- 結果例: レコード数 = 1000

-- 全レコード削除実行
DELETE FROM users;

-- 削除後の確認
SELECT COUNT(*) AS 'レコード数' FROM users;
-- 結果: レコード数 = 0

-- テーブル構造は残っていることを確認
.schema users
-- 結果: CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT);

WHERE句を使った削除との違い

-- 特定条件での削除
DELETE FROM users WHERE age < 18;

-- 全削除(WHERE句なし)
DELETE FROM users;

-- 全削除(WHERE句で常に真の条件)
DELETE FROM users WHERE 1=1;
DELETE FROM users WHERE TRUE;

最後の2つの書き方も全削除と同じ効果がありますが、誤解を招きやすいため推奨しません。

SQLiteにはTRUNCATEがない!

多くのデータベース(MySQL、PostgreSQLなど)では、TRUNCATE TABLE という高速削除コマンドがありますが、SQLiteにはこの構文が存在しません

他のデータベースでのTRUNCATE

-- MySQL、PostgreSQLなどでは使用可能(SQLiteでは使えない)
TRUNCATE TABLE users;

SQLiteでTRUNCATEと同等の効果を得る方法

SQLiteでTRUNCATEと同じような効果を得るには、以下の方法があります:

方法1:DELETE + VACUUM

DELETE FROM users;
VACUUM;

方法2:テーブルの再作成

DROP TABLE users;
CREATE TABLE users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    email TEXT UNIQUE
);

方法3:DELETE + AUTO INCREMENT初期化

DELETE FROM users;
DELETE FROM sqlite_sequence WHERE name='users';

自動採番(AUTOINCREMENT)の扱い

SQLiteのAUTOINCREMENT機能について詳しく説明します。

AUTOINCREMENTとは

CREATE TABLE users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT,
    email TEXT
);

このテーブルでは、id列が自動的に1, 2, 3…と連番になります。

DELETEでは自動採番がリセットされない

-- データ挿入
INSERT INTO users (name, email) VALUES ('田中', 'tanaka@example.com');
INSERT INTO users (name, email) VALUES ('佐藤', 'sato@example.com');
-- この時点で id は 1, 2

-- 全削除
DELETE FROM users;

-- 新しいデータ挿入
INSERT INTO users (name, email) VALUES ('鈴木', 'suzuki@example.com');
-- id は 3 になる(1や2ではない)

AUTOINCREMENTをリセットする方法

-- 方法1: sqlite_sequenceテーブルを直接操作
DELETE FROM users;
DELETE FROM sqlite_sequence WHERE name='users';

-- 方法2: VACUUMと組み合わせ
DELETE FROM users;
VACUUM;
-- 注意: VACUUMだけではAUTOINCREMENTはリセットされません

-- 方法3: テーブル再作成
DROP TABLE users;
CREATE TABLE users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT,
    email TEXT
);

sqlite_sequenceテーブルの確認

-- 現在のAUTOINCREMENT値を確認
SELECT * FROM sqlite_sequence WHERE name='users';

-- 手動でリセット
UPDATE sqlite_sequence SET seq = 0 WHERE name='users';

VACUUM文による最適化

VACUUMとは

VACUUMは、SQLiteデータベースファイルの最適化を行うコマンドです。

VACUUMの効果

  • 削除されたデータの領域を回収
  • ファイルサイズの縮小
  • 断片化の解消
  • パフォーマンスの改善

VACUUMの実行方法

-- 基本的な実行
VACUUM;

-- 特定のスキーマに対して実行
VACUUM main;

-- 出力先を指定してVACUUM
VACUUM INTO 'backup.db';

DELETE + VACUUMの効果確認

-- ファイルサイズ確認(コマンドライン)
.shell ls -lh database.db

-- データベース情報確認
PRAGMA database_list;

-- ページ情報確認
PRAGMA page_count;
PRAGMA page_size;

実用例

-- 大量データの削除とファイル最適化
DELETE FROM large_table;
VACUUM;

-- 結果: ファイルサイズが大幅に縮小される

複数の削除方法の比較

方法別の特徴まとめ

方法実行速度ID初期化構造保持ファイルサイズ用途
DELETE FROM普通×変わらず基本的な削除
DELETE + VACUUM遅い×縮小容量節約重視
DELETE + sqlite_sequence削除普通変わらずID初期化重視
DROP + CREATE速い大幅縮小完全リセット

パフォーマンステスト例

-- テスト用大量データ作成
CREATE TABLE test_table (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    data TEXT
);

-- 100万件のデータ挿入
INSERT INTO test_table (data) 
SELECT 'test_data_' || (ROW_NUMBER() OVER())
FROM (SELECT 0 UNION ALL SELECT 1) t1,
     (SELECT 0 UNION ALL SELECT 1) t2,
     -- ... 20回繰り返し
     LIMIT 1000000;

-- 削除時間の測定
.timer ON

-- 方法1: 単純DELETE
DELETE FROM test_table;

-- 方法2: DELETE + VACUUM
DELETE FROM test_table;
VACUUM;

.timer OFF

Pythonでの実装例

基本的な削除スクリプト

import sqlite3
import os

def delete_all_records(db_path, table_name):
    """指定テーブルの全レコードを削除"""
    try:
        # データベース接続
        conn = sqlite3.connect(db_path)
        cursor = conn.cursor()
        
        # 削除前のレコード数確認
        cursor.execute(f"SELECT COUNT(*) FROM {table_name}")
        before_count = cursor.fetchone()[0]
        print(f"削除前のレコード数: {before_count}")
        
        # 全レコード削除
        cursor.execute(f"DELETE FROM {table_name}")
        
        # 削除後のレコード数確認
        cursor.execute(f"SELECT COUNT(*) FROM {table_name}")
        after_count = cursor.fetchone()[0]
        print(f"削除後のレコード数: {after_count}")
        
        # 変更をコミット
        conn.commit()
        print(f"{before_count}件のレコードを削除しました")
        
    except sqlite3.Error as e:
        print(f"エラーが発生しました: {e}")
        conn.rollback()
    finally:
        if conn:
            conn.close()

# 使用例
delete_all_records('example.db', 'users')

より高度な削除スクリプト

import sqlite3
import os
from datetime import datetime

class SQLiteManager:
    def __init__(self, db_path):
        self.db_path = db_path
    
    def connect(self):
        """データベースに接続"""
        return sqlite3.connect(self.db_path)
    
    def backup_database(self):
        """削除前にバックアップを作成"""
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        backup_path = f"{self.db_path}.backup_{timestamp}"
        
        try:
            with self.connect() as conn:
                with open(backup_path, 'wb') as backup_file:
                    for line in conn.iterdump():
                        backup_file.write(f"{line}\n".encode())
            print(f"バックアップを作成しました: {backup_path}")
            return backup_path
        except Exception as e:
            print(f"バックアップエラー: {e}")
            return None
    
    def delete_all_with_options(self, table_name, reset_autoincrement=False, 
                               vacuum=False, backup=True):
        """オプション付き全削除"""
        if backup:
            backup_path = self.backup_database()
            if not backup_path:
                print("バックアップに失敗したため、削除を中止します")
                return False
        
        try:
            with self.connect() as conn:
                cursor = conn.cursor()
                
                # 削除前の情報取得
                cursor.execute(f"SELECT COUNT(*) FROM {table_name}")
                record_count = cursor.fetchone()[0]
                
                cursor.execute("SELECT page_count * page_size FROM pragma_page_count(), pragma_page_size()")
                file_size_before = cursor.fetchone()[0]
                
                print(f"削除前: レコード数={record_count}, ファイルサイズ={file_size_before:,}bytes")
                
                # 全レコード削除
                cursor.execute(f"DELETE FROM {table_name}")
                
                # AUTOINCREMENTリセット(オプション)
                if reset_autoincrement:
                    cursor.execute(f"DELETE FROM sqlite_sequence WHERE name='{table_name}'")
                    print("AUTOINCREMENTをリセットしました")
                
                # VACUUM実行(オプション)
                if vacuum:
                    cursor.execute("VACUUM")
                    print("VACUUMを実行しました")
                
                # 削除後の情報取得
                cursor.execute(f"SELECT COUNT(*) FROM {table_name}")
                record_count_after = cursor.fetchone()[0]
                
                cursor.execute("SELECT page_count * page_size FROM pragma_page_count(), pragma_page_size()")
                file_size_after = cursor.fetchone()[0]
                
                print(f"削除後: レコード数={record_count_after}, ファイルサイズ={file_size_after:,}bytes")
                print(f"ファイルサイズ削減: {file_size_before - file_size_after:,}bytes")
                
                return True
                
        except sqlite3.Error as e:
            print(f"削除エラー: {e}")
            return False

# 使用例
manager = SQLiteManager('example.db')

# 基本的な削除
manager.delete_all_with_options('users')

# 全オプション有効
manager.delete_all_with_options('logs', 
                               reset_autoincrement=True, 
                               vacuum=True, 
                               backup=True)

エラーハンドリングを含む安全な削除

import sqlite3
import sys

def safe_delete_all(db_path, table_name, confirm=True):
    """安全な全削除(確認付き)"""
    
    # 確認メッセージ
    if confirm:
        print(f"警告: {table_name}テーブルの全データを削除します")
        response = input("本当に削除しますか? (yes/no): ")
        if response.lower() != 'yes':
            print("削除をキャンセルしました")
            return False
    
    try:
        # ファイル存在確認
        if not os.path.exists(db_path):
            print(f"データベースファイルが見つかりません: {db_path}")
            return False
        
        with sqlite3.connect(db_path) as conn:
            cursor = conn.cursor()
            
            # テーブル存在確認
            cursor.execute("""
                SELECT name FROM sqlite_master 
                WHERE type='table' AND name=?
            """, (table_name,))
            
            if not cursor.fetchone():
                print(f"テーブルが見つかりません: {table_name}")
                return False
            
            # トランザション開始
            cursor.execute("BEGIN TRANSACTION")
            
            try:
                # 削除実行
                cursor.execute(f"DELETE FROM {table_name}")
                deleted_count = cursor.rowcount
                
                # コミット
                cursor.execute("COMMIT")
                print(f"正常に削除されました: {deleted_count}件")
                return True
                
            except Exception as e:
                # ロールバック
                cursor.execute("ROLLBACK")
                print(f"削除中にエラーが発生しました: {e}")
                return False
                
    except sqlite3.Error as e:
        print(f"データベースエラー: {e}")
        return False

# 使用例
if __name__ == "__main__":
    safe_delete_all('example.db', 'users', confirm=True)

削除の注意点とベストプラクティス

重要な注意点

1. バックアップの重要性

# 削除前に必ずバックアップ
import shutil
shutil.copy2('original.db', 'backup.db')

2. トランザクションの使用

BEGIN TRANSACTION;
DELETE FROM users;
-- 問題なければ
COMMIT;
-- 問題があれば
-- ROLLBACK;

3. 外部キー制約の確認

-- 外部キー制約を確認
PRAGMA foreign_key_list(users);

-- 外部キー制約を一時的に無効化
PRAGMA foreign_keys = OFF;
DELETE FROM users;
PRAGMA foreign_keys = ON;

パフォーマンス最適化

1. 大量データの削除

-- 一度に削除せず、分割して削除
DELETE FROM large_table WHERE id IN (
    SELECT id FROM large_table LIMIT 10000
);
-- これを繰り返し実行

2. インデックスの考慮

-- 削除前にインデックスを削除
DROP INDEX IF EXISTS idx_users_email;

-- データ削除
DELETE FROM users;

-- インデックスを再作成
CREATE INDEX idx_users_email ON users(email);

削除の確認方法

-- 削除前の状態確認
.tables
.schema users
SELECT COUNT(*) FROM users;

-- 削除実行
DELETE FROM users;

-- 削除後の確認
SELECT COUNT(*) FROM users;
PRAGMA table_info(users);  -- 構造が残っていることを確認

応用例とよくある使用場面

テストデータのリセット

def reset_test_data():
    """テスト用データベースの初期化"""
    tables_to_clear = ['users', 'posts', 'comments', 'logs']
    
    with sqlite3.connect('test.db') as conn:
        cursor = conn.cursor()
        
        # 外部キー制約を無効化
        cursor.execute("PRAGMA foreign_keys = OFF")
        
        for table in tables_to_clear:
            try:
                cursor.execute(f"DELETE FROM {table}")
                cursor.execute(f"DELETE FROM sqlite_sequence WHERE name='{table}'")
                print(f"{table} テーブルをクリアしました")
            except sqlite3.Error as e:
                print(f"{table} のクリアに失敗: {e}")
        
        # 外部キー制約を再有効化
        cursor.execute("PRAGMA foreign_keys = ON")
        
        print("テストデータのリセットが完了しました")

# 使用例
reset_test_data()

ログデータの定期削除

import sqlite3
from datetime import datetime, timedelta

def cleanup_old_logs(days_to_keep=30):
    """指定日数より古いログを削除"""
    cutoff_date = datetime.now() - timedelta(days=days_to_keep)
    
    with sqlite3.connect('app.db') as conn:
        cursor = conn.cursor()
        
        # 削除対象の確認
        cursor.execute("""
            SELECT COUNT(*) FROM logs 
            WHERE created_at < ?
        """, (cutoff_date.isoformat(),))
        
        count_to_delete = cursor.fetchone()[0]
        
        if count_to_delete == 0:
            print("削除対象のログはありません")
            return
        
        print(f"{count_to_delete}件の古いログを削除します")
        
        # 古いログを削除
        cursor.execute("""
            DELETE FROM logs 
            WHERE created_at < ?
        """, (cutoff_date.isoformat(),))
        
        print(f"{cursor.rowcount}件のログを削除しました")
        
        # ファイルサイズ最適化
        cursor.execute("VACUUM")
        print("データベースを最適化しました")

# 使用例
cleanup_old_logs(30)  # 30日より古いログを削除

開発環境の初期化スクリプト

import sqlite3
import os

def initialize_dev_environment():
    """開発環境の初期化"""
    db_path = 'development.db'
    
    # バックアップ作成
    if os.path.exists(db_path):
        backup_path = f"{db_path}.backup"
        shutil.copy2(db_path, backup_path)
        print(f"バックアップを作成: {backup_path}")
    
    with sqlite3.connect(db_path) as conn:
        cursor = conn.cursor()
        
        # 全てのユーザーデータテーブルをクリア
        user_tables = ['users', 'profiles', 'posts', 'comments', 'likes']
        
        for table in user_tables:
            cursor.execute(f"DELETE FROM {table}")
            cursor.execute(f"DELETE FROM sqlite_sequence WHERE name='{table}'")
        
        # 初期管理者アカウントを作成
        cursor.execute("""
            INSERT INTO users (username, email, is_admin) 
            VALUES ('admin', 'admin@example.com', 1)
        """)
        
        # テストデータを少量挿入
        test_users = [
            ('test1', 'test1@example.com', 0),
            ('test2', 'test2@example.com', 0),
        ]
        
        cursor.executemany("""
            INSERT INTO users (username, email, is_admin) 
            VALUES (?, ?, ?)
        """, test_users)
        
        print("開発環境の初期化が完了しました")
        print(f"管理者アカウント: admin")
        print(f"テストアカウント: test1, test2")

# 使用例
initialize_dev_environment()

まとめ

SQLiteでの全削除は簡単ですが、取り返しのつかない操作でもあるため、実行前には必ずバックアップを取りましょう。

重要なポイント

  1. 基本的な削除DELETE FROM テーブル名;
  2. SQLiteにはTRUNCATE文がない
  3. AUTOINCREMENTは自動でリセットされない
  4. VACUUMでファイルサイズを最適化
  5. 必ずバックアップを取る

方法別の選択指針

  • 基本的な削除DELETE FROM テーブル名
  • ID初期化が必要DELETE + sqlite_sequence削除
  • 容量節約が必要DELETE + VACUUM
  • 完全リセットDROP + CREATE

コメント

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