Batch insert values to a specific column in sqlite3 on iOS - ios

I have a table in my database. I want to add values to a specific column in the table. Since there are like thousands plus rows in my table i figured i have to use batch update. The code is this but i get syntax error and a message saying there is no such row:
NSString *addProductColQuery = #"ALTER TABLE Sale ADD COLUMN Product text";
[self.dbManager loadDataFromDB:addProductColQuery];
NSString *batchstart = #"BEGIN IMMEDIATE TRANSACTION";
[self.dbManager loadDataFromDB:batchstart];
for (int i = 0; i<self.productInfo.count; i++)
{
NSString *addValue = [NSString stringWithFormat:#"INSERT INTO Sale (Product) VALUES (%#)",[tempProductArray objectAtIndex:i]];
[self.dbManager loadDataFromDB:addValue];
}
NSString *batchComit = #"COMMIT TRANSACTION";
[self.dbManager loadDataFromDB:batchComit];
Update:
I have managed to get the above code to work but now i am getting 90% CPU Usage on iphone6! I actually saw this coming since im using a for loop which is dead wrong to loop a query. Is there a way to batch insert the values to the rows in a specific column?

Are the values in tempProductArray text values? If so, you have to quote them in your SQL:
NSString *addValue = [NSString stringWithFormat:#"INSERT INTO Sale (Product) VALUES ('%#')", tempProductArray[i]];
Having shown you a quick solution, that's actually the wrong way to do it. It is inadvisable to use stringWithFormat to build SQL with text values. If any of those strings themselves have apostrophe characters in them, the SQL will fail. Also, if any of this data was provided by the end-user or from the network, you're exposed to SQL injection attacks.
The correct solution is to use sqlite3_prepare_v2 of the SQL without quotes, but with ? placeholder:
NSString *addValue = #"INSERT INTO Sale (Product) VALUES (?)";
And then, before calling sqlite3_step, you would call sqlite3_bind_text to bind a text value to the ? placeholder.
If your dbManager doesn't offer that capability, you'll have to add it. Or use a library, like FMDB, that offers this capability out of the box.

Related

SQLite and Objective C - WHERE LIKE Statement Error

I am trying to write a query that involves a WHERE LIKE statement, however I'm running into the following issue....
When I write the query like:
"SELECT * FROM db WHERE name LIKE \"%%#%%\""
It gets interpreted as:
SELECT * FROM db WHERE name LIKE "(null)"
When I query the following:
"SELECT * FROM db WHERE name LIKE \"%%%#%%\""
It gets interpreted as:
SELECT * FROM db WHERE name LIKE "0x0p+0pple"
This:
[#"SELECT * FROM db WHERE name LIKE \"%" stringByAppendingString:name] stringByAppendingString:#"%\""]
Is interpreted as:
SELECT * FROM db WHERE name LIKE "0x0p+0pple"
Is there a way to correct or work around this?
If you want to use a string format, it needs to be:
NSString *query = [NSString stringWithFormat:#"SELECT * FROM db WHERE name LIKE '%%%#%%'", someValue];
The first two %% become a single %. The %# gets replaced with whatever string is in someValue. And the final two %% become a single %.
query ends up being:
SELECT * FROM db WHERE db LIKE '%apple%'
assuming someValue is the string #"apple".
On a side note, it's best to avoid using SELECT * FROM if you are using the various sqlite3_column_xxx functions and specific index numbers because you can't be sure the order of the returned columns matches your code. It's safer and clearer to explicitly list the columns you want from the query.
Doi!
This:
[#"SELECT * FROM db WHERE name LIKE \"%%" stringByAppendingString:name] stringByAppendingString:#"%%\""]
Is interpreted as:
SELECT * FROM db WHERE db LIKE "%apple%"
Which is ACTUALLY what I'm looking for (hadn't realize that at first), this work around just feels a bit... unnecessarily lengthy.
Try this one.Its gonna work.
NSString *yourSqlQuery = [NSString stringWithFormat:#"Select * from yourTableName where name LIKE '%%%#%%'",name];

NSString localizedCompare: inconsistent results given longer strings

We're trying to use an NSFetchedResultsController to return people names and populate a UITableView in sorted order, using localizedCompare:. We're also trying to provide a section index in the UI (the right column of first characters of each section). We provide the NSFetchedResultsController with a selector on our entity which provides the section each entity should belong to (specifically, the first character of the person's name, capitalized).
When dealing with people names which utilize Unicode code points we've run into an issue. NSFetchedResultsController complains the entities are not sorted by section.
Specifically:
reason=The fetched object at index 103 has an out of order section name 'Ø. Objects must be sorted by section name'}, {
reason = "The fetched object at index 103 has an out of order section name '\U00d8. Objects must be sorted by section name'";
The issue appears to be that the comparison value returned by localizedCompare: is different for the whole "word" versus the leading character.
The following tests pass though I would expect consistent comparison results between ("Ø" and "O") vs. ("Østerhus" and "Osypowicz").
- (void)testLocalizedSortOrder300
{
NSString *str1 = #"Osowski";
NSString *str2 = #"Østerhus";
NSString *str3 = #"Osypowicz";
NSString *letter1 = #"O";
NSString *letter2 = #"Ø";
//localizedCompare:
//"Osowski" < "Østerhus"
NSComparisonResult res = [str1 localizedCompare:str2];
XCTAssertTrue(res == NSOrderedAscending, #"(localizedCompare:) Expected '%#' and '%#' to be NSOrderedAscending, but got %#", str1, str2, res == NSOrderedSame ? #"NSOrderedSame" : #"NSOrderedDescending");
//"Østerhus" < "Osypowicz"
res = [str2 localizedCompare:str3];
XCTAssertTrue(res == NSOrderedAscending, #"(localizedCompare:) Expected '%#' and '%#' to be NSOrderedAscending, but got %#", str2, str3, res == NSOrderedSame ? #"NSOrderedSame" : #"NSOrderedDescending");
//"O" < "Ø"
res = [letter1 localizedCompare:letter2];
XCTAssertTrue(res == NSOrderedAscending, #"(localizedCompare:) Expected '%#' and '%#' to be NSOrderedAscending, but got %#", letter1, letter2, res == NSOrderedSame ? #"NSOrderedSame" : #"NSOrderedDescending");
}
So, the question ultimately is, given a person name (or any other string) which utilize Unicode code points, how do we properly (in a localized manner) return a section name which will correspond with the sort order as dictated by localizedCompare:?
Additionally, what's going on with the localizedCompare: apparently treating "Ø" and "O" as NSOrderedSame when followed by additional characters?
I expect localizedCompare: is using a specific combination of NSStringCompareOptions flags that are causing this behavior.
https://developer.apple.com/documentation/foundation/nsstringcompareoptions?preferredLanguage=occ
You might get the outcome you want by using compare:options: and turning on NSDiacriticInsensitiveSearch.
For generating the section index, it might be best to strip the value of all extended characters first, and then take the first letter. Something like:
[[str1 stringByFoldingWithOptions:NSCaseInsensitiveSearch | NSDiacriticInsensitiveSearch] substringToIndex:1]
That way a name starting with an accented letter such as "Édward" will get converted to "Edward" before you take the first letter for the section.
Yeah, been there. The only solution I found was to create a second field for search that simplifies the characters (don't remember off hand the method) and store it as a second field which is used for search. Not super elegant but it worked.
Ultimately the approach which solved this was to store normalized section names in the database.
#MartinR suggested SO post lead me to https://stackoverflow.com/a/13292767/397210 which talks about this approach and was the key "ah ha" moment to solve it.
While this does not explain the goofy behavior of localizedCompare: apparently treating "Ø" and "O" as NSOrderedSame when followed by additional characters it is, IMHO, a more robust and complete solution which works for all Unicode code points, in our testing.
Specifically, the approach is:
Create (or utilize an existing) field on your entity to receive a normalized section name for the entity (let's call it sectionName).
Populate this field (sectionName) with the normalized section name*, initially, and as needed (when the person name changes, for instance).
Use this section name field (sectionName) for the sectionNameKeyPath of NSFetchedResultsController -initWithFetchRequest:managedObjectContext:sectionNameKeyPath:cacheName:
For the sort descriptors used by the fetch request passed to the NSFetchedResultsController be sure to sort first by section name then by how to sort the contents of the section (person name, for instance), paying attention to the use of the localized version of the comparison selectors. e.g.:
[NSSortDescriptor sortDescriptorWithKey:#"sectionName" ascending:YES selector:#selector(localizedStandardCompare:)],
[NSSortDescriptor sortDescriptorWithKey:#"personName" ascending:YES selector:#selector(localizedCaseInsensitiveCompare:)]
Test.
*Normalized Section Name
We need to be careful about assuming what the first "character" is when dealing with unicode. "Characters" may be composed of more than one character.
See https://www.objc.io/issues/9-strings/unicode/
and also Compare arabic strings with special characters ios
This is the direction I used to generate a normalized section name:
NSString *decomposedString = name.decomposedStringWithCanonicalMapping;
NSRange firstCharRange = [decomposedString rangeOfComposedCharacterSequenceAtIndex:0];
NSString *firstChar = [decomposedString substringWithRange:firstCharRange];
retVal = [firstChar localizedUppercaseString];
Hopefully this approach is clear and useful to others, and thanks for the assist, all.

FMDB Database Query IOS FIND_IN_SET()

I am using FMDB database in IOS. In my database I have a one column called tags.
Tags column have a comma separated Value like apple,banana,. I want Title column where my keyword matches. I want to know how to search comma separated value in table column Please help.
You need to use LIKE operator, when you have a field data like apple,banana,... you can add a , at the beginning and end of it like ,apple,banana,..., that makes all tags between two commas. Now you can check it if it contains ,yourTag,.
I'm not so familiar with FMDB, But I think below code can help you:
FMResultSet *rs = [db executeQuery:#"SELECT Title FROM yourTable WHERE ','+tags+',' LIKE ?",
[NSString stringWithFormat:#"%%%#%%", #"," + search_text + #","]];

Line breaks using FMDB and Obj-C

I'm relatively new to iOS programming, so please bear with me.
I'm creating an app that calls recipes from a table view, then listing the measurements and ingredients as labels in the detail view. I catalogued my recipes in Google Sheets, downloaded it as a .csv file and populated a table using SQLiteStudio. I then exported the database, and using FMDB, plopped it in my app. Everything works fine there and I'm able to retrieve various fields.
What I'm trying to do is list out the measurements and ingredients so that it displays with line breaks:
0.5 oz.
1.0 oz.
1 jigger
And not as: 0.5 oz., 1.0 oz., 1 jigger which is how I wrote it into the Google doc.
I've tried using \n in the SQLite viewer but it outputs it as a String rather than encoding it as a new line. However, when I view the accompanying .sql file, TextMate2 reads it as new lines. I'm not sure if I have to apply some code within my tableviewcontroller implementation file where I call the FMDB or elsewhere.
I have been looking for a solution for awhile now but no luck. Any help in steering me in the right direction would be greatly appreciated. Thanks!
CocktailListTableViewController.m
- (NSMutableArray *)recipeCocktails {
recipeCocktails = [[NSMutableArray alloc] init];
NSString *databasePath = [(AppDelegate *) [[UIApplication sharedApplication] delegate] databasePath];
FMDatabase *fmDB = [FMDatabase databaseWithPath:databasePath];
if(![fmDB open])
{
NSLog(#"Could not open DB, try again");
return nil;
}
FMResultSet *results = nil;
results = [fmDB executeQuery:#"SELECT * FROM recipesCocktails"];
NSLog(#"result %# ",results);
if ([fmDB hadError]) {
NSLog(#"DB Error %d: %#", [fmDB lastErrorCode], [fmDB lastErrorMessage]);
}
while ([results next]) {
Cocktails *cocktails = [[Cocktails alloc] init];
cocktails.recipeName = [results stringForColumn:#"recipeName"];
cocktails.recipeMeasure = [results stringForColumn:#"recipeMeasure"];
cocktails.recipeIngred = [results stringForColumn:#"recipeIngred"];
cocktails.recipeGlass = [results stringForColumn:#"recipeGlass"];
cocktails.recipeShaker = [results stringForColumn:#"recipeShaker"];
cocktails.recipeDirections = [results stringForColumn:#"recipeDirections"];
[recipeCocktails addObject:cocktails];
}
[fmDB close];
return recipeCocktails;
CocktailsDetailTableViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.title = cocktails.recipeName;
self.recipeIngred.text = cocktails.recipeIngred;
self.recipeMeasure.text = cocktails.recipeMeasure;
I can see you're trying to store all the ingredients as one long, pre-built string, and I think that approach is not the best.
It's gonna work, but it's not future-friendly or simply... usual.
I strongly suggest having a new table that knows all the possible ingredients, with ID's and other informations of your will (color, average weight, picture* and whatnot)
( please note that it's not advised to store images in a SQL database and you'd rather store references to where you saved them as files or URL's)*
Once you have that table, you're golden. You simply reference an array of ingredients in your recipe table. For example [1,27,133], and with that you can do what you want. You have an array of ingredient objects, which you can treat how you see fit.
I would, for example, loop over it and create labels under each other and you'd have your lines.
I could also simply use an NSMutableString and fill it with the ingredients.name (again, using examples)
You could fill a UITextView with newlines between each ingredient (by using the NSMutableString and adding newlines between each object name).
And more importantly, you can modify everything without having to re-work your layouts or labels or re-write your whole database because you have static newlines ingredients for each recipe.
That is the most important point I wanted to make. With that approach you can remove any ingredient from any recipe without having to think really far or risk forgetting something. You could also permanently destroy any ingredient to every recipe by removing it from the ingredient table. Same for adding new ones. Or saying you want to change the name of ingredient X, you dont' have to do it in every recipe, and so on.
EDIT: Answering your question in comment
To load the recipes in the tableview and the ingredients on the detail page, you can do it like so :
You load all your recipes in an array and display that (I think you're successful already on that part).
Note that all the recipes have a property called "Ingredients" which is an array of ingredient keys/ids/something
On cell tap, you want to load the ingredients that you only know the ID of, you simply get them from the array of Id's that you stored in your recipe, and SELECT them from your db.
Globally, you have no idea what the ingredients are until the user taps : if you don't need to display them in the table view, then you don't need to laod them yet. But you already know their id (otherwise you wouldn't be able to find them!).
There are two approaches to this problem.
One approach is to store the list of ingredients in as a single text field, with new line characters inside the string. SQLite (and FMDB) handle this fine.
From the UI perspective, you would then show this text field in a multiline UITextField or a UILabel. If you use UILabel, make sure the number of lines is set to something other than 1 (i.e. either zero or the actual number of lines).
The trick here is now to get this newline string into the database. And it depends upon how you're doing this. But if you're using some SQLite tool, you cannot use \n sequence, as that's not acceptable SQL syntax. You can use x'0a' or char(10):
insert into foo (bar) values ('baz' || x'0a' || 'qux');
or:
insert into foo (bar) values ('baz' || char(10) || 'qux');
or, if using SQLite command line tool, you can just insert newline:
insert into foo (bar) values ('baz
qux');
But you cannot use \n sequence. You can do this from your Objective-C code, but not from within a third party SQLite tool.
The other approach is to store the list of recipes and the list of ingredients for each recipe in two different tables (e.g. recipes and ingredients). Then you could store each of the ingredients in a separate row in the ingredients table.
This strikes me as a more logical implementation. If you did this, you would then have the choice as to how you wanted to show the list of ingredients (separate UILabel controls, a UITableView, or, if you really wanted to, concatenate them together, separated with newline characters and show them in a single multiline control.

ios sqlite connect to database and retrieve value

I have an SQLite database which contains three columns: ID, Name, Dept. I have one main ViewController in which I have three fields: enter mobileNumber (a textField), Name (a label), and Dept (a label). When i enter first 4 digit of any number in the textfield, the two labels automatically show the relevant value from the database. i am getting output fine when i give a default value in database. what i need is without giving any default value in database i want to run my app directly when i enter any number it should show the value of Name and Dept to the label. My database contains more than 3000 records.
i am using this query to get data from database:
sqlStatement = [[NSString stringWithFormat:#"SELECT _id,Name,Dept FROM Codes
WHERE _id=\'%#\'",idString.text]UTF8String];
Use LIKE Clause which will return relative outputs.
sqlStatement = [[NSString stringWithFormat:#"SELECT _id,Name,Dept FROM Codes WHERE _id like '%%#'",idString.text]UTF8String];
It will return you matching results as per your id.
For more clarity on LIKE Clause please check this Link.
I Assume
You are running a query with the first 4 digits
In case of more than a single match to the database, you are selecting one of the rows returned by the query
Things to try to debug this:
Debug the query: Programmatically, write a query using 4 digits of a number that returns only one row. Check that the query returns what you expected according to your data source.
Debug displaying information: Hardcode that query and show that you can display the Name and Dept. information.

Resources