1

In Swift4 for iOS11 I would like to attach two SQLite database-files (file1.db and file2.db) in order to make a query over all data (i.e. over both databases).

I have successfully implemented a SQL-query in one SQLite database using the following code (..here with an example of a name-query...):

func readOneDB() -> String? {

    // create SQL-DB FilePath
    do {
        // create a documents-URL from filename
        self.file_1_URL = try createSQLFilePath(fileName: "file1", fileExtension: "db")
    }
    catch {
        return nil
    }
    let query =
    """
    SELECT DISTINCT n.locations
    FROM names n
    WHERE  n.name = "\(self.myName)"
    """

    // Open SQLite database
    var db: OpaquePointer? = nil
    if sqlite3_open(self.file_1_URL?.absoluteString, &db) == SQLITE_OK {

        var statement: OpaquePointer? = nil

        // Run SELECT query from db
        if sqlite3_prepare_v2(db, query, -1, &statement, nil) == SQLITE_OK {

            // Loop through all results from query
            while sqlite3_step(statement) == SQLITE_ROW {

                let name = sqlite3_column_text(statement, 0)
                if name != nil {
                    let versionString = String(cString:name!)
                    return nameString
                } else {
                    print("name not found", terminator: "")
                    return nil
                }
            }
        } else {
            let errmsg = String(cString: sqlite3_errmsg(db))
            print("error running query: \(errmsg)")
            return nil
        }
    } else {
        print("error opening database")
        return nil
    }
    return nil
}

Now, I read that with the following sql-cmd it is possible to attach two SQL-databases together (in order to make a query over both of them): ATTACH

In fact, I have the following code that does not throw an exception - but still not working 100%

let attachStr = "ATTACH DATABASE \'\(self.file_2_URL!)\' AS SECOND"
if (sqlite3_exec(db, attachStr, nil, &statement, nil) == SQLITE_OK) {
    print("Success!")
}

There is a print of "Success!" when running the code.

Of course, getting the success-message with the attach-code, I thought this is it (and the db-object has both databases united)

But actually NO ! : The query still only brings results from the first database (i.e. file1) and no query-results of the attached second file2, unfortunately.

What is wrong ?

Here is the complete code with the attachment-code and the query (see below).

Why is the query still only delivering results from the first database (file1) and not from both databases (i.e. file1 and file2) ????

What changes do I need to make in the code for this to work ????

func readTwoDBs() -> String? {

    // create SQL-DB FilePath
    do {
        // create documents-URLs for two filenames
        self.file_1_URL = try createSQLFilePath(fileName: "file1", fileExtension: "db")
        self.file_2_URL = try createSQLFilePath(fileName: "file2", fileExtension: "db")
    }
    catch {
        return nil
    }
    let query =
    """
    SELECT DISTINCT n.locations
    FROM names n
    WHERE  n.name = "\(self.myName)"
    """

    // Open SQLite database
    var db: OpaquePointer? = nil
    if sqlite3_open(self.file_1_URL?.absoluteString, &db) == SQLITE_OK {

        var statement: OpaquePointer? = nil

        let attachStr = "ATTACH DATABASE \'\(self.file_2_URL!)\' AS SECOND"
        if (sqlite3_exec(db, attachStr, nil, &statement, nil) == SQLITE_OK) {
            print("Success!")
        }

        //// !!!!!!! Success is printed but the query still only works with file1 WHY ????????????????????

        // Run SELECT query from db
        statement = nil
        if sqlite3_prepare_v2(db, query, -1, &statement, nil) == SQLITE_OK {

            // Loop through all results from query
            while sqlite3_step(statement) == SQLITE_ROW {

                let name = sqlite3_column_text(statement, 0)
                if name != nil {
                    let versionString = String(cString:name!)
                    return nameString
                } else {
                    print("name not found", terminator: "")
                    return nil
                }
            }
        } else {
            let errmsg = String(cString: sqlite3_errmsg(db))
            print("error running query: \(errmsg)")
            return nil
        }
    } else {
        print("error opening database")
        return nil
    }
    return nil
}
iKK
  • 6,394
  • 10
  • 58
  • 131
  • You have only one db variable that you use with both databases, how is that supposed to work, I assume you need one per opened database. I only see you executing the query once, don't you need to query both databases separately? – Joakim Danielson Mar 23 '18 at 07:50
  • Thank you for your input. I understand what you are saying. But then what is the cmd ATTACH really doing ?? (if not combine the two databases into one variable ??). Please explain. Or can you provide some code on how to combine two .db-files and query once over both of them ?? – iKK Mar 23 '18 at 08:31

1 Answers1

1

With the help of Joakim, I think I found a solution:

The ATTACH-cmd actually means that more than one database is sharing the same database-connection. As Joakim pointed out, one still needs to do two queries if one wants to get results from both database-files!

Here is the code : (for illustration purposes, the method's return-value is a tuple containing the two results of the two queries (one from the first database, second one from the second database)

func readTwoDBs() -> (String, String)? {

    var returnValue_1: String = ""
    var returnValue_2: String = ""

    // create SQL-DB FilePath
    do {
        // create documents-URLs for two filenames
        self.file_1_URL = try createSQLFilePath(fileName: "file1", fileExtension: "db")
        self.file_2_URL = try createSQLFilePath(fileName: "file2", fileExtension: "db")
    }
    catch {
        return nil
    }
    let query_1 =
    """
    SELECT DISTINCT n.locations
    FROM names n
    WHERE  n.name = "\(self.myName)"
    """

    // !! FOR QUERY_2 YOU NEED THE "MY_DB_TWO" NAME AND ITS TABLE IN THE FROM STATEMENT !!!!!!!
    // !! THIS "MY_DB_TWO" IS GIVEN BY THE ATTACH-cmd FURTHER DOWN... !!!!!!!! 
    let query_2 =
    """
    SELECT DISTINCT n.locations
    FROM MY_DB_TWO.names n
    WHERE  n.name = "\(self.myName)"
    """

    // Open SQLite database
    var db: OpaquePointer? = nil
    if sqlite3_open(self.file_1_URL?.absoluteString, &db) == SQLITE_OK {

        var statement: OpaquePointer? = nil

        let attachStr = "ATTACH DATABASE \'\(self.file_2_URL!)\' AS MY_DB_TWO"
        if (sqlite3_exec(db, attachStr, nil, &statement, nil) == SQLITE_OK) {
            print("Success!")
        }

        // Run SELECT query Nr1 from main.db
        statement = nil
        if sqlite3_prepare_v2(db, query_1, -1, &statement, nil) == SQLITE_OK {

            // Loop through all results from query
            while sqlite3_step(statement) == SQLITE_ROW {

                let name = sqlite3_column_text(statement, 0)
                if name != nil {
                    let nameString = String(cString:name!)
                    returnValue_1 = nameString
                } else {
                    print("name not found", terminator: "")
                    returnValue_1 = ""
                }
            }
        } else {
            let errmsg = String(cString: sqlite3_errmsg(db))
            print("error running query: \(errmsg)")
            returnValue_1 = ""
        }

        // Run SELECT query Nr2 from MY_DB_TWO
        statement = nil
        if sqlite3_prepare_v2(db, query_2, -1, &statement, nil) == SQLITE_OK {

            // Loop through all results from query
            while sqlite3_step(statement) == SQLITE_ROW {

                let name = sqlite3_column_text(statement, 0)
                if name != nil {
                    let nameString = String(cString:name!)
                    returnValue_2 = nameString
                } else {
                    print("name not found", terminator: "")
                    returnValue_2 = ""
                }
            }
        } else {
            let errmsg = String(cString: sqlite3_errmsg(db))
            print("error running query: \(errmsg)")
            returnValue_2 = ""
        }
    } else {
        print("error opening database")
    }

    return (returnValue_1, returnValue_2)
}
iKK
  • 6,394
  • 10
  • 58
  • 131