5

Here's an example SQL query which you can't build using the convenience builders in GRDB...

let q = "job.id, job.name, job.city, 

ifnull(jobcategory.value, 'no category'),
ifnull(jobpriority.value, 'no priority'),

from job  

left join jobcategory on job.category = jobcategory.id
left join jobpriority on job.priority = jobpriority.id

where job.user = 13"

(indeed, I made an example you can't build in any of the older, less supported iOS Swift SQL libraries either)

and then something like

for ..
 let id = ..
 let name = ..
 let city = ..
 let category = ..
 let priority = ..

I have heard that with GRDB, you can actually use the raw SQL (i.e., actually not even use the query builder of GRDB), but, in the result still use the convenience consumption*, with care for types, etc.

If so, in fact how do you do this in the example?

4

1 回答 1

5

GRDB 提供了一个查询构建器:

let persons = try Person.filter(emailColumn != nil).fetchAll(db) // [Person]

它还理解 SQL:

let persons = try Person.fetchAll(db, "SELECT * FROM persons WHERE email IS NOT NULL")

上面的两个代码片段都将数据库行转换为 Person 实例。GRDB 提供的RowConvertible协议和全功能类都支持这种转换。Record下面的代码使用该协议:

struct Person {
    let email: String
    let name: String
}

extension Person : RowConvertible {
    init(row: Row) {
        email = row.value(named: "email")
        name = row.value(named: "name")
    }
}

构造init(row:)函数用于“查询接口”请求Person.filter(...).fetchAll(db)和 SQL 请求Person.fetchAll(db, "SELECT ...")

这就是当您想使用原始 SQL 时 GRDB 不会惩罚您的意思。您的自定义记录类型支持开箱即用的查询接口请求和 SQL 请求。使用这两种技术获取记录同样容易:

// Two one-liners:
let persons = try Person.filter(emailColumn != nil).fetchAll(db)
let persons = try Person.fetchAll(db, "SELECT * FROM persons WHERE email IS NOT NULL")

现在,您的示例可以写成:

struct Job {
    let id: Int64
    let name: String
    let city: String
    let category: String
    let priority: String
}

extension Job : RowConvertible {
    init(row: Row) {
        id = row.value(named: "id")
        name = row.value(named: "name")
        city = row.value(named: "city")
        category = row.value(named: "category")
        priority = row.value(named: "priority")
    }
}

try dbQueue.inDatabase { db in
    let q = "SELECT job.id, job.name, job.city, " +
        " IFNULL(jobcategory.value, 'no category') AS category, " +
        " IFNULL(jobpriority.value, 'no priority') AS priority " +
        "FROM job " +
        "LEFT JOIN jobcategory ON job.category = jobcategory.id " +
        "LEFT JOIN jobpriority ON job.priority = jobpriority.id " +
        "WHERE job.user = 13"

    let jobs = try Job.fetchAll(db, q)
}

由于类别和优先级不是作业列,您可能更喜欢将上述结构分成两部分:

struct Job {
    let id: Int64
    let name: String
    let city: String
}

struct ExtendedJob {
    let job: Job
    let category: String
    let priority: String
}

extension Job : RowConvertible {
    init(row: Row) {
        id = row.value(named: "id")
        name = row.value(named: "name")
        city = row.value(named: "city")
    }
}

extension ExtendedJob : RowConvertible {
    init(row: Row) {
        job = Job(row: row)
        category = row.value(named: "category")
        priority = row.value(named: "priority")
    }
}

try dbQueue.inDatabase { db in
    let q = "SELECT job.id, job.name, job.city, " +
        " IFNULL(jobcategory.value, 'no category') AS category, " +
        " IFNULL(jobpriority.value, 'no priority') AS priority " +
        "FROM job " +
        "LEFT JOIN jobcategory ON job.category = jobcategory.id " +
        "LEFT JOIN jobpriority ON job.priority = jobpriority.id " +
        "WHERE job.user = 13"

    let jobs = try ExtendedJob.fetchAll(db, q)
}

您最终可以将自定义 SQL 查询封装在“自定义请求”中:

extension ExtendedJob {
    static func filter(userId: Int64) -> AnyTypedRequest<ExtendedJob> {
        let request = SQLRequest(
            "SELECT job.id, job.name, job.city, " +
            " IFNULL(jobcategory.value, 'no category') AS category, " +
            " IFNULL(jobpriority.value, 'no priority') AS priority " +
            "FROM job " +
            "LEFT JOIN jobcategory ON job.category = jobcategory.id " +
            "LEFT JOIN jobpriority ON job.priority = jobpriority.id " +
            "WHERE job.user = ?",
            arguments: [userId])
        return request.asRequest(of: ExtendedJob.self)
    }
}

// No SQL in sight:
let jobs = try dbQueue.inDatabase { db in
    try ExtendedJob.filter(userId: 13).fetchAll(db)
}
于 2017-04-30T07:57:04.993 回答