GRDB in Swift - .fetchAll Only Ever Returns One Column - swift

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.
}

Related

TableView Cell -> UISearchBar : Sort Data, where the records which have most keywords matched should be shown first and then go towards the least

Search should sort results based on a number of matched keywords. It should show the most matched keyword results on top of the list, then the rest records going in descending order. I have a UITableView wherein I have implemented UISearchBar which is going to search my tableview cells as per query inserted in the search-box. Let me now recreate a scenario to explain it. Lets say my Api fetched different key value datas and among them is a key named as "tags" which will contain a string of comma separated values as "dog, cat, pug" now whenever user enters dog into the search-box as this value is shown in one of my tableview cell it will be shown as my tags contain value dog, again if we insert second keyword as cat the above record needs to be fetched and shown to the top and after that if there exists a record which contains only dog in one of the tag values or cat as one of the tag values then it needs to be shown after it. Similarly for third word.
Query -> Cat Dog Pug
It should show on the top only those records which have all of the above keywords
After that, it should show records where any two of the above values are present
After that, it should show single single records if there exists any from above keywords.
Please help.
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
print("\(searchText)")
let arrSearchTexts = searchText.components(separatedBy: " ")
filteredStorage = originalStorage1.filter { (obj) -> Bool in
if let tags = obj["tags"] as? String{
var hasTag = false
for searchedTag in arrSearchTexts{
if tags.contains(searchedTag.lowercased()){
hasTag = true
break
}
}
return hasTag //tags.contains(searchText.lowercased())
}
return false
}
//Sorting data
filteredStorage.sort { (obj1, obj2) -> Bool in
if let tags1 = obj1["tags"] as? String{
var hasAllTags = true
for tagVal in arrSearchTexts{
if !tags1.contains(tagVal.lowercased()){
hasAllTags = false
break
}
}
return hasAllTags
}
return obj1.contains { (obj) -> Bool in
if let val = obj.value as? String,
let firstTag = arrSearchTexts.last{
return val.hasPrefix(firstTag)
}
return false
}
}
if searchText == "" {
filteredStorage = originalStorage1
}
tblView.reloadData()
}
for i in 0..<filteredStorage.count {
var dict = filteredStorage[i]
dict["priority"] = 0
filteredStorage[i] = dict
}
for i in 0..<filteredStorage.count{
let tagInArr : String = filteredStorage[i]["tags"] as! String
var priorityInArr : Int = 0
for tagVal in arrSearchTexts{
if tagInArr.contains(tagVal){
priorityInArr += 1
}
}
filteredStorage[i]["priority"] = priorityInArr
}
filteredStorage.sort{
((($0 as Dictionary<String, AnyObject>)["priority"] as? Int)!) > ((($1 as Dictionary<String, AnyObject>)["priority"] as? Int)!)
}

"Unwrapping" data retrieved from Firebase

So I have managed to retrieve some data from Firebase and it looks like this when printed:
[Resturant.CustomerList(key: "-LQQlhEmNZb8Kaha9uCk", customerLastName:
“Kendrick”, customerFirstName: “Anna”, customerSeat: "100",
customerOrder: “Noodle”, Timestamp: 1541290545703.0)]
Question: How do I unwrap them so I can put individual value into other String variables?
I tried many ways but I get errors such cannot subscript a value of type [CustomerList] with an index of type String if I do something like let custName = self.list["Name"] as? String
ps. CustomerList is a struct
pps. The print out of list is what is shown
As you have a list of CustomerList objects i.e, [CustomerList] so you should first get a single object from this list. Lets say we want the very first object from this list to access its properties then we can do it as below,
if let firstCustomer = self.list.first {
let firstName = firstCustomer.customerFirstName
let lastName = firstCustomer.customerLastName
}
If you want to access an object at a specific index then you can do as below,
let index = 0
let customer = self.list[index]
let firstName = customer.customerFirstName
let lastName = customer.customerLastName
To find a particular customer, you can filter that as below,
let johny = self.list.filter{ $0.customerFirstName == "Jonhny"}.first {
print(johny.customerLastName)
}
To get a custom list created from the customers list, you can use map as below,
let lastNamesArray = self.list.map({ $0.customerLastName })

Trouble using UILocalizedIndexedCollation to filter a table view with the inputed user's name.

I'm new to this forum! I am having a little bit of trouble sorting my Person's array with the use of UILocalizedIndexedCollation.
Code
I created the following class:
#objc class Person : NSObject {
#objc var firstName: String!
#objc var lastName: String!
init(firstName: String, lastName: String) {
self.firstName = firstName
self.lastName = lastName
}
}
With the help of this class, I created a bunch of instances to be displayed and sorted alphabetically on the table view by their last names.
For me to do so, I created the following function:
func partitionObjects(array:[AnyObject], collationStringSelector:Selector) -> ([AnyObject], [String]) {
var unsortedSections = [[AnyObject]]()
//1. Create a array to hold the data for each section
for _ in self.sectionTitles {
unsortedSections.append([]) //appending an empty array
}
//2. Put each objects into a section
for item in array {
let index:Int = self.section(for: item, collationStringSelector:collationStringSelector)
unsortedSections[index].append(item)
}
//3. sorting the array of each sections
var sectionTitles = [String]()
var sections = [AnyObject]()
sectionTitles.append("Frequently Requested")
for index in 0 ..< unsortedSections.count { if unsortedSections[index].count > 0 {
sectionTitles.append(self.sectionTitles[index])
sections.append(self.sortedArray(from: unsortedSections[index], collationStringSelector: collationStringSelector) as AnyObject)
}
}
return (sections, sectionTitles)
}
With this function, I am able to assign my array of Person and array of titles to the returning values of the function:
var sectionTitles = [Person]()
var personsWithSections = [[Person]]()
var contacts = [Person]()
#objc func setUpCollation(){
let (arrayPersons, arrayTitles) = collation.partitionObjects(array: self.persons, collationStringSelector: #selector(getter: Person.lastName))
self.personsWithSections = arrayPersons as! [[Person]]
self.sectionTitles = arrayTitles
}
With this, I am able able to set up my whole table view and it works great. I get every person sorted up by their last name to their corresponding alphabetical section.
The Issue, Specifically
The problem is that if the user has only inputed their first name and not their last name, that Person object gets appended to the last section of the array, where objects with last names that start with numbers or special characters go.
How can I append these object's with only first names to the corresponding alphabetical
section if the user doesn't input a last name? The same goes with
numbers.
As you can see, my collationsStringSelector takes Person.lastName, I was thinking maybe I could create a computed variable that would return the right person's property under some conditions? I am not sure how I would do so. It would be nice to have some guidance or help!
ps: NEW to the forum! if I'm doing something wrong let me know guys! thanks

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)
}
}

passing array as parameter in sqlite Swift

I implemented Sqlite in my project by adding #import <sqlite3.h> in my header.h file and libsqlite3.dylib.
How can I pass an array as parameter to my query, here is what I thought:
var arrayId = [1,2] // array with interested Id
var query:NSString = "Select id from Product where id IN \(arrayId)" // I want to select Products with id that are in the array
Edit:
Does it change if arrayId is NSArray ? Because I also need arrayId as NSArray.
then I proceed with open sqlite database, prepare query and so on.
Thank you in advance.
You can easily combine the array into a string with join function.
var arrayId = [1,2] // array with interested Id
var inExpression = ",".join(map(arrayId) { "\($0)"})
// inExpression = "1,2"
var query = "Select id from Product where id IN (\(inExpression))"
Update for Swift3:
var arrayId = [1,2] // array with interested Id
var inExpression = arrayId.flatMap{ String($0) }.joined(separator: ",")
// inExpression = "1,2"
var query = "SELECT id FROM Product WHERE id IN (\(inExpression))"
You need to accomplish two things: convert your array of Ints to Strings and then implode the array into a string, by joining them with commas (as you would want to do with an IN SQL statement).
Here's a rudimentary function that does just that:
func implode(ints: [Int]) -> String {
// Convert to Strings
let strs = ints.map { String($0) }
// Join Strings with commas
return ",".join(strs)
}
And then in use:
"WHERE id IN (\(implode(arrayId)))"
I'd probably use something like:
var arrayId = [1,2] // array with interested Id
var str = ",".join(arrayId.map { return "\($0)" })
var query = "SELECT id FROM Product WHERE id IN [\(str)]"
Using Swift's own string interpolation to create SQL statements can be risky (as with any language). The sqlite3 library provides parameter binding for this purpose:
if (statement.prepare("SELECT name FROM products WHERE id = ?") != .Ok) {
// Deal with error here
}
// Bind the question mark to your value
statement.bindInt(1, value: 8766)
if (statement.step() == .Row) {
let name = statement.getStringAt(1)
// ...do something with your data from the statement
}
// Done.
statement.finalizeStatement()
EDIT:
For the comment below, you need () brackets, not []:
select id, body from test where id in (1,2);
not
select id, body from test where id in [1,2];