IOSデータベースアップグレードデータ移行の詳細な例

IOSデータベースアップグレードデータ移行の詳細な例

IOSデータベースアップグレードデータ移行の詳細な例

まとめ:

昔、データベースのバージョン アップグレードの参考シナリオに遭遇しました。当時のアプローチは、古いデータベース ファイルを削除し、データベースとテーブル構造を再構築するだけでした。この乱暴なアップグレード方法では、古いデータが失われます。今では、これはエレガントな解決策ではないようです。現在、新しいプロジェクトでデータベースが再び使用されるため、この問題を再検討する必要があります。この問題をよりエレガントな方法で解決したいと考えています。今後も同様のシナリオに遭遇するでしょうが、私たちは皆、より良い方法を望んでいますよね?

理想的な状況は、データベースをアップグレードすると、テーブル構造、主キー、制約が変更されることです。新しいテーブル構造が確立されると、古いテーブルからデータが自動的に取得され、同じフィールドがマッピングされて移行されます。ほとんどのビジネス シナリオでは、データベース バージョンのアップグレードには、フィールドの追加または削除と主キー制約の変更のみが含まれます。したがって、以下で実装するソリューションも、最も基本的で最も一般的に使用されるビジネス シナリオに基づいて実装することです。より複雑なシナリオについては、これに基づいて拡張して期待に応えることができます。

選択と確定

オンラインで検索したところ、データベースのアップグレードとデータ移行のためのシンプルで完全なソリューションは見つかりませんでした。いくつかのアイデアを見つけました。

1. 古いデータを消去してテーブルを再構築する

長所: シンプル 短所: データ損失

2. 既存のテーブルに基づいてテーブル構造を変更する

メリット: データを保持できる デメリット: ルールが比較的複雑です。データベース フィールド構成ファイルを作成し、構成ファイルを読み込み、SQL を実行してテーブル構造、制約、主キーなどを変更する必要があります。複数のバージョンにまたがってデータベースをアップグレードするのは面倒で面倒です。

3. 一時テーブルを作成し、古いデータを一時テーブルにコピーしてから、古いデータ テーブルを削除し、一時テーブルをデータ テーブルとして設定します。

利点: データを保持できる、テーブル構造の変更をサポート、制約と主キーの変更をサポート、実装が比較的簡単 欠点: 実装に多くの手順が必要

すべての要素を考慮すると、3 番目の方法の方が信頼性の高い解決策です。

主な手順

この考えに基づいて、データベース アップグレードの主な手順は次のように分析されます。

  • データベース内の古いテーブルを取得する
  • テーブル名を変更し、サフィックス「_bak」を追加し、古いテーブルをバックアップテーブルとして使用します。
  • 新しいテーブルを作成する
  • 新しく作成されたテーブルを取得する
  • 古いテーブルと新しいテーブルを走査し、移行する必要があるテーブルのフィールドを比較して抽出します。
  • データ移行処理
  • バックアップテーブルを削除する

使用されたSQL文の分析

これらの操作はすべてデータベース操作に関連しているため、問題の鍵となるのは対応するステップの SQL ステートメントです。以下は、使用される主な SQL ステートメントの分析です。

データベース内の古いテーブルを取得する

sqlite_master から * を選択 WHERE type='table'

結果は次のようになります。type | name | tbl_name | rootpage | sql などのデータベース フィールドがあることがわかります。使用する必要があるのは、データベース名である name フィールドだけです。

sqlite> SELECT * from sqlite_master WHERE type='table'
 ...> ;
+-------+---------------+---------------+----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| タイプ | 名前 | tbl_name | ルートページ | sql |
+-------+---------------+---------------+----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| テーブル | t_message_bak | t_message_bak | 2 | テーブル「t_message_bak」を作成します (messageID TEXT、messageType INTEGER、messageJsonContent TEXT、retriveTimeString INTEGER、postTimeString INTEGER、readState INTEGER、PRIMARY KEY(messageID)) |
| テーブル | t_message | t_message | 4 | CREATE TABLE t_message (
 メッセージIDテキスト、 
 メッセージタイプ INTEGER、
 messageJsonContent テキスト、 
 取得時間文字列 INTEGER、 
 postTimeString INTEGER、 
 読み取り状態 INTEGER、 
 列の追加 INTEGER、
 主キー(メッセージID)
) |
+-------+---------------+---------------+----------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
データセット内の 2 行 (0.03 秒)

テーブル名を変更し、サフィックス「_bak」を追加し、古いテーブルをバックアップテーブルとして使用します。

-- t_message テーブルを t_message_bak テーブルに変更します ALTER TABLE t_message RENAME TO t_message_bak

テーブルフィールド情報を取得する

-- t_message_bak テーブルのフィールド情報を取得します。PRAGMA table_info('t_message_bak')

取得されたテーブル フィールド情報は次のとおりです。| cid | name | type | notnull | dflt_value | pk | を確認できます。これらのデータベース フィールドでは、フィールド名である name フィールドのみを使用する必要があります。

sqlite> プラグマ table_info('t_message_bak');
+------+--------------------+----------+----------+----------+------+
| cid | 名前 | タイプ | notnull | dflt_value | pk |
+------+--------------------+----------+----------+----------+------+
| 0 | メッセージID | テキスト | 0 | NULL | 1 |
| 1 | メッセージタイプ | INTEGER | 0 | NULL | 0 |
| 2 | messageJsonContent | テキスト | 0 | NULL | 0 |
| 3 | 取得時間文字列 | INTEGER | 0 | NULL | 0 |
| 4 | postTimeString | INTEGER | 0 | NULL | 0 |
| 5 | 読み取り状態 | 整数 | 0 | NULL | 0 |
+------+--------------------+----------+----------+----------+------+
データセットの6行(0.01秒)

データ移行にサブクエリを使用する

t_messageに挿入(メッセージID、メッセージタイプ、メッセージJsonContent、取得時間文字列、
 postTimeString、readState) SELECT messageID、messageType、messageJsonContent、retriveTimeString、
 postTimeString、t_message_bak からの readState

t_message_bakテーブルのmessageID、messageType、messageJsonContent、retriveTimeString、postTimeString、readStateフィールドの値をt_messageテーブルにコピーします。

コードの実装

次はコード実装のステップです。

// 新しい一時テーブルを作成し、データを一時テーブルにインポートし、元のテーブルを一時テーブルに置き換えます - (void)baseDBVersionControl {
 NSString * version_old = ValueOrEmpty(MMUserDefault.dbVersion);
 NSString * version_new = [NSString stringWithFormat:@"%@", DB_Version];
 NSLog(@"dbVersionControl 前: %@ 後: %@",version_old,version_new);

 // データベースバージョンのアップグレード if (version_old != nil && ![version_new isEqualToString:version_old]) {

  // データベース内の古いテーブルを取得します NSArray* existsTables = [self sqliteExistsTables];
  NSMutableArray* tmpExistsTables = [NSMutableArray 配列];

  // テーブル名を変更し、サフィックス「_bak」を追加し、古いテーブルをバックアップテーブルとして使用します (NSString* tablename in existsTables) {
   [tmpExistsTables addObject:[NSString stringWithFormat:@"%@_bak", テーブル名]];
   [self.databaseQueue inDatabase:^(FMDatabase *db) {
    NSString* sql = [NSString stringWithFormat:@"ALTER TABLE %@ RENAME TO %@_bak", テーブル名, テーブル名];
    [db 実行更新:sql];
   }];
  }
  存在するテーブル = tmpExistsTables;

  // 新しいテーブルを作成します [self initTables];

  // 新しく作成されたテーブルを取得します NSArray* newAddedTables = [self sqliteNewAddedTables];

  // 古いテーブルと新しいテーブルを走査し、移行する必要があるテーブルのフィールドを比較して抽出します NSDictionary* migrationInfos = [self generateMigrationInfosWithOldTables:existsTables newTables:newAddedTables];

  // データ移行処理 [migrationInfos enumerateKeysAndObjectsUsingBlock:^(NSString* newTableName, NSArray* publicColumns, BOOL * _Nonnull stop) {
   NSMutableString* 列文字列 = [NSMutableString new];
   (int i = 0; i<publicColumns.count; i++) の場合 {
    [列文字列 appendString:publicColumns[i]];
    if (i != publicColumns.count-1) {
     [列文字列追加文字列:@", "];
    }
   }
   NSMutableString* sql = [NSMutableString new];
   [sql appendString:@"INSERT INTO "];
   [sql 追加文字列:新しいテーブル名];
   [sql 追加文字列:@"("];
   [sql 追加文字列:列文字列];
   [sql 追加文字列:@")"];
   [sql 追加文字列:@" SELECT "];
   [sql 追加文字列:列文字列];
   [sql appendString:@" FROM "];
   [sql 追加フォーマット:@"%@_bak", 新しいテーブル名];

   [self.databaseQueue inDatabase:^(FMDatabase *db) {
    [db 実行更新:sql];
   }];
  }];

  // バックアップテーブルを削除します [self.databaseQueue inDatabase:^(FMDatabase *db) {
   [db トランザクション開始];
   (NSString* oldTableName in existsTables) の場合 {
    NSString* sql = [NSString stringWithFormat:@"存在する場合はテーブルを削除 %@", oldTableName];
    [db 実行更新:sql];
   }
   [dbコミット];
  }];

  MMUserDefault.dbVersion = version_new;

 } それ以外 {
  MMUserDefault.dbVersion = version_new;
 }
}

- (NSDictionary*)generateMigrationInfosWithOldTables:(NSArray*)oldTables newTables:(NSArray*)newTables {
 NSMutableDictionary<NSString*, NSArray* >* migrationInfos = [NSMutableDictionary 辞書];
 (NSString* newTableName in newTables) の場合 {
  NSString* oldTableName = [NSString stringWithFormat:@"%@_bak", newTableName];
  if ([oldTables containsObject:oldTableName]) {
   // テーブルデータベースフィールド情報を取得します NSArray* oldTableColumns = [self sqliteTableColumnsWithTableName:oldTableName];
   NSArray* newTableColumns = [self sqliteTableColumnsWithTableName:newTableName];
   NSArray* publicColumns = [self publicColumnsWithOldTableColumns:oldTableColumns newTableColumns:newTableColumns];

   (publicColumns.count > 0)の場合{
    [migrationInfos setObject:publicColumns forKey:newTableName];
   }
  }
 }
 移行情報を返します。
}

- (NSArray*)publicColumnsWithOldTableColumns:(NSArray*)oldTableColumns newTableColumns:(NSArray*)newTableColumns {
 NSMutableArray* publicColumns = [NSMutableArray 配列];
 (NSString* oldTableColumn in oldTableColumns) の場合 {
  if ([newTableColumns containsObject:oldTableColumn]) {
   [publicColumns addObject:oldTableColumn];
  }
 }
 publicColumns を返します。
}

- (NSArray*)sqliteTableColumnsWithTableName:(NSString*)テーブル名 {
 __block NSMutableArray<NSString*>* tableColumes = [NSMutableArray 配列];
 [self.databaseQueue inDatabase:^(FMDatabase *db) {
  NSString* sql = [NSString stringWithFormat:@"PRAGMA table_info('%@')", tableName];
  FMResultSet *rs = [db executeQuery:sql];
  while ([rs next]) {
   NSString* columnName = [rs stringForColumn:@"name"];
   [tableColumes addObject:列名];
  }
 }];
 tableColumes を返します。
}

- (NSArray*)sqliteExistsTables {
 __block NSMutableArray<NSString*>* existsTables = [NSMutableArray 配列];
 [self.databaseQueue inDatabase:^(FMDatabase *db) {
  NSString* sql = @"sqlite_master から * を選択し、 type='table' を指定します";
  FMResultSet *rs = [db executeQuery:sql];
  while ([rs next]) {
   NSString* テーブル名 = [rs stringForColumn:@"name"];
   [existsTables addObject:テーブル名];
  }
 }];
 existsTables を返します。
}

- (NSArray*)sqlite新しく追加されたテーブル{
 __block NSMutableArray<NSString*>* newAddedTables = [NSMutableArray 配列];
 [self.databaseQueue inDatabase:^(FMDatabase *db) {
  NSString* sql = @"sqlite_master から * を選択し、type='table' かつ name が '%_bak' ではない場合";
  FMResultSet *rs = [db executeQuery:sql];
  while ([rs next]) {
   NSString* テーブル名 = [rs stringForColumn:@"name"];
   [newAddedTables addObject:テーブル名];
  }
 }];
 newAddedTablesを返します。
}

質問

SQLite がサイズを変更せずにテーブルファイルを削除する問題

ご質問がありましたら、メッセージを残すか、コミュニティに参加して話し合いましょう。お読みいただきありがとうございます。お役に立てれば幸いです。このサイトをサポートしていただき、ありがとうございます。

以下もご興味があるかもしれません:
  • VUE での HTTP ライブラリ Axios メソッドの使用の詳細な説明
  • iOSシステムの基盤となる通知フレームワークライブラリの例の詳細な説明
  • URL書き換えをサポートする強力で使いやすいiOSルーティングライブラリFFRouterについて簡単に説明します。
  • iOS 開発ノート: キーボード、静的ライブラリ、アニメーション、クラッシュの位置
  • iOS Realmデータベースに基づく詳細な使用例
  • iOS開発でデータベースをエレガントにデバッグする方法
  • iOS での .a および .framework 静的ライブラリの作成と .bundle リソース パッケージの使用について詳しく説明します。
  • IOS UIImagePickerController はカメラ、ギャラリー、アルバムから写真を取得します
  • iOS での FMDB データベースの追加、削除、変更、確認の例
  • iOS の動的ライブラリと静的ライブラリの違い

<<:  Linux周辺ファイルシステムのカスタマイズ方法

>>:  WeChatミニプログラムページ間の価値転送を実装する方法の例

推薦する

Dockerが新しいイメージをロードした後にリポジトリとタグ名が両方ともnoneになる問題を解決する

次のコマンドを使用できます: docker tag [イメージID] [名前]:[バージョン]例えば...

dl、dt、dd はいつ使用するのが適切ですか?

dl:定義一覧定義リストdt:定義タイトルタイトルを定義するdd:定義説明定義の説明dt は情報のタ...

MySQL における varchar 型と char 型の違い

目次前述のVARCHAR型VARCHAR適用可能な状況CHAR型テストVARCHAR(5)とVARC...

HTML 基本コントロール入門_PowerNode Java アカデミー

<input> タグ<input> タグはユーザー情報を収集するために使用さ...

require loaderの実装原理の深い理解

序文Node は新しいプログラミング言語ではなく、JavaScript のランタイムに過ぎないとよく...

地域のカスタムカラーのためのechars 3Dマップソリューション

目次質問伸ばす問題を解決する要約する質問プロジェクトの要件に従って、以下の州地図で個々の都市を(異な...

JSはモバイル端末の画面を1つずつ上下にスライドさせる機能を実装します

この記事では、モバイル端末を一度に1画面ずつ上下にスライドさせるためのJSの具体的なコードを参考まで...

Vue 開発者向けの VSCode 拡張機能ベスト 7

適切な VS Code 拡張機能を Visual Studio に追加すると、開発者としての作業がは...

ドメイン名を指定されたポートに転送するようにNginxを設定する方法

/usr/local/nginx/conf と入力する sudo cd /usr/local/ngi...

docker-compose ポートと expose の違いの詳細な説明

docker-compose でコンテナ ポートを公開する方法は、ports と expose の ...

jsはショッピングサイトの虫眼鏡機能を実現します

この記事では、ショッピングサイトの虫眼鏡機能を実現するためのjsの具体的なコードを紹介します。具体的...

JavaScript マクロタスクとマイクロタスクの実行順序についての簡単な説明

目次1. JavaScriptはシングルスレッドです1. 同期タスク2. 非同期タスク2. タスクキ...

Firefoxでリンクをクリックしたときに点線の枠線を削除する方法

今日、ブラウザの互換性の問題にいくつか遭遇しました。そのうちの 1 つは奇妙に感じました。Firef...

js はマウスインとマウスアウトによるカード切り替えコンテンツを実装します

この記事では、マウスでカード内外のコンテンツを切り替えるためのjsの具体的なコードを紹介します。具体...

LeetCode の SQL 実装 (183. 注文をしたことがない顧客)

[LeetCode] 183.注文しない顧客Web サイトに、Customers テーブルと Or...