The Query Interface lets you write database queries using Swift methods instead of raw SQL.
Overview
The Query Interface provides a type-safe way to build SQL queries:
// Instead of raw SQL:
let sql = "SELECT * FROM player WHERE score > ? ORDER BY score DESC LIMIT 10"
let players = try Player.fetchAll(db, sql: sql, arguments: [1000])
// Use the Query Interface:
let players = try Player
.filter(Column("score") > 1000)
.order(Column("score").desc)
.limit(10)
.fetchAll(db)
Building Queries
Starting a Query
Start with a TableRecord type:
struct Player: TableRecord {
static let databaseTableName = "player"
}
let request = Player.all()
Filtering Rows
Use filter to add WHERE clauses:
// WHERE score > 1000
Player.filter(Column("score") > 1000)
// WHERE name = 'Arthur'
Player.filter(Column("name") == "Arthur")
// WHERE score > 1000 AND teamId = 1
Player.filter(Column("score") > 1000 && Column("teamId") == 1)
// WHERE score > 1000 OR score < 100
Player.filter(Column("score") > 1000 || Column("score") < 100)
Ordering Results
Use order to add ORDER BY clauses:
// ORDER BY score DESC
Player.order(Column("score").desc)
// ORDER BY name ASC
Player.order(Column("name"))
// ORDER BY score DESC, name ASC
Player.order(Column("score").desc, Column("name"))
Limiting Results
// LIMIT 10
Player.limit(10)
// LIMIT 10 OFFSET 20
Player.limit(10, offset: 20)
Selecting Columns
// SELECT id, name
Player.select(Column("id"), Column("name"))
// SELECT COUNT(*)
Player.select(count(Column("*")))
// SELECT MAX(score) AS maxScore
Player.select(max(Column("score")).forKey("maxScore"))
Distinct Results
// SELECT DISTINCT name
Player.select(Column("name")).distinct()
Grouping
// GROUP BY teamId
Player.group(Column("teamId"))
// GROUP BY teamId HAVING COUNT(*) > 5
Player
.group(Column("teamId"))
.having(count(Column("*")) > 5)
Column Expressions
Comparisons
Column("score") == 100
Column("score") != 100
Column("score") < 100
Column("score") <= 100
Column("score") > 100
Column("score") >= 100
Arithmetic
Column("score") + 10
Column("score") - 10
Column("score") * 2
Column("score") / 2
Logical Operators
Column("active") && Column("verified")
Column("active") || Column("admin")
!Column("deleted")
String Operations
// LIKE
Column("name").like("Art%")
// case-insensitive comparison
Column("email").collating(.nocase) == "[email protected]"
NULL Checks
Column("deletedAt") == nil // IS NULL
Column("deletedAt") != nil // IS NOT NULL
IN Operator
[1, 2, 3].contains(Column("id"))
Column("status").in(["active", "pending"])
BETWEEN
(100...200).contains(Column("score"))
Aggregate Functions
count
count(Column("*"))
count(Column("id"))
count(distinct: Column("teamId"))
min and max
min(Column("score"))
max(Column("score"))
sum and average
sum(Column("score"))
average(Column("score"))
length
Joining Tables
See associations for details on defining and using joins.
Basic Join
struct Player: TableRecord {
static let team = belongsTo(Team.self)
}
// INNER JOIN
Player.joining(required: Player.team)
// LEFT JOIN
Player.joining(optional: Player.team)
Including Associated Records
// Fetch players with their teams
let request = Player.including(required: Player.team)
for row in try Row.fetchAll(db, request) {
let player: Player = row["player"]
let team: Team = row["team"]
}
SQL Literals
Use SQL literals for complex expressions:
Player.filter(literal: "score > 1000")
let minScore = 1000
Player.filter(literal: "score > \(minScore)")
Player.filter(sql: "score > ?", arguments: [minScore])
Annotations
Add calculated columns to your queries:
let request = Player.annotated(with: {
(Column("score") * 2).forKey("doubleScore")
})
for row in try Row.fetchAll(db, request) {
let score: Int = row["score"]
let doubled: Int = row["doubleScore"]
}
Common Table Expressions
let cte = CommonTableExpression(
named: "averageScore",
sql: "SELECT AVG(score) AS value FROM player"
)
let request = Player
.with(cte)
.filter(sql: "score > (SELECT value FROM averageScore)")
Combining Requests
UNION
let request1 = Player.filter(Column("score") > 1000)
let request2 = Player.filter(Column("isVIP") == true)
let union = request1.union(request2)
Request Operators
Chaining
Chain multiple operations:
let request = Player
.filter(Column("active") == true)
.order(Column("score").desc)
.limit(10)
Reversed
let request = Player
.order(Column("score").desc)
.reversed() // Changes to ASC
Unordered
let request = Player
.order(Column("name"))
.unordered() // Removes ordering
Type-Safe Queries with Key Paths
When you define DatabaseComponents, you can use key paths:
struct Player: TableRecord {
enum Columns {
static let id = Column("id")
static let name = Column("name")
static let score = Column("score")
}
}
// Use key paths
Player.filter { $0.score > 1000 }
Player.order { $0.score.desc }
Player.select { [$0.id, $0.name] }
Executing Queries
Fetching Records
let players = try Player
.filter(Column("score") > 1000)
.fetchAll(db)
Fetching Rows
let rows = try Row.fetchAll(db, Player.all())
Fetching Values
let scores = try Int.fetchAll(db, Player.select(Column("score")))
Counting
let count = try Player
.filter(Column("active") == true)
.fetchCount(db)
The Query Interface compiles to efficient SQL. There’s minimal overhead compared to writing raw SQL.
Use indexes for columns in WHERE and JOIN conditions:try db.create(index: "player_on_score", on: "player", columns: ["score"])
Avoid fetching all records without filters or limits on large tables. Use pagination:let pageSize = 100
let page = 2
let players = try Player
.order(Column("id"))
.limit(pageSize, offset: (page - 1) * pageSize)
.fetchAll(db)
See Also