2019年9月5日

SQLite資料庫,不覆蓋舊資料庫的流程,建議方式 for Swift

完整範例
compareDb.swift

呼叫方式:
compareDbVersion(iDbName: "db",iDbType: "sqlite")



如果想知道原理可以看以下說明:

在製作資料庫的過程,通常可能因為一段時間的使用,使得某些因素,讓我們不得不去建立新的資料表或者是欄位,但覆蓋資料庫不是一個好的作法,以下是說如何在不動到原本的資料情況下,建立新的資料表和欄位。

  以下是寫的流程:

1. 我們將利用比對的方式去查資料庫的資料是否有不一樣。

 if(getLocalDbVersion() != getNewDbVersion())
 {
       
    compareDB()
    //最後修改版本
    update(updateStatementString: "UPDATE dbVersion SET version='\(getNewDbVersion())'")

 }else{
       print("資料庫版本一樣:\(dbVersion)|\(getNewDbVersion())")

 }


2.可以先檢查兩個資料庫版本的不一樣「本機資料庫」、「新的資料庫」。

假設,你的資料庫是叫 db.sqlite

    2.1 在「本機資料庫」開啟的方式,這個只能用iTunes才看得到。

  //開啟本機資料庫
    func openLocalDatabase() -> OpaquePointer? {
        
        var db: OpaquePointer? = nil
        let dirPaths =  NSSearchPathForDirectoriesInDomains(.documentDirectory,.userDomainMask, true)
        let docsDir:String! = dirPaths[0]
        let destPath = (docsDir as NSString).appendingPathComponent("/db.sqlite")
        if sqlite3_open(destPath, &db) == SQLITE_OK {
            //print("Successfully opened connection to database at \(destPath)")
            return db
        }
        return nil

    }

    2.2 「新的資料庫」開啟的方式,這個的意思是你放在app裡面的db.sqlite,如圖1:

  //開啟新的sqlite資料
    func openNewDatabase() -> OpaquePointer?{
        var db: OpaquePointer? = nil
        let path = Bundle.main.path(forResource: "db", ofType:"sqlite")
        if sqlite3_open(path, &db) == SQLITE_OK {
            //print("Successfully opened connection to database at \(path)")
            return db
        } else {
            
        }
        return nil

    }

圖1、App的db.sqlite


3.判斷這兩個版本是否一樣,不一樣的話,才進行「第4步」之後的比對動作。以下是取得「本機資料庫」和「新的資料庫」版本方式

      3.1 「本機資料庫」版本的程式

    //取得本機版本
    func getLocalDbVersion()->String{
        
        var Version = ""
        
        var db = openLocalDatabase()
        var queryStatementString = "SELECT version FROM dbVersion"
        var queryStatement: OpaquePointer? = nil
        if sqlite3_prepare_v2(db, queryStatementString, -1, &queryStatement, nil) == SQLITE_OK {
            if sqlite3_step(queryStatement) == SQLITE_ROW {
                
                let queryResultCol1 = sqlite3_column_text(queryStatement, 0)
                Version = String(cString: queryResultCol1!)
                
            } else {
                print("Query returned no results")
            }
        } else {
            print("SELECT statement could not be prepared")
        }
        
        sqlite3_finalize(queryStatement)
        
        return Version

    }

  3.2 「新的資料庫」版本的程式

    //取得新的資料庫的資料
    func getNewDbVersion()->String
    {
        var Version = ""
        var db = openNewDatabase()
        
        var queryStatementString = "SELECT version FROM dbVersion"
        var queryStatement: OpaquePointer? = nil
        if sqlite3_prepare_v2(db, queryStatementString, -1, &queryStatement, nil) == SQLITE_OK {
    
            if sqlite3_step(queryStatement) == SQLITE_ROW {
                let queryResultCol1 = sqlite3_column_text(queryStatement, 0)
                Version = String(cString: queryResultCol1!)
            }
            
        } else {
            print("SELECT statement could not be prepared")
        }
        
        sqlite3_finalize(queryStatement)
        return Version
        

    }


4.當版本的比對不一樣時,我們就開始以下的動作。

      4.1 利用 SELECT name, sql FROM sqlite_master WHERE type='table' 取得「新的資料庫」各「資料表的名稱」和「Create Table SQL語法」,圖2。

    4.2 利用迴圈查詢「本機資料庫」的資料表是否和「新的資料表」的資料表相符的資料,語法:SELECT name FROM sqlite_master WHERE type='table' AND name='\(name)'。如果符合,就繼續「檢查欄位」(4.5),如果沒有,就直接利用「Create Table SQL語法」建立新得資料表(4.3、4.4)

圖2

      4.3 比對資料表與欄位的程式碼

  //當資料版本不一樣的時候,會進行,比對db
    func compareDB()
    {
        var dbLocal = openLocalDatabase()//手機本身的sqlite
        var dbNew = openNewDatabase()//新的資料庫
        
        var queryStatementString = "SELECT name, sql FROM sqlite_master WHERE type='table'"
        var queryStatementLocal: OpaquePointer? = nil
        var queryStatementNew: OpaquePointer? = nil
        
        //取得新資料庫的資料表名稱和Crate Table SQL語法
        if sqlite3_prepare_v2(dbNew, queryStatementString, -1, &queryStatementNew, nil) == SQLITE_OK {
            // 
            while sqlite3_step(queryStatementNew) == SQLITE_ROW {
                
                //取得「新資料庫」資料表名稱
                let queryResultCol1 = sqlite3_column_text(queryStatementNew, 0)
                let name = String(cString: queryResultCol1!)
                
                //開始比對「本機資料庫」是否有相對應的資料表
                queryStatementString = "SELECT name FROM sqlite_master WHERE type='table' AND name='\(name)'"
                //
                if sqlite3_prepare_v2(dbLocal, queryStatementString, -1, &queryStatementLocal, nil) == SQLITE_OK {

                    if sqlite3_step(queryStatementLocal) == SQLITE_ROW {
              //如果「本機資料庫」有該比資料,那就再去比對「欄位」
                        compareColumn(tableName: name)
                    }else{
                        //如果「本機資料庫」沒有該資料表,那就取得Create Table SQL語法,建立新的
                        let queryResultCol1 = sqlite3_column_text(queryStatementLocal, 1)
                        let createTableString = String(cString: queryResultCol1!)
                        createTable(createTableString: createTableString)
                    }
                }
                
                
            }
        } else {
            print("SELECT statement could not be prepared")
        }
        
        // 6
        sqlite3_finalize(queryStatementNew)
        sqlite3_finalize(queryStatementLocal)
        

    }

      4.3 建立資料表的Swift寫法如下:

 func createTable(createTableString:String) {
        var db = openLocalDatabase()
        var createTableStatement: OpaquePointer? = nil
        if sqlite3_prepare_v2(db, createTableString, -1, &createTableStatement, nil) == SQLITE_OK {
            if sqlite3_step(createTableStatement) == SQLITE_DONE {
                print("Contact table created.")
            } else {
                print("Contact table could not be created.")
            }
        } else {
            print("CREATE TABLE statement could not be prepared.")
        }
        sqlite3_finalize(createTableStatement)

    }

       4.4 比對欄位的寫法:

  //比較欄位
    func compareColumn(tableName:String)
    {
        var dbNew = openNewDatabase()
        
        var queryStatementString = "pragma table_info(\(tableName))"
        var queryStatementNew: OpaquePointer? = nil
        //取得新的資料表的資料欄位
        if sqlite3_prepare_v2(dbNew, queryStatementString, -1, &queryStatementNew, nil) == SQLITE_OK {
            while sqlite3_step(queryStatementNew) == SQLITE_ROW {
                
                //取得所有欄位名稱
                let queryResultCol1 = sqlite3_column_text(queryStatementNew, 1)
                let fieldName = String(cString: queryResultCol1!)
                
          //判斷本機資料庫欄位是否存在
                if(!isFieldExist(tableName: tableName, fieldName: fieldName))
                {
                     //如果不存在,就在該資料表建立新的欄位
                    let queryResultCol1 = sqlite3_column_text(queryStatementNew, 1)
                    let col1 = String(cString: queryResultCol1!)
                    
                    let queryResultCol2 = sqlite3_column_text(queryStatementNew, 2)
                    let col2 = String(cString: queryResultCol2!)
                    
                    
                    let col3 = sqlite3_column_int(queryStatementNew, 3)
                    let col5 = sqlite3_column_int(queryStatementNew, 5)
                    
                    var sql = "ALTER TABLE \(tableName) ADD COLUMN \(col1) \(col2) ";
                    if col3 == 1 {
                        sql = sql + " not null"
                    }
                    
                    if col5 == 1 {
                        sql = sql + " PRIMARY KEY(\(tableName))";
                    }
                    
                    //建立新的欄位
                    createTable(createTableString: sql)
                    
                }else{
                    print("表格:\(tableName),欄位:\(fieldName) 存在")
                }
                
                
            }
        } else {
            print("SELECT statement could not be prepared")
        }
        
        sqlite3_finalize(queryStatementNew)

    }

       4.5 檢查「本機資料庫」是否有相對應的欄位:

    //檢查本機是否有這個欄位
    func isFieldExist(tableName:String,fieldName:String)->Bool{
        var isExist :Bool = false
        var dbLocal = openLocalDatabase()
        var queryStatementString = "pragma table_info(\(tableName))"
        
        var queryStatementLocal: OpaquePointer? = nil

        if sqlite3_prepare_v2(dbLocal, queryStatementString, -1, &queryStatementLocal, nil) == SQLITE_OK {
            while sqlite3_step(queryStatementLocal) == SQLITE_ROW {
                //檢查「本機資料庫」所有欄位名稱
                let queryResultCol1 = sqlite3_column_text(queryStatementLocal,1)
                let field = String(cString: queryResultCol1!)
               
                if fieldName == field
                {
            //如果一樣的欄位就直接離開。
                    isExist = true
                    break
                }
            }
            
            
        }
        sqlite3_finalize(queryStatementLocal)
        return isExist
        

    }

5.最後、最後,更新本機端的版本號 

檢查完畢後,取得「新的資料庫」版本號,進行「本機資料庫」的更新。
程式碼如下:

   //最後修改版本

    update(updateStatementString: "UPDATE dbVersion SET version='(getNewDbVersion())'")


    func update(updateStatementString:String) {
        var db = openLocalDatabase()
        var updateStatement: OpaquePointer? = nil
        if sqlite3_prepare_v2(db, updateStatementString, -1, &updateStatement, nil) == SQLITE_OK {
            if sqlite3_step(updateStatement) == SQLITE_DONE {
                print("Successfully updated row.")
            } else {
                print("Could not update row.")
            }
        } else {
            print("UPDATE statement could not be prepared")
        }
        sqlite3_finalize(updateStatement)

    }





 

沒有留言:

張貼留言