How to remove all data from table using FMDB - iphone

I want to delete all data from table in my database. I am using FMDB.And i have used this code but it will not delete data from my table.
-(BOOL) deleteAll
{
FMDatabase *db = [FMDatabase databaseWithPath:[Utility getDatabasePath]];
[db open];
BOOL success = [db executeUpdate:#"TRUNCATE TABLE customers"];
[db close];
return success;
return YES;
}

Try to use this code.
BOOL success = [db executeUpdate:#"DELETE FROM customers"];
As long as i know Sqlite does not support TRUNCATE query.

Although DELETE command will work it is slow because it selects each row and than proceeds to delete it.
If you are deleting the whole table it is better to DROP the table and than recreate it:
BOOL result = [db executeUpdate:#"DROP TABLE IF EXISTS `customers`;"];
BOOL resultTwo = [db executeUpdate:#"CREATE TABLE IF NOT EXISTS customers(name text primary key, age int)"]; //or course fields in your table will be different
Swift (for completeness sakes):
let dropTable = "DROP TABLE customers"
let result = contactDB.executeUpdate(dropTable, withArgumentsInArray: nil)
if !result {
print("Error: \(contactDB.lastErrorMessage())")
}
let createTable = "CREATE TABLE IF NOT EXISTS customers(name text primary key, age int)"
if !contactDB.executeStatements(createTable) {
print("Error: \(contactDB.lastErrorMessage())")
}
reference: truncate SQLite

Related

Swift with FMDB DELETE ignoring foreign key constraints. XCode 12.5

I have been trying to solve this problem for some time now. I'm writing an application using Swift/SwiftUI and FMDB over SQlite3. When a delete is executed on a table where there are existing foreign keys I expect the delete to fail. It doesn't and there is no error thrown.
In XCode I'm linking with libsqlite3.tbd listed as date modified 03/16/2021. Inside the tbd file I find:
install-name: '/usr/lib/libsqlite3.dylib'
current-version: 321.3
compatibility-version: 9
I find the libsqlite3.dylib in:
/System/Volumes/Data/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/usr/lib
Info shows it was created 04/09/2021. No apparent version
What is happening is that I have foreign keys on a table named BudgetedExpense to a table named BudgetDescription.
BudgetDescription DDL
CREATE TABLE budget_description (
id INTEGER DEFAULT (0)
PRIMARY KEY AUTOINCREMENT
UNIQUE,
income_expense_code INTEGER (8) NOT NULL
DEFAULT (0),
description STRING NOT NULL
DEFAULT (0),
income_or_expense INTEGER NOT NULL
DEFAULT (0)
);
BudgetedExpense DDL
CREATE TABLE budgeted_expense (
expense_id INTEGER NOT NULL
DEFAULT (0)
PRIMARY KEY AUTOINCREMENT
UNIQUE,
account_code INTEGER (8) NOT NULL
DEFAULT (0),
expense_code INTEGER (8) NOT NULL
DEFAULT (0),
budget_year INTEGER (4) NOT NULL
DEFAULT (0),
budget_month INTEGER (2) NOT NULL
DEFAULT (0),
expense_budget DECIMAL (12, 2) NOT NULL
DEFAULT (0),
expense_spent DECIMAL (12, 2) NOT NULL
DEFAULT (0),
unexpected_expense DECIMAL (12, 2) NOT NULL
DEFAULT (0),
category_code INTEGER (8) NOT NULL
DEFAULT (0),
hidden BOOLEAN NOT NULL
DEFAULT (0),
hidden_id INTEGER DEFAULT (0),
FOREIGN KEY (
account_code,
budget_year,
budget_month
)
REFERENCES budget (account_code,
budget_year,
budget_month),
FOREIGN KEY (
expense_code
)
REFERENCES budget_description (income_expense_code) ON DELETE RESTRICT,
FOREIGN KEY (
category_code
)
REFERENCES categories (category_code)
);
My FMDB code that executes the database updates is:
func updateDatabaseTable(query: String)-> Bool
{
var updateSuccessful: Bool = false
let pragmaQuery = "PRAGMA foreign_keys=ON;"
if openDatabase() {
do {
try database.executeUpdate(pragmaQuery, values: nil)
} catch {
}
queue.inTransaction() {
database, rollback in
do {
try database.executeUpdate(query, values: nil)
updateSuccessful = true
if showQuerys && updateSuccessful {
print("Update query is:\n \(query)")
}
}
catch {
rollback.initialize(to: true)
print(error.localizedDescription)
print(database.lastError())
print(database.lastErrorMessage())
print("UPDATE QUERY FAILED")
print(query)
updateSuccessful = false
}
}
_ = closeDatabase()
}
return updateSuccessful
}
The SQL that executes to delete a row in the BudgetDescription table is:
// Delete a specific record for a given income/expense code.
func deleteBudgetDescriptionRecordSQL(forIncomeExpenseCode incomeExpenseCode: Int) -> String
{
let query =
"""
DELETE FROM \(budget_description_table)
WHERE \(income_expense_code_field) = \(incomeExpenseCode)
"""
return query
}
The function I've written is:
// **** DELETE BUDGET DESCRIPTION RECORD FOR A GIVEN ACCOUNT CODE **** //
func deleteBudgetDescriptionRecord(forIncomeExpenseCode incomeExpenseCode: Int) -> Bool
{
var updateResult: Bool
updateResult = updateBudgetDescriptions(updateQuery: deleteBudgetDescriptionRecordSQL(forIncomeExpenseCode: incomeExpenseCode))
if showQuerys {
print("\nQUERY: deleteBudgetDescriptionRecordSQL(forIncomeExpenseCode: incomeExpenseCode)")
print(deleteBudgetDescriptionRecordSQL(forIncomeExpenseCode: incomeExpenseCode))
}
if updateResult == false {
print("deleteBudgetDescriptionRecordSQL returned FALSE when executing QUERY:")
print(deleteBudgetDescriptionRecordSQL(forIncomeExpenseCode: incomeExpenseCode))
return updateResult
}
return updateResult
}
Which executes:
// ********************** PRIVATE DRIVER FUNCTIONS TO EXECUTE DBMANAGER UPDATE DATABASE TABLE METHOD ***********//
private func updateBudgetDescriptions(updateQuery: String) -> Bool
{
var updateResult: Bool
updateResult = DBManager.shared.updateDatabaseTable(query: updateQuery)
return updateResult
}
When I execute this DELETE sql in a utility program I have called SQLiteStudio, it fails with a foreign_key notice. But when executed on the same database with FMDB it executes without error or any logging notice.
I'm relying to an extent on the database integrity to throw errors if there is a foreign key when a delete occurs. I've worked for days on this.
I have an XCode project that will demonstrate this but don't know where or how to upload it for someone to examine though.
I'd appreciate any thoughts that might help me get the constraints to work.
Bob
Well after quite a few tests I've discovered what I need to do to be able to have the DELETE throw the "Foreign Key Constraint" error. There are three things I needed to do. 1). I need the PRAGMA statement as a part of the DELETE query like this:
PRAGMA FOREIGN_KEYS = ON;
DELETE FROM \(budget_description_table)
WHERE \(income_expense_code_field) = \(incomeExpenseCode);
Then I needed to change the call to database.executeUpdate to database.executeStatements to execute the sequence of two statments...
// EXECUTE A SERIES OF SQL STATEMENTS
func executeSQLStatements(query: String)-> Bool
{
// let time1 = Date()
if openDatabase() {
// queue.inTransaction() {
// database, rollback in
if !database.executeStatements(query) {
// rollback.initialize(to: true)
print("Failed to execute the query \(query)")
print(database.lastError())
print(database.lastErrorMessage())
if database.lastErrorMessage()=="FOREIGN KEY constraint failed" {
print("You can't do dat")
}
} else {
if showQuerys
{
print("IN executeSQLStatements - Successfully executed the query \(query)")
}
}
// }
_ = closeDatabase()
}
return true
}
BUT FINALLY and this is the clincher.... I needed to eliminate the use of the FMDatabaseQueue. When the transactions are put through the queue for some reason the constraints aren't adhered to... When I use the function as above with the queue commented out I get:
//***********
2021-05-22 14:31:57.089895-0400 SqliteTest[2400:219563] Error inserting batch: FOREIGN KEY constraint failed
Failed to execute the query PRAGMA FOREIGN_KEYS = ON;
DELETE FROM budget_description
WHERE income_expense_code = 20010
Error Domain=FMDatabase Code=19 "FOREIGN KEY constraint failed" UserInfo={NSLocalizedDescription=FOREIGN KEY constraint failed}
FOREIGN KEY constraint failed
//***********
I tried a number of combinations of how to use the PRAGMA statement with the DELETE statement and this combination is the only one that worked. And it only worked in combination without using the FMDatabaseQueue.
When used with the database.executeUpdate even without the FMDatabaseQueue only one statement processed and the row wasn't deleted, but that is because only the PRAGMA statement was executed. There was no error thrown, as the DELETE wasn't executed.
When used with database.executeStatements with the FMDatabaseQueue the row was deleted and no error was thrown...
So 1) Use the PRAGMA foreign_keys=ON; with the DELETE 2) Use executeStatements 3) DON'T use FMDatabaseQueue.
Regards,
Bob

Entity Framework Interceptor to set Id field of patricular entities

In my current project i am working with the database which has very strange table structure (All Id Fields in most tables are marked as not nullable and primary while there is not auto increment increment enabled for them those Id fields need to be unique as well).
unfortunately there is not way i can modify DB so i find another why to handle my problem.
I have no issues while querying for data but during insert What i want to do is,
To get max Id from table where entity is about to be inserted and increment it by one or even better use SSELECT max(id) pattern during insert.
I was hoping to use Interceptor inside EF to achieve this but is looks too difficult for me now and all i managed to do is to identify if this is insert command or not.
Can someone help me through my way on this problem? how can i achieve this and set ID s during insert either by selecting max ID or using SELECT max(id)
public void TreeCreated(DbCommandTreeInterceptionContext context)
{
if (context.OriginalResult.CommandTreeKind != DbCommandTreeKind.Insert && context.OriginalResult.DataSpace != DataSpace.CSSpace) return;
{
var insertCommand = context.Result as DbInsertCommandTree;
var property = insertCommand?.Target.VariableType.EdmType.MetadataProperties.FirstOrDefault(x => x.Name == "TableName");
if (property == null) return;
var tbaleName = property?.Value as ReadOnlyCollection<EdmMember>;
var variableReference = insertCommand.Target.VariableType.Variable(insertCommand.Target.VariableName);
var tenantProperty = variableReference.Property("ID");
var tenantSetClause = DbExpressionBuilder.SetClause(tenantProperty, DbExpression.FromString("(SELECT MAX(ID) FROM SOMEHOWGETTABLENAME)"));
var filteredSetClauses = insertCommand.SetClauses.Cast<DbSetClause>().Where(sc => ((DbPropertyExpression)sc.Property).Property.Name != "ID");
var finalSetClauses = new ReadOnlyCollection<DbModificationClause>(new List<DbModificationClause>(filteredSetClauses) { tenantSetClause });
var newInsertCommand = new DbInsertCommandTree(
insertCommand.MetadataWorkspace,
insertCommand.DataSpace,
insertCommand.Target,
finalSetClauses,
insertCommand.Returning);
context.Result = newInsertCommand;
}
}
Unfortunately that concept of Interceptor is a little bit new for me and i do not understand it completely.
UPDATE
I manage to dynamically build that expression so that ID field is now included in insert statement, but the problem here is that I can not use SQL query inside it. whenever i try to use this it always results in some wrong SQL query so is there anyway i tweak insert statement so that this SELECT MAX(ID) FROM TABLE_NAME is executed during insert?
Get the next id from the context, and then set the parameter of the insert command accordingly.
void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
{
var context = interceptionContext.DbContexts.First() as WhateverYourEntityContainerNameIs;
// get the next id from the database using the context
var theNextId = (from foo in context...)
// update the parameter on the command
command.Parameters["YourIdField"].Value = theNextId;
}
Just bear in mind this is not terribly thread safe; if two users update the same table at exactly the same time, they could theoretically get the same id. This is going to be a problem no matter what update method you use if you manage keys in the application instead of the database. But it looks like that decision is out of your hands.
If this is going to be a problem, you might have to do something more drastic like alter the command.CommandText to replace the value in the values clause with a subquery, for example change
insert into ... values (#YourIdField, ...)
to
insert into ... values ((select max(id) from...), ...)

Autoincrementation of numbers in textfield

Hello all
I am generating an application in which I am having 4 fields ( product id, product name, product price, product description).
Now I want that the product id would be generated automatically when I save my data, i.e. when I run my app the text field of product id should be generated automatically and when I click on save button this id should be incremented by one and the id of last data should be saved.
So kindly help me out.
You have to take a look on this tutorials
http://upadhyayajayiphone.blogspot.in/2012/11/insert-record-in-sqlite-table-get.html
http://www.techotopia.com/index.php/An_Example_SQLite_based_iOS_4_iPhone_Application
It contains all the operations for you. While you fetching data use SELECT MAX(product_id) from TABLE_NAME to fetch the last data.
try this way....
//fetch the MAX id
[self databaseOpen];
NSString *cc=[NSString stringWithFormat:#"Select MAX(ProductID) from Table"];
NSMutableArray *temp=[[NSMutableArray alloc]init];
temp=[[database executeQuery:cc]mutableCopy];
[database close];
Explaination I am fetching the Maximum product_id from database Table.
int Product_id;
if (temp.count!=0 && ([[temp objectAtIndex:0]valueForKey:#"MAX(ProductID)"] !=[NSNull null]))
Product_id=[[[temp objectAtIndex:0]valueForKey:#"MAX(ProductID)"]intValue]+1;
else
Product_id=1;
//fetch the MAX id
Explaination If there is no record in the Table then Product_id=1
else It will incremented by 1;
Txt_productID.text=[NSString stringwithFormat:#"%d",Product_id];
Explaination Print the Product_id in the Text Field
[self databaseOpen];
NSString *q=[NSString stringWithFormat:#"Insert into Table (NAme,Price,Description) values ('%#','%#','%#')",txt_name.text,txt_price.text,txt_des.text];
[database executeNonQuery:q];
[database close];
Explaination Save the new Record in database.
If u want to achive by NSUserDefaults, then use following code
// get total count
int count=[[NSUserDefaults standardUserDefaults]integerForKey:#"TotalIdCount"];
// on save button click increment count by one and stored it
count=count+1;
[[NSUserDefaults standardUserDefaults ] setInteger:count forKey:#"TotalIdCount"];
[[NSUserDefaults standardUserDefaults] synchronize];

INSERT OR REPLACE does not update the record in SQLITE

I am trying to insert the record in the database and whenever I do so, it inserts the record twice in the database.
I am using the following query to do my insert:
-(void) insert {
NSString *sqlQuery = [NSString stringWithFormat:#"INSERT OR REPLACE INTO test(ID,KEY ) VALUES ('%d','%#');", test.id, test.key];
const char *insert_sql = [sqlQuery UTF8String];
sqlite3_exec(db, insert_sql, NULL, NULL, NULL);
}
It always enters duplicate records whenever I run this query. Is there a way to check if the data already exists then donot insert just update.
You're initializing the SQL string using stringWithFormat: but the string you pass is not a format string. The ? parameters are only recognized by the sqlite parser.
You can fix this either by replacing the SQL string with
// Assuming id is an int and key is a string
NSString *sqlQuery = [NSString stringWithFormat:#"INSERT OR REPLACE INTO test(ID,KEY ) VALUES (%d,%#);", test.id, test.key];
Or you need to call the sqlite prepare / bind functions to bind the parameters to the wildcards. For this see the prepare and bind documentation.
I suggest the second way, as this prevents SQL injection.
Is ID a primary key for the table, or otherwise indexed? It needs to be unique for a replacement to occur.

FMDB SQLite question: row count of a query?

does anyone know how to return the count of a query when using FMDB? If I executeQuery #"select count(*) from sometable were..." I get an empty FMResultSet back. How can I get the row count of the query? Do I need to do a query like "select * from sometable where.." and iterate through the result set? Or can I use useCount or whats the best way (in terms of performance) to do this?
Thanks!
Shorter code to accomplish the same thing:
NSUInteger count = [db intForQuery:#"SELECT COUNT(field) FROM table_name"];
Make sure to include the FMDatabaseAdditions.h header file to use intForQuery:.
try this. It works for me. Iterating all the records is not recommended.
FMResultSet *rs = [db executeQuery:#"select count(FIELD) as cnt from TABLENAME"];
while ([rs next]) {
NSLog(#"Total Records :%d", [rs intForColumn:#"cnt"]);
}
May be you should check your Where clause.
Swift 2 Example
This code snippet will print the count for you.
if let rs = db.executeQuery("SELECT COUNT(*) as Count FROM TABLE_NAME", withArgumentsInArray: nil) {
while rs.next() {
print("Total Records:", rs.intForColumn("Count"))
}
}
If it did not work, a few suggestions:
a) Look for a line in your project that says let database = or var database =. If you find one then change db to database
b) Did you change the TABLE_NAME in the Select statement to whatever your table is called?
The first one is also right but by using this method you can retrieve records and count using the same query , no headache to write another one. Just add count(*) as count to your query.
You could always just run the proper SQL statement. I do something like:
FMResultSet *rs = [database executeQuery:#"select count(*) as count from words"];
[rs next];
wordsThatExist = [rs intForColumn:#"count"];
Setting up the SQL query may be quicker and cheaper then iterating.. I believe counts are cheap.
updated for Swift 3 minor change to "int For Column"
if let rs = db.executeQuery("SELECT COUNT(*) as Count FROM TABLE_NAME", withArgumentsInArray: nil) {
while rs.next() {
print("Total Records:", rs.int(forColumn: "Count"))
}
}
updated for Swift 4 minor change in method parameter name
if let rs = db.executeQuery("SELECT COUNT(*) as Count FROM TABLE_NAME", withArgumentsIn: nil) {
while rs.next() {
print("Total Records:", rs.int(forColumn: "Count"))
}
}
Please Try Following Code, this works for me
let objManager = ModelManager.getInstance()
objManager.database?.open()
let resultSet1: FMResultSet! = sharedInstance.database!.executeQuery("SELECT COUNT(Field) FROM TableNameā€, withArgumentsInArray:nil)
if (resultSet1 != nil)
{
while resultSet1.next()
{
countRecord = Int(resultSet1.intForColumn("COUNT(Field)"))
}
}
print(countRecord)
You Will get Count of Field
If you want to know the count of the rows before make something with the result, you can even do a simple query and ask for the results columnCount that give you the number of rows and you can save one query if you really want to make something with the resultSet
FMResultSet *results = [database executeQuery:#"SELECT * from tableName"];
int numberOfRows = [results columnCount];
while ([results next]){
... do your stuff ...
}