SQLite.Swift - Querying an existing Db table - swift

I am just now learning about SQLite.swift and was reading the documentation on it. I am trying to query an existing table that I already have but do not know how to do this. In the documentation it shows how to Query a table that is created (shown below https://github.com/stephencelis/SQLite.swift/blob/master/Documentation/Index.md#selecting-rows)
let users = Table("users")
try db.run(users.create { t in // CREATE TABLE "users" (
t.column(id, primaryKey: true) // "id" INTEGER PRIMARY KEY NOT NULL,
t.column(email, unique: true) // "email" TEXT UNIQUE NOT NULL,
t.column(name) // "name" TEXT
})
// )
for user in try db.prepare(users) {
print("id: \(user[id]), email: \(user[email]), name: \(user[name])")
// id: 1, email: alice#mac.com, name: Optional("Alice")
}
// SELECT * FROM "users"
I have an existing table that I am able to connect to it but the only way I'm able to get information from it is by doing db.scalar but not sure if this is the correct way of doing it.
let home = FileManager.default.homeDirectoryForCurrentUser
let dbURL = "Desktop/MyPracticeCode/EpubParser/"
let myPath = home.appendingPathComponent(dbURL)
let db = try Connection("\(myPath)/TestTable.sqlite3")
print(db.scalar("SELECT WtName FROM MyTable"))
this prints out the data I need but not sure if this is the correct approach. Is there a way to assign my existing table to a type "Table" and query it just like they did in the example. I searched everywhere online but couldn't find a clear answer. Thanks

I managed to figure out the answer. If anyone ran into this problem or the same question this is how I managed to solve it. Still not sure if it is the correct way. Basically you need to recreate the table with the columns just like in the example but with the name of your columns. Create a table and name it the exact name as the table in your db. Below is how I did it
let id = Expression<Int64>("id")
let Article = Expression<String?>("Article")
let ArticleName = Expression<String?>("ArticleName")
let ImageThumbnail = Expression<String?>("ImageThumbnail")
let WtCoverImage = Expression<String?>("WtCoverImage")
let myTable = Table("MyTable")
try db.run(myTable.create(ifNotExists: true){ t in
t.column(id,primaryKey: true)
t.column(Article)
t.column(ArticleName)
t.column(ImageThumbnail)
t.column(WtCoverImage)
})
This here is what I used:
try db.run(users.create(ifNotExists: true) { t in /* ... */ })
// CREATE TABLE "users" IF NOT EXISTS -- ...
Now I'm able to query it like this
for myData in try db.prepare(myTable){
print("WtCoverImage\(myData[WtCoverImage])")
}

class SqliteDB{
static let shared:SqliteDB = SqliteDB()
private init(){}
var DB:Connection!
/// path for creat a databased file.
func createDBPath(){
do {
let dbPath = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false).appendingPathComponent("chatDB.sqlite").path
DB = try Connection(dbPath)
print(dbPath)
} catch {
print(error.localizedDescription)
}
}
func dbTables_ChatMessage(){
let tableQuery = """
CREATE TABLE IF NOT EXISTS message_list (id INTEGER, conversation_id INTEGER UNIQUE, conversation_recipient_id INTEGER, timestamp INTEGER UNIQUE, content_type TEXT, message TEXT, user_id INTEGER, user_name TEXT, group_id INTEGER,isOnline TEXT,room_id INTEGER, local_conversation_id INTEGER ,room_unique_id TEXT,PRIMARY KEY(id AUTOINCREMENT))
"""
do {
try DB.run(tableQuery)
} catch {
print(error.localizedDescription)
}
}
}

Related

Updating multiple rows using instr

I am trying to make the following call:
UPDATE MyTable SET path = ? WHERE instr(title, ?) AND start - ? < 60
However I have not been able to use instr with GRDB.
_ = try dbQueue?.write { db in
try MyTable
.filter(Column("start") > date - 60)
.filter(title.contains(Column("title")))
.updateAll(db,
Column("path").set(to: path)
)
}
How can I do this correctly? Could I also run a raw query instead? How can I fill the ? with my variables if using a raw query?
GRDB does not ship with built-in support for the instr function. You can define it in your code:
func instr(_ lhs: some SQLExpressible, rhs: some SQLExpressible) -> SQLExpression {
SQL("INSTR(\(lhs), \(rhs))").sqlExpression
}
// SELECT * FROM myTable WHERE instr(?, title)
let title: String = ...
let request = MyTable.filter(instr(title, Column("title")))
// UPDATE myTable SET path = ? WHERE instr(?, title)
let path: String = ...
try request.updateAll(db, Column("path").set(to: path))
See the How do I print a request as SQL? faq in order to control the SQL generated by GRDB.
Here is how I solved it with raw SQL in case it is too complicated to extend the framework.
I choose so, because I think this is easier to understand for someone who needs to read the code and has no experience with GRDB or frameworks in general.
do {
var dbQueue:DatabaseQueue? = try DatabaseQueue(path: "PATH_TO_DATABASE")
try dbQueue?.write { db in
try db.execute(
sql: "UPDATE MyTable SET path = :path WHERE instr(title, :title)",
arguments: ["path": path, "title": title]
)
}
} catch {
print("UPDATE MyTable \(error)")
}

GRDB in Swift - .fetchAll Only Ever Returns One Column

My Original Code
func getUserByEmpNum(_ id: Int) -> String {
let nameQuery: String = "SELECT fld_str_firstname, fld_str_lastName FROM userView WHERE fld_int_id = \(id);"
var returnStr = ""
do {
let dbQueue = try DatabaseQueue(path: MYCDatabase.pathToDatabase)
try dbQueue.inTransaction(.none) { (db) -> Database.TransactionCompletion in
let returnStrs = try String.fetchAll(db, sql: nameQuery)
// Placing a breakpoint here, returnStrs only has one element?
return .commit
}
} catch {
print (error)
}
return returnStr
}
My Question
In this code if I do a query like select fld_str_firstname from myOwnUserView where fld_int_id = 2; I get one element in my returnStrs array, which is as expected. Then selecting the two fields, as in nameQuery, I still only ever get one string in the returnStrs array.
Why is this, and how do I fit it to get all the selected columns in the response?
String.fetchAll returns an array of Strings extracted from the leftmost selected column, as documented. One string for each fetched row. Not one string for each selected column.
If you want to grab strings from several columns, use Row.fetchAll, which returns an array of database rows. From those rows, you can extract each column you are interested into:
let rows = try Row.fetchAll(db, sql: "SELECT fld_str_firstname, fld_str_lastName FROM ...")
for row in rows {
let firstName: String = row["fld_str_firstname"]
let lastName: String = row["fld_str_lastName"]
}
See this chapter of the documentation for more information about extracting values from database rows.
Since you are reading the name from a single row identified with its id, you may prefer the fetchOne method, which consumes a single database row (see Fetching Methods):
if let row = try Row.fetchOne(db, sql: "SELECT ... WHERE fld_int_id = ?", arguments: [id]) {
let firstName: String = row["fld_str_firstname"]
let lastName: String = row["fld_str_lastName"]
// Use first and last name
} else {
// ID does not exist in the database: do what is appropriate.
}

How to make a select query using a local variable in SQLite on Swift?

In Swift, if you want to make a query from a local variable it is done by 'selectedButton'. So how is it possible in SQLite?
{
var selectedButton = 1;
let dbPath = NSBundle.mainBundle().pathForResource("practise2015", ofType:"sqlite3")
let db = FMDatabase(path: dbPath)
if db.open(){
let categoryLevelID = try! db.executeQuery("SELECT * FROM `LEVEL` WHERE CATEGORY_ID = " + selectedButton, values: nil)
}
}
Can't you just do something like this?
let categoryLevelID = try! db.executeQuery("SELECT * FROM LEVEL WHERE CATEGORY_ID = \(selectedButton.tag)", values: nil)
actuall you can't just pass a UIButton as an argument, you need some String, Int or Double value. Category_id sounds like an Int to me, so I guess you have the correct category_id in your UIButton as it's Tag
But you should do queries by using data binding anyway. Take a look at this tutorial by Ray Wenderlich: https://www.raywenderlich.com/123579/sqlite-tutorial-swift
Or this one: http://www.techotopia.com/index.php/An_Example_SQLite_based_iOS_8_Application_using_Swift_and_FMDB

Getting results from arbitrary SQL statements with correct binding in SQLite.swift

The SQLite.swift documentation says about executing arbitrary SQL:
let stmt = try db.prepare("SELECT id, email FROM users")
for row in stmt {
for (index, name) in stmt.columnNames.enumerate() {
print ("\(name)=\(row[index]!)")
// id: Optional(1), email: Optional("alice#mac.com")
}
}
I wanted to get the values directly like this
let stmt = try db.prepare("SELECT id, email FROM users")
for row in stmt {
let myInt: Int64 = row[0] // error: Cannot convert value of type 'Binding?' to specified type 'Int64'
let myString: String = row[1] // error: Cannot convert value of type 'Binding?' to specified type 'String'
}
but the row index is of type Binding? and I can't figure out how to convert that to the type I need. I see there is a Statement.bind method in the source code but I am still not discovering how to apply it.
You can retrieve correctly typed selected columns from a table like this:
// The database.
let db = try Connection(...)
// The table.
let users = Table("users")
// Typed column expressions.
let id = Expression<Int64>("id")
let email = Expression<String>("email")
// The query: "SELECT id, email FROM users"
for user in try db.prepare(users.select(id, email)) {
let id = user[id] // Int64
let mail = user[email] // String
print(id, mail)
}
An alternative is to (optionally) cast the Binding values
to the correct type:
let stmt = try db.prepare("SELECT id, email FROM users")
for row in stmt {
if let id = row[0] as? Int64,
let mail = row[1] as? String {
print(id, mail)
}
}

Realm.create will it update the object with the same primary key?

I am just curious, if I call realm.create, will it auto update realm object from the realm results?
// Assuming a "Book" with a primary key of `1` already exists.
try! realm.write {
realm.create(Book.self, value: ["id": 1, "price": 9000.0], update: true)
// the book's `title` property will remain unchanged.
}
Currently it seems like I need to read from realm again to get the latest object. Do correct me if I'm wrong.
Thanks
Yes, specifying update: true when calling Realm.create(_:value:update:) will result in the existing object being updated.
Here's a snippet based on the code you provided that demonstrates this:
class Book: Object {
dynamic var id = ""
dynamic var title = ""
dynamic var price = 0.0
override class func primaryKey() -> String? { return "id" }
}
let realm = try! Realm()
let book = Book(value: ["1", "To Kill a Mockingbird", 9.99])
try! realm.write {
realm.add(book)
}
let results = realm.allObjects(ofType: Book.self)
try! realm.write {
realm.createObject(ofType: Book.self, populatedWith: ["id": "1", "price": 7.99], update: true)
}
print(book)
print(results)
This code produces the following output:
Book {
id = 1;
title = To Kill a Mockingbird;
price = 7.99;
}
Results<Book> (
[0] Book {
id = 1;
title = To Kill a Mockingbird;
price = 7.99;
}
)
As you can see the price property of the existing objects has updated to the new value.
In case someone stumbles upon this question again, there are two ways to upsert for Realm in Swift. And to answer your question, both will result in the existing object being updated.
If you have the object, upsert with Realm.add(_:update:).
try! realm.write {
realm.add(bookToUpsert, update: .modified)
}
If you have JSON, use Realm.create(_:value:update:)
try! realm.write {
realm.add(Book.self, value: ["id": "1", "price": 7.99], update: .modified)
}
I pass .modified to both, but update takes an UpdatePolicy. Which can be .error, .modified, .all.