Load large number of data from sqlite db - ios

I have more than 10000+ record in database. I want to load it on a table view. It will take 3 to 4 seconds to fetch all data from database and than load it on table view. Is there is more efficient way to increase the response and load the data in a uitableview?
Here is my code for get All Data fro database
- (void)getAllData {
NSString * convertInttoStr = [NSString stringWithFormat:#"%d", rowNumber];
// Getting the database path.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docsPath = [paths objectAtIndex:0];
NSString *dbPath = [docsPath stringByAppendingPathComponent:#"iandroidquran_database 3.sqlite"];
FMDatabase *database = [FMDatabase databaseWithPath:dbPath];
[database open];
NSString *sqlSelectQuery = [NSString stringWithFormat:
#"SELECT * FROM qSurahContent WHERE surahID=%#" ,
convertInttoStr];
// Query result
FMResultSet *resultsWithNameLocation = [database executeQuery:sqlSelectQuery];
while([resultsWithNameLocation next]) {
NSString *strID = [NSString stringWithFormat:#"%d",[resultsWithNameLocation intForColumn:#"surahID"]];
NSString *strName = [NSString stringWithFormat:#"%#",[resultsWithNameLocation stringForColumn:#"surahContentArabic"]];
NSLog(#"surahID = %#, surahName = %#",strID, strName);
[surahId addObject:strID];
[surahContentArabic addObject:strName];
}
[self.tblView reloadData];
[database close];
}
Any solution?? Thanks

Try to fetch Data from DB in main Thread as following :
dispatch_async(dispatch_get_main_queue(), ^{
[self getAllData];
});
This main thread has high priority and will execute faster than ever. So try this one.
Hope it helps..

Related

Insert Query not working (FMDB)

Here is the code which I'm using to insert a record into a sqlite table
FMDatabase *dataBase = [self openDatabase];
[dataBase open];
if ([dataBase open] != YES) {
NSLog(#"DB Error %d: %#", [dataBase lastErrorCode], [dataBase lastErrorMessage]);
//VERY IMPORTANT
}
BOOL success= [dataBase executeUpdate:#"Insert into CrewUnits (pkCrewUnits,fkUnits,fkAgencyVehicles,fkLookupCodes_PrimaryRole, fkLookupCodes_LevelOfCare, PrimaryUnit) values (?, ?, ?, ?, ?, ?);", pkCrewUnits, fkUnits, fkAgencyVehicle, fkLookupCodes_PrimaryRole, fkLookupCodes_LevelOfCare, [NSNumber numberWithInt:1]];
NSLog(success ?#"YES" :#"NO");
NSLog(#"Error %d: %#", [dataBase lastErrorCode], [dataBase lastErrorMessage]);
FMResultSet*resultSet= [dataBase executeQuery:#"select * from CrewUnits"];
NSLog(#"ResultSet : %#", [resultSet resultDictionary]);
[dataBase close];
Database Path
- (FMDatabase *)openDatabase
{
NSLog(#"Open Database");
NSString *documents_dir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *db_path = [documents_dir stringByAppendingPathComponent:[NSString stringWithFormat:#"HPSix_05BD.db"]]; // DatabasePath
FMDatabase *db = [FMDatabase databaseWithPath:db_path];
if (![db open])
NSLog(#"Failed to open database!!!!!");
return db;
}
I'm using the same logic to fetch data from table that works fine for me. But I can't insert a record. I don't know what I'm doing wrong here.
Two problems:
You are calling open three times. Once in openDatabase and twice in the code that called it.
So, have your open routine open the database if it can, but return nil if it can't:
- (FMDatabase *)openDatabase
{
NSLog(#"Open Database");
NSString *documents_dir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *db_path = [documents_dir stringByAppendingPathComponent:[NSString stringWithFormat:#"HPSix_05BD.db"]]; // DatabasePath
FMDatabase *db = [FMDatabase databaseWithPath:db_path];
if (![db open]) {
NSLog(#"Failed to open database!!!!!");
return nil;
}
return db;
}
Then check that result:
FMDatabase *dataBase = [self openDatabase];
if (!dataBase) {
// quit; note, no point in checking error message as it only returns meaningful messages if you have an open database
}
After calling executeQuery, you have to call next.
FMResultSet *resultSet = [dataBase executeQuery:#"select * from CrewUnits"];
if ([resultSet next]) {
NSLog(#"ResultSet : %#", [resultSet resultDictionary]);
} else {
NSLog(#"No data found");
}
I think I see the problem. You are committing after running the SELECT query.
You have to commit right after the UPDATE query. When you do it like you have now when selecting, the UPDATE query has not been committed and thus will not be returned.
[dataBase beginTransaction];
BOOL success= [dataBase executeUpdate:#"Insert into CrewUnits (pkCrewUnits,fkUnits,fkAgencyVehicles,fkLookupCodes_PrimaryRole, fkLookupCodes_LevelOfCare, PrimaryUnit) values (?, ?, ?, ?, ?, ?);", pkCrewUnits, fkUnits, fkAgencyVehicle, fkLookupCodes_PrimaryRole, fkLookupCodes_LevelOfCare, [NSNumber numberWithInt:1]];
[dataBase commit]; // commit the query
NSLog(success ?#"YES" :#"NO");
NSLog(#"Error %d: %#", [dataBase lastErrorCode], [dataBase lastErrorMessage]);
// now at this point the transaction has been committed and can be selected
FMResultSet*resultSet= [dataBase executeQuery:#"select * from CrewUnits"];
NSLog(#"ResultSet : %#", [resultSet resultDictionary]);
Also according to FMDB docs:
The parameters must start with a colon. SQLite itself supports other
characters, but internally the Dictionary keys are prefixed with a
colon, do not include the colon in your dictionary keys.
Thus this line of code must be rewritten to:
BOOL success= [dataBase executeUpdate:#"Insert into CrewUnits (pkCrewUnits,fkUnits,fkAgencyVehicles,fkLookupCodes_PrimaryRole, fkLookupCodes_LevelOfCare, PrimaryUnit) values (:pkCrewUnits,:fkUnits,:fkAgencyVehicles,:fkLookupCodes_PrimaryRole, :fkLookupCodes_LevelOfCare, :PrimaryUnit);", pkCrewUnits, fkUnits, fkAgencyVehicle, fkLookupCodes_PrimaryRole, fkLookupCodes_LevelOfCare, [NSNumber numberWithInt:1]];
Lastly according to the docs you use next for looping through the results:
while ([resultSet next]) {
//retrieve values for each record
}

Warning: I could not find the column named 'rowid'

I use FMDB in my iOS project. But when I read ROWID with FMDB,the Xcode log "Warning: I could not find the column named 'rowid'."
...
//Create database
NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *dbPath = [documentsPath stringByAppendingPathComponent:#"bookmarksDatabase.sqlite"];
BOOL needCreateTable = ![[NSFileManager defaultManager] fileExistsAtPath:dbPath];
FMDatabase *db = [FMDatabase databaseWithPath:dbPath];
[db open];
if (needCreateTable) {
[db executeUpdate:#"CREATE TABLE bookmarks (title TEXT ,url TEXT ,folderID INTEGER ,locationIndex INTEGER)"];
}
else
{
[self reloadBookmarkDatabase:db];
}
[db close];
...
//Read database. Only "ROWID" column can't find.
FMResultSet *results = [db executeQuery:#"SELECT * FROM bookmarks ORDER BY locationIndex"];
while([results next]) {
BookmarkData *temp = [[BookmarkData alloc] initWithID:[results intForColumn:#"ROWID"] title:[results stringForColumn:#"title"] url:[results stringForColumn:#"url"] folderID:[results intForColumn:#"folderID"] locationIndex:[results intForColumn:#"locationIndex"]];
[self.bookmarkArray addObject:temp];
NSLog(#"bookmark id:%d, title:%#, url:%#, folderID:%d, locationIndex:%d",temp.ID,temp.title,temp.url,temp.folderID,temp.locationIndex);
}
SELECT * returns only the columns from the table definition.
If you want to get the rowid column, you have to list it explicitly:
SELECT *, rowid FROM ...
And if you actually need to use this column, it would be a better idea to make it part of the table definition:
CREATE TABLE bookmarks (
ID INTEGER PRIMARY KEY, -- same as rowid
title TEXT,
...
)

How to check if a record exist in sqlite

Hi in Application i have already inserted some data into my sqlite database now i want to check the particular record is already inserted or not in sqlite. I'm working in UITableView. I want to check using my product id it is a unique id. Id which comes from my server. I want check the particular id is exit in my sqlite database. I have stored my id in NSMutableArray.
My code to store the arrayid into a string in my UITableViewcell.
for (int i=0; i<[menuarray2 count]; i++) {
rowvalue =[menuarray2 objectAtIndex:i];
NSLog(#"rowid%#",rowvalue);
}
I have passed the string to my sqlite query its showing null.
-(void)checkdata:(NSString*)query{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *databasePath = [documentsDirectory stringByAppendingPathComponent:#"bp.sqlite"];
if(sqlite3_open([databasePath UTF8String],&_myDataBase) == SQLITE_OK)
{
NSString *querySQL = [NSString stringWithFormat: #"%#",query];
char *errmsg=nil;
if(sqlite3_exec(_myDataBase, [querySQL UTF8String], NULL, NULL, &errmsg)==SQLITE_OK)
{
NSLog(#".. id ..");
}
}
sqlite3_close(_myDataBase);
}
-(void)rowcount{
NSLog(#"rowdata%#",rowvalue);
NSString *sql = [NSString stringWithFormat:#"SELECT * from br where PID='%#'",rowvalue];
NSLog(#"%#",sql);
[self checkdata:sql];
}
I'm using above to check but its showing null in console which print the query like
SELECT * from br where PID='(null)'
Please tell me how to resolve this issue i have stuck here for long time please help me out.
Thanks..

Fetch Image From Sqllite Database in iphone

I Want to save the image in Database and fetch the image in database.i am convert the image in NSData and the image is store successfully in database but when i fetch that image from database then it crash.it give this warning
Warning :- -[__NSCFString bytes]: unrecognized selector sent to instance 0x7916000
Below code For save the image in Database
NSData *dataImage1 = UIImagePNGRepresentation(image3.image);
NSString *str = [NSString stringWithFormat:#"Insert into Gallery(ImageName) VALUES ('%#')",dataImage1];
[Database executeQuery:str];
//Database.m
+(NSString* )getDatabasePath{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *writableDBPath = [documentsDirectory stringByAppendingPathComponent:#"Database"];
return writableDBPath;
}
+(NSMutableArray *)executeQuery:(NSString*)str{
sqlite3_stmt *statement= nil;
sqlite3 *database;
NSString *strPath = [self getDatabasePath];
NSMutableArray *allDataArray = [[NSMutableArray alloc] init];
if (sqlite3_open([strPath UTF8String],&database) == SQLITE_OK) {
if (sqlite3_prepare_v2(database, [str UTF8String], -1, &statement, NULL) == SQLITE_OK) {
while (sqlite3_step(statement) == SQLITE_ROW) {
NSInteger i = 0;
NSInteger iColumnCount = sqlite3_column_count(statement);
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
while (i< iColumnCount) {
NSString *str = [self encodedString:(const unsigned char*)sqlite3_column_text(statement, i)];
NSString *strFieldName = [self encodedString:(const unsigned char*)sqlite3_column_name(statement, i)];
[dict setObject:str forKey:strFieldName];
i++;
}
[allDataArray addObject:dict];
[dict release];
}
}
sqlite3_finalize(statement);
}
sqlite3_close(database);
return allDataArray;
}
//Fetch image
NSString *str =[NSString stringWithFormat:#"Select * from Gallery where id = 1"];
NSMutableArray *arra =[Database executeQuery:str];
NSData *d =[[arra objectAtIndex:0] valueForKey:#"ImageName"];
UIImage *img = [[UIImage alloc] initWithData:d];
NSLog(#"%#",img);
Thanks in Advance
Your problem is that you cannot insert binary data by building a SQL statement with a stringWithFormat. You're inserting the NSData (binary data) into a field called ImageName, and you cannot use stringWithFormat to build that SQL statement.
Did you really mean to insert the image itself? In that case you'd use SQL like below, with a ? placeholder, and then use sqlite3_bind_blob for column number 1 with your NSData before you call sqlite3_step. Thus, the SQL would look like:
Insert into Gallery(ImageName) VALUES (?)
(As an aside, I'm not a fan of the choice of ImageName for the actual data of the image. I'd either use Image for the actual image data, or ImageName for the name of the image.)
And then, after preparing your SQL, you'd then call:
sqlite3_bind_blob(statement, 1, [dataImage1 bytes], [dataImage1 length], SQLITE_TRANSIENT);
You can then invoke sqlite3_step, like normal, to execute the SQL to insert the data.
When retrieving the data, after you successfully sqlite3_step, you'd use the appropriate sqlite3_column_blob functions:
const void *bytes = sqlite3_column_blob(statement, 0);
int len = sqlite3_column_bytes(statement, 0);
NSData *data = [NSData dataWithBytes:bytes length:len];
UIImage *image = [UIImage imageWithData:data];
Having said that, SQLite is notoriously inefficient in storing large blob data. You might want to store the image in the Documents folder and then just store the filename in your ImageName field.
i think you are getting bytes in string type in
[[arra objectAtIndex:0] valueForKey:#"ImageName"];
so first get string and then convert it to data.
try like this:
NSData* data = [[[arra objectAtIndex:0] valueForKey:#"ImageName"] dataUsingEncoding:NSUTF8StringEncoding];

fmdb path and using db 2nd time from another viewController

I am using the code below to create db in one class and using it in the viewController and same code again in another class in newViewController .
Is it correct ? Or does it create new db in its place or uses if there is an existing db ?
or Do I need to use only the path ?
When I tried extern NSString *path in the example below I received Chinese letters and could not open it.
Basically in the 2nd contoller I am only reading the data.
So I am trying to access db via path,database with path and database open.
How do it if I have used the code below to create it and I would like to open it 2nd time from another viewcontoller.
Please help.
Thanks in Advance.
In 1st Contoller:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docsPath = [paths objectAtIndex:0];
NSString *path = [docsPath stringByAppendingPathComponent:#"test.db"];
FMDatabase *database;
database = [FMDatabase databaseWithPath:path];
[database open];
[database executeUpdate:#"DROP TABLE IF EXISTS **"];
[database executeUpdate:#"CREATE TABLE "];
//Select query for single row
FMResultSet *s = [database executeQuery:#"SELECT COUNT(*) FROM table"];
if ([s next]) {
int Count = [s intForColumnIndex:0];
}
//DO Something
[database close];
Above code is working perfectly in viewcontroller 1 .
Things I tried in view controller 2 .And it still did not work.
extern NSString *path;
FMDatabase *database;
database = [FMDatabase databaseWithPath:path];
[database open];
I get an error here since the path is in What looks like Chinese language.
Error is EXC_BAD_ACCESS code .
Also tried global database by :
extern FMDatabase *database;
and using it as
[database open];
I still get the same error.
Finally it worked with the following code in the 2nd controller.
I just wanted to see if thats the right way to do it or can I use something as db open .
But I tried that and its not working.Also I am not using any error handler as I dont know how to do it in FMDB .
Thanks in Advance.
Please let me know if the following implementation is correct ? :
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docsPath = [paths objectAtIndex:0];
NSString *newpath = [docsPath stringByAppendingPathComponent:#"test.db"];
FMDatabase *database2 ;
database2 = [FMDatabase databaseWithPath:newpath];
[database2 open];
//DO Something
[database2 close];
As an example of how you should be checking for the success of the FMDB methods, here is an example:
#import "FMDatabase.h"
#import "FMDatabaseAdditions.h"
- (void)performDatabaseDemonstration
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docsPath = [paths objectAtIndex:0];
NSString *path = [docsPath stringByAppendingPathComponent:#"test.db"];
// open db
FMDatabase *database = [FMDatabase databaseWithPath:path];
FMResultSet *rs;
if ([database open])
{
// drop table
if (![database executeUpdate:#"DROP TABLE IF EXISTS test"])
NSLog(#"%s: drop error: %#", __FUNCTION__, [database lastErrorMessage]);
// create table
if (![database executeUpdate:#"CREATE TABLE test(col1 INTEGER, col2 TEXT)"])
NSLog(#"%s: create error: %#", __FUNCTION__, [database lastErrorMessage]);
// insert a row of data
if (![database executeUpdate:#"INSERT INTO test VALUES(1, 'Rob')"])
NSLog(#"%s: insert 1 error: %#", __FUNCTION__, [database lastErrorMessage]);
// insert another row of data
if (![database executeUpdate:#"INSERT INTO test VALUES(2, 'Rachel')"])
NSLog(#"%s: insert 2 error: %#", __FUNCTION__, [database lastErrorMessage]);
// Select query for single row
if (!(rs = [database executeQuery:#"SELECT count(*) FROM test"]))
NSLog(#"%s: select error: %#", __FUNCTION__, [database lastErrorMessage]);
if ([rs next]) {
int count = [rs intForColumnIndex:0];
NSLog(#"%s: count = %d", __FUNCTION__, count);
}
[rs close];
// if getting single value from single row, you can use FMDatabaseAdditions.h methods:
NSInteger count2 = [database intForQuery:#"SELECT col1, col2 FROM test"];
NSLog(#"%s: count2 = %d", __FUNCTION__, count2);
// let's actually retrieve data
if (!(rs = [database executeQuery:#"SELECT col1, col2 FROM test"]))
NSLog(#"%s: select error: %#", __FUNCTION__, [database lastErrorMessage]);
while ([rs next]) {
NSLog(#"%s: col1 = %#; col2 = %#", __FUNCTION__, [rs objectForColumnIndex:0], [rs objectForColumnIndex:1]);
}
[rs close];
//close
[database close];
}
else
{
NSLog(#"%s: open error: %#", __FUNCTION__, [database lastErrorMessage]);
}
}
In terms of how to open a database from different controllers there are a ton of options. But I might suggest that above and beyond just retrieving some shared path/filename variable from two controllers, you might want to actually encapsulate not only the filename, but shared FMDB code, too, in an object.
Personally, whenever I find myself writing the same or similar code in two different controllers, I ask myself whether I want to abstract that code out into its own class. In this case, for example, rather than debating how these two classes access the database filename, I would create my own own model object (a NSObject subclass), that encapsulates your SQL, does the opening of the database, etc. Then I could have my two view controllers invoke the methods from that method object, and really minimize not only redundancy in the filenames, but in the code itself, too. For example, the creating of the database path, opening the database, checking the success of the open, etc., are identical. So why not have one method in some model object, that does all of that for you.
To make this model object accessible from both controllers, you have a bunch of options:
Make it a property of your first view controller, and have it pass it to your second controller during prepareForSegue;
Create a singleton object for it; or
Make that a property of your app delegate, and both of your controllers can get it from there.
You should put the database path defined as global variables, the first time to use the closing operation, the second use of time to open, open the database, it will automatically judge whether there are database, if there is no will automatically create.If present, it will directly open the. Used you to judge whether the database open success:
if (! [db open]) {
/ / error
return;
}
/ / some operation
/ /...
[db close];

Resources