技術ブログ

プログラミング、IT関連の記事中心

SQLite【Swift】

■はじめに

データを永続的に保存する方法で、数々の方法があります。
以下に一例を挙げます。
・UserDefaults
・CoreData
SQLite
・Realm

ここでは、「SQLite」に関して記載します。
SQL文に関しては、簡単なサンプルや記載方法は記載するが細かい部分は別途調査する事。

◾️SQLiteとは

SQLiteとは、数あるSQLの中の一つです。
※ちなみに、SQLとはDB(データベース)を定義したり操作したりするプログラミング言語です。

SQLiteの特徴として、他のSQLMySQL,Oracleなど)と違い、DBをファイル形式で保持する事ができます。
※他のSQLMySQL,Oracleなど)のほとんどは、サーバー上にDBを保持します。

上記の特徴があるため、ローカルにDBファイルを保存しておく事で、サーバーへ接続する必要がなくなるメリットがあります。

◾️SQLiteのインポート

SwiftでSQLiteを使用する為に、便利なライブラリが色々とありますが
ライブラリを使用したくない場合もありますよね。(現場の方針など。。。)

ここでは、ライブラリを使用しないSQLiteの操作を記載します。

まずは、SQLiteを操作する為に以下をインポートします。
SQLiteの操作をするクラスでは必ずインポートしてください。

import SQLite3

◾️DBファイルの定義

SQLiteとは」で説明した通り、SQLiteはDBをファイル形式で保持しています。
そのため、まずはDBファイルを生成する必要があります。

既に、DBファイルを生成している場合でも、対象のDBファイルの場所をポインタとして保持しておく必要がある為
まずは、フィールド変数に以下を定義します。
※都度、DBファイルの場所を取得するなら不要ですが、それは手間ですよね。。。

var db: OpaquePointer?

上記のフィールド変数を定義したら、次に以下のフィールド定数を定義します。
このフィールド定数は、DBファイル名を定義しています。

let dbfile: String = "sample.db"

ここまでの下準備ができたら、以下のメソッドを実行します。
※テーブル作成などを行う前に、一番最初に実行する必要があります。

func openDB() {
    let fileURL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false).appendingPathComponent(self.dbfile)
    if sqlite3_open(fileURL.path, &db) != SQLITE_OK {
        print("DBファイルが見つからず、生成もできません。")
    } else {
        print("DBファイルが生成できました。(対象のパスにDBファイルが存在しました。)")
    }
}

これで、DBファイルが生成できるか、既に存在する場合には、そのポインタがフィールド変数の「db」に保存されます。
以降、このポインタを使用していきます。

◾️Table作成(CREATE TABLE)

ここでは、テーブルの作成方法を記載します。
※「DBファイルの定義」で説明したDBファイルへのポインタ変数は既に保持している前提です。

以下のメソッドを実行する事で、Tableの作成が可能です。
※以下のメソッドに記載しているSQL文は各自書き換えてください。

【テーブル作成のSQLの基本の記載方法】

CREATE TABLE [テーブル名] ([カラム名] [データ型], [カラム名] [データ型] ・・・)
func createTable() {
    let createTable = "CREATE TABLE sampleTable (name TEXT, age INTEGER)"
    if sqlite3_exec(db, createTable, nil, nil, nil) != SQLITE_OK {
        print("テーブルの作成に失敗しました。")
    } else {
        print("テーブルが作成されました。")
    }
}

◾️カラムの登録(INSERT)

ここでは、カラムの登録方法を記載します。
※「DBファイルの定義」で説明したDBファイルへのポインタ変数は既に保持している前提です。

以下のメソッドを実行する事で、カラムの登録が可能です。
※以下のメソッドに記載しているSQL文は各自書き換えてください。

【カラムの登録のSQLの基本の記載方法】

INSERT INTO [テーブル名] ([カラム名], [カラム名]・・・) VALUES ([データ], [データ]・・・)
func insert() {
    var stmt: OpaquePointer?
    
    let queryString = "INSERT INTO sampleTable (name, age) VALUES ('aaa', 12)"
    
    // クエリを準備する
    if sqlite3_prepare(db, queryString, -1, &stmt, nil) != SQLITE_OK{
        let errmsg = String(cString: sqlite3_errmsg(db)!)
        print("error preparing insert: \(errmsg)")
        return
    }
    
    // クエリを実行する
    if sqlite3_step(stmt) != SQLITE_DONE {
        let errmsg = String(cString: sqlite3_errmsg(db)!)
        print("failure inserting hero: \(errmsg)")
        return
    }
    
    print("データが登録されました")
}

◾️データの取得(SELECT)

ここでは、データの取得方法を記載します。
※「DBファイルの定義」で説明したDBファイルへのポインタ変数は既に保持している前提です。

以下のメソッドを実行する事で、データの取得が可能です。
※以下のメソッドに記載しているSQL文は各自書き換えてください。
※取得したレコードのループ処理は各自書き換えてください。

【データの取得のSQLの基本の記載方法】

SELECT * FROM [テーブル名]
func select(){
    let queryString = "SELECT * FROM sampleTable"
    
    var stmt:OpaquePointer?
    
    // クエリを準備する
    if sqlite3_prepare(db, queryString, -1, &stmt, nil) != SQLITE_OK{
        let errmsg = String(cString: sqlite3_errmsg(db)!)
        print("error preparing insert: \(errmsg)")
        return
    }
    
    // クエリを実行し、取得したレコードをループする
    while(sqlite3_step(stmt) == SQLITE_ROW){
        let name = String(cString: sqlite3_column_text(stmt, 0))
        let age = sqlite3_column_int(stmt, 1)
        
        print("name : \(name)")
        print("age : \(age)")
    }
}

◾️データの更新(UPDATE)

ここでは、データの更新方法を記載します。
※「DBファイルの定義」で説明したDBファイルへのポインタ変数は既に保持している前提です。

以下のメソッドを実行する事で、データの更新が可能です。
※以下のメソッドに記載しているSQL文は各自書き換えてください。
※取得したレコードのループ処理は各自書き換えてください。

【データの更新のSQLの基本の記載方法】

UPDATE [テーブル名] SET [カラム名] = [データ] WHERE [カラム名] = [データ]
func update() {
    var stmt: OpaquePointer?
    
    let queryString = "UPDATE sampleTable SET age = 99 WHERE name = 'ccc'"
    
    // クエリを準備する
    if sqlite3_prepare(db, queryString, -1, &stmt, nil) != SQLITE_OK{
        let errmsg = String(cString: sqlite3_errmsg(db)!)
        print("error preparing insert: \(errmsg)")
        return
    }
    
    // クエリを実行する
    if sqlite3_step(stmt) != SQLITE_DONE {
        let errmsg = String(cString: sqlite3_errmsg(db)!)
        print("failure inserting hero: \(errmsg)")
        return
    }
    
    print("データが更新されました")
}

◾️データの削除(DELETE)

ここでは、データの削除方法を記載します。
※「DBファイルの定義」で説明したDBファイルへのポインタ変数は既に保持している前提です。

以下のメソッドを実行する事で、データの削除が可能です。
※以下のメソッドに記載しているSQL文は各自書き換えてください。
※取得したレコードのループ処理は各自書き換えてください。

【データの削除のSQLの基本の記載方法】

DELETE FROM [テーブル名] WHERE [カラム名] = [データ]
func delete() {
    var stmt: OpaquePointer?
    
    let queryString = "DELETE FROM sampleTable WHERE name = 'aaa'"
    
    // クエリを準備する
    if sqlite3_prepare(db, queryString, -1, &stmt, nil) != SQLITE_OK{
        let errmsg = String(cString: sqlite3_errmsg(db)!)
        print("error preparing insert: \(errmsg)")
        return
    }
    
    // クエリを実行する
    if sqlite3_step(stmt) != SQLITE_DONE {
        let errmsg = String(cString: sqlite3_errmsg(db)!)
        print("failure inserting hero: \(errmsg)")
        return
    }
    
    print("データが削除されました")
}

■おまけ

何回も呼び出すような場合には、シングルトンでSQLiteの処理を記載することをお勧めします。

データベースファイルが保持され、「database is locke」が発生する場合があるようなので。