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ミニプログラムページ間の価値転送を実装する方法の例

推薦する

スタイル属性 (element.style) で定義されたインライン スタイルを削除する方法

Magento を頻繁に変更する場合、element.style に遭遇することがあります。 これは...

CSS レスポンシブ レイアウト システムの例コード

レスポンシブ レイアウト システムは、今日の一般的な CSS フレームワークではすでに非常に一般的で...

Vueスロットの詳細な説明

1. 機能: 親コンポーネントが子コンポーネントの指定された位置に HTML 構造を挿入できるように...

jsはシンプルなカウントダウンを実装します

この記事の例では、参考までに簡単なカウントダウンを実装するためのjsの具体的なコードを共有しています...

JavaScript によるデータ視覚化: ECharts マップの作成

目次概要予防1. 使用方法2. 実装手順予備実装コード効果: Geo共通設定上記の構成を追加した後の...

カレンダーウィジェットのネイティブJS実装

この記事の例では、カレンダーウィジェットを実装するためのjsの具体的なコードを参考までに共有していま...

JavaScript 組み込みオブジェクトの概要

目次1. 組み込みオブジェクト2. 数学オブジェクト1. Mathオブジェクトの使用2. 指定された...

Vue.js スタイルレイアウト Flutter ビジネス開発共通スキル

シャドウスタイルにおけるフラッターとCSSの対応UIによって指定されたCSSスタイル 幅: 75px...

エラー 1862 (HY000): パスワードの有効期限が切れています。ログインするには、..... を使用してパスワードを変更する必要があります。

エラーメッセージ:エラー 1862 (HY000): パスワードの有効期限が切れています。ログインす...

IE6/IE7/IE8/IE9/FF 向け CSS ハック (概要)

IE8.0の正式版をインストールしたので、基本的なCSS HACKをいくつかまとめてみました。We...

Ubuntu 上の Apache で SSL (https 証明書) を設定する正しい方法の詳細な説明

まず、Alibaba Cloud の公式チュートリアルをご覧ください。ファイルの説明: 1. 証明書...

デザイナーと開発者に役立つ 9 つの超実用的な CSS のヒント

Web デザイナーの頭の中には、仕事に関連する多くの知識が詰まっている必要があります。 CSS は、...

CSS でのフレックスレイアウトの詳細な説明

フレックス レイアウトは、エラスティック レイアウトとも呼ばれます。任意のコンテナーをフレックス レ...

CSS モジュールソリューション

CSS のモジュール ソリューションは、JS のモジュール ソリューションと同じくらい多く存在すると...