© Kevin Languedoc 2016

Kevin Languedoc, Build iOS Database Apps with Swift and SQLite, 10.1007/978-1-4842-2232-4_8

8. Deleting Records

Kevin Languedoc

(1)Montreal, Canada

In this chapter, we will discuss the DELETE function in SQLite. In contrast to the other CRUD functions in SQLite, the DELETE function API is the same as in other platforms and implements the basic SQL API, except for the LIMIT clause, which allows a developer to set a limit on the number of rows to delete. We will cover the following:

  • The DELETE statement

  • DELETE using a WHERE clause

  • DELETE restrictions and TRIGGERS

  • LIMITS

  • A Swift DELETE example

The DELETE Statement in SQLite

The DELETE statement is a standard SQL statement that is used to permanently remove one or more records from a table in a SQLite database. The basic syntax is as follows:

DELETE FROM main.tablename

or,

DELETE FROM main.tablename
WHERE Boolean expression

If the WHERE clause is not used, then the entire contents of a given table is deleted.

Using the WHERE Clause

To better control the records that you are deleting, you can use the WHERE clause to specify a Boolean variable. As with other SQL WHERE clauses, you can use the AND keyword to specify more than one column or the OR keyword to specify one column or another. There is no limit on the number of columns that you can add to your WHERE arguments. In addition, you can use NOT, as in NOT IN or NOT EQUAL. You can also use the BETWEEN keyword; for example, to specify a date or number range. By using the WHERE clause along with DELETE, you can limit the number of records that are affected by the query.

The syntax is as follows:

        DELETE FROM table
        WHERE columnA = 'value'
        AND columnB = 'value'

You can also write a query as follows:

        DELETE FROM table
        WHERE (columnA = 'value'
        AND columnB = 'value') OR
        (columnC = 'value'
        AND columnD = 'value')

Restrictions and Triggers

When using the DELETEstatement with a TRIGGER, you are not allowed to use the schema name, only the table name. In other words, you must use the DELETE as follows:

DELETE FROM table

Rather than:

DELETE FROM main.tablename

Also, if the DELETE statement in the trigger is not associated with a TEMP table, then the trigger must be in the same database as the trigger. The trigger will search for tables in each of the attached databases in the order in which the database was attached.

DELETE Limits

You can use the LIMITS clause along with the ORDER BY clause to restrict the number of rows to delete. By using the ORDER BY, you can sort the records in either an ascending (ASC) or descending (DESC) order so as to ensure the proper rows are targeted for deletion.

In order to use the LIMIT and ORDER BY clauses, you must enable the SQLITE_ENABLE_UPDATE_DELETE_LIMIT option when compiling the database. Also, it is important to keep in mind that the limits and triggers are not supported for the DELETE statement in SQLite.

A Swift SQLite Delete Example

Using the SQLite DELETE statement in Swift is very easy, as the following example illustrates. After setting the usual variables and constants, we set up the database in the viewDidLoad function and call the setupSampleTable, addRecords, and sampleDelete functions sequentially.

The first two functions create a table and add one record to the table. The main purpose of this code is to demonstrate the DELETE query in Swift, which I do in the sampleDelete function .

    let dbName:String="chapter8.sqlite"
    var db:COpaquePointer?=nil
    var sqlStatement:COpaquePointer?=nil
    var errMsg: UnsafeMutablePointer<UnsafeMutablePointer<Int8>>! = nil
    internal let SQLITE_STATIC = unsafeBitCast(0, for:sqlite3_destructor_type.self)
    internal let SQLITE_TRANSIENT = unsafeBitCast(-1, for:sqlite3_destructor_type.self)
    var dbPath:URL = URL(fileWithPath:””)
    var errStr:String = ""
    override func viewDidLoad() {
        super.viewDidLoad()
        let dirManager = FileManager.default()
        do {
            let directoryURL = try dirManager.urlForDirectory(FileManager.SearchPathDirectory.documentDirectory, in: FileManager.SearchPathDomainMask.userDomainMask, appropriateForL: nil, create: true)
        dbPath = try! directoryURL.appendingPathComponent(dbName)
        } catch let err as NSError {
            print("Error: (err.domain)")
        }
        self.setupSampleTable()
        self.addRecords()
        self.sampleDelete()


    }

func setupSampleTable(){
        let creatSQL:String = "CREATE TABLE IF NOT EXISTS sample(id int, name varchar)"
        if(sqlite3_open(dbPath.path!, &db)==SQLITE_OK){
            if(sqlite3_prepare_v2(db, creatSQL, -1, &sqlStatement, nil)==SQLITE_OK){
                if(sqlite3_step(sqlStatement)==SQLITE_DONE){
                    print("table created")
                    sqlite3_finalize(sqlStatement)
                    sqlite3_close(db)
                }else{
                    print("unable to create table")
                }
            }
        }
    }
    
    func addRecords(){
        let insertSQL:String = "INSERT INTO TABLE main.sample (id, name) VALUES(?,?)"
        if(sqlite3_open(dbPath.path!, &db)==SQLITE_OK){
            if(sqlite3_prepare_v2(db, insertSQL, -1, &sqlStatement, nil)==SQLITE_OK){
                sqlite3_bind_int(sqlStatement, 1, 1)
                sqlite3_bind_text(sqlStatement, 2, "kevin", -1, SQLITE_TRANSIENT)
                if(sqlite3_step(sqlStatement)==SQLITE_DONE){
                    print("table created")
                    sqlite3_finalize(sqlStatement)
                    sqlite3_close(db)
                }else{
                    print("unable to create table")
                }
            }
        }
    }

As you can see from the code that follows, the DELETE query follows a pattern similar to that of the other CRUD operations. We first set a SQL query string for the DELETE statement, then we attempt to open the database and load the DELETE SQL query string into memory using the sqlite3_prepare_v2 function.

Then we bind the WHERE value using the sqlite3_bind_int function and execute the query using the sqlite3_step function. If we get a successful status result, we clean up the query with sqlite3_finalize and close the database.

func sampleDelete(){
        let deleteStmt:String = "DELETE FROM sample WHERE id = ?"


         if(sqlite3_open(dbPath.path!, &db)==SQLITE_OK){
            if(sqlite3_prepare_v2(db, deleteStmt, -1, &sqlStatement, nil)==SQLITE_OK){
                sqlite3_bind_int(sqlStatement, 1, 1)
                if(sqlite3_step(sqlStatement)==SQLITE_DONE){
                    print("item deleted")
                    sqlite3_finalize(sqlStatement)
                    sqlite3_close(db)
                }else{
                    print("unable to delete")
                }
            }
         }
    }

Alternatively, we could perform the same query operation using the sqlite3_exec function, as in the following example. As you may remember, the sqlite3_exec function encapsulates the sqlite3_prepare_v2, sqlite3_step, and sqlite3_finalize functions. See here:

func sampleExecDelete(){
        let deleteStmt:String = "DELETE FROM sampleTable WHERE id = ?"
       if(sqlite3_open(dbPath.path!, &db)==SQLITE_OK){
            if(sqlite3_exec(db, deleteStmt, nil, &sqlStatement, errMsg)==SQLITE_OK){
                sqlite3_bind_int(sqlStatement, 1, 1)
                sqlite3_close(db)
            }else{
                print("unable to delete")
        }
    }
}

Adding the Delete Functionality to the Winery App

This is the final installment of the CRUD application that we have been building. To begin adding the DELETE functionality to our app, open the header and the deleteRecords method as shown in the code that follows. The deleteRecords method will take only one parameter for the WHERE clause variable.

To implement the DELETE functionality in the Winery app, we will need to make several modifications to the various view controllers and table view controllers as well as to the storyboard's UITableViewControllers. We will also need to add two functions to the WineryDAO class.

Modifying the WineryDAO Class

In the WineryDAO class, we will need to add two functions to handle the deletes in the Wine table as well as the deletes in the Winery table. Both functions will perform a DELETE statement on the winery database. Both functions will implement the same design using two different approaches. Actually, this code could be replaced by one function, and we could pass the query string as an input parameter, but I decided to use sqlite3_exec for the second function to demonstrate the two ways to execute a query in SQLite.

Add the deleteWineRecord Function

The deleteWineRecord function contains a string constant that defines a DELETE SQL statement and takes one parameter for the WHERE clause.

The SQLite execution operation includes the opening of the SQLite database using the sqlite3_open function, followed by the sqlite3_prepared_v2 function if the database is successfully opened.

If the SQL statement contains no errors and is successfully loaded into memory, the parameter is passed to the query using the sqlite3_bind_text function, and finally the query is executed with the sqlite3_step function.

I will test the function and post the results at the end of the chapter, as usual.

func deleteWineRecord(record:String){
    let deleteSQL = "DELETE FROM wine WHERE name = ?"


    if(sqlite3_open(dbPath.path!, &db)==SQLITE_OK){
        if(sqlite3_prepare_v2(db, deleteSQL, -1, &sqlStatement, nil)==SQLITE_OK){
            sqlite3_bind_text(sqlStatement, 1, record, -1, SQLITE_TRANSIENT)
            if(sqlite3_step(sqlStatement)==SQLITE_DONE){
                print("item deleted")
            }else{
                print("unable to delete")
            }
        }


    }
}

Add the deleteWineryRecord Function

As with the previous function, the deleteWineryRecordwill delete a record from the SQLite database that is selected in the WineryListTableViewController. The first line of the body of the function contains a SQL string constant that defines a SQL DELETE statement. Notice how the schema is absent from the table definition as per the API requirements.

Next, sqlite3_open is used to open the database, followed by the sqlite3_exec function, which encapsulates the execute API of sqlite3_prepare_v2, sqlite3_step, and sqlite3_finalize.

func deleteWineryRecord(record:String){
        let deleteSQL = "DELETE FROM winery WHERE name = ?"
       if(sqlite3_open(dbPath.path!, &db)==SQLITE_OK){
                if(sqlite3_exec(db, deleteSQL, nil, &sqlStatement, errMsg)==SQLITE_OK){
                   sqlite3_bind_text(sqlStatement, 1, record, -1, SQLITE_TRANSIENT)
                   sqlite3_close(db)
                }else{
                  print("unable to delete")
                }
       }
    }

Modifying the ViewControllers

For this part of the app, we don’t need to make any changes to either the FirstViewController or the SecondViewController.

Modifying the TableViewControllers

Adding the delete functionality requires a couple of changes to both the WineryListTableControllerand the WineListTableViewController. We have to enable the Edit button in the menu as well as call the delete functions in the WineDAO class.

WineryListTableViewController

For the WineryListTableViewController, we enable the Edit button in the viewDidLoad method just below the loadWineList() function. The edit.editButtonItem is assigned to the self.navigationItem.rightBarButtonItem method, which you will able to see when the app runs.

override func viewDidLoad() {
        super.viewDidLoad()
        loadWineList()


        // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
        self.navigationItem.rightBarButtonItem = self.editButtonItem()
    }

The second change is to enable the following tableView standard function. The code is already included when we create a sub-class of the UITableViewController; we only need to uncomment it to enable it.

However, we still need to add some code to actually delete the row from the database, as deleteRowsAtIndexPath will remove an item from the array, but if we don’t remove the same row from the database, when the view is refreshed, the record will re-appear. We accomplish this by getting the row index of the selected record and retrieving that record from the wineries array. Then, we create an instance object of the winery class and pass the name of the winery to the deleteWineryRecord function.

We also add tableView.beginUpdates and tableView.endUpdates at the beginning and end of the delete operation. These indicate that subsequent operations are performed and terminated.

override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .delete {
            tableView.beginUpdates()
            // Delete the row from the data source
            let winery:Wineries = wineryListArray[(indexPath as NSIndexPath).row]
            tableView.deleteRows(at: [indexPath], with: .fade)
             wineDAO.deleteWineryRecord(winery.name)
            tableView.endUpdates()        } else if editingStyle == .Insert {
            // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
        }    
    }

WineListTableViewController  

In kind, we implement the Edit button for the WineListTableViewController as we did with the previous TableViewController:

override func viewDidLoad() {
        super.viewDidLoad()
        self.loadWineList()
        self.navigationItem.rightBarButtonItem = self.editButtonItem()
    }

Again, we implement the following tableView function to enable the delete capabilities of the TableView. In the delete editingStyle, we enclose tableView.beginUpdates and tableView.endUpdates and fetch the row index from the wineListArray before creating an instance object and passing the name value of the selected record to the deleteWineRecord function.

override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
        if editingStyle == .delete {
            tableView.beginUpdates()
            let wine = wineListArray[(indexPath as NSIndexPath).row]
            // Delete the row from the data source
            tableView.deleteRows(at: [indexPath], with: .fade)
            wineDAO.deleteWineRecord(wine.name)
            tableView.endUpdates()       
          } else if editingStyle == .Insert {
            // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
        }    
    }

With this code in place, all that is needed now is to run the app and test it.

Modifying the UI for Delete

Modifying the UI

When we enabled the Edit button in the viewDidLoad methods of both the table view controllers, we activated the Edit buttons. When you click on the Edit menu item, the UI changes to display the Delete icon. Other than this, no change is needed to the UI. In order to see the Edit and Delete buttons, you will need to run the app, which I will do next.

Running the App

Figure 8-1 is the list of wines in the Cellar TableViewController. Instead of selecting an item, click on the “Edit” link. This will switch the view into Edit mode.

A427374_1_En_8_Fig1_HTML.jpg
Figure 8-1. The Cellar TableView

Figure 8-2 shows the Cellar TableView in Edit mode. Notice how the Edit button was converted to Done. All this is provided with one line of code in the viewDidLoad function. Select one of the empty rows, and the row will display a Delete button.

A427374_1_En_8_Fig2_HTML.jpg
Figure 8-2. The Cellar TableView in Edit mode

Figure 8-3 shows the Delete button for the selected row. If you click on the button, the override tableView ( tableView: UITableView,commitEditingStyle editingStyle: UITableViewCellEditingStyle,forRowAtIndexPath indexPath: NSIndexPath) will be called, and the selected item will be removed from the array using the deleteRowsAtIndexPaths function . As you may remember, we also call deleteWineRecord to delete the record from the database.

A427374_1_En_8_Fig3_HTML.jpg
Figure 8-3. the Selected item for deletion

Figure 8-4 displays the TableView for the Wineries table view. It currently has only one entry. We will repeat the same process as before and click on the Edit button to switch into Edit mode.

A427374_1_En_8_Fig4_HTML.jpg
Figure 8-4. The Wineries TableView

As shown in Figure 8-5, with the TableView in Edit mode , you can select the entry, which will trigger the row to display a Delete button. Also notice the "Done" link, which you use to switch the TableView back into Read mode.

A427374_1_En_8_Fig5_HTML.jpg
Figure 8-5. The Wineries in Edit mode

Figure 8-6 displays the Delete button, and if you click on the button the selected entry will be deleted from both the winery array and the winery table in the database.

A427374_1_En_8_Fig6_HTML.jpg
Figure 8-6. The Wineries TableView with item selected for deletion

Figure 8-7 displays the absent-entry Wineries TableView.

A427374_1_En_8_Fig7_HTML.jpg
Figure 8-7. The selected ite m has been deleted

Summary

In this chapter we discussed how to use the SQLite DELETE statement to delete records from a SQLite database. Also how we can use a WHERE clause to limit the number of records to delete or to target a certain record or records.

We also explored how to implement the DELETE statement in Swift 3 and finally we added the delete functionality to the Wine app using Swift. In the next chapter, we will implement searching for records in a SQLite database and displaying those records.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset