Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using array in SQL arguments #18

Closed
chiliec opened this issue Jan 18, 2016 · 7 comments
Closed

Using array in SQL arguments #18

chiliec opened this issue Jan 18, 2016 · 7 comments
Labels
blocked Progress is impossible due to some external blocking reason enhancement

Comments

@chiliec
Copy link

chiliec commented Jan 18, 2016

I'd like to use in sql expressions with array:

// SELECT * FROM table WHERE id IN (1,2,3,5,8)
db.execute("SELECT * FROM table WHERE id IN (?)", arguments: [[1,2,3,5,8]] )

but I get an error:
Contextual type 'DatabaseValueConvertible' cannot be used with array literal
What do you think about this?

@groue
Copy link
Owner

groue commented Jan 18, 2016

Hello @chiliec

GRDB supports your precise use case through the RowConvertible protocol:

struct Book: RowConvertible, DatabaseTableMapping {
    static func databaseTableName() -> String { return "table" }
    static func fromRow(row: Row) -> Book {
        return Book(...)
    }
}

dbQueue.inDatabase { db in
    // SELECT * FROM table WHERE id IN (?,?,?)
    let books = Book.fetchAll(db, keys: [1,2,3])
}

More generally speaking, I wish GRDB would support arrays in arguments. I even sent a pull request to FMDB which exactly supports this feature.

But I don't know how to make Swift accept as arguments an array of values or arrays of values:

// Swift can't express the type of [1,[2,3,4]]
db.execute("SELECT * FROM table WHERE  x > ? AND y IN (?)", arguments: [1,[2,3,4]])

Precisely speaking, Swift supports the [1,[2,3,4]] literal thanks to Foundation. But it fails to compile values that NSArray would not support (nil, enum cases, etc.):

import Foundation
let a = [1, [2,3,4]]    // Array<NSObject>
let b = [nil, [2,3,4]]  // error: type of expression is ambiguous without more context

@groue groue changed the title Using array in SQL expressions Using array in SQL arguments Jan 18, 2016
@groue groue added enhancement help wanted Extra attention is needed labels Jan 18, 2016
@groue
Copy link
Owner

groue commented Jan 30, 2016

@chiliec: I'm closing this issue since you did not answer, and since Swift actively prevents us from implementing your feature request.

@groue groue closed this as completed Jan 30, 2016
@groue groue added the wontfix Impossible to fix, or bad idea label Jan 30, 2016
@groue groue added blocked Progress is impossible due to some external blocking reason and removed wontfix Impossible to fix, or bad idea help wanted Extra attention is needed labels Jun 16, 2016
@siuying
Copy link

siuying commented Oct 20, 2017

@groue I noticed collection.contains(column) worked for select statement, but what if I want to do an update like this?

db.execute("UPDATE table SET state = ? where id in (?)", arguments: ["completed", [1,2,3,5,8]] )

@groue
Copy link
Owner

groue commented Oct 20, 2017

Hello @siuying

For SQLite a ? placeholder represents a simple value, and can't be used for an array.

A request such as UPDATE table SET state = ? where id in (?) must thus be written as UPDATE table SET state = ? where id in (?,?,...), where the IN operator is followed by as many question marks as there are ids.

Following the same idea, the arguments to a query are only made of values, and won't accept arrays. Each value in a query arguments matches its own ? in the SQL query, from left to right.

You would thus write something like:

let ids = [1,2,3,5,8]
let questionMarks = repeatElement("?", count: ids.count).joined(separator: ",")
let query = "UPDATE t SET state = ? WHERE id IN (\(questionMarks))"
var arguments: StatementArguments = ["completed"]
arguments += StatementArguments(ids)
print(arguments) // Prints ["completed", 1, 2, 3, 5, 8]
try db.execute(query, arguments: arguments)

@siuying
Copy link

siuying commented Oct 21, 2017

Thank you for your pointer! I don't know where do I get the idea I can use array here... everything make sense now, thanks again!

@martindufort
Copy link

martindufort commented Sep 21, 2020

Is this still the recommend mechanism of injecting an array of IDS into an IN clause ?
Anything new in GRDB 5.0 that supersedes this ?

@groue
Copy link
Owner

groue commented Sep 22, 2020

When you build a raw SQL string, you have to embed as many question marks as there are arguments. That's how SQLite interprets SQL:

let ids = [1, 2, 3]
let questionMarks = databaseQuestionMarks(count: ids.count) // "?,?,?"
let sql = "SELECT * FROM player WHERE id IN (\(questionMarks))"
let arguments = StatementArguments(ids)
let players = try Player.fetchAll(db, sql: sql, arguments: arguments)

You can also use SQL Interpolation:

let ids = [1, 2, 3]
let request: SQLRequest<Player> = "SELECT * FROM player WHERE id IN \(ids)"
let players = try request.fetchAll(db)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
blocked Progress is impossible due to some external blocking reason enhancement
Projects
None yet
Development

No branches or pull requests

4 participants