FMDB:我的SQLite救星


好滴~離上次發文又給他隔了三個月,為什麼咧??因為林盃我很忙,這三個月我跑去訂婚、結婚、歸寧外加峇里島蜜月,所以部落格就這麼給他放著了。以為這裡不會再更新的朋友們~快回來哦~有新文章了。

這次要分享的是在iOS中使用SQLite。其實我對SQLite一點概念都沒有,因為一直沒有機會使用它。有時候突然想到,就會找幾本手邊的工具書或在網路上找尋在iOS中使用SQLite的教學。但是每次學,每次失敗,讓我愈來愈討厭SQLite 囧。直到有一次在網路上發現了FMDB這個東西,才發現不是SQLite很難學,而是SQLite的C API對初學者來說實在太麻煩太瑣碎,難度太高,難怪我怎麼學都學不會。FMDB說穿了其實只是把C API包裝成簡單易用的Objective-C物件。不過這對我這個SQLite初學者來說,可是大大減低了上手的難度。有了FMDB,寫程式時只要專心在SQLite的語法上,而不用去理那堆有看沒有懂的C API,實在是件快樂的事情。

廢話不多說,先來安裝FMDB吧!

首先到這裡下載FMDB的source code,接著在解開的檔案裡,把src資料夾下除了fmdb.m的檔案加入到自己的iOS專案,最後在專案中加入libsqlite3.dylib這個函式庫就可以了。啥?有人問為什麼不用加入fmdb.m?簡單講,這個檔案是fmdb的使用說明。裡面的註解清楚,範例又簡單,如果有興趣,直接看fmdb.m,大概就會用fmdb了。

以下介紹幾個常用的指令,分享給大家:

-開啟/關閉資料庫

使用資料庫的第一件事,就是建立一個資料庫。要注意的是,在iOS環境下,只有document directory 是可以進行讀寫的。在寫程式時用的那個Resource資料夾底下的東西都是read-only。因此,建立的資料庫要放在document 資料夾下。方法如下:

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);

NSString *documentDirectory = [paths objectAtIndex:0];

NSString *dbPath = [documentDirectory stringByAppendingPathComponent:@”MyDatabase.db”];

FMDatabase

*db = [FMDatabase databaseWithPath:dbPath] ;

if (![db open]) {

NSLog(@“Could not open db.”);

return ;

}

通常這段程式碼會放在UIViewController中viewDidLoad的函式裡。指定路徑後,用[FMDatabase databaseWithPath:]回傳一個FMDatabase物件,如果該路徑本來沒有檔案,會新增檔案,不然會開啟舊檔。最後呼叫[db open]可以開啟該資料庫檔案,[db close]則關閉該檔案。

-建立table

如果是新建的資料庫檔,一開始是沒有table的。建立table的方式很簡單:

[db executeUpdate:@”CREATE TABLE PersonList (Name text, Age integer, Sex integer, Phone text, Address text, Photo blob)”];

這是FMDB裡很常用的指令,[FMDatabase_object executeUpdate:]後面用NSString塞入SQLite語法,就解決了。因為這篇主要是在講FMDB,所以SQLite的語法就不多說了,上述程式碼建立了一個名為PersonList的table,裡面有姓名、年齡、性別、電話、地址和照片。(嗯….很範例的一個table)

-插入資料

插入資料跟前面一樣,用executeUpdate後面加語法就可以了。比較不同的是,因為插入的資料會跟Objective-C的變數有關,所以在string裡使用?號來代表這些變數。

[db executeUpdate:@”INSERT INTO PersonList (Name, Age, Sex, Phone, Address, Photo) VALUES (?,?,?,?,?,?)”,

@”Jone”, [NSNumber numberWithInt:20], [NSNumber numberWithInt:0], @“091234567”, @“Taiwan, R.O.C”, [NSData dataWithContentsOfFile: filepath]];

其中,在SQLite中的text對應到的是NSString,integer對應NSNumber,blob則是NSData。該做的轉換FMDB都做好了,只要了解SQLite語法,應該沒有什麼問題才是。

-更新資料

太簡單了,不想講,請看範例:

[db executeUpdate:@”UPDATE PersonList SET Age = ? WHERE Name = ?”,[NSNumber numberWithInt:30],@“John”];

-取得資料

取得特定的資料,則需使用FMResultSet物件接收傳回的內容:

FMResultSet *rs = [db executeQuery:@”SELECT Name, Age, FROM PersonList”];

while ([rs next]) {

NSString *name = [rs stringForColumn:@”Name”];

int age = [rs intForColumn:@”Age”];

}

[rs close];

用[rs next]可以輪詢query回來的資料,每一次的next可以得到一個row裡對應的數值,並用[rs stringForColumn:]或[rs intForColumn:]等方法把值轉成Object-C的型態。取用完資料後則用[rs close]把結果關閉。

-快速取得資料

在有些時候,只會query某一個row裡特定的一個數值(比方只是要找John的年齡),FMDB提供了幾個比較簡便的方法。這些方法定義在FMDatabaseAdditions.h,如果要使用,記得先import進來。

//找地址

NSString *address = [db stringForQuery:@”SELECT Address FROM PersonList WHERE Name = ?”,@”John”];

//找年齡

int age = [db intForQuery:@”SELECT Age FROM PersonList WHERE Name = ?”,@”John”];

大概就是這樣囉~對於在Objective-C上使用SQLite有困難的朋友,看完之後是不是覺得一切都變的很簡單呢?趕快去試試吧~


59 thoughts on “FMDB:我的SQLite救星

  1. 想深入學習cocos2d,
    除了官方網站外,
    不知道有沒有推薦的參考資料呢?

    總覺得跟itune上面的遊戲程式,
    level天差地遠!

  2. 您好, 我用 iOS 4.3 搭配 XCode 4.0.1 開發.
    但我看到一本電子書的資料說, 在:
    /Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/lib
    會找到:
    libsqlite3.dylib

    似乎 iOS 已經整合 SQLite 了?

    我在這本電子書裡面, Page 287 看到的.

    Click to access %5BiPhone%E5%BC%80%E5%8F%91%E4%B9%A6%E7%B1%8D%E5%A4%A7%E5%85%A8%5D.Beginning.iPhone.SDK.Programming.with.Objective-C%28Wrox.2010-01%29.pdf

    用法似乎跟您寫的類似, 是不是 iOS 已經可以像 FMDB 一樣使用 Objective-C 語法來用了?

    謝謝~

    1. hello,
      iOS本來就整合了SQLite在裡面,但你得去呼叫最原始的C API去使用SQLite,我個人是覺得用起來不直覺又麻煩。FMDB則是幫你把所有的C API都包進一個objective-C物件裡,所以使用上來說比較方便。這就好像iOS本身支援OpenGL ES,但你不會去直接用他,而是靠cocos2d幫忙。FMDB不是官方的模組,需要自己import進來。

    2. 補充說明,那本書上教的就是最原始的SQLite C API。如果您覺得那樣比較方便,其實就不需要FMDB了。

  3. 您好,我用XCODE4開發IPHONE上APP,現在遇到一個問題,在使用您上述的程式碼建立新的資料庫時,卻發生在document資料夾中,沒有看到我所建立的資料庫的檔案,但是確定可以在資料庫上新增、查詢。想請這問這是回事?謝謝您

    1. 請問您所說的“在document資料夾中沒看到所建立的資料庫”,這個document資料夾的路徑為何呢?

      1. 您好,我是直接使用NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
        NSString *documentDirectory = [paths objectAtIndex:0];
        NSString *dbPath = [documentDirectory stringByAppendingPathComponent:@”MyDatabase.db”];
        FMDatabase*db = [FMDatabase databaseWithPath:dbPath] ;
        這段程式碼。並且建立TALBE和輸入一筆資料。
        再來搜尋建立的資料庫檔案,都搜尋不到檔案。

      2. hello,我想問的是,您是怎麼“搜尋建立的資料庫檔案”? 程式碼?finder?

    1. finder。。。您該不會是去帳號下的Document資料夾下找吧?iPhone模擬器的document不在那裡喔!

      1. 原來如此,我一直以為是在那邊,怪不得找不到。
        很謝謝您的解答。

  4. 那想請教一下如何判斷table 是否存在
    [db executeUpdate:@”CREATE TABLE PersonList (Name text, Age integer, Sex integer, Phone text, Address text, Photo blob)”];

    會自動判斷嗎 如果不存在才建立

  5. 請問-刪除重複資料要怎麼做?
    我有參考http://www.dotblogs.com.tw/lastsecret/archive/2010/07/13/16532.aspx
    的方式,1.DELETE Product where ID NOT IN (Select Max(ID) From [Product] Group By 產品名稱)2.Select * From [Product] Where ID In (Select Max(ID) From [Product] Group By 產品名稱)
    但是沒有用,請問要怎麼做?謝謝

  6. 另外還看到http://blog.miniasp.com/post/2010/08/12/Remove-duplicate-rows-from-a-table-in-SQL-Server.aspx
    說到“若要避免資料重複,建議額外建立含有多個欄位的「唯一索引鍵」,從資料庫層級就阻擋所有可能重複資料的出現。”

    我主要是要避免資料重複,那我該怎麼建立含有多個欄位的「唯一索引鍵」?
    謝謝!!

  7. 你好請問一下,假設我想用一個singleton的class專門來處理DB的建立,寫入跟刪除等功能
    我應該要在哪邊建立table ?
    總覺得有點看不太懂何時應該建立Table?
    使用FMDB是不用先拉任何.sqlite 檔案到Project裡面
    然後就會自動產生.sqlite檔案在Docment裡面嗎?
    我有打開看,看起來是有產生檔案,不過印不出任何資料

    1. 不好意思我發現我有寫檔案了,應該是讀出的時候有問題,我再查看看。

      1. 我知道問題了…我在參考你的程式碼的時候
        在這行FMResultSet *rs = [db executeQuery:@”SELECT Name, Age, FROM PersonList”];
        Age後面還有多一個 ” , ” 沒發現,會導致無法select 結果出來。FMDB真是三八啦,小小逗號還計較這麼多

  8. 我想請問 為何我把fmdb 您說的那幾檔案 add 進我的project裡

    fmdatabase 出現 一大堆錯誤

    請問 是為什麼呢?

    我方法錯嘛?

    1. 那個。。。。。您的錯誤是什麼?至少貼出來吧?不然我也不知道錯在哪說@@

  9. 不好意思 我還是新手

    db 函示庫 我該怎麼新增呢??

    我用的是xcode 4.2版本的

    感謝您

  10. 您好 我解決了基本錯誤…..

    那個錯誤是xcode4.2的問題 自動釋放內存

    我想問的是 享用fmdb 只要單純的把fmdb那幾個資料 丟進去 並在framwork那增加sqlite3就好了嗎??

    不知可否在問您 由於小的最近在做專題 這個程式需要用到資料夾
    假設 畫面有兩個textfield 一個button
    我想要在textfield 1 中 輸入數字 然後按下button 後

    這筆數字 會存到 資料庫中 並且同時被取出 在text 2 中顯示出來

    不知道這個該怎麼做…. 小弟隊資料庫 目前真的不熟

    希望可以幫幫忙 讓我的專題可以順利完成 ><

  11. 不知道您有沒有 即時通 或是 qq 之類的

    可以讓我有問題時請教您><

    感謝

  12. 請問iPhone模擬器的document資料夾在哪?小弟翻遍整個專案找不著阿==a

    1. 你的家目錄/Library/Application Support/iPhone Simulator/5.0
      裡面有一堆被裝在模擬器裡的app,找到以後,裡面的document資料夾才是你要的。
      翻遍專案當然找不到= =

  13. 老大,我還是找不著阿TT我用的是LION的系統,xcode4.1,雖然有Application Support跟IPhone Simulator這兩個資料夾,但Iphone Simulator卻不是在Application Support的子目錄下,直接搜尋MyDatabase完全找不著阿==資料能插入也能取出,但找不到資料庫所在的位置阿冏

  14. 我剛試著把路徑印出來,顯示/Users/ctp/Library/Application Support/iPhone Simulator/4.3.2/Applications/,但奇怪的是我從find去找卻找不到這個路徑==搜尋4.3.2也找無結果,請問這是怎麼一回事阿==

  15. 找到了==那資料夾真的是隱藏的,浪費了一天時間阿,還是覺得window比較好用冏

  16. 我是剛入手的.
    請問 建立的資料庫要放在document ,

    因為我在 macbook terminal中的sqlite3 , 建立了一個 db, 是如何放在docuement ?

    是否我的app上, 寫一個程式把db 複製入去嗎 ? 只執行一次?

    Thanks

      1. 版主阿,這樣就換我有疑問了,資料庫找不到時不是會自己建立嗎?既然自己建立了那我還需要複製嗎?

      2. 哦~因為我猜John是先建好一個database和table,所以直接複製過去比較快,這樣該有的就都有了,不用再用程式create table

        (現在這裡是FMDB討論版了嗎 XD)

  17. 不好意思, 還有幾個問題, 想請問你,謝謝你了。
    1. 資料庫放在Document, Document  是指 Finder  中見到的“文件” 嗎?,還是 Iphone 上嗎?
    2. iPhone模擬器的 Document, 可以用Finder 進入嗎 ?
    3. 是否把 sqlite 建立的db,  插入xcode project  任一地方, 再用以下代碼放在viewDidLoad 執行, 用作複製 db 到 iphone 的 Document  中?
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsPath = [paths objectAtIndex:0];
    NSString *dbPath = [documentsPath stringByAppendingPathComponent:@”demo”];
    NSFileManager *fileManager = [NSFileManager defaultManager];

    if (![fileManager fileExistsAtPath: dbPath])
    {
    NSString *bundle = [[ NSBundle mainBundle] pathForResource:@”demo” ofType:@””];
    [fileManager copyItemAtPath:bundle toPath:dbPath error:error];
    NSLog(@”YES”);
    }
    else
    { NSLog(@”NO”);}
    4.即是每次開啟app 時, 都要查詢db 是否存在, 不存在才複製嗎?
    5.放到 app store  時, 方法和路徑是否不需要修改?  自動會把db放入真機的 Document 嗎?
    6.  我用以下sqlite 代碼直接在程式中建立的 db, table  , 在那裹可以看到真的存在?
    // Get the documents directory
    dirPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);

    docsDir = [dirPaths objectAtIndex:0];

    // Build the path to the database file
    databasePath = [[NSString alloc] initWithString: [docsDir stringByAppendingPathComponent: @”contacts.db”]];

    NSFileManager *filemgr = [NSFileManager defaultManager];

    if ([filemgr fileExistsAtPath: databasePath ] == NO)
    {
    const char *dbpath = [databasePath UTF8String];

    if (sqlite3_open(dbpath, &contactDB) == SQLITE_OK)
    {
    char *errMsg;
    const char *sql_stmt = “CREATE TABLE IF NOT EXISTS CONTACTS (ID INTEGER PRIMARY KEY AUTOINCREMENT, NAME TEXT, ADDRESS TEXT, PHONE TEXT)”;

    if (sqlite3_exec(contactDB, sql_stmt, NULL, NULL, &errMsg) != SQLITE_OK)
    {
    status.text = @”Failed to create table”;
    }

    sqlite3_close(contactDB);

    } else {
    status.text = @”Failed to open/create database”;
    }
    }

    1. 1.你在模擬器run,在finder找;你在實機run,去機器找。
      2.請爬文 XD(之前有人問過了)
      3.是
      4.是
      5.是
      6.(請合併解答1+2)

      1. 謝謝你的回覆,

        我已經在模擬器的 Document 找到 db.

        使用FMDB, 語法真係簡單了. 但如果不使用FMDB, 問題應該都不大嗎?

        因為平常只是使用 select , insert , update.
        每次只要按照固定的方法修改 sql stmt , 就可以了,

        我的想法正確嗎? 因為我不知道FMDB還有其他更好的方法 ?

        – (void) saveData
        {
        sqlite3_stmt *statement;

        const char *dbpath = [databasePath UTF8String];

        if (sqlite3_open(dbpath, &contactDB) == SQLITE_OK)
        {
        NSString *insertSQL = [NSString stringWithFormat: @”INSERT INTO CONTACTS (name, address, phone) VALUES (\”%@\”, \”%@\”, \”%@\”)”, name.text, address.text, phone.text];

        const char *insert_stmt = [insertSQL UTF8String];

        sqlite3_prepare_v2(contactDB, insert_stmt, -1, &statement, NULL);
        if (sqlite3_step(statement) == SQLITE_DONE)
        {
        status.text = @”Contact added”;
        name.text = @””;
        address.text = @””;
        phone.text = @””;
        } else {
        status.text = @”Failed to add contact”;
        }
        sqlite3_finalize(statement);
        sqlite3_close(contactDB);
        }
        }

  18. 您好 我想問 要如何把fmdb的資料 .db檔 讀取出來 並儲存到NSMutableArray 裡

    並且每當資料有新增 或刪除 nsmutablearray也會跟著變動

    希望您可以幫幫忙

  19. 我剛好也有跟樓上一樣的問題
    想要把FMDB建好的table用uitableview顯示出來
    不過不知道要怎麼把資料先使用一個array存起來
    希望大大可以幫忙一下

    1. 請參考“取得資料“那個項目。
      SQL跟Objective-C是兩回事,所以只能自己手動把資料全部拉出來,再自己轉成你要用的物件(或者用NSDictionary)才存成array。
      希望資料更新時,array也跟著變動,那也是自己手動。我想不到更好的辦法了。有的話還請高手指教。

      真的嫌麻煩,Core Data是你的好朋友。(不過好像更麻煩 XD)

  20. 不知道您可不可以給個例子

    我不知道要怎麼在取得資料那裡 把我的值轉成nsdictionary

    希望您可以教一下 困擾很久了呢….

  21. 謝謝 我解決了~

    不過又衍生出一個問題

    當我要更改資料時

    我要怎麼更改

    像我現在有10比資料的話

    我可能要改第5筆 那我要如何改呢?

  22. 您好,我转载了您的文章,因为国内要翻墙才能看到,blog中已经帖出了原文地址,地址在http://www.sudobeta.com/mac_os%20/200

  23. 請問一下我想要把從sqlite取出的值存成陣列該如何做呢@@?要怎麼從 NSResultset抓出的直宣告過去呢??

    1. 沒有什麼簡單的方法,只能一個個拉出來塞進去。
      while ([rs next]) {

      NSString *name = [rs stringForColumn:@”Name”];

      int age = [rs intForColumn:@”Age”];

      // 在這裡存進NSMutableArray裡
      }

      1. 謝謝版主喔!!!我找到方法了

        跟上面應該差不多 我是用addobject一個一個加進去!!

      2. 附帶提問一下版主alertview如果加入textfield

        在透過另外一個函式驗證button (Switch這裡)
        因為兩個是不同的區域

        如何將textfield的值傳到button的函式裡頭呢???
        不知道版主有沒有涉獵這個部分@@!!

      3. 不好意思,有點不太懂你的意思,可否詳細說明?

Leave a reply to none1982 Cancel reply