ScalaSql Reference Library
April 21, 2026 · View on GitHub
ScalaSql Reference Library
This page contains example queries for the ScalaSql, taken from the ScalaSql test suite. You can use this as a reference to see what kinds of operations ScalaSql supports and how these operations are translated into raw SQL to be sent to the database for execution.
If browsing this on Github, you can open the Outline pane on the right
to quickly browse through the headers, or use Cmd-F to search for specific
SQL keywords to see how to generate them from Scala (e.g. try searching for
LEFT JOIN or WHEN)
Note that ScalaSql may generate different SQL in certain cases for different
databases, due to differences in how each database parses SQL. These differences
are typically minor, and as long as you use the right Dialect for your database
ScalaSql should do the right thing for you.
A note for users of
SimpleTable: The examples in this document assume usage ofTable, with a higher kinded type parameter on a case class. If you are usingSimpleTable, then the same code snippets should work by dropping[Sc].
DbApi
Basic usage of db.* operations such as db.run
DbApi.renderSql
You can use .renderSql on the DbApi or DbClient to see the SQL
that is generated without actually running it
dbClient.renderSql(Buyer.select) ==>
"SELECT buyer0.id AS id, buyer0.name AS name, buyer0.date_of_birth AS date_of_birth FROM buyer buyer0"
DbApi.run
Most common usage of dbClient.transaction/db.run
to run a simple query within a transaction
dbClient.transaction { db =>
db.run(Buyer.select) ==> List(
Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")),
Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")),
Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09"))
)
}
DbApi.runSql
db.runSql can be used to run sql"..." strings, while providing a
specified type that the query results will be deserialized as the specified
type. db.runSql supports the all the same data types as db.run:
primitives, date and time types, tuples, Foo[Sc] case classs, and
any combination of these.
The sql"..." string interpolator automatically converts interpolated values
into prepared statement variables, avoidin SQL injection vulnerabilities. You
can also interpolate other sql"..." strings, or finally use SqlStr.raw for
the rare cases where you want to interpolate a trusted java.lang.String into
the sql"..." query without escaping.
dbClient.transaction { db =>
val filterId = 2
val output = db.runSql[String](
sql"SELECT name FROM buyer WHERE id = $filterId"
)
assert(output == Seq("叉烧包"))
val output2 = db.runSql[(String, LocalDate)](
sql"SELECT name, date_of_birth FROM buyer WHERE id = $filterId"
)
assert(
output2 ==
Seq(("叉烧包", LocalDate.parse("1923-11-12")))
)
val output3 = db.runSql[(String, LocalDate, Buyer[Sc])](
sql"SELECT name, date_of_birth, * FROM buyer WHERE id = $filterId"
)
assert(
output3 ==
Seq(
(
"叉烧包",
LocalDate.parse("1923-11-12"),
Buyer[Sc](
id = 2,
name = "叉烧包",
dateOfBirth = LocalDate.parse("1923-11-12")
)
)
)
)
}
DbApi.updateSql
Similar to db.runQuery, db.runUpdate allows you to pass in a SqlStr, but runs
an update rather than a query and expects to receive a single number back from the
database indicating the number of rows inserted or updated
dbClient.transaction { db =>
val newName = "Moo Moo Cow"
val newDateOfBirth = LocalDate.parse("2000-01-01")
val count = db
.updateSql(
sql"INSERT INTO buyer (name, date_of_birth) VALUES($newName, $newDateOfBirth)"
)
assert(count == 1)
db.run(Buyer.select) ==> List(
Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")),
Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")),
Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")),
Buyer[Sc](4, "Moo Moo Cow", LocalDate.parse("2000-01-01"))
)
}
DbApi.updateGetGeneratedKeysSql
Allows you to fetch the primary keys that were auto-generated for an INSERT
defined as a SqlStr.
Note: not supported by Sqlite https://github.com/xerial/sqlite-jdbc/issues/980
dbClient.transaction { db =>
val newName = "Moo Moo Cow"
val newDateOfBirth = LocalDate.parse("2000-01-01")
val generatedIds = db
.updateGetGeneratedKeysSql[Int](
sql"INSERT INTO buyer (name, date_of_birth) VALUES ($newName, $newDateOfBirth), ($newName, $newDateOfBirth)"
)
assert(generatedIds == Seq(4, 5))
db.run(Buyer.select) ==> List(
Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")),
Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")),
Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")),
Buyer[Sc](4, "Moo Moo Cow", LocalDate.parse("2000-01-01")),
Buyer[Sc](5, "Moo Moo Cow", LocalDate.parse("2000-01-01"))
)
}
DbApi.runRaw
runRawQuery is similar to runQuery but allows you to pass in the SQL strings
"raw", along with ? placeholders and interpolated variables passed separately.
dbClient.transaction { db =>
val output = db.runRaw[String]("SELECT name FROM buyer WHERE id = ?", Seq(2))
assert(output == Seq("叉烧包"))
}
DbApi.updateRaw
runRawUpdate is similar to runRawQuery, but for update queries that
return a single number
dbClient.transaction { db =>
val count = db.updateRaw(
"INSERT INTO buyer (name, date_of_birth) VALUES(?, ?)",
Seq("Moo Moo Cow", LocalDate.parse("2000-01-01"))
)
assert(count == 1)
db.run(Buyer.select) ==> List(
Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")),
Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")),
Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")),
Buyer[Sc](4, "Moo Moo Cow", LocalDate.parse("2000-01-01"))
)
}
DbApi.updateGetGeneratedKeysRaw
Allows you to fetch the primary keys that were auto-generated for an INSERT
defined using a raw java.lang.String and variables.
Note: not supported by Sqlite https://github.com/xerial/sqlite-jdbc/issues/980
dbClient.transaction { db =>
val generatedKeys = db.updateGetGeneratedKeysRaw[Int](
"INSERT INTO buyer (name, date_of_birth) VALUES (?, ?), (?, ?)",
Seq(
"Moo Moo Cow",
LocalDate.parse("2000-01-01"),
"Moo Moo Cow",
LocalDate.parse("2000-01-01")
)
)
if (!this.isInstanceOf[MsSqlSuite])
assert(generatedKeys == Seq(4, 5))
else
assert(generatedKeys == Seq(5))
db.run(Buyer.select) ==> List(
Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")),
Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")),
Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")),
Buyer[Sc](4, "Moo Moo Cow", LocalDate.parse("2000-01-01")),
Buyer[Sc](5, "Moo Moo Cow", LocalDate.parse("2000-01-01"))
)
}
DbApi.stream
db.stream can be run on queries that return Seq[T]s, and makes them
return geny.Generator[T]s instead. This allows you to deserialize and
process the returned database rows incrementally without buffering the
entire Seq[T] in memory. Not that the underlying JDBC driver and the
underlying database may each perform their own buffering depending on
their implementation
dbClient.transaction { db =>
val output = collection.mutable.Buffer.empty[String]
db.stream(Buyer.select).generate { buyer =>
output.append(buyer.name)
if (buyer.id >= 2) Generator.End else Generator.Continue
}
output ==> List("James Bond", "叉烧包")
}
DbApi.streamSql
.streamSql provides a lower level interface to .stream, allowing you to pass
in a SqlStr of the form sql"...", while allowing you to process the returned rows
in a streaming fashion without.
dbClient.transaction { db =>
val excluded = "James Bond"
val output = db
.streamSql[Buyer[Sc]](sql"SELECT * FROM buyer where name != $excluded")
.takeWhile(_.id <= 2)
.map(_.name)
.toList
output ==> List("叉烧包")
}
DbApi.streamRaw
.streamRaw provides a lowest level interface to .stream, allowing you to pass
in a java.lang.String and a Seq[Any] representing the interpolated prepared
statement variables
dbClient.transaction { db =>
val excluded = "James Bond"
val output = db
.streamRaw[Buyer[Sc]]("SELECT * FROM buyer WHERE buyer.name <> ?", Seq(excluded))
.takeWhile(_.id <= 2)
.map(_.name)
.toList
output ==> List("叉烧包")
}
Transaction
Usage of transactions, rollbacks, and savepoints
Transaction.simple.commit
Common workflow to create a transaction and run a delete inside of it. The effect
of the delete is visible both inside the transaction and outside after the
transaction completes successfully and commits
dbClient.transaction { implicit db =>
db.run(Purchase.select.size) ==> 7
db.run(Purchase.delete(_ => true)) ==> 7
db.run(Purchase.select.size) ==> 0
}
dbClient.transaction(_.run(Purchase.select.size)) ==> 0
Transaction.simple.isolation
You can use .updateRaw to perform SET TRANSACTION ISOLATION LEVEL commands,
allowing you to configure the isolation and performance characteristics of
concurrent transactions in your database
dbClient.transaction { implicit db =>
db.updateRaw("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE")
db.run(Purchase.select.size) ==> 7
db.run(Purchase.delete(_ => true)) ==> 7
db.run(Purchase.select.size) ==> 0
}
dbClient.transaction(_.run(Purchase.select.size)) ==> 0
Transaction.simple.rollback
Example of explicitly rolling back a transaction using the db.rollback() method.
After rollback, the effects of the delete query are undone, and subsequent select
queries can see the previously-deleted entries both inside and outside the transaction
dbClient.transaction { implicit db =>
db.run(Purchase.select.size) ==> 7
db.run(Purchase.delete(_ => true)) ==> 7
db.run(Purchase.select.size) ==> 0
db.rollback()
db.run(Purchase.select.size) ==> 7
}
dbClient.transaction(_.run(Purchase.select.size)) ==> 7
Transaction.simple.throw
Transactions are also rolled back if they terminate with an uncaught exception
try {
dbClient.transaction { implicit db =>
db.run(Purchase.select.size) ==> 7
db.run(Purchase.delete(_ => true)) ==> 7
db.run(Purchase.select.size) ==> 0
throw new FooException
}
} catch {
case e: FooException => /*donothing*/
}
dbClient.transaction(_.run(Purchase.select.size)) ==> 7
Transaction.savepoint.commit
Savepoints are like "sub" transactions: they let you declare a savepoint and roll back any changes to the savepoint later. If a savepoint block completes successfully, the savepoint changes are committed ("released") and remain visible later in the transaction and outside of it
dbClient.transaction { implicit db =>
db.run(Purchase.select.size) ==> 7
db.run(Purchase.delete(_.id <= 3)) ==> 3
db.run(Purchase.select.size) ==> 4
db.savepoint { _ =>
db.run(Purchase.delete(_ => true)) ==> 4
db.run(Purchase.select.size) ==> 0
}
db.run(Purchase.select.size) ==> 0
}
dbClient.transaction(_.run(Purchase.select.size)) ==> 0
Transaction.savepoint.rollback
Like transactions, savepoints support the .rollback() method, to undo any
changes since the start of the savepoint block.
dbClient.transaction { implicit db =>
db.run(Purchase.select.size) ==> 7
db.run(Purchase.delete(_.id <= 3)) ==> 3
db.run(Purchase.select.size) ==> 4
db.savepoint { sp =>
db.run(Purchase.delete(_ => true)) ==> 4
db.run(Purchase.select.size) ==> 0
sp.rollback()
db.run(Purchase.select.size) ==> 4
}
db.run(Purchase.select.size) ==> 4
}
dbClient.transaction(_.run(Purchase.select.size)) ==> 4
Transaction.savepoint.throw
Savepoints also roll back their enclosed changes automatically if they terminate with an uncaught exception
dbClient.transaction { implicit db =>
db.run(Purchase.select.size) ==> 7
db.run(Purchase.delete(_.id <= 3)) ==> 3
db.run(Purchase.select.size) ==> 4
try {
db.savepoint { _ =>
db.run(Purchase.delete(_ => true)) ==> 4
db.run(Purchase.select.size) ==> 0
throw new FooException
}
} catch {
case e: FooException => /*donothing*/
}
db.run(Purchase.select.size) ==> 4
}
dbClient.transaction(_.run(Purchase.select.size)) ==> 4
Transaction.doubleSavepoint.commit
Only one transaction can be present at a time, but savepoints can be arbitrarily nested.
Uncaught exceptions or explicit .rollback() calls would roll back changes done during
the inner savepoint/transaction blocks, while leaving changes applied during outer
savepoint/transaction blocks in-place
dbClient.transaction { implicit db =>
db.run(Purchase.select.size) ==> 7
db.run(Purchase.delete(_.id <= 2)) ==> 2
db.run(Purchase.select.size) ==> 5
db.savepoint { _ =>
db.run(Purchase.delete(_.id <= 4)) ==> 2
db.run(Purchase.select.size) ==> 3
db.savepoint { _ =>
db.run(Purchase.delete(_.id <= 6)) ==> 2
db.run(Purchase.select.size) ==> 1
}
db.run(Purchase.select.size) ==> 1
}
db.run(Purchase.select.size) ==> 1
}
dbClient.transaction(_.run(Purchase.select.size)) ==> 1
Transaction.useBlock
Both transaction and savepoint accessors are actually returning a special UseBlock[...] types.
When you call transaction(use) it desugars into transaction.apply(use) that provides a default
resource-style management - it creates a transaction (or savepoint), runs use block immediately, then releases
(commits or rolls back) the transaction.
If you need more control over the lifecycle, you may use transaction.allocate() method that returns
(resource, releaseFunction) pair.
This is especially useful when delaying side-effects with FP libraries like cats-effect or ZIO.
Important: allocate() is impure and must be delayed in that case also.
The dbClient.transaction expression itself does not perform any side-effects, it just creates closures.
dbClient.transaction
Select
Basic SELECT operations: map, filter, join, etc.
Select.constant
The most simple thing you can query in the database is an Expr. These do not need
to be related to any database tables, and translate into raw SELECT calls without
FROM.
Expr(1) + Expr(2)
-
SELECT (? + ?) AS res -
3
Select.table
You can list the contents of a table via the query Table.select. It returns a
Seq[CaseClass[Sc]] with the entire contents of the table. Note that listing
entire tables can be prohibitively expensive on real-world databases, and you
should generally use filters as shown below
Buyer.select
-
SELECT buyer0.id AS id, buyer0.name AS name, buyer0.date_of_birth AS date_of_birth FROM buyer buyer0 -
Seq( Buyer[Sc](id = 1, name = "James Bond", dateOfBirth = LocalDate.parse("2001-02-03")), Buyer[Sc](id = 2, name = "叉烧包", dateOfBirth = LocalDate.parse("1923-11-12")), Buyer[Sc](id = 3, name = "Li Haoyi", dateOfBirth = LocalDate.parse("1965-08-09")) )
Select.filter.single
ScalaSql's .filter translates to SQL WHERE, in this case we
are searching for rows with a particular buyerId
ShippingInfo.select.filter(_.buyerId `=` 2)
-
SELECT shipping_info0.id AS id, shipping_info0.buyer_id AS buyer_id, shipping_info0.shipping_date AS shipping_date FROM shipping_info shipping_info0 WHERE (shipping_info0.buyer_id = ?) -
Seq( ShippingInfo[Sc](1, 2, LocalDate.parse("2010-02-03")), ShippingInfo[Sc](3, 2, LocalDate.parse("2012-05-06")) )
Select.filter.multiple
You can stack multiple .filters on a query.
ShippingInfo.select
.filter(_.buyerId `=` 2)
.filter(_.shippingDate `=` LocalDate.parse("2012-05-06"))
-
SELECT shipping_info0.id AS id, shipping_info0.buyer_id AS buyer_id, shipping_info0.shipping_date AS shipping_date FROM shipping_info shipping_info0 WHERE (shipping_info0.buyer_id = ?) AND (shipping_info0.shipping_date = ?) -
Seq(ShippingInfo[Sc](id = 3, buyerId = 2, shippingDate = LocalDate.parse("2012-05-06")))
Select.filter.dotSingle.pass
Queries that you expect to return a single row can be annotated with .single.
This changes the return type of the .select from Seq[T] to just T, and throws
an exception if zero or multiple rows were returned
ShippingInfo.select
.filter(_.buyerId `=` 2)
.filter(_.shippingDate `=` LocalDate.parse("2012-05-06"))
.single
-
SELECT shipping_info0.id AS id, shipping_info0.buyer_id AS buyer_id, shipping_info0.shipping_date AS shipping_date FROM shipping_info shipping_info0 WHERE (shipping_info0.buyer_id = ?) AND (shipping_info0.shipping_date = ?) -
ShippingInfo[Sc](id = 3, buyerId = 2, shippingDate = LocalDate.parse("2012-05-06"))
Select.filter.combined
You can perform multiple checks in a single filter using &&
ShippingInfo.select
.filter(p => p.buyerId `=` 2 && p.shippingDate `=` LocalDate.parse("2012-05-06"))
-
SELECT shipping_info0.id AS id, shipping_info0.buyer_id AS buyer_id, shipping_info0.shipping_date AS shipping_date FROM shipping_info shipping_info0 WHERE ((shipping_info0.buyer_id = ?) AND (shipping_info0.shipping_date = ?)) -
Seq(ShippingInfo[Sc](3, 2, LocalDate.parse("2012-05-06")))
Select.filterIf.filter not added
ShippingInfo.select.filterIf(false)(_.buyerId `=` 2)
-
SELECT shipping_info0.id AS id, shipping_info0.buyer_id AS buyer_id, shipping_info0.shipping_date AS shipping_date FROM shipping_info shipping_info0 -
Seq( ShippingInfo[Sc](1, 2, LocalDate.parse("2010-02-03")), ShippingInfo[Sc](2, 1, LocalDate.parse("2012-04-05")), ShippingInfo[Sc](3, 2, LocalDate.parse("2012-05-06")) )
Select.filterIf.filter added
ShippingInfo.select.filterIf(true)(_.buyerId `=` 2)
-
SELECT shipping_info0.id AS id, shipping_info0.buyer_id AS buyer_id, shipping_info0.shipping_date AS shipping_date FROM shipping_info shipping_info0 WHERE (shipping_info0.buyer_id = ?) -
Seq( ShippingInfo[Sc](1, 2, LocalDate.parse("2010-02-03")), ShippingInfo[Sc](3, 2, LocalDate.parse("2012-05-06")) )
Select.filterOpt.filter not added
ShippingInfo.select.filterOpt[Int](None)((table, value) => table.buyerId `=` value)
-
SELECT shipping_info0.id AS id, shipping_info0.buyer_id AS buyer_id, shipping_info0.shipping_date AS shipping_date FROM shipping_info shipping_info0 -
Seq( ShippingInfo[Sc](1, 2, LocalDate.parse("2010-02-03")), ShippingInfo[Sc](2, 1, LocalDate.parse("2012-04-05")), ShippingInfo[Sc](3, 2, LocalDate.parse("2012-05-06")) )
Select.filterOpt.filter added
ShippingInfo.select.filterOpt(Some(2))((table, value) => table.buyerId `=` value)
-
SELECT shipping_info0.id AS id, shipping_info0.buyer_id AS buyer_id, shipping_info0.shipping_date AS shipping_date FROM shipping_info shipping_info0 WHERE (shipping_info0.buyer_id = ?) -
Seq( ShippingInfo[Sc](1, 2, LocalDate.parse("2010-02-03")), ShippingInfo[Sc](3, 2, LocalDate.parse("2012-05-06")) )
Select.map.single
.map allows you to select exactly what you want to return from
a query, rather than returning the entire row. Here, we return
only the names of the Buyers
Buyer.select.map(_.name)
-
SELECT buyer0.name AS res FROM buyer buyer0 -
Seq("James Bond", "叉烧包", "Li Haoyi")
Select.map.filterMap
The common use case of SELECT FROM WHERE can be achieved via .select.filter.map in ScalaSql
Product.select.filter(_.price < 100).map(_.name)
-
SELECT product0.name AS res FROM product product0 WHERE (product0.price < ?) -
Seq("Face Mask", "Socks", "Cookie")
Select.map.tuple2
You can return multiple values from your .map by returning a tuple in your query,
which translates into a Seq[Tuple] being returned when the query is run
Buyer.select.map(c => (c.name, c.id))
-
SELECT buyer0.name AS res_0, buyer0.id AS res_1 FROM buyer buyer0 -
Seq(("James Bond", 1), ("叉烧包", 2), ("Li Haoyi", 3))
Select.map.tuple3
Buyer.select.map(c => (c.name, c.id, c.dateOfBirth))
-
SELECT buyer0.name AS res_0, buyer0.id AS res_1, buyer0.date_of_birth AS res_2 FROM buyer buyer0 -
Seq( ("James Bond", 1, LocalDate.parse("2001-02-03")), ("叉烧包", 2, LocalDate.parse("1923-11-12")), ("Li Haoyi", 3, LocalDate.parse("1965-08-09")) )
Select.map.interpolateInMap
You can perform operations inside the .map to change what you return
Product.select.map(_.price * 2)
-
SELECT (product0.price * ?) AS res FROM product product0 -
Seq(17.76, 600, 6.28, 246.9, 2000.0, 0.2)
Select.map.heterogenousTuple
.map can return any combination of tuples, case classes, and primitives,
arbitrarily nested. here we return a tuple of (Int, Buyer[Sc])
Buyer.select.map(c => (c.id, c))
-
SELECT buyer0.id AS res_0, buyer0.id AS res_1_id, buyer0.name AS res_1_name, buyer0.date_of_birth AS res_1_date_of_birth FROM buyer buyer0 -
Seq( (1, Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03"))), (2, Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12"))), (3, Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09"))) )
Select.toExpr
SELECT queries that return a single row and column can be used as SQL expressions
in standard SQL databases. In ScalaSql, this is done by the .toExpr method,
which turns a Select[T] into an Expr[T]. Note that if the Select returns more
than one row or column, the database may select a row arbitrarily or will throw
an exception at runtime (depend on implenmentation)
Product.select.map(p =>
(
p.name,
Purchase.select
.filter(_.productId === p.id)
.sortBy(_.total)
.desc
.take(1)
.map(_.total)
.toExpr
)
)
-
SELECT product0.name AS res_0, (SELECT purchase1.total AS res FROM purchase purchase1 WHERE (purchase1.product_id = product0.id) ORDER BY res DESC LIMIT ?) AS res_1 FROM product product0 -
Seq( ("Face Mask", 888.0), ("Guitar", 900.0), ("Socks", 15.7), ("Skate Board", 493.8), ("Camera", 10000.0), ("Cookie", 1.3) )
Select.subquery
ScalaSql generally combines operations like .map and .filter to minimize the
number of subqueries to keep the generated SQL readable. If you explicitly want
a subquery for some reason (e.g. to influence the database query planner), you can
use the .subquery to force a query to be translated into a standalone subquery
Buyer.select.subquery.map(_.name)
-
SELECT subquery0.name AS res FROM (SELECT buyer0.name AS name FROM buyer buyer0) subquery0 -
Seq("James Bond", "叉烧包", "Li Haoyi")
Select.aggregate.single
You can use methods like .sumBy or .sum on your queries to generate
SQL SUM(...) aggregates
Purchase.select.sumBy(_.total)
-
SELECT SUM(purchase0.total) AS res FROM purchase purchase0 -
12343.2
Select.aggregate.multiple
If you want to perform multiple aggregates at once, you can use the .aggregate method
which takes a function allowing you to call multiple aggregates inside of it
Purchase.select.aggregate(q => (q.sumBy(_.total), q.maxBy(_.total)))
-
SELECT SUM(purchase0.total) AS res_0, MAX(purchase0.total) AS res_1 FROM purchase purchase0 -
(12343.2, 10000.0)
Select.groupBy.simple
ScalaSql's .groupBy method translates into a SQL GROUP BY. Unlike the normal
.groupBy provided by scala.Seq, ScalaSql's .groupBy requires you to pass
an aggregate as a second parameter, mirroring the SQL requirement that any
column not part of the GROUP BY clause has to be in an aggregate.
Purchase.select.groupBy(_.productId)(_.sumBy(_.total))
-
SELECT purchase0.product_id AS res_0, SUM(purchase0.total) AS res_1 FROM purchase purchase0 GROUP BY purchase0.product_id -
Seq((1, 932.4), (2, 900.0), (3, 15.7), (4, 493.8), (5, 10000.0), (6, 1.30))
Select.groupBy.having
.filter calls following a .groupBy are automatically translated to SQL HAVING clauses
Purchase.select.groupBy(_.productId)(_.sumBy(_.total)).filter(_._2 > 100).filter(_._1 > 1)
-
SELECT purchase0.product_id AS res_0, SUM(purchase0.total) AS res_1 FROM purchase purchase0 GROUP BY purchase0.product_id HAVING (SUM(purchase0.total) > ?) AND (purchase0.product_id > ?) -
Seq((2, 900.0), (4, 493.8), (5, 10000.0))
Select.groupBy.filterHaving
Purchase.select
.filter(_.count > 5)
.groupBy(_.productId)(_.sumBy(_.total))
.filter(_._2 > 100)
-
SELECT purchase0.product_id AS res_0, SUM(purchase0.total) AS res_1 FROM purchase purchase0 WHERE (purchase0.count > ?) GROUP BY purchase0.product_id HAVING (SUM(purchase0.total) > ?) -
Seq((1, 888.0), (5, 10000.0))
Select.groupBy.multipleKeys
Purchase.select.groupBy(x => (x.shippingInfoId, x.productId))(_.sumBy(_.total))
-
SELECT purchase0.shipping_info_id AS res_0_0, purchase0.product_id AS res_0_1, SUM(purchase0.total) AS res_1 FROM purchase purchase0 GROUP BY purchase0.shipping_info_id, purchase0.product_id -
Seq( ((1, 1), 888.0), ((1, 2), 900.0), ((1, 3), 15.7), ((2, 4), 493.8), ((2, 5), 10000.0), ((3, 1), 44.4), ((3, 6), 1.3) )
Select.groupBy.multipleKeysHaving
Purchase.select
.groupBy(x => (x.shippingInfoId, x.productId))(_.sumBy(_.total))
.filter(_._2 > 10)
.filter(_._2 < 100)
-
SELECT purchase0.shipping_info_id AS res_0_0, purchase0.product_id AS res_0_1, SUM(purchase0.total) AS res_1 FROM purchase purchase0 GROUP BY purchase0.shipping_info_id, purchase0.product_id HAVING (SUM(purchase0.total) > ?) AND (SUM(purchase0.total) < ?) -
Seq(((1, 3), 15.7), ((3, 1), 44.4))
Select.distinct.nondistinct
Normal queries can allow duplicates in the returned row values, as seen below.
You can use the .distinct operator (translates to SQl's SELECT DISTINCT)
to eliminate those duplicates
Purchase.select.map(_.shippingInfoId)
-
SELECT purchase0.shipping_info_id AS res FROM purchase purchase0 -
Seq(1, 1, 1, 2, 2, 3, 3)
Select.distinct.distinct
Purchase.select.map(_.shippingInfoId).distinct
-
SELECT DISTINCT purchase0.shipping_info_id AS res FROM purchase purchase0 -
Seq(1, 2, 3)
Select.distinct.subquery
Columns inside nested subqueries cannot be elided when SELECT DISTINCT is used
ShippingInfo.select.distinct.subquery.map(_.buyerId)
-
SELECT subquery0.buyer_id AS res FROM (SELECT DISTINCT shipping_info0.id AS id, shipping_info0.buyer_id AS buyer_id, shipping_info0.shipping_date AS shipping_date FROM shipping_info shipping_info0) subquery0 -
Seq(1, 2, 2)
Select.contains
ScalaSql's .contains method translates into SQL's IN syntax, e.g. here checking if a
subquery contains a column as part of a WHERE clause
Buyer.select.filter(b => ShippingInfo.select.map(_.buyerId).contains(b.id))
-
SELECT buyer0.id AS id, buyer0.name AS name, buyer0.date_of_birth AS date_of_birth FROM buyer buyer0 WHERE (buyer0.id IN (SELECT shipping_info1.buyer_id AS res FROM shipping_info shipping_info1)) -
Seq( Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")), Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")) )
Select.containsMultiple
ScalaSql's .contains can take a compound Scala value, which translates into
SQL's IN syntax on a tuple with multiple columns. e.g. this query uses that ability
to find the Buyer which has a shipment on a specific date, as an alternative
to doing a JOIN.
Buyer.select.filter(b =>
ShippingInfo.select
.map(s => (s.buyerId, s.shippingDate))
.contains((b.id, LocalDate.parse("2010-02-03")))
)
-
SELECT buyer0.id AS id, buyer0.name AS name, buyer0.date_of_birth AS date_of_birth FROM buyer buyer0 WHERE ((buyer0.id, ?) IN (SELECT shipping_info1.buyer_id AS res_0, shipping_info1.shipping_date AS res_1 FROM shipping_info shipping_info1)) -
Seq( Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")) )
Select.nonEmpty
ScalaSql's .nonEmpty and .isEmpty translates to SQL's EXISTS and NOT EXISTS syntax
Buyer.select
.map(b => (b.name, ShippingInfo.select.filter(_.buyerId `=` b.id).map(_.id).nonEmpty))
-
SELECT buyer0.name AS res_0, (EXISTS (SELECT shipping_info1.id AS res FROM shipping_info shipping_info1 WHERE (shipping_info1.buyer_id = buyer0.id))) AS res_1 FROM buyer buyer0 -
Seq(("James Bond", true), ("叉烧包", true), ("Li Haoyi", false))
Select.isEmpty
Buyer.select
.map(b => (b.name, ShippingInfo.select.filter(_.buyerId `=` b.id).map(_.id).isEmpty))
-
SELECT buyer0.name AS res_0, (NOT EXISTS (SELECT shipping_info1.id AS res FROM shipping_info shipping_info1 WHERE (shipping_info1.buyer_id = buyer0.id))) AS res_1 FROM buyer buyer0 -
Seq(("James Bond", false), ("叉烧包", false), ("Li Haoyi", true))
Select.nestedTuples
Queries can output arbitrarily nested tuples of Expr[T] and case class
instances of Foo[Expr], which will be de-serialized into nested tuples
of T and Foo[Sc]s. The AS aliases assigned to each column will contain
the path of indices and field names used to populate the final returned values
Buyer.select
.join(ShippingInfo)(_.id === _.buyerId)
.sortBy(_._1.id)
.map { case (b, s) => (b.id, (b, (s.id, s))) }
-
SELECT buyer0.id AS res_0, buyer0.id AS res_1_0_id, buyer0.name AS res_1_0_name, buyer0.date_of_birth AS res_1_0_date_of_birth, shipping_info1.id AS res_1_1_0, shipping_info1.id AS res_1_1_1_id, shipping_info1.buyer_id AS res_1_1_1_buyer_id, shipping_info1.shipping_date AS res_1_1_1_shipping_date FROM buyer buyer0 JOIN shipping_info shipping_info1 ON (buyer0.id = shipping_info1.buyer_id) ORDER BY res_1_0_id -
Seq[(Int, (Buyer[Sc], (Int, ShippingInfo[Sc])))]( ( 1, ( Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")), (2, ShippingInfo[Sc](2, 1, LocalDate.parse("2012-04-05"))) ) ), ( 2, ( Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), (1, ShippingInfo[Sc](1, 2, LocalDate.parse("2010-02-03"))) ) ), ( 2, ( Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), (3, ShippingInfo[Sc](3, 2, LocalDate.parse("2012-05-06"))) ) ) )
Select.case.when
ScalaSql's caseWhen method translates into SQL's CASE/WHEN/ELSE/END syntax,
allowing you to perform basic conditionals as part of your SQL query
Product.select.map(p =>
db.caseWhen(
(p.price > 200) -> (p.name + " EXPENSIVE"),
(p.price > 5) -> (p.name + " NORMAL"),
(p.price <= 5) -> (p.name + " CHEAP")
)
)
-
SELECT CASE WHEN (product0.price > ?) THEN (product0.name || ?) WHEN (product0.price > ?) THEN (product0.name || ?) WHEN (product0.price <= ?) THEN (product0.name || ?) END AS res FROM product product0 -
Seq( "Face Mask NORMAL", "Guitar EXPENSIVE", "Socks CHEAP", "Skate Board NORMAL", "Camera EXPENSIVE", "Cookie CHEAP" )
Select.case.else
Product.select.map(p =>
db.caseWhen(
(p.price > 200) -> (p.name + " EXPENSIVE"),
(p.price > 5) -> (p.name + " NORMAL")
).`else` { p.name + " UNKNOWN" }
)
-
SELECT CASE WHEN (product0.price > ?) THEN (product0.name || ?) WHEN (product0.price > ?) THEN (product0.name || ?) ELSE (product0.name || ?) END AS res FROM product product0 -
Seq( "Face Mask NORMAL", "Guitar EXPENSIVE", "Socks UNKNOWN", "Skate Board NORMAL", "Camera EXPENSIVE", "Cookie UNKNOWN" )
Join
inner JOINs, JOIN ONs, self-joins, LEFT/RIGHT/OUTER JOINs
Join.joinFilter
ScalaSql's .join or .join methods correspond to SQL JOIN and JOIN ... ON ....
These perform an inner join between two tables, with an optional ON predicate. You can
also .filter and .map the results of the join, making use of the columns joined from
the two tables
Buyer.select.join(ShippingInfo)(_.id `=` _.buyerId).filter(_._1.name `=` "叉烧包")
-
SELECT buyer0.id AS res_0_id, buyer0.name AS res_0_name, buyer0.date_of_birth AS res_0_date_of_birth, shipping_info1.id AS res_1_id, shipping_info1.buyer_id AS res_1_buyer_id, shipping_info1.shipping_date AS res_1_shipping_date FROM buyer buyer0 JOIN shipping_info shipping_info1 ON (buyer0.id = shipping_info1.buyer_id) WHERE (buyer0.name = ?) -
Seq( ( Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), ShippingInfo[Sc](1, 2, LocalDate.parse("2010-02-03")) ), ( Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), ShippingInfo[Sc](3, 2, LocalDate.parse("2012-05-06")) ) )
Join.joinFilterMap
Buyer.select
.join(ShippingInfo)(_.id `=` _.buyerId)
.filter(_._1.name `=` "James Bond")
.map(_._2.shippingDate)
-
SELECT shipping_info1.shipping_date AS res FROM buyer buyer0 JOIN shipping_info shipping_info1 ON (buyer0.id = shipping_info1.buyer_id) WHERE (buyer0.name = ?) -
Seq(LocalDate.parse("2012-04-05"))
Join.selfJoin
ScalaSql supports a "self join", where a table is joined with itself. This
is done by simply having the same table be on the left-hand-side and right-hand-side
of your .join or .join method. The two example self-joins below are trivial,
but illustrate how to do it in case you want to do a self-join in a more realistic setting.
Buyer.select.join(Buyer)(_.id `=` _.id)
-
SELECT buyer0.id AS res_0_id, buyer0.name AS res_0_name, buyer0.date_of_birth AS res_0_date_of_birth, buyer1.id AS res_1_id, buyer1.name AS res_1_name, buyer1.date_of_birth AS res_1_date_of_birth FROM buyer buyer0 JOIN buyer buyer1 ON (buyer0.id = buyer1.id) -
Seq( ( Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")), Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")) ), ( Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")) ), ( Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")), Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")) ) )
Join.selfJoin2
Buyer.select.join(Buyer)(_.id <> _.id)
-
SELECT buyer0.id AS res_0_id, buyer0.name AS res_0_name, buyer0.date_of_birth AS res_0_date_of_birth, buyer1.id AS res_1_id, buyer1.name AS res_1_name, buyer1.date_of_birth AS res_1_date_of_birth FROM buyer buyer0 JOIN buyer buyer1 ON (buyer0.id <> buyer1.id) -
Seq( ( Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")), Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")) ), ( Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")), Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")) ), ( Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")) ), ( Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")) ), ( Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")), Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")) ), ( Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")), Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")) ) )
Join.mapForGroupBy
Using non-trivial queries in the for-comprehension may result in subqueries
being generated
for ((name, dateOfBirth) <- Buyer.select.groupBy(_.name)(_.minBy(_.dateOfBirth)))
yield (name, dateOfBirth)
-
SELECT buyer0.name AS res_0, MIN(buyer0.date_of_birth) AS res_1 FROM buyer buyer0 GROUP BY buyer0.name -
Seq( ("James Bond", LocalDate.parse("2001-02-03")), ("Li Haoyi", LocalDate.parse("1965-08-09")), ("叉烧包", LocalDate.parse("1923-11-12")) )
Join.leftJoin
ScalaSql supports LEFT JOINs, RIGHT JOINs and OUTER JOINs via the
.leftJoin/.rightJoin/.outerJoin methods
Buyer.select.leftJoin(ShippingInfo)(_.id `=` _.buyerId)
-
SELECT buyer0.id AS res_0_id, buyer0.name AS res_0_name, buyer0.date_of_birth AS res_0_date_of_birth, shipping_info1.id AS res_1_id, shipping_info1.buyer_id AS res_1_buyer_id, shipping_info1.shipping_date AS res_1_shipping_date FROM buyer buyer0 LEFT JOIN shipping_info shipping_info1 ON (buyer0.id = shipping_info1.buyer_id) -
Seq( ( Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")), Some(ShippingInfo[Sc](2, 1, LocalDate.parse("2012-04-05"))) ), ( Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), Some(ShippingInfo[Sc](1, 2, LocalDate.parse("2010-02-03"))) ), ( Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), Some(ShippingInfo[Sc](3, 2, LocalDate.parse("2012-05-06"))) ), (Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")), None) )
Join.leftJoinMap
.leftJoins return a JoinNullable[Q] for the right hand entry. This is similar
to Option[Q] in Scala, supports a similar set of operations (e.g. .map),
and becomes an Option[Q] after the query is executed
Buyer.select
.leftJoin(ShippingInfo)(_.id `=` _.buyerId)
.map { case (b, si) => (b.name, si.map(_.shippingDate)) }
-
SELECT buyer0.name AS res_0, shipping_info1.shipping_date AS res_1 FROM buyer buyer0 LEFT JOIN shipping_info shipping_info1 ON (buyer0.id = shipping_info1.buyer_id) -
Seq( ("James Bond", Some(LocalDate.parse("2012-04-05"))), ("Li Haoyi", None), ("叉烧包", Some(LocalDate.parse("2010-02-03"))), ("叉烧包", Some(LocalDate.parse("2012-05-06"))) )
Join.leftJoinMap2
Buyer.select
.leftJoin(ShippingInfo)(_.id `=` _.buyerId)
.map { case (b, si) => (b.name, si.map(s => (s.id, s.shippingDate))) }
-
SELECT buyer0.name AS res_0, shipping_info1.id AS res_1_0, shipping_info1.shipping_date AS res_1_1 FROM buyer buyer0 LEFT JOIN shipping_info shipping_info1 ON (buyer0.id = shipping_info1.buyer_id) -
Seq( ("James Bond", Some((2, LocalDate.parse("2012-04-05")))), ("Li Haoyi", None), ("叉烧包", Some((1, LocalDate.parse("2010-02-03")))), ("叉烧包", Some((3, LocalDate.parse("2012-05-06")))) )
Join.leftJoinExpr
JoinNullable[Expr[T]]s can be implicitly used as Expr[Option[T]]s. This allows
them to participate in any database query logic than any other Expr[Option[T]]s
can participate in, such as being used as sort key or in computing return values
(below).
Buyer.select
.leftJoin(ShippingInfo)(_.id `=` _.buyerId)
.map { case (b, si) => (b.name, si.map(_.shippingDate)) }
.sortBy(_._2)
-
SELECT buyer0.name AS res_0, shipping_info1.shipping_date AS res_1 FROM buyer buyer0 LEFT JOIN shipping_info shipping_info1 ON (buyer0.id = shipping_info1.buyer_id) ORDER BY res_1 -
Seq[(String, Option[LocalDate])]( ("Li Haoyi", None), ("叉烧包", Some(LocalDate.parse("2010-02-03"))), ("James Bond", Some(LocalDate.parse("2012-04-05"))), ("叉烧包", Some(LocalDate.parse("2012-05-06"))) )
Join.leftJoinIsEmpty
You can use the .isEmpty method on JoinNullable[T] to check whether a joined table
is NULL, by specifying a specific non-nullable column to test against.
Buyer.select
.leftJoin(ShippingInfo)(_.id `=` _.buyerId)
.map { case (b, si) => (b.name, si.nonEmpty(_.id)) }
.distinct
.sortBy(_._1)
-
SELECT DISTINCT buyer0.name AS res_0, (shipping_info1.id IS NOT NULL) AS res_1 FROM buyer buyer0 LEFT JOIN shipping_info shipping_info1 ON (buyer0.id = shipping_info1.buyer_id) ORDER BY res_0 -
Seq( ("James Bond", true), ("Li Haoyi", false), ("叉烧包", true) )
Join.leftJoinExpr2
Buyer.select
.leftJoin(ShippingInfo)(_.id `=` _.buyerId)
.map { case (b, si) => (b.name, si.map(_.shippingDate) > b.dateOfBirth) }
-
SELECT buyer0.name AS res_0, (shipping_info1.shipping_date > buyer0.date_of_birth) AS res_1 FROM buyer buyer0 LEFT JOIN shipping_info shipping_info1 ON (buyer0.id = shipping_info1.buyer_id) -
Seq( ("James Bond", true), ("Li Haoyi", false), ("叉烧包", true), ("叉烧包", true) )
Join.leftJoinExprExplicit
The conversion from JoinNullable[T] to Expr[Option[T]] can also be performed
explicitly via JoinNullable.toExpr(...)
Buyer.select
.leftJoin(ShippingInfo)(_.id `=` _.buyerId)
.map { case (b, si) =>
(b.name, JoinNullable.toExpr(si.map(_.shippingDate)) > b.dateOfBirth)
}
-
SELECT buyer0.name AS res_0, (shipping_info1.shipping_date > buyer0.date_of_birth) AS res_1 FROM buyer buyer0 LEFT JOIN shipping_info shipping_info1 ON (buyer0.id = shipping_info1.buyer_id) -
Seq( ("James Bond", true), ("Li Haoyi", false), ("叉烧包", true), ("叉烧包", true) )
Join.rightJoin
ShippingInfo.select.rightJoin(Buyer)(_.buyerId `=` _.id)
-
SELECT shipping_info0.id AS res_0_id, shipping_info0.buyer_id AS res_0_buyer_id, shipping_info0.shipping_date AS res_0_shipping_date, buyer1.id AS res_1_id, buyer1.name AS res_1_name, buyer1.date_of_birth AS res_1_date_of_birth FROM shipping_info shipping_info0 RIGHT JOIN buyer buyer1 ON (shipping_info0.buyer_id = buyer1.id) -
Seq( ( Some(ShippingInfo[Sc](2, 1, LocalDate.parse("2012-04-05"))), Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")) ), ( Some(ShippingInfo[Sc](1, 2, LocalDate.parse("2010-02-03"))), Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")) ), ( Some(ShippingInfo[Sc](3, 2, LocalDate.parse("2012-05-06"))), Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")) ), (None, Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09"))) )
Join.outerJoin
ShippingInfo.select.outerJoin(Buyer)(_.buyerId `=` _.id)
-
SELECT shipping_info0.id AS res_0_id, shipping_info0.buyer_id AS res_0_buyer_id, shipping_info0.shipping_date AS res_0_shipping_date, buyer1.id AS res_1_id, buyer1.name AS res_1_name, buyer1.date_of_birth AS res_1_date_of_birth FROM shipping_info shipping_info0 FULL OUTER JOIN buyer buyer1 ON (shipping_info0.buyer_id = buyer1.id) -
Seq( ( Option(ShippingInfo[Sc](2, 1, LocalDate.parse("2012-04-05"))), Option(Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03"))) ), ( Option(ShippingInfo[Sc](1, 2, LocalDate.parse("2010-02-03"))), Option(Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12"))) ), ( Option(ShippingInfo[Sc](3, 2, LocalDate.parse("2012-05-06"))), Option(Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12"))) ), (Option.empty, Option(Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")))) )
Join.crossJoin
.crossJoin can be used to generate a SQL CROSS JOIN, which allows you
to perform a JOIN with an ON clause in a consistent way across databases
Buyer.select
.crossJoin(ShippingInfo)
.filter { case (b, s) => b.id `=` s.buyerId }
.map { case (b, s) => (b.name, s.shippingDate) }
-
SELECT buyer0.name AS res_0, shipping_info1.shipping_date AS res_1 FROM buyer buyer0 CROSS JOIN shipping_info shipping_info1 WHERE (buyer0.id = shipping_info1.buyer_id) -
Seq( ("James Bond", LocalDate.parse("2012-04-05")), ("叉烧包", LocalDate.parse("2010-02-03")), ("叉烧包", LocalDate.parse("2012-05-06")) )
FlatJoin
inner JOINs, JOIN ONs, self-joins, LEFT/RIGHT/OUTER JOINs
FlatJoin.join
"flat" joins using for-comprehensions are allowed. These allow you to
"flatten out" the nested tuples you get from normal .join clauses,
letting you write natural looking queries without deeply nested tuples.
for {
b <- Buyer.select
si <- ShippingInfo.join(_.buyerId `=` b.id)
} yield (b.name, si.shippingDate)
-
SELECT buyer0.name AS res_0, shipping_info1.shipping_date AS res_1 FROM buyer buyer0 JOIN shipping_info shipping_info1 ON (shipping_info1.buyer_id = buyer0.id) -
Seq( ("James Bond", LocalDate.parse("2012-04-05")), ("叉烧包", LocalDate.parse("2010-02-03")), ("叉烧包", LocalDate.parse("2012-05-06")) )
FlatJoin.join3
"flat" joins using for-comprehensions can have multiple .join clauses that
translate to SQL JOIN ONs, as well as if clauses that translate to SQL
WHERE clauses. This example uses multiple flat .joins together with if
clauses to query the products purchased by the user "Li Haoyi" that have
a price more than 1.0 dollars
for {
b <- Buyer.select
if b.name === "Li Haoyi"
si <- ShippingInfo.join(_.id `=` b.id)
pu <- Purchase.join(_.shippingInfoId `=` si.id)
pr <- Product.join(_.id `=` pu.productId)
if pr.price > 1.0
} yield (b.name, pr.name, pr.price)
-
SELECT buyer0.name AS res_0, product3.name AS res_1, product3.price AS res_2 FROM buyer buyer0 JOIN shipping_info shipping_info1 ON (shipping_info1.id = buyer0.id) JOIN purchase purchase2 ON (purchase2.shipping_info_id = shipping_info1.id) JOIN product product3 ON (product3.id = purchase2.product_id) WHERE (buyer0.name = ?) AND (product3.price > ?) -
Seq( ("Li Haoyi", "Face Mask", 8.88) )
FlatJoin.leftJoin
Flat joins can also support .leftJoins, where the table being joined
is given to you as a JoinNullable[T]
for {
b <- Buyer.select
si <- ShippingInfo.leftJoin(_.buyerId `=` b.id)
} yield (b.name, si.map(_.shippingDate))
-
SELECT buyer0.name AS res_0, shipping_info1.shipping_date AS res_1 FROM buyer buyer0 LEFT JOIN shipping_info shipping_info1 ON (shipping_info1.buyer_id = buyer0.id) -
Seq( ("James Bond", Some(LocalDate.parse("2012-04-05"))), ("Li Haoyi", None), ("叉烧包", Some(LocalDate.parse("2010-02-03"))), ("叉烧包", Some(LocalDate.parse("2012-05-06"))) )
FlatJoin.flatMap
You can also perform inner joins via flatMap, either by directly
calling .flatMap or via for-comprehensions as below. This can help
reduce the boilerplate when dealing with lots of joins.
Buyer.select
.flatMap(b => ShippingInfo.crossJoin().map((b, _)))
.filter { case (b, s) => b.id `=` s.buyerId && b.name `=` "James Bond" }
.map(_._2.shippingDate)
-
SELECT shipping_info1.shipping_date AS res FROM buyer buyer0 CROSS JOIN shipping_info shipping_info1 WHERE ((buyer0.id = shipping_info1.buyer_id) AND (buyer0.name = ?)) -
Seq(LocalDate.parse("2012-04-05"))
FlatJoin.flatMapFor
You can also perform inner joins via `flatMap
for {
b <- Buyer.select
s <- ShippingInfo.crossJoin()
if b.id `=` s.buyerId && b.name `=` "James Bond"
} yield s.shippingDate
-
SELECT shipping_info1.shipping_date AS res FROM buyer buyer0 CROSS JOIN shipping_info shipping_info1 WHERE ((buyer0.id = shipping_info1.buyer_id) AND (buyer0.name = ?)) -
Seq(LocalDate.parse("2012-04-05"))
FlatJoin.flatMapForFilter
for {
b <- Buyer.select.filter(_.name `=` "James Bond")
s <- ShippingInfo.crossJoin().filter(b.id `=` _.buyerId)
} yield s.shippingDate
-
SELECT shipping_info1.shipping_date AS res FROM buyer buyer0 CROSS JOIN shipping_info shipping_info1 WHERE (buyer0.name = ?) AND (buyer0.id = shipping_info1.buyer_id) -
Seq(LocalDate.parse("2012-04-05"))
FlatJoin.flatMapForJoin
Using queries with joins in a for-comprehension is supported, with the
generated JOINs being added to the FROM clause generated by the .flatMap.
for {
(b, si) <- Buyer.select.join(ShippingInfo)(_.id `=` _.buyerId)
(pu, pr) <- Purchase.select.join(Product)(_.productId `=` _.id).crossJoin()
if si.id `=` pu.shippingInfoId
} yield (b.name, pr.name)
-
SELECT buyer0.name AS res_0, subquery2.res_1_name AS res_1 FROM buyer buyer0 JOIN shipping_info shipping_info1 ON (buyer0.id = shipping_info1.buyer_id) CROSS JOIN (SELECT purchase2.shipping_info_id AS res_0_shipping_info_id, product3.name AS res_1_name FROM purchase purchase2 JOIN product product3 ON (purchase2.product_id = product3.id)) subquery2 WHERE (shipping_info1.id = subquery2.res_0_shipping_info_id) -
Seq( ("James Bond", "Camera"), ("James Bond", "Skate Board"), ("叉烧包", "Cookie"), ("叉烧包", "Face Mask"), ("叉烧包", "Face Mask"), ("叉烧包", "Guitar"), ("叉烧包", "Socks") )
FlatJoin.flatMapForGroupBy
Using non-trivial queries in the for-comprehension may result in subqueries
being generated
for {
(name, dateOfBirth) <- Buyer.select.groupBy(_.name)(_.minBy(_.dateOfBirth))
shippingInfo <- ShippingInfo.crossJoin()
} yield (name, dateOfBirth, shippingInfo.id, shippingInfo.shippingDate)
-
SELECT subquery0.res_0 AS res_0, subquery0.res_1 AS res_1, shipping_info1.id AS res_2, shipping_info1.shipping_date AS res_3 FROM (SELECT buyer0.name AS res_0, MIN(buyer0.date_of_birth) AS res_1 FROM buyer buyer0 GROUP BY buyer0.name) subquery0 CROSS JOIN shipping_info shipping_info1 -
Seq( ("James Bond", LocalDate.parse("2001-02-03"), 1, LocalDate.parse("2010-02-03")), ("James Bond", LocalDate.parse("2001-02-03"), 2, LocalDate.parse("2012-04-05")), ("James Bond", LocalDate.parse("2001-02-03"), 3, LocalDate.parse("2012-05-06")), ("Li Haoyi", LocalDate.parse("1965-08-09"), 1, LocalDate.parse("2010-02-03")), ("Li Haoyi", LocalDate.parse("1965-08-09"), 2, LocalDate.parse("2012-04-05")), ("Li Haoyi", LocalDate.parse("1965-08-09"), 3, LocalDate.parse("2012-05-06")), ("叉烧包", LocalDate.parse("1923-11-12"), 1, LocalDate.parse("2010-02-03")), ("叉烧包", LocalDate.parse("1923-11-12"), 2, LocalDate.parse("2012-04-05")), ("叉烧包", LocalDate.parse("1923-11-12"), 3, LocalDate.parse("2012-05-06")) )
FlatJoin.flatMapForGroupBy2
Using non-trivial queries in the for-comprehension may result in subqueries
being generated
for {
(name, dateOfBirth) <- Buyer.select.groupBy(_.name)(_.minBy(_.dateOfBirth))
(shippingInfoId, shippingDate) <- ShippingInfo.select
.groupBy(_.id)(_.minBy(_.shippingDate))
.crossJoin()
} yield (name, dateOfBirth, shippingInfoId, shippingDate)
-
SELECT subquery0.res_0 AS res_0, subquery0.res_1 AS res_1, subquery1.res_0 AS res_2, subquery1.res_1 AS res_3 FROM (SELECT buyer0.name AS res_0, MIN(buyer0.date_of_birth) AS res_1 FROM buyer buyer0 GROUP BY buyer0.name) subquery0 CROSS JOIN (SELECT shipping_info1.id AS res_0, MIN(shipping_info1.shipping_date) AS res_1 FROM shipping_info shipping_info1 GROUP BY shipping_info1.id) subquery1 -
Seq( ("James Bond", LocalDate.parse("2001-02-03"), 1, LocalDate.parse("2010-02-03")), ("James Bond", LocalDate.parse("2001-02-03"), 2, LocalDate.parse("2012-04-05")), ("James Bond", LocalDate.parse("2001-02-03"), 3, LocalDate.parse("2012-05-06")), ("Li Haoyi", LocalDate.parse("1965-08-09"), 1, LocalDate.parse("2010-02-03")), ("Li Haoyi", LocalDate.parse("1965-08-09"), 2, LocalDate.parse("2012-04-05")), ("Li Haoyi", LocalDate.parse("1965-08-09"), 3, LocalDate.parse("2012-05-06")), ("叉烧包", LocalDate.parse("1923-11-12"), 1, LocalDate.parse("2010-02-03")), ("叉烧包", LocalDate.parse("1923-11-12"), 2, LocalDate.parse("2012-04-05")), ("叉烧包", LocalDate.parse("1923-11-12"), 3, LocalDate.parse("2012-05-06")) )
FlatJoin.flatMapForCompound
Using non-trivial queries in the for-comprehension may result in subqueries
being generated
for {
b <- Buyer.select.sortBy(_.id).asc.take(1)
si <- ShippingInfo.select.sortBy(_.id).asc.take(1).crossJoin()
} yield (b.name, si.shippingDate)
-
SELECT subquery0.name AS res_0, subquery1.shipping_date AS res_1 FROM (SELECT buyer0.id AS id, buyer0.name AS name FROM buyer buyer0 ORDER BY id ASC LIMIT ?) subquery0 CROSS JOIN (SELECT shipping_info1.id AS id, shipping_info1.shipping_date AS shipping_date FROM shipping_info shipping_info1 ORDER BY id ASC LIMIT ?) subquery1 -
Seq( ("James Bond", LocalDate.parse("2010-02-03")) )
Insert
Basic INSERT operations
Insert.single.values
Table.insert.values with a single value inserts a single row into the given table
Buyer.insert.values(
Buyer[Sc](4, "test buyer", LocalDate.parse("2023-09-09"))
)
-
INSERT INTO buyer (id, name, date_of_birth) VALUES (?, ?, ?) -
1
Buyer.select.filter(_.name `=` "test buyer")
-
Seq(Buyer[Sc](4, "test buyer", LocalDate.parse("2023-09-09")))
Insert.single.skipped
You can pass in one or more columns to .skipColumns to avoid inserting them.
This is useful for columns where you want to rely on the value being
auto-generated by the database.
Buyer.insert
.values(
Buyer[Sc](-1, "test buyer", LocalDate.parse("2023-09-09"))
)
.skipColumns(_.id)
-
INSERT INTO buyer (name, date_of_birth) VALUES (?, ?) -
1
Buyer.select.filter(_.name `=` "test buyer")
-
Seq(Buyer[Sc](4, "test buyer", LocalDate.parse("2023-09-09")))
Insert.single.columns
Table.insert.columns inserts a single row into the given table, with the specified
columns assigned to the given values, and any non-specified columns left NULL
or assigned to their default values
Buyer.insert.columns(
_.name := "test buyer",
_.dateOfBirth := LocalDate.parse("2023-09-09"),
_.id := 4
)
-
INSERT INTO buyer (name, date_of_birth, id) VALUES (?, ?, ?) -
1
Buyer.select.filter(_.name `=` "test buyer")
-
Seq(Buyer[Sc](4, "test buyer", LocalDate.parse("2023-09-09")))
Insert.single.partial
Buyer.insert
.columns(_.name := "test buyer", _.dateOfBirth := LocalDate.parse("2023-09-09"))
-
INSERT INTO buyer (name, date_of_birth) VALUES (?, ?) -
1
Buyer.select.filter(_.name `=` "test buyer")
-
Seq(Buyer[Sc](4, "test buyer", LocalDate.parse("2023-09-09")))
Insert.batch.values
You can insert multiple rows at once by passing them to Buyer.insert.values
Buyer.insert.values(
Buyer[Sc](4, "test buyer A", LocalDate.parse("2001-04-07")),
Buyer[Sc](5, "test buyer B", LocalDate.parse("2002-05-08")),
Buyer[Sc](6, "test buyer C", LocalDate.parse("2003-06-09"))
)
-
INSERT INTO buyer (id, name, date_of_birth) VALUES (?, ?, ?), (?, ?, ?), (?, ?, ?) -
3
Buyer.select
-
Seq( Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")), Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")), // id=4,5,6 comes from auto increment Buyer[Sc](4, "test buyer A", LocalDate.parse("2001-04-07")), Buyer[Sc](5, "test buyer B", LocalDate.parse("2002-05-08")), Buyer[Sc](6, "test buyer C", LocalDate.parse("2003-06-09")) )
Insert.batch.partial
Buyer.insert.batched(_.name, _.dateOfBirth)(
("test buyer A", LocalDate.parse("2001-04-07")),
("test buyer B", LocalDate.parse("2002-05-08")),
("test buyer C", LocalDate.parse("2003-06-09"))
)
-
INSERT INTO buyer (name, date_of_birth) VALUES (?, ?), (?, ?), (?, ?) -
3
Buyer.select
-
Seq( Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")), Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")), // id=4,5,6 comes from auto increment Buyer[Sc](4, "test buyer A", LocalDate.parse("2001-04-07")), Buyer[Sc](5, "test buyer B", LocalDate.parse("2002-05-08")), Buyer[Sc](6, "test buyer C", LocalDate.parse("2003-06-09")) )
Insert.select.caseclass
Table.insert.select inserts rows into the given table based on the given Table.select
clause, and translates directly into SQL's INSERT INTO ... SELECT syntax.
Buyer.insert.select(
identity,
Buyer.select
.filter(_.name <> "Li Haoyi")
.map(b => b.copy(id = b.id + Buyer.select.maxBy(_.id)))
)
-
INSERT INTO buyer (id, name, date_of_birth) SELECT (buyer0.id + (SELECT MAX(buyer1.id) AS res FROM buyer buyer1)) AS id, buyer0.name AS name, buyer0.date_of_birth AS date_of_birth FROM buyer buyer0 WHERE (buyer0.name <> ?) -
2
Buyer.select
-
Seq( Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")), Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")), Buyer[Sc](4, "James Bond", LocalDate.parse("2001-02-03")), Buyer[Sc](5, "叉烧包", LocalDate.parse("1923-11-12")) )
Insert.select.simple
Buyer.insert.select(
x => (x.name, x.dateOfBirth),
Buyer.select.map(x => (x.name, x.dateOfBirth)).filter(_._1 <> "Li Haoyi")
)
-
INSERT INTO buyer (name, date_of_birth) SELECT buyer0.name AS res_0, buyer0.date_of_birth AS res_1 FROM buyer buyer0 WHERE (buyer0.name <> ?) -
2
Buyer.select
-
Seq( Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")), Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")), // id=4,5 comes from auto increment, 6 is filtered out in the select Buyer[Sc](4, "James Bond", LocalDate.parse("2001-02-03")), Buyer[Sc](5, "叉烧包", LocalDate.parse("1923-11-12")) )
Update
Basic UPDATE queries
Update.update
Table.update takes a predicate specifying the rows to update, and a
.set clause that allows you to specify the values assigned to columns
on those rows
Buyer
.update(_.name `=` "James Bond")
.set(_.dateOfBirth := LocalDate.parse("2019-04-07"))
-
UPDATE buyer SET date_of_birth = ? WHERE (buyer.name = ?) -
1
Buyer.select.filter(_.name `=` "James Bond").map(_.dateOfBirth).single
-
LocalDate.parse("2019-04-07")
Buyer.select.filter(_.name `=` "Li Haoyi").map(_.dateOfBirth).single
-
LocalDate.parse("1965-08-09" /* not updated */ )
Update.bulk
The predicate to Table.update is mandatory, to avoid anyone forgetting to
provide one and accidentally bulk-updating all rows in their table. If you
really do want to update all rows in the table, you can provide the predicate _ => true
Buyer.update(_ => true).set(_.dateOfBirth := LocalDate.parse("2019-04-07"))
-
UPDATE buyer SET date_of_birth = ? -
3
Buyer.select.filter(_.name `=` "James Bond").map(_.dateOfBirth).single
-
LocalDate.parse("2019-04-07")
Buyer.select.filter(_.name `=` "Li Haoyi").map(_.dateOfBirth).single
-
LocalDate.parse("2019-04-07")
Update.multiple
This example shows how to update multiple columns in a single Table.update call
Buyer
.update(_.name `=` "James Bond")
.set(_.dateOfBirth := LocalDate.parse("2019-04-07"), _.name := "John Dee")
-
UPDATE buyer SET date_of_birth = ?, name = ? WHERE (buyer.name = ?) -
1
Buyer.select.filter(_.name `=` "James Bond").map(_.dateOfBirth)
-
Seq[LocalDate]( /* not found due to rename */ )
Buyer.select.filter(_.name `=` "John Dee").map(_.dateOfBirth)
-
Seq(LocalDate.parse("2019-04-07"))
Update.dynamic
The values assigned to columns in Table.update can also be computed Expr[T]s,
not just literal Scala constants. This example shows how to to update the name of
the row for James Bond with it's existing name in uppercase
Buyer.update(_.name `=` "James Bond").set(c => c.name := c.name.toUpperCase)
-
UPDATE buyer SET name = UPPER(buyer.name) WHERE (buyer.name = ?) -
1
Buyer.select.filter(_.name `=` "James Bond").map(_.dateOfBirth)
-
Seq[LocalDate]( /* not found due to rename */ )
Buyer.select.filter(_.name `=` "JAMES BOND").map(_.dateOfBirth)
-
Seq(LocalDate.parse("2001-02-03"))
Delete
Basic DELETE operations
Delete.single
Table.delete takes a mandatory predicate specifying what rows you want to delete.
The most common case is to specify the ID of the row you want to delete
Purchase.delete(_.id `=` 2)
-
DELETE FROM purchase WHERE (purchase.id = ?) -
1
Purchase.select
-
Seq( Purchase[Sc](id = 1, shippingInfoId = 1, productId = 1, count = 100, total = 888.0), // id==2 got deleted Purchase[Sc](id = 3, shippingInfoId = 1, productId = 3, count = 5, total = 15.7), Purchase[Sc](id = 4, shippingInfoId = 2, productId = 4, count = 4, total = 493.8), Purchase[Sc](id = 5, shippingInfoId = 2, productId = 5, count = 10, total = 10000.0), Purchase[Sc](id = 6, shippingInfoId = 3, productId = 1, count = 5, total = 44.4), Purchase[Sc](id = 7, shippingInfoId = 3, productId = 6, count = 13, total = 1.3) )
Delete.multiple
Although specifying a single ID to delete is the most common case, you can pass in arbitrary predicates, e.g. in this example deleting all rows except for the one with a particular ID
Purchase.delete(_.id <> 2)
-
DELETE FROM purchase WHERE (purchase.id <> ?) -
6
Purchase.select
-
Seq(Purchase[Sc](id = 2, shippingInfoId = 1, productId = 2, count = 3, total = 900.0))
Delete.all
If you actually want to delete all rows in the table, you can explicitly
pass in the predicate _ => true
Purchase.delete(_ => true)
-
DELETE FROM purchase -
7
Purchase.select
-
Seq[Purchase[Sc]]( // all Deleted )
CompoundSelect
Compound SELECT operations: sort, take, drop, union, unionAll, etc.
CompoundSelect.sort.simple
ScalaSql's .sortBy method translates into SQL ORDER BY
Product.select.sortBy(_.price).map(_.name)
-
SELECT product0.name AS res FROM product product0 ORDER BY product0.price -
Seq("Cookie", "Socks", "Face Mask", "Skate Board", "Guitar", "Camera")
CompoundSelect.sort.twice
If you want to sort by multiple columns, you can call .sortBy multiple times,
each with its own call to .asc or .desc. Note that the rightmost call to .sortBy
takes precedence, following the Scala collections .sortBy semantics, and so the
right-most .sortBy in ScalaSql becomes the left-most entry in the SQL ORDER BY clause
Purchase.select.sortBy(_.productId).asc.sortBy(_.shippingInfoId).desc
-
SELECT purchase0.id AS id, purchase0.shipping_info_id AS shipping_info_id, purchase0.product_id AS product_id, purchase0.count AS count, purchase0.total AS total FROM purchase purchase0 ORDER BY shipping_info_id DESC, product_id ASC -
Seq( Purchase[Sc](6, 3, 1, 5, 44.4), Purchase[Sc](7, 3, 6, 13, 1.3), Purchase[Sc](4, 2, 4, 4, 493.8), Purchase[Sc](5, 2, 5, 10, 10000.0), Purchase[Sc](1, 1, 1, 100, 888.0), Purchase[Sc](2, 1, 2, 3, 900.0), Purchase[Sc](3, 1, 3, 5, 15.7) )
CompoundSelect.sort.sortLimit
ScalaSql also supports various combinations of .take and .drop, translating to SQL
LIMIT or OFFSET
Product.select.sortBy(_.price).map(_.name).take(2)
-
SELECT product0.name AS res FROM product product0 ORDER BY product0.price LIMIT ? -
Seq("Cookie", "Socks")
CompoundSelect.sort.sortOffset
Product.select.sortBy(_.price).map(_.name).drop(2)
-
SELECT product0.name AS res FROM product product0 ORDER BY product0.price OFFSET ? -
Seq("Face Mask", "Skate Board", "Guitar", "Camera")
CompoundSelect.sort.sortLimitTwiceHigher
Note that .drop and .take follow Scala collections' semantics, so calling e.g. .take
multiple times takes the value of the smallest .take, while calling .drop multiple
times accumulates the total amount dropped
Product.select.sortBy(_.price).map(_.name).take(2).take(3)
-
SELECT product0.name AS res FROM product product0 ORDER BY product0.price LIMIT ? -
Seq("Cookie", "Socks")
CompoundSelect.sort.sortLimitTwiceLower
Product.select.sortBy(_.price).map(_.name).take(2).take(1)
-
SELECT product0.name AS res FROM product product0 ORDER BY product0.price LIMIT ? -
Seq("Cookie")
CompoundSelect.sort.sortLimitOffset
Product.select.sortBy(_.price).map(_.name).drop(2).take(2)
-
SELECT product0.name AS res FROM product product0 ORDER BY product0.price LIMIT ? OFFSET ? -
Seq("Face Mask", "Skate Board")
CompoundSelect.sort.sortLimitOffsetTwice
Product.select.sortBy(_.price).map(_.name).drop(2).drop(2).take(1)
-
SELECT product0.name AS res FROM product product0 ORDER BY product0.price LIMIT ? OFFSET ? -
Seq("Guitar")
CompoundSelect.sort.sortOffsetLimit
Product.select.sortBy(_.price).map(_.name).drop(2).take(2)
-
SELECT product0.name AS res FROM product product0 ORDER BY product0.price LIMIT ? OFFSET ? -
Seq("Face Mask", "Skate Board")
CompoundSelect.distinct
ScalaSql's .distinct translates to SQL's SELECT DISTINCT
Purchase.select.sortBy(_.total).desc.take(3).map(_.shippingInfoId).distinct
-
SELECT DISTINCT subquery0.res AS res FROM (SELECT purchase0.shipping_info_id AS res FROM purchase purchase0 ORDER BY purchase0.total DESC LIMIT ?) subquery0 -
Seq(1, 2)
CompoundSelect.flatMap
Many operations in SQL cannot be done in certain orders, unless you move part of the logic into
a subquery. ScalaSql does this automatically for you, e.g. doing a flatMap, .sumBy, or
.aggregate after a .sortBy/.take, the LHS .sortBy/.take is automatically extracted
into a subquery
Purchase.select.sortBy(_.total).desc.take(3).flatMap { p =>
Product.crossJoin().filter(_.id === p.productId).map(_.name)
}
-
SELECT product1.name AS res FROM (SELECT purchase0.product_id AS product_id, purchase0.total AS total FROM purchase purchase0 ORDER BY total DESC LIMIT ?) subquery0 CROSS JOIN product product1 WHERE (product1.id = subquery0.product_id) -
Seq("Camera", "Face Mask", "Guitar")
CompoundSelect.sumBy
Purchase.select.sortBy(_.total).desc.take(3).sumBy(_.total)
-
SELECT SUM(subquery0.total) AS res FROM (SELECT purchase0.total AS total FROM purchase purchase0 ORDER BY total DESC LIMIT ?) subquery0 -
11788.0
CompoundSelect.aggregate
Purchase.select
.sortBy(_.total)
.desc
.take(3)
.aggregate(p => (p.sumBy(_.total), p.avgBy(_.total)))
-
SELECT SUM(subquery0.total) AS res_0, AVG(subquery0.total) AS res_1 FROM (SELECT purchase0.total AS total FROM purchase purchase0 ORDER BY total DESC LIMIT ?) subquery0 -
(11788.0, 3929.0)
CompoundSelect.union
ScalaSql's .union/.unionAll/.intersect/.except translate into SQL's
UNION/UNION ALL/INTERSECT/EXCEPT.
Product.select
.map(_.name.toLowerCase)
.union(Product.select.map(_.kebabCaseName.toLowerCase))
-
SELECT LOWER(product0.name) AS res FROM product product0 UNION SELECT LOWER(product0.kebab_case_name) AS res FROM product product0 -
Seq( "camera", "cookie", "face mask", "face-mask", "guitar", "skate board", "skate-board", "socks" )
CompoundSelect.unionAll
Product.select
.map(_.name.toLowerCase)
.unionAll(Product.select.map(_.kebabCaseName.toLowerCase))
-
SELECT LOWER(product0.name) AS res FROM product product0 UNION ALL SELECT LOWER(product0.kebab_case_name) AS res FROM product product0 -
Seq( "face mask", "guitar", "socks", "skate board", "camera", "cookie", "face-mask", "guitar", "socks", "skate-board", "camera", "cookie" )
CompoundSelect.intersect
Product.select
.map(_.name.toLowerCase)
.intersect(Product.select.map(_.kebabCaseName.toLowerCase))
-
SELECT LOWER(product0.name) AS res FROM product product0 INTERSECT SELECT LOWER(product0.kebab_case_name) AS res FROM product product0 -
Seq("camera", "cookie", "guitar", "socks")
CompoundSelect.except
Product.select
.map(_.name.toLowerCase)
.except(Product.select.map(_.kebabCaseName.toLowerCase))
-
SELECT LOWER(product0.name) AS res FROM product product0 EXCEPT SELECT LOWER(product0.kebab_case_name) AS res FROM product product0 -
Seq("face mask", "skate board")
CompoundSelect.unionAllUnionSort
Performing a .sortBy after .union or .unionAll applies the sort
to both sides of the union/unionAll, behaving identically to Scala or SQL
Product.select
.map(_.name.toLowerCase)
.unionAll(Buyer.select.map(_.name.toLowerCase))
.union(Product.select.map(_.kebabCaseName.toLowerCase))
.sortBy(identity)
-
SELECT LOWER(product0.name) AS res FROM product product0 UNION ALL SELECT LOWER(buyer0.name) AS res FROM buyer buyer0 UNION SELECT LOWER(product0.kebab_case_name) AS res FROM product product0 ORDER BY res -
Seq( "camera", "cookie", "face mask", "face-mask", "guitar", "james bond", "li haoyi", "skate board", "skate-board", "socks", "叉烧包" )
CompoundSelect.unionAllUnionSortLimit
Product.select
.map(_.name.toLowerCase)
.unionAll(Buyer.select.map(_.name.toLowerCase))
.union(Product.select.map(_.kebabCaseName.toLowerCase))
.sortBy(identity)
.drop(4)
.take(4)
-
SELECT LOWER(product0.name) AS res FROM product product0 UNION ALL SELECT LOWER(buyer0.name) AS res FROM buyer buyer0 UNION SELECT LOWER(product0.kebab_case_name) AS res FROM product product0 ORDER BY res LIMIT ? OFFSET ? -
Seq("guitar", "james bond", "li haoyi", "skate board")
UpdateJoin
Basic UPDATE queries
UpdateJoin.join
ScalaSql supports performing UPDATEs with FROM/JOIN clauses using the
.update.join methods
Buyer
.update(_.name `=` "James Bond")
.join(ShippingInfo)(_.id `=` _.buyerId)
.set(c => c._1.dateOfBirth := c._2.shippingDate)
-
UPDATE buyer SET date_of_birth = shipping_info0.shipping_date FROM shipping_info shipping_info0 WHERE (buyer.id = shipping_info0.buyer_id) AND (buyer.name = ?) -
1
Buyer.select.filter(_.name `=` "James Bond").map(_.dateOfBirth)
-
Seq(LocalDate.parse("2012-04-05"))
UpdateJoin.multijoin
Multiple joins are supported, e.g. the below example where we join the Buyer table
three times against ShippingInfo/Purchase/Product to determine what to update
Buyer
.update(_.name `=` "James Bond")
.join(ShippingInfo)(_.id `=` _.buyerId)
.join(Purchase)(_._2.id `=` _.shippingInfoId)
.join(Product)(_._3.productId `=` _.id)
.filter(t => t._4.name.toLowerCase `=` t._4.kebabCaseName.toLowerCase)
.set(c => c._1.name := c._4.name)
-
UPDATE buyer SET name = product2.name FROM shipping_info shipping_info0 JOIN purchase purchase1 ON (shipping_info0.id = purchase1.shipping_info_id) JOIN product product2 ON (purchase1.product_id = product2.id) WHERE (buyer.id = shipping_info0.buyer_id) AND (buyer.name = ?) AND (LOWER(product2.name) = LOWER(product2.kebab_case_name)) -
1
Buyer.select.filter(_.id `=` 1).map(_.name)
-
Seq("Camera")
UpdateJoin.joinSubquery
In addition to JOINing against another table, you can also perform JOINs against
subqueries by passing in a .select query to .join
Buyer
.update(_.name `=` "James Bond")
.join(ShippingInfo.select.sortBy(_.id).asc.take(2))(_.id `=` _.buyerId)
.set(c => c._1.dateOfBirth := c._2.shippingDate)
-
UPDATE buyer SET date_of_birth = subquery0.shipping_date FROM (SELECT shipping_info0.id AS id, shipping_info0.buyer_id AS buyer_id, shipping_info0.shipping_date AS shipping_date FROM shipping_info shipping_info0 ORDER BY id ASC LIMIT ?) subquery0 WHERE (buyer.id = subquery0.buyer_id) AND (buyer.name = ?) -
1
Buyer.select.filter(_.name `=` "James Bond").map(_.dateOfBirth)
-
Seq(LocalDate.parse("2012-04-05"))
UpdateJoin.joinSubqueryEliminatedColumn
Buyer
.update(_.name `=` "James Bond")
// Make sure the `SELECT shipping_info0.shipping_info_id AS shipping_info_id`
// column gets eliminated since it is not used outside the subquery
.join(ShippingInfo.select.sortBy(_.id).asc.take(2))(_.id `=` _.buyerId)
.set(c => c._1.dateOfBirth := LocalDate.parse("2000-01-01"))
-
UPDATE buyer SET date_of_birth = ? FROM (SELECT shipping_info0.id AS id, shipping_info0.buyer_id AS buyer_id FROM shipping_info shipping_info0 ORDER BY id ASC LIMIT ?) subquery0 WHERE (buyer.id = subquery0.buyer_id) AND (buyer.name = ?) -
1
Buyer.select.filter(_.name `=` "James Bond").map(_.dateOfBirth)
-
Seq(LocalDate.parse("2000-01-01"))
UpdateSubQuery
UPDATE queries that use Subqueries
UpdateSubQuery.setSubquery
You can use subqueries to compute the values you want to update, using
aggregates like .maxBy to convert the Select[T] into an Expr[T]
Product.update(_ => true).set(_.price := Product.select.maxBy(_.price))
-
UPDATE product SET price = (SELECT MAX(product1.price) AS res FROM product product1) -
6
Product.select.map(p => (p.id, p.name, p.price))
-
Seq( (1, "Face Mask", 1000.0), (2, "Guitar", 1000.0), (3, "Socks", 1000.0), (4, "Skate Board", 1000.0), (5, "Camera", 1000.0), (6, "Cookie", 1000.0) )
UpdateSubQuery.whereSubquery
Subqueries and aggregates can also be used in the WHERE clause, defined by the
predicate passed to `Table.update
Product.update(_.price `=` Product.select.maxBy(_.price)).set(_.price := 0)
-
UPDATE product SET price = ? WHERE (product.price = (SELECT MAX(product1.price) AS res FROM product product1)) -
1
Product.select.map(p => (p.id, p.name, p.price))
-
Seq( (1, "Face Mask", 8.88), (2, "Guitar", 300.0), (3, "Socks", 3.14), (4, "Skate Board", 123.45), (5, "Camera", 0.0), (6, "Cookie", 0.1) )
Returning
Queries using INSERT or UPDATE with RETURNING
Returning.insert.single
ScalaSql's .returning clause translates to SQL's RETURNING syntax, letting
you perform insertions or updates and return values from the query (rather than
returning a single integer representing the rows affected). This is especially
useful for retrieving the auto-generated table IDs that many databases support.
Note that .returning/RETURNING is not supported in MySql, H2 or HsqlDB
Buyer.insert
.columns(_.name := "test buyer", _.dateOfBirth := LocalDate.parse("2023-09-09"))
.returning(_.id)
-
INSERT INTO buyer (name, date_of_birth) VALUES (?, ?) RETURNING buyer.id AS res -
Seq(4)
Buyer.select.filter(_.name `=` "test buyer")
-
Seq(Buyer[Sc](4, "test buyer", LocalDate.parse("2023-09-09")))
Returning.insert.dotSingle
If your .returning query is expected to be a single row, the .single method is
supported to convert the returned Seq[T] into a single T. .single throws an
exception if zero or multiple rows are returned.
Buyer.insert
.columns(_.name := "test buyer", _.dateOfBirth := LocalDate.parse("2023-09-09"))
.returning(_.id)
.single
-
INSERT INTO buyer (name, date_of_birth) VALUES (?, ?) RETURNING buyer.id AS res -
4
Buyer.select.filter(_.name `=` "test buyer")
-
Seq(Buyer[Sc](4, "test buyer", LocalDate.parse("2023-09-09")))
Returning.insert.multiple
Buyer.insert
.batched(_.name, _.dateOfBirth)(
("test buyer A", LocalDate.parse("2001-04-07")),
("test buyer B", LocalDate.parse("2002-05-08")),
("test buyer C", LocalDate.parse("2003-06-09"))
)
.returning(_.id)
-
INSERT INTO buyer (name, date_of_birth) VALUES (?, ?), (?, ?), (?, ?) RETURNING buyer.id AS res -
Seq(4, 5, 6)
Buyer.select
-
Seq( Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")), Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")), // id=4,5,6 comes from auto increment Buyer[Sc](4, "test buyer A", LocalDate.parse("2001-04-07")), Buyer[Sc](5, "test buyer B", LocalDate.parse("2002-05-08")), Buyer[Sc](6, "test buyer C", LocalDate.parse("2003-06-09")) )
Returning.insert.select
All variants of .insert and .update support .returning, e.g. the example below
applies to .insert.select, and the examples further down demonstrate its usage with
.update and .delete
Buyer.insert
.select(
x => (x.name, x.dateOfBirth),
Buyer.select.map(x => (x.name, x.dateOfBirth)).filter(_._1 <> "Li Haoyi")
)
.returning(_.id)
-
INSERT INTO buyer (name, date_of_birth) SELECT buyer1.name AS res_0, buyer1.date_of_birth AS res_1 FROM buyer buyer1 WHERE (buyer1.name <> ?) RETURNING buyer.id AS res -
Seq(4, 5)
Buyer.select
-
Seq( Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")), Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")), // id=4,5 comes from auto increment, 6 is filtered out in the select Buyer[Sc](4, "James Bond", LocalDate.parse("2001-02-03")), Buyer[Sc](5, "叉烧包", LocalDate.parse("1923-11-12")) )
Returning.update.single
Buyer
.update(_.name `=` "James Bond")
.set(_.dateOfBirth := LocalDate.parse("2019-04-07"))
.returning(_.id)
-
UPDATE buyer SET date_of_birth = ? WHERE (buyer.name = ?) RETURNING buyer.id AS res -
Seq(1)
Buyer.select.filter(_.name `=` "James Bond").map(_.dateOfBirth)
-
Seq(LocalDate.parse("2019-04-07"))
Returning.update.multiple
Buyer
.update(_.name `=` "James Bond")
.set(_.dateOfBirth := LocalDate.parse("2019-04-07"), _.name := "John Dee")
.returning(c => (c.id, c.name, c.dateOfBirth))
-
UPDATE buyer SET date_of_birth = ?, name = ? WHERE (buyer.name = ?) RETURNING buyer.id AS res_0, buyer.name AS res_1, buyer.date_of_birth AS res_2 -
Seq((1, "John Dee", LocalDate.parse("2019-04-07")))
Returning.delete
Purchase.delete(_.shippingInfoId `=` 1).returning(_.total)
-
DELETE FROM purchase WHERE (purchase.shipping_info_id = ?) RETURNING purchase.total AS res -
Seq(888.0, 900.0, 15.7)
Purchase.select
-
Seq( // id=1,2,3 had shippingInfoId=1 and thus got deleted Purchase[Sc](id = 4, shippingInfoId = 2, productId = 4, count = 4, total = 493.8), Purchase[Sc](id = 5, shippingInfoId = 2, productId = 5, count = 10, total = 10000.0), Purchase[Sc](id = 6, shippingInfoId = 3, productId = 1, count = 5, total = 44.4), Purchase[Sc](id = 7, shippingInfoId = 3, productId = 6, count = 13, total = 1.3) )
OnConflict
Queries using ON CONFLICT DO UPDATE or ON CONFLICT DO NOTHING
OnConflict.ignore
ScalaSql's .onConflictIgnore translates into SQL's ON CONFLICT DO NOTHING
Note that H2 and HsqlExpr do not support onConflictIgnore and onConflictUpdate, while
MySql only supports onConflictUpdate but not onConflictIgnore.
Buyer.insert
.columns(
_.name := "test buyer",
_.dateOfBirth := LocalDate.parse("2023-09-09"),
_.id := 1 // This should cause a primary key conflict
)
.onConflictIgnore(_.id)
-
INSERT INTO buyer (name, date_of_birth, id) VALUES (?, ?, ?) ON CONFLICT (id) DO NOTHING -
0
with insert.values
Buyer.insert
.values(
Buyer[Sc](
id = 1,
name = "test buyer",
dateOfBirth = LocalDate.parse("2023-09-09")
)
)
.onConflictIgnore(_.id)
-
INSERT INTO buyer (id, name, date_of_birth) VALUES (?, ?, ?) ON CONFLICT (id) DO NOTHING -
0
with insert.select
Buyer.insert
.select(
identity,
Buyer.select
.filter(_.id === 1)
.map(b => b.copy(name = b.name + "."))
)
.onConflictIgnore(_.id)
-
INSERT INTO buyer (id, name, date_of_birth) SELECT buyer0.id AS id, (buyer0.name || ?) AS name, buyer0.date_of_birth AS date_of_birth FROM buyer buyer0 WHERE (buyer0.id = ?) ON CONFLICT (id) DO NOTHING -
0
OnConflict.ignore.returningEmpty
Buyer.insert
.columns(
_.name := "test buyer",
_.dateOfBirth := LocalDate.parse("2023-09-09"),
_.id := 1 // This should cause a primary key conflict
)
.onConflictIgnore(_.id)
.returning(_.name)
-
INSERT INTO buyer (name, date_of_birth, id) VALUES (?, ?, ?) ON CONFLICT (id) DO NOTHING RETURNING buyer.name AS res -
Seq.empty[String]
with insert.values
Buyer.insert
.values(
Buyer[Sc](
id = 1,
name = "test buyer",
dateOfBirth = LocalDate.parse("2023-09-09")
)
)
.onConflictIgnore(_.id)
.returning(_.name)
-
INSERT INTO buyer (id, name, date_of_birth) VALUES (?, ?, ?) ON CONFLICT (id) DO NOTHING RETURNING buyer.name AS res -
Seq.empty[String]
OnConflict.ignore.returningOne
Buyer.insert
.columns(
_.name := "test buyer",
_.dateOfBirth := LocalDate.parse("2023-09-09"),
_.id := 4
)
.onConflictIgnore(_.id)
.returning(_.name)
-
INSERT INTO buyer (name, date_of_birth, id) VALUES (?, ?, ?) ON CONFLICT (id) DO NOTHING RETURNING buyer.name AS res -
Seq("test buyer")
with insert.values
Buyer.insert
.values(
Buyer[Sc](
id = 5,
name = "test buyer",
dateOfBirth = LocalDate.parse("2023-09-09")
)
)
.onConflictIgnore(_.id)
.returning(_.name)
-
INSERT INTO buyer (id, name, date_of_birth) VALUES (?, ?, ?) ON CONFLICT (id) DO NOTHING RETURNING buyer.name AS res -
Seq("test buyer")
OnConflict.update
ScalaSql's .onConflictUpdate translates into SQL's ON CONFLICT DO UPDATE
Buyer.insert
.columns(
_.name := "test buyer",
_.dateOfBirth := LocalDate.parse("2023-09-09"),
_.id := 1 // This should cause a primary key conflict
)
.onConflictUpdate(_.id)(_.name := "TEST BUYER CONFLICT")
-
INSERT INTO buyer (name, date_of_birth, id) VALUES (?, ?, ?) ON CONFLICT (id) DO UPDATE SET name = ? -
1
with insert.values
Buyer.insert
.values(
Buyer[Sc](
id = 1,
name = "test buyer",
dateOfBirth = LocalDate.parse("2023-09-09")
)
)
.onConflictUpdate(_.id)(_.dateOfBirth := LocalDate.parse("2023-10-10"))
-
INSERT INTO buyer (id, name, date_of_birth) VALUES (?, ?, ?) ON CONFLICT (id) DO UPDATE SET date_of_birth = ? -
1
with insert.select
Buyer.insert
.select(
identity,
Buyer.select
.filter(_.id === 1)
.map(b => b.copy(name = b.name + "."))
)
.onConflictUpdate(_.id)(_.dateOfBirth := LocalDate.parse("2023-10-09"))
-
INSERT INTO buyer (id, name, date_of_birth) SELECT buyer1.id AS id, (buyer1.name || ?) AS name, buyer1.date_of_birth AS date_of_birth FROM buyer buyer1 WHERE (buyer1.id = ?) ON CONFLICT (id) DO UPDATE SET date_of_birth = ? -
1
Buyer.select
-
Seq( Buyer[Sc](1, "TEST BUYER CONFLICT", LocalDate.parse("2023-10-09")), Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")) )
OnConflict.computed
Buyer.insert
.columns(
_.name := "test buyer",
_.dateOfBirth := LocalDate.parse("2023-09-09"),
_.id := 1 // This should cause a primary key conflict
)
.onConflictUpdate(_.id)(v => v.name := v.name.toUpperCase)
-
INSERT INTO buyer (name, date_of_birth, id) VALUES (?, ?, ?) ON CONFLICT (id) DO UPDATE SET name = UPPER(buyer.name) -
1
with insert.values
Buyer.insert
.values(
Buyer[Sc](
id = 3,
name = "test buyer",
dateOfBirth = LocalDate.parse("2023-09-09")
)
)
.onConflictUpdate(_.id)(v => v.name := v.name.toUpperCase)
-
INSERT INTO buyer (id, name, date_of_birth) VALUES (?, ?, ?) ON CONFLICT (id) DO UPDATE SET name = UPPER(buyer.name) -
1
Buyer.select
-
Seq( Buyer[Sc](1, "JAMES BOND", LocalDate.parse("2001-02-03")), Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), Buyer[Sc](3, "LI HAOYI", LocalDate.parse("1965-08-09")) )
OnConflict.returning
Buyer.insert
.columns(
_.name := "test buyer",
_.dateOfBirth := LocalDate.parse("2023-09-09"),
_.id := 1 // This should cause a primary key conflict
)
.onConflictUpdate(_.id)(v => v.name := v.name.toUpperCase)
.returning(_.name)
.single
-
INSERT INTO buyer (name, date_of_birth, id) VALUES (?, ?, ?) ON CONFLICT (id) DO UPDATE SET name = UPPER(buyer.name) RETURNING buyer.name AS res -
"JAMES BOND"
with insert.values
Buyer.insert
.values(
Buyer[Sc](
id = 1,
name = "test buyer",
dateOfBirth = LocalDate.parse("2023-09-09")
)
)
.onConflictUpdate(_.id)(v => v.name := v.name.toLowerCase)
.returning(_.name)
.single
-
INSERT INTO buyer (id, name, date_of_birth) VALUES (?, ?, ?) ON CONFLICT (id) DO UPDATE SET name = LOWER(buyer.name) RETURNING buyer.name AS res -
"james bond"
Values
Basic VALUES operations
Values.basic
You can use Values to generate a SQL VALUES clause
db.values(Seq(1, 2, 3))
-
VALUES (?), (?), (?) -
Seq(1, 2, 3)
Values.contains
Values supports .contains
db.values(Seq(1, 2, 3)).contains(1)
-
SELECT (? IN (VALUES (?), (?), (?))) AS res -
true
Values.max
Values supports aggregate functions like .max
db.values(Seq(1, 2, 3)).max
-
SELECT MAX(subquery0.column1) AS res FROM (VALUES (?), (?), (?)) subquery0 -
3
Values.map
Values supports most .select operators like .map, .filter, .crossJoin, and so on
db.values(Seq(1, 2, 3)).map(_ + 1)
-
SELECT (subquery0.column1 + ?) AS res FROM (VALUES (?), (?), (?)) subquery0 -
Seq(2, 3, 4)
Values.filter
db.values(Seq(1, 2, 3)).filter(_ > 2)
-
SELECT subquery0.column1 AS res FROM (VALUES (?), (?), (?)) subquery0 WHERE (subquery0.column1 > ?) -
Seq(3)
Values.crossJoin
db.values(Seq(1, 2, 3)).crossJoin(db.values(Seq(4, 5, 6))).map {
case (a, b) => (a * 10 + b)
}
-
SELECT ((subquery0.column1 * ?) + subquery1.column1) AS res FROM (VALUES (?), (?), (?)) subquery0 CROSS JOIN (VALUES (?), (?), (?)) subquery1 -
Seq(14, 15, 16, 24, 25, 26, 34, 35, 36)
Values.joinValuesAndTable
You can also mix values calls and normal selects in the same query, e.g. with joins
for {
name <- db.values(Seq("Socks", "Face Mask", "Camera"))
product <- Product.join(_.name === name)
} yield (name, product.price)
-
SELECT subquery0.column1 AS res_0, product1.price AS res_1 FROM (VALUES (?), (?), (?)) subquery0 JOIN product product1 ON (product1.name = subquery0.column1) -
Seq(("Socks", 3.14), ("Face Mask", 8.88), ("Camera", 1000.0))
Values.multiple.tuple
values supports tuples and other data structures as well
db.values(Seq((1, 2), (3, 4), (5, 6)))
-
VALUES (?, ?), (?, ?), (?, ?) -
Seq((1, 2), (3, 4), (5, 6))
Values.multiple.caseClass
db.values(
Seq(
Buyer[Sc](1, "hello", LocalDate.parse("2001-02-03")),
Buyer[Sc](2, "world", LocalDate.parse("2004-05-06"))
)
)
-
VALUES (?, ?, ?), (?, ?, ?) -
Seq( Buyer[Sc](1, "hello", LocalDate.parse("2001-02-03")), Buyer[Sc](2, "world", LocalDate.parse("2004-05-06")) )
Values.multiple.map
values supports tuples and other data structures as well
db.values(Seq((1, 2), (3, 4), (5, 6))).map { case (a, b) => (a + 10, b + 100) }
-
SELECT (subquery0.column1 + ?) AS res_0, (subquery0.column2 + ?) AS res_1 FROM (VALUES (?, ?), (?, ?), (?, ?)) subquery0 -
Seq((11, 102), (13, 104), (15, 106))
Values.multiple.mapCaseClass
values supports tuples and other data structures as well
{
val buyers = Seq(
Buyer[Sc](1, "hello", LocalDate.parse("2001-02-03")),
Buyer[Sc](2, "world", LocalDate.parse("2004-05-06"))
)
val query = db.values(buyers).map { b => (b.id + 100, b) }
query
}
-
SELECT (subquery0.column1 + ?) AS res_0, subquery0.column1 AS res_1_id, subquery0.column2 AS res_1_name, subquery0.column3 AS res_1_date_of_birth FROM (VALUES (?, ?, ?), (?, ?, ?)) subquery0 -
Seq( (101, Buyer[Sc](1, "hello", LocalDate.parse("2001-02-03"))), (102, Buyer[Sc](2, "world", LocalDate.parse("2004-05-06"))) )
Values.multiple.caseClassContains
You can use .contains on multi-column Scala values, which are translated
to a SQL IN clause on a tuple.
{
val buyers = Seq(
Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")),
Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09"))
)
Buyer.select.filter(!db.values(buyers).contains(_))
}
-
SELECT buyer0.id AS id, buyer0.name AS name, buyer0.date_of_birth AS date_of_birth FROM buyer buyer0 WHERE (NOT ((buyer0.id, buyer0.name, buyer0.date_of_birth) IN (VALUES (?, ?, ?), (?, ?, ?)))) -
Seq( Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")) )
LateralJoin
`JOIN LATERAL`, for the databases that support it. This allows you to use the
expressions defined in tables on the left-hand-side of the join in a
subquery on the right-hand-side of the join, v.s. normal `JOIN`s which only
allow you to use left-hand-side expressions in the `ON` expression but not
in the `FROM` subquery.
LateralJoin.crossJoinLateral
Buyer.select
.crossJoinLateral(b => ShippingInfo.select.filter { s => b.id `=` s.buyerId })
.map { case (b, s) => (b.name, s.shippingDate) }
-
SELECT buyer0.name AS res_0, subquery1.shipping_date AS res_1 FROM buyer buyer0 CROSS JOIN LATERAL (SELECT shipping_info1.shipping_date AS shipping_date FROM shipping_info shipping_info1 WHERE (buyer0.id = shipping_info1.buyer_id)) subquery1 -
Seq( ("James Bond", LocalDate.parse("2012-04-05")), ("叉烧包", LocalDate.parse("2010-02-03")), ("叉烧包", LocalDate.parse("2012-05-06")) )
LateralJoin.crossJoinLateralFor
for {
b <- Buyer.select
s <- ShippingInfo.select.filter { s => b.id `=` s.buyerId }.crossJoinLateral()
} yield (b.name, s.shippingDate)
-
SELECT buyer0.name AS res_0, subquery1.shipping_date AS res_1 FROM buyer buyer0 CROSS JOIN LATERAL (SELECT shipping_info1.shipping_date AS shipping_date FROM shipping_info shipping_info1 WHERE (buyer0.id = shipping_info1.buyer_id)) subquery1 -
Seq( ("James Bond", LocalDate.parse("2012-04-05")), ("叉烧包", LocalDate.parse("2010-02-03")), ("叉烧包", LocalDate.parse("2012-05-06")) )
LateralJoin.joinLateral
Buyer.select
.joinLateral(b => ShippingInfo.select.filter { s => b.id `=` s.buyerId })((_, _) => true)
.map { case (b, s) => (b.name, s.shippingDate) }
-
SELECT buyer0.name AS res_0, subquery1.shipping_date AS res_1 FROM buyer buyer0 JOIN LATERAL (SELECT shipping_info1.shipping_date AS shipping_date FROM shipping_info shipping_info1 WHERE (buyer0.id = shipping_info1.buyer_id)) subquery1 ON ? -
Seq( ("James Bond", LocalDate.parse("2012-04-05")), ("叉烧包", LocalDate.parse("2010-02-03")), ("叉烧包", LocalDate.parse("2012-05-06")) )
LateralJoin.joinLateralFor
for {
b <- Buyer.select
s <- ShippingInfo.select.filter { s => b.id `=` s.buyerId }.joinLateral(_ => Expr(true))
} yield (b.name, s.shippingDate)
-
SELECT buyer0.name AS res_0, subquery1.shipping_date AS res_1 FROM buyer buyer0 JOIN LATERAL (SELECT shipping_info1.shipping_date AS shipping_date FROM shipping_info shipping_info1 WHERE (buyer0.id = shipping_info1.buyer_id)) subquery1 ON ? -
Seq( ("James Bond", LocalDate.parse("2012-04-05")), ("叉烧包", LocalDate.parse("2010-02-03")), ("叉烧包", LocalDate.parse("2012-05-06")) )
LateralJoin.leftJoin
ScalaSql supports LEFT JOINs, RIGHT JOINs and OUTER JOINs via the
.leftJoin/.rightJoin/.outerJoin methods
Buyer.select.leftJoinLateral(b => ShippingInfo.select.filter(b.id `=` _.buyerId))((_, _) =>
Expr(true)
)
-
SELECT buyer0.id AS res_0_id, buyer0.name AS res_0_name, buyer0.date_of_birth AS res_0_date_of_birth, subquery1.id AS res_1_id, subquery1.buyer_id AS res_1_buyer_id, subquery1.shipping_date AS res_1_shipping_date FROM buyer buyer0 LEFT JOIN LATERAL (SELECT shipping_info1.id AS id, shipping_info1.buyer_id AS buyer_id, shipping_info1.shipping_date AS shipping_date FROM shipping_info shipping_info1 WHERE (buyer0.id = shipping_info1.buyer_id)) subquery1 ON ? -
Seq( ( Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")), Some(ShippingInfo[Sc](2, 1, LocalDate.parse("2012-04-05"))) ), ( Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), Some(ShippingInfo[Sc](1, 2, LocalDate.parse("2010-02-03"))) ), ( Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), Some(ShippingInfo[Sc](3, 2, LocalDate.parse("2012-05-06"))) ), (Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")), None) )
LateralJoin.leftJoinFor
ScalaSql supports LEFT JOINs, RIGHT JOINs and OUTER JOINs via the
.leftJoin/.rightJoin/.outerJoin methods
for {
b <- Buyer.select
s <- ShippingInfo.select.filter(b.id `=` _.buyerId).leftJoinLateral(_ => Expr(true))
} yield (b, s)
-
SELECT buyer0.id AS res_0_id, buyer0.name AS res_0_name, buyer0.date_of_birth AS res_0_date_of_birth, subquery1.id AS res_1_id, subquery1.buyer_id AS res_1_buyer_id, subquery1.shipping_date AS res_1_shipping_date FROM buyer buyer0 LEFT JOIN LATERAL (SELECT shipping_info1.id AS id, shipping_info1.buyer_id AS buyer_id, shipping_info1.shipping_date AS shipping_date FROM shipping_info shipping_info1 WHERE (buyer0.id = shipping_info1.buyer_id)) subquery1 ON ? -
Seq( ( Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")), Some(ShippingInfo[Sc](2, 1, LocalDate.parse("2012-04-05"))) ), ( Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), Some(ShippingInfo[Sc](1, 2, LocalDate.parse("2010-02-03"))) ), ( Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), Some(ShippingInfo[Sc](3, 2, LocalDate.parse("2012-05-06"))) ), (Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")), None) )
WindowFunction
Window functions using OVER
WindowFunction.simple.rank
Window functions like rank() are supported. You can use the .over, .partitionBy,
and .sortBy
Purchase.select.map(p =>
(
p.shippingInfoId,
p.total,
db.rank().over.partitionBy(p.shippingInfoId).sortBy(p.total).asc
)
)
-
SELECT purchase0.shipping_info_id AS res_0, purchase0.total AS res_1, RANK() OVER (PARTITION BY purchase0.shipping_info_id ORDER BY purchase0.total ASC) AS res_2 FROM purchase purchase0 -
Seq[(Int, Double, Int)]( (1, 15.7, 1), (1, 888.0, 2), (1, 900.0, 3), (2, 493.8, 1), (2, 10000.0, 2), (3, 1.3, 1), (3, 44.4, 2) )
WindowFunction.simple.rowNumber
Purchase.select.map(p =>
(
p.shippingInfoId,
p.total,
db.rowNumber().over.partitionBy(p.shippingInfoId).sortBy(p.total).asc
)
)
-
SELECT purchase0.shipping_info_id AS res_0, purchase0.total AS res_1, ROW_NUMBER() OVER (PARTITION BY purchase0.shipping_info_id ORDER BY purchase0.total ASC) AS res_2 FROM purchase purchase0 -
Seq[(Int, Double, Int)]( (1, 15.7, 1), (1, 888.0, 2), (1, 900.0, 3), (2, 493.8, 1), (2, 10000.0, 2), (3, 1.3, 1), (3, 44.4, 2) )
WindowFunction.simple.denseRank
Purchase.select.map(p =>
(
p.shippingInfoId,
p.total,
db.denseRank().over.partitionBy(p.shippingInfoId).sortBy(p.total).asc
)
)
-
SELECT purchase0.shipping_info_id AS res_0, purchase0.total AS res_1, DENSE_RANK() OVER (PARTITION BY purchase0.shipping_info_id ORDER BY purchase0.total ASC) AS res_2 FROM purchase purchase0 -
Seq[(Int, Double, Int)]( (1, 15.7, 1), (1, 888.0, 2), (1, 900.0, 3), (2, 493.8, 1), (2, 10000.0, 2), (3, 1.3, 1), (3, 44.4, 2) )
Purchase.select.map(p =>
(
p.shippingInfoId,
p.total,
db.denseRank().over.partitionBy(p.shippingInfoId).sortBy(p.total).asc
)
)
-
SELECT purchase0.shipping_info_id AS res_0, purchase0.total AS res_1, DENSE_RANK() OVER (PARTITION BY purchase0.shipping_info_id ORDER BY purchase0.total ASC) AS res_2 FROM purchase purchase0 -
Seq[(Int, Double, Int)]( (1, 15.7, 1), (1, 888.0, 2), (1, 900.0, 3), (2, 493.8, 1), (2, 10000.0, 2), (3, 1.3, 1), (3, 44.4, 2) )
WindowFunction.simple.percentRank
Purchase.select.map(p =>
(
p.shippingInfoId,
p.total,
db.percentRank().over.partitionBy(p.shippingInfoId).sortBy(p.total).asc
)
)
-
SELECT purchase0.shipping_info_id AS res_0, purchase0.total AS res_1, PERCENT_RANK() OVER (PARTITION BY purchase0.shipping_info_id ORDER BY purchase0.total ASC) AS res_2 FROM purchase purchase0 -
Seq[(Int, Double, Double)]( (1, 15.7, 0.0), (1, 888.0, 0.5), (1, 900.0, 1.0), (2, 493.8, 0.0), (2, 10000.0, 1.0), (3, 1.3, 0.0), (3, 44.4, 1.0) )
WindowFunction.simple.cumeDist
Purchase.select.map(p =>
(
p.shippingInfoId,
p.total,
db.cumeDist().over.partitionBy(p.shippingInfoId).sortBy(p.total).asc
)
)
-
SELECT purchase0.shipping_info_id AS res_0, purchase0.total AS res_1, CUME_DIST() OVER (PARTITION BY purchase0.shipping_info_id ORDER BY purchase0.total ASC) AS res_2 FROM purchase purchase0 -
Seq[(Int, Double, Double)]( (1, 15.7, 0.3333333333333333), (1, 888.0, 0.6666666666666666), (1, 900.0, 1.0), (2, 493.8, 0.5), (2, 10000.0, 1.0), (3, 1.3, 0.5), (3, 44.4, 1.0) )
WindowFunction.simple.ntile
Purchase.select.map(p =>
(
p.shippingInfoId,
p.total,
db.ntile(3).over.partitionBy(p.shippingInfoId).sortBy(p.total).asc
)
)
-
SELECT purchase0.shipping_info_id AS res_0, purchase0.total AS res_1, NTILE(?) OVER (PARTITION BY purchase0.shipping_info_id ORDER BY purchase0.total ASC) AS res_2 FROM purchase purchase0 -
Seq[(Int, Double, Int)]( (1, 15.7, 1), (1, 888.0, 2), (1, 900.0, 3), (2, 493.8, 1), (2, 10000.0, 2), (3, 1.3, 1), (3, 44.4, 2) )
WindowFunction.simple.lag
Purchase.select.map(p =>
(
p.shippingInfoId,
p.total,
db.lag(p.total, 1, -1.0).over.partitionBy(p.shippingInfoId).sortBy(p.total).asc
)
)
-
SELECT purchase0.shipping_info_id AS res_0, purchase0.total AS res_1, LAG(purchase0.total, ?, ?) OVER (PARTITION BY purchase0.shipping_info_id ORDER BY purchase0.total ASC) AS res_2 FROM purchase purchase0 -
Seq[(Int, Double, Double)]( (1, 15.7, -1.0), (1, 888.0, 15.7), (1, 900.0, 888.0), (2, 493.8, -1.0), (2, 10000.0, 493.8), (3, 1.3, -1.0), (3, 44.4, 1.3) )
WindowFunction.simple.lead
Purchase.select.map(p =>
(
p.shippingInfoId,
p.total,
db.lead(p.total, 1, -1.0).over.partitionBy(p.shippingInfoId).sortBy(p.total).asc
)
)
-
SELECT purchase0.shipping_info_id AS res_0, purchase0.total AS res_1, LEAD(purchase0.total, ?, ?) OVER (PARTITION BY purchase0.shipping_info_id ORDER BY purchase0.total ASC) AS res_2 FROM purchase purchase0 -
Seq[(Int, Double, Double)]( (1, 15.7, 888.0), (1, 888.0, 900.0), (1, 900.0, -1.0), (2, 493.8, 10000.0), (2, 10000.0, -1.0), (3, 1.3, 44.4), (3, 44.4, -1.0) )
WindowFunction.simple.firstValue
Purchase.select.map(p =>
(
p.shippingInfoId,
p.total,
db.firstValue(p.total).over.partitionBy(p.shippingInfoId).sortBy(p.total).asc
)
)
-
SELECT purchase0.shipping_info_id AS res_0, purchase0.total AS res_1, FIRST_VALUE(purchase0.total) OVER (PARTITION BY purchase0.shipping_info_id ORDER BY purchase0.total ASC) AS res_2 FROM purchase purchase0 -
Seq[(Int, Double, Double)]( (1, 15.7, 15.7), (1, 888.0, 15.7), (1, 900.0, 15.7), (2, 493.8, 493.8), (2, 10000.0, 493.8), (3, 1.3, 1.3), (3, 44.4, 1.3) )
WindowFunction.simple.lastValue
Purchase.select.map(p =>
(
p.shippingInfoId,
p.total,
db.lastValue(p.total).over.partitionBy(p.shippingInfoId).sortBy(p.total).asc
)
)
-
SELECT purchase0.shipping_info_id AS res_0, purchase0.total AS res_1, LAST_VALUE(purchase0.total) OVER (PARTITION BY purchase0.shipping_info_id ORDER BY purchase0.total ASC) AS res_2 FROM purchase purchase0 -
Seq[(Int, Double, Double)]( (1, 15.7, 15.7), (1, 888.0, 888.0), (1, 900.0, 900.0), (2, 493.8, 493.8), (2, 10000.0, 10000.0), (3, 1.3, 1.3), (3, 44.4, 44.4) )
WindowFunction.simple.nthValue
Purchase.select.map(p =>
(
p.shippingInfoId,
p.total,
db.nthValue(p.total, 2).over.partitionBy(p.shippingInfoId).sortBy(p.total).asc
)
)
-
SELECT purchase0.shipping_info_id AS res_0, purchase0.total AS res_1, NTH_VALUE(purchase0.total, ?) OVER (PARTITION BY purchase0.shipping_info_id ORDER BY purchase0.total ASC) AS res_2 FROM purchase purchase0 -
Seq[(Int, Double, Double)]( (1, 15.7, 0.0), (1, 888.0, 888.0), (1, 900.0, 888.0), (2, 493.8, 0.0), (2, 10000.0, 10000.0), (3, 1.3, 0.0), (3, 44.4, 44.4) )
WindowFunction.aggregate.sumBy
You can use .mapAggregate to use aggregate functions as window function
Purchase.select.mapAggregate((p, ps) =>
(
p.shippingInfoId,
p.total,
ps.sumBy(_.total).over.partitionBy(p.shippingInfoId).sortBy(p.total).asc
)
)
-
SELECT purchase0.shipping_info_id AS res_0, purchase0.total AS res_1, SUM(purchase0.total) OVER (PARTITION BY purchase0.shipping_info_id ORDER BY purchase0.total ASC) AS res_2 FROM purchase purchase0 -
Seq( (1, 15.7, 15.7), (1, 888.0, 903.7), (1, 900.0, 1803.7), (2, 493.8, 493.8), (2, 10000.0, 10493.8), (3, 1.3, 1.3), (3, 44.4, 45.699999999999996) )
WindowFunction.aggregate.avgBy
Window functions like rank() are supported. You can use the .over, .partitionBy,
and .sortBy
Purchase.select.mapAggregate((p, ps) =>
(
p.shippingInfoId,
p.total,
ps.avgBy(_.total).over.partitionBy(p.shippingInfoId).sortBy(p.total).asc
)
)
-
SELECT purchase0.shipping_info_id AS res_0, purchase0.total AS res_1, AVG(purchase0.total) OVER (PARTITION BY purchase0.shipping_info_id ORDER BY purchase0.total ASC) AS res_2 FROM purchase purchase0 -
Seq( (1, 15.7, 15.7), (1, 888.0, 451.85), (1, 900.0, 601.2333333333333), (2, 493.8, 493.8), (2, 10000.0, 5246.9), (3, 1.3, 1.3), (3, 44.4, 22.849999999999998) )
WindowFunction.frames
You can have further control over the window function call via .frameStart,
.frameEnd, .exclude
Purchase.select.mapAggregate((p, ps) =>
(
p.shippingInfoId,
p.total,
ps.sumBy(_.total)
.over
.partitionBy(p.shippingInfoId)
.sortBy(p.total)
.asc
.frameStart
.preceding()
.frameEnd
.following()
.exclude
.currentRow
)
)
-
SELECT purchase0.shipping_info_id AS res_0, purchase0.total AS res_1, SUM(purchase0.total) OVER (PARTITION BY purchase0.shipping_info_id ORDER BY purchase0.total ASC ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING EXCLUDE CURRENT ROW) AS res_2 FROM purchase purchase0 -
Seq[(Int, Double, Double)]( (1, 15.7, 1788.0), (1, 888.0, 915.7), (1, 900.0, 903.7), (2, 493.8, 10000.0), (2, 10000.0, 493.8), (3, 1.3, 44.4), (3, 44.4, 1.3) )
WindowFunction.filter
ScalaSql allows .filter to be used after over to add a SQL FILTER clause
to your window function call, allowing you to exclude certain rows from the
window.
Purchase.select.mapAggregate((p, ps) =>
(
p.shippingInfoId,
p.total,
ps.sumBy(_.total)
.over
.filter(p.total > 100)
.partitionBy(p.shippingInfoId)
.sortBy(p.total)
.asc
)
)
-
SELECT purchase0.shipping_info_id AS res_0, purchase0.total AS res_1, SUM(purchase0.total) FILTER (WHERE (purchase0.total > ?)) OVER (PARTITION BY purchase0.shipping_info_id ORDER BY purchase0.total ASC) AS res_2 FROM purchase purchase0 -
Seq[(Int, Double, Double)]( (1, 15.7, 0.0), (1, 888.0, 888.0), (1, 900.0, 1788.0), (2, 493.8, 493.8), (2, 10000.0, 10493.8), (3, 1.3, 0.0), (3, 44.4, 0.0) )
GetGeneratedKeys
INSERT operations with .getGeneratedKeys. Not supported by Sqlite (see https://github.com/xerial/sqlite-jdbc/issues/980)
GetGeneratedKeys.single.values
getGeneratedKeys on an insert returns the primary key, even if it was provided
explicitly.
Buyer.insert
.values(
Buyer[Sc](17, "test buyer", LocalDate.parse("2023-09-09"))
)
.getGeneratedKeys[Int]
-
INSERT INTO buyer (id, name, date_of_birth) VALUES (?, ?, ?) -
Seq(17)
Buyer.select.filter(_.name `=` "test buyer")
-
Seq(Buyer[Sc](17, "test buyer", LocalDate.parse("2023-09-09")))
GetGeneratedKeys.single.columns
All styles of INSERT query support .getGeneratedKeys, with this example
using insert.columns rather than insert.values. You can also retrieve
the generated primary keys using any compatible type, here shown using Long
rather than Int
Buyer.insert
.columns(
_.name := "test buyer",
_.dateOfBirth := LocalDate.parse("2023-09-09"),
_.id := 4
)
.getGeneratedKeys[Long]
-
INSERT INTO buyer (name, date_of_birth, id) VALUES (?, ?, ?) -
Seq(4L)
Buyer.select.filter(_.name `=` "test buyer")
-
Seq(Buyer[Sc](4, "test buyer", LocalDate.parse("2023-09-09")))
GetGeneratedKeys.single.partial
If the primary key was not provided but was auto-generated by the database,
getGeneratedKeys returns the generated value
Buyer.insert
.columns(_.name := "test buyer", _.dateOfBirth := LocalDate.parse("2023-09-09"))
.getGeneratedKeys[Int]
-
INSERT INTO buyer (name, date_of_birth) VALUES (?, ?) -
Seq(4)
Buyer.select.filter(_.name `=` "test buyer")
-
Seq(Buyer[Sc](4, "test buyer", LocalDate.parse("2023-09-09")))
GetGeneratedKeys.batch.partial
getGeneratedKeys can return multiple generated primary key values for
a batch insert statement
Buyer.insert
.batched(_.name, _.dateOfBirth)(
("test buyer A", LocalDate.parse("2001-04-07")),
("test buyer B", LocalDate.parse("2002-05-08")),
("test buyer C", LocalDate.parse("2003-06-09"))
)
.getGeneratedKeys[Int]
-
INSERT INTO buyer (name, date_of_birth) VALUES (?, ?), (?, ?), (?, ?) -
Seq(4, 5, 6)
Buyer.select
-
Seq( Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")), Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")), // id=4,5,6 comes from auto increment Buyer[Sc](4, "test buyer A", LocalDate.parse("2001-04-07")), Buyer[Sc](5, "test buyer B", LocalDate.parse("2002-05-08")), Buyer[Sc](6, "test buyer C", LocalDate.parse("2003-06-09")) )
GetGeneratedKeys.select.simple
getGeneratedKeys can return multiple generated primary key values for
an insert based on a select
Buyer.insert
.select(
x => (x.name, x.dateOfBirth),
Buyer.select.map(x => (x.name, x.dateOfBirth)).filter(_._1 <> "Li Haoyi")
)
.getGeneratedKeys[Int]
-
INSERT INTO buyer (name, date_of_birth) SELECT buyer0.name AS res_0, buyer0.date_of_birth AS res_1 FROM buyer buyer0 WHERE (buyer0.name <> ?) -
Seq(4, 5)
Buyer.select
-
Seq( Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")), Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")), // id=4,5 comes from auto increment, 6 is filtered out in the select Buyer[Sc](4, "James Bond", LocalDate.parse("2001-02-03")), Buyer[Sc](5, "叉烧包", LocalDate.parse("1923-11-12")) )
Schema
If your table belongs to a schema other than the default schema of your database, you can specify this in your table definition with
`override def schemaName = "otherschema"`
Schema.schema.select
Invoice.select
-
SELECT invoice0.id AS id, invoice0.total AS total, invoice0.vendor_name AS vendor_name FROM otherschema.invoice invoice0 -
Seq( Invoice[Sc](id = 1, total = 150.4, vendor_name = "Siemens"), Invoice[Sc](id = 2, total = 213.3, vendor_name = "Samsung"), Invoice[Sc](id = 3, total = 407.2, vendor_name = "Shell") )
Schema.schema.insert.columns
Invoice.insert.columns(
_.total := 200.3,
_.vendor_name := "Huawei"
)
-
INSERT INTO otherschema.invoice (total, vendor_name) VALUES (?, ?) -
1
Schema.schema.insert.values
Invoice.insert
.values(
Invoice[Sc](
id = 0,
total = 200.3,
vendor_name = "Huawei"
)
)
.skipColumns(_.id)
-
INSERT INTO otherschema.invoice (total, vendor_name) VALUES (?, ?) -
1
Schema.schema.update
Invoice
.update(_.id === 1)
.set(
_.total := 200.3,
_.vendor_name := "Huawei"
)
-
UPDATE otherschema.invoice SET total = ?, vendor_name = ? WHERE (invoice.id = ?) -
1
Schema.schema.delete
Invoice.delete(_.id === 1)
-
DELETE FROM otherschema.invoice WHERE (invoice.id = ?) -
1
Schema.schema.insert into
Invoice.insert.select(
i => (i.total, i.vendor_name),
Invoice.select.map(i => (i.total, i.vendor_name))
)
-
INSERT INTO otherschema.invoice (total, vendor_name) SELECT invoice0.total AS res_0, invoice0.vendor_name AS res_1 FROM otherschema.invoice invoice0 -
4
Schema.schema.join
Invoice.select.join(Invoice)(_.id `=` _.id).map(_._1.id)
-
SELECT invoice0.id AS res FROM otherschema.invoice invoice0 JOIN otherschema.invoice invoice1 ON (invoice0.id = invoice1.id) -
Seq(2, 3, 4, 5, 6, 7, 8, 9)
EscapedTableName
If your table name is a reserved sql world, e.g. `order`, you can specify this in your table definition with
`override def escape = true`
EscapedTableName.escape table name.select
Select.select
-
SELECT select0.id AS id, select0.name AS name FROM "select" select0 -
Seq.empty[Select[Sc]]
EscapedTableName.escape table name.select with filter
Select.select.filter(_.id `=` 1)
-
SELECT select0.id AS id, select0.name AS name FROM "select" select0 WHERE (select0.id = ?) -
Seq.empty[Select[Sc]]
EscapedTableName.escape table name.delete
Select.delete(_ => true)
-
DELETE FROM "select" -
0
EscapedTableName.escape table name.join
Select.select.join(Select)(_.id `=` _.id)
-
SELECT select0.id AS res_0_id, select0.name AS res_0_name, select1.id AS res_1_id, select1.name AS res_1_name FROM "select" select0 JOIN "select" select1 ON (select0.id = select1.id) -
Seq.empty[(Select[Sc], Select[Sc])]
EscapedTableName.escape table name.update
Select.update(_ => true).set(_.name := "hello")
-
UPDATE "select" SET name = ? -
0
EscapedTableName.escape table name.update where
Select.update(_.id `=` 1).set(_.name := "hello")
-
UPDATE "select" SET name = ? WHERE ("select".id = ?) -
0
EscapedTableName.escape table name.insert
Select.insert.values(
Select[Sc](
id = 0,
name = "hello"
)
)
-
INSERT INTO "select" (id, name) VALUES (?, ?) -
1
EscapedTableNameWithReturning
If your table name is a reserved sql world, e.g. `order`, you can specify this in your table definition with
`override def escape = true`
EscapedTableNameWithReturning.insert with returning
Select.insert
.values(
Select[Sc](
id = 0,
name = "hello"
)
)
.returning(_.id)
-
INSERT INTO "select" (id, name) VALUES (?, ?) RETURNING "select".id AS res -
Seq(0)
SubQuery
Queries that explicitly use subqueries (e.g. for JOINs) or require subqueries to preserve the Scala semantics of the various operators
SubQuery.sortTakeJoin
A ScalaSql .join referencing a .select translates straightforwardly
into a SQL JOIN on a subquery
Purchase.select
.join(Product.select.sortBy(_.price).desc.take(1))(_.productId `=` _.id)
.map { case (purchase, product) => purchase.total }
-
SELECT purchase0.total AS res FROM purchase purchase0 JOIN (SELECT product1.id AS id, product1.price AS price FROM product product1 ORDER BY price DESC LIMIT ?) subquery1 ON (purchase0.product_id = subquery1.id) -
Seq(10000.0)
SubQuery.sortTakeFrom
Some sequences of operations cannot be expressed as a single SQL query,
and thus translate into an outer query wrapping a subquery inside the FROM.
An example of this is performing a .join after a .take: SQL does not
allow you to put JOINs after LIMITs, and so the only way to write this
in SQL is as a subquery.
Product.select.sortBy(_.price).desc.take(1).join(Purchase)(_.id `=` _.productId).map {
case (product, purchase) => purchase.total
}
-
SELECT purchase1.total AS res FROM (SELECT product0.id AS id, product0.price AS price FROM product product0 ORDER BY price DESC LIMIT ?) subquery0 JOIN purchase purchase1 ON (subquery0.id = purchase1.product_id) -
Seq(10000.0)
SubQuery.sortTakeFromAndJoin
This example shows a ScalaSql query that results in a subquery in both
the FROM and the JOIN clause of the generated SQL query.
Product.select
.sortBy(_.price)
.desc
.take(3)
.join(Purchase.select.sortBy(_.count).desc.take(3))(_.id `=` _.productId)
.map { case (product, purchase) => (product.name, purchase.count) }
-
SELECT subquery0.name AS res_0, subquery1.count AS res_1 FROM (SELECT product0.id AS id, product0.name AS name, product0.price AS price FROM product product0 ORDER BY price DESC LIMIT ?) subquery0 JOIN (SELECT purchase1.product_id AS product_id, purchase1.count AS count FROM purchase purchase1 ORDER BY count DESC LIMIT ?) subquery1 ON (subquery0.id = subquery1.product_id) -
Seq(("Camera", 10))
SubQuery.sortLimitSortLimit
Performing multiple sorts with .takes in between is also something
that requires subqueries, as a single query only allows a single LIMIT
clause after the ORDER BY
Product.select.sortBy(_.price).desc.take(4).sortBy(_.price).asc.take(2).map(_.name)
-
SELECT subquery0.name AS res FROM (SELECT product0.name AS name, product0.price AS price FROM product product0 ORDER BY price DESC LIMIT ?) subquery0 ORDER BY subquery0.price ASC LIMIT ? -
Seq("Face Mask", "Skate Board")
SubQuery.sortGroupBy
Purchase.select.sortBy(_.count).take(5).groupBy(_.productId)(_.sumBy(_.total))
-
SELECT subquery0.product_id AS res_0, SUM(subquery0.total) AS res_1 FROM (SELECT purchase0.product_id AS product_id, purchase0.count AS count, purchase0.total AS total FROM purchase purchase0 ORDER BY count LIMIT ?) subquery0 GROUP BY subquery0.product_id -
Seq((1, 44.4), (2, 900.0), (3, 15.7), (4, 493.8), (5, 10000.0))
SubQuery.groupByJoin
Purchase.select.groupBy(_.productId)(_.sumBy(_.total)).join(Product)(_._1 `=` _.id).map {
case (productId, total, product) => (product.name, total)
}
-
SELECT product1.name AS res_0, subquery0.res_1 AS res_1 FROM (SELECT purchase0.product_id AS res_0, SUM(purchase0.total) AS res_1 FROM purchase purchase0 GROUP BY purchase0.product_id) subquery0 JOIN product product1 ON (subquery0.res_0 = product1.id) -
Seq( ("Camera", 10000.0), ("Cookie", 1.3), ("Face Mask", 932.4), ("Guitar", 900.0), ("Skate Board", 493.8), ("Socks", 15.7) )
SubQuery.subqueryInFilter
You can use .selects and aggregate operations like .size anywhere an expression is
expected; these translate into SQL subqueries as expressions. SQL
subqueries-as-expressions require that the subquery returns exactly 1 row and 1 column,
which is something the aggregate operation (in this case .sum/COUNT(1)) helps us
ensure. Here, we do subquery in a .filter/WHERE.
Buyer.select.filter(c => ShippingInfo.select.filter(p => c.id `=` p.buyerId).size `=` 0)
-
SELECT buyer0.id AS id, buyer0.name AS name, buyer0.date_of_birth AS date_of_birth FROM buyer buyer0 WHERE ((SELECT COUNT(1) AS res FROM shipping_info shipping_info1 WHERE (buyer0.id = shipping_info1.buyer_id)) = ?) -
Seq(Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")))
SubQuery.subqueryInMap
Similar to the above example, but we do the subquery/aggregate in
a .map instead of a .filter
Buyer.select.map(c => (c, ShippingInfo.select.filter(p => c.id `=` p.buyerId).size))
-
SELECT buyer0.id AS res_0_id, buyer0.name AS res_0_name, buyer0.date_of_birth AS res_0_date_of_birth, (SELECT COUNT(1) AS res FROM shipping_info shipping_info1 WHERE (buyer0.id = shipping_info1.buyer_id)) AS res_1 FROM buyer buyer0 -
Seq( (Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")), 1), (Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), 2), (Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")), 0) )
SubQuery.subqueryInMapNested
Buyer.select.map(c => (c, ShippingInfo.select.filter(p => c.id `=` p.buyerId).size `=` 1))
-
SELECT buyer0.id AS res_0_id, buyer0.name AS res_0_name, buyer0.date_of_birth AS res_0_date_of_birth, ((SELECT COUNT(1) AS res FROM shipping_info shipping_info1 WHERE (buyer0.id = shipping_info1.buyer_id)) = ?) AS res_1 FROM buyer buyer0 -
Seq( (Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")), true), (Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), false), (Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")), false) )
SubQuery.selectLimitUnionSelect
Buyer.select
.map(_.name.toLowerCase)
.take(2)
.unionAll(Product.select.map(_.kebabCaseName.toLowerCase))
-
SELECT subquery0.res AS res FROM (SELECT LOWER(buyer0.name) AS res FROM buyer buyer0 LIMIT ?) subquery0 UNION ALL SELECT LOWER(product0.kebab_case_name) AS res FROM product product0 -
Seq("james bond", "叉烧包", "face-mask", "guitar", "socks", "skate-board", "camera", "cookie")
SubQuery.selectUnionSelectLimit
Buyer.select
.map(_.name.toLowerCase)
.unionAll(Product.select.map(_.kebabCaseName.toLowerCase).take(2))
-
SELECT LOWER(buyer0.name) AS res FROM buyer buyer0 UNION ALL SELECT subquery0.res AS res FROM (SELECT LOWER(product0.kebab_case_name) AS res FROM product product0 LIMIT ?) subquery0 -
Seq("james bond", "叉烧包", "li haoyi", "face-mask", "guitar")
SubQuery.exceptAggregate
Product.select
.map(p => (p.name.toLowerCase, p.price))
// `p.name.toLowerCase` and `p.kebabCaseName.toLowerCase` are not eliminated, because
// they are important to the semantics of EXCEPT (and other non-UNION-ALL operators)
.except(Product.select.map(p => (p.kebabCaseName.toLowerCase, p.price)))
.aggregate(ps => (ps.maxBy(_._2), ps.minBy(_._2)))
-
SELECT MAX(subquery0.res_1) AS res_0, MIN(subquery0.res_1) AS res_1 FROM (SELECT LOWER(product0.name) AS res_0, product0.price AS res_1 FROM product product0 EXCEPT SELECT LOWER(product0.kebab_case_name) AS res_0, product0.price AS res_1 FROM product product0) subquery0 -
(123.45, 8.88)
SubQuery.unionAllAggregate
Product.select
.map(p => (p.name.toLowerCase, p.price))
// `p.name.toLowerCase` and `p.kebabCaseName.toLowerCase` get eliminated,
// as they are not selected by the enclosing query, and cannot affect the UNION ALL
.unionAll(Product.select.map(p => (p.kebabCaseName.toLowerCase, p.price)))
.aggregate(ps => (ps.maxBy(_._2), ps.minBy(_._2)))
-
SELECT MAX(subquery0.res_1) AS res_0, MIN(subquery0.res_1) AS res_1 FROM (SELECT product0.price AS res_1 FROM product product0 UNION ALL SELECT product0.price AS res_1 FROM product product0) subquery0 -
(1000.0, 0.1)
SubQuery.deeplyNested
Subqueries can be arbitrarily nested. This example traverses four tables
to find the price of the most expensive product bought by each Buyer, but
instead of using JOINs it uses subqueries nested 4 layers deep. While this
example is contrived, it demonstrates how nested ScalaSql .select calls
translate directly into nested SQL subqueries.
To turn the ScalaSql Select[T] into an Expr[T], you can either use
an aggregate method like .sumBy(...): Expr[Int] that generates a SUM(...)
aggregate, or via the .toExpr method that leaves the subquery untouched.
SQL requires that subqueries used as expressions must return a single row
and single column, and if the query returns some other number of rows/columns
most databases will throw an exception, though some like Sqlite will pick
the first row/column arbitrarily.
Buyer.select.map { buyer =>
buyer.name ->
ShippingInfo.select
.filter(_.buyerId === buyer.id)
.map { shippingInfo =>
Purchase.select
.filter(_.shippingInfoId === shippingInfo.id)
.map { purchase =>
Product.select
.filter(_.id === purchase.productId)
.map(_.price)
.sorted
.desc
.take(1)
.toExpr
}
.sorted
.desc
.take(1)
.toExpr
}
.sorted
.desc
.take(1)
.toExpr
}
-
SELECT buyer0.name AS res_0, (SELECT (SELECT (SELECT product3.price AS res FROM product product3 WHERE (product3.id = purchase2.product_id) ORDER BY res DESC LIMIT ?) AS res FROM purchase purchase2 WHERE (purchase2.shipping_info_id = shipping_info1.id) ORDER BY res DESC LIMIT ?) AS res FROM shipping_info shipping_info1 WHERE (shipping_info1.buyer_id = buyer0.id) ORDER BY res DESC LIMIT ?) AS res_1 FROM buyer buyer0 -
Seq( ("James Bond", 1000.0), ("叉烧包", 300.0), ("Li Haoyi", 0.0) )
WithCte
Basic WITH/Common-Table-Expression operations
WithCte.simple
ScalaSql supports WITH-clauses, also known as "Common Table Expressions"
(CTEs), via the .withCte syntax.
db.withCte(Buyer.select.map(_.name)) { bs =>
bs.map(_ + "-suffix")
}
-
WITH cte0 (res) AS (SELECT buyer0.name AS res FROM buyer buyer0) SELECT (cte0.res || ?) AS res FROM cte0 -
Seq("James Bond-suffix", "叉烧包-suffix", "Li Haoyi-suffix")
WithCte.multiple
Multiple withCte blocks can be stacked, turning into chained WITH clauses
in the generated SQL
db.withCte(Buyer.select) { bs =>
db.withCte(ShippingInfo.select) { sis =>
bs.join(sis)(_.id === _.buyerId)
.map { case (b, s) => (b.name, s.shippingDate) }
}
}
-
WITH cte0 (id, name) AS (SELECT buyer0.id AS id, buyer0.name AS name FROM buyer buyer0), cte1 (buyer_id, shipping_date) AS (SELECT shipping_info1.buyer_id AS buyer_id, shipping_info1.shipping_date AS shipping_date FROM shipping_info shipping_info1) SELECT cte0.name AS res_0, cte1.shipping_date AS res_1 FROM cte0 JOIN cte1 ON (cte0.id = cte1.buyer_id) -
Seq( ("叉烧包", LocalDate.parse("2010-02-03")), ("James Bond", LocalDate.parse("2012-04-05")), ("叉烧包", LocalDate.parse("2012-05-06")) )
WithCte.eliminated
Only the necessary columns are exported from the WITH clause; columns that
are un-used in the downstream SELECT clause are eliminated
db.withCte(Buyer.select) { bs =>
bs.map(_.name + "-suffix")
}
-
WITH cte0 (name) AS (SELECT buyer0.name AS name FROM buyer buyer0) SELECT (cte0.name || ?) AS res FROM cte0 -
Seq("James Bond-suffix", "叉烧包-suffix", "Li Haoyi-suffix")
WithCte.subquery
ScalaSql's withCte can be used anywhere a .select operator can be used. The
generated WITH clauses may be wrapped in sub-queries in scenarios where they
cannot be easily combined into a single query
db.withCte(Buyer.select) { bs =>
db.withCte(ShippingInfo.select) { sis =>
bs.join(sis)(_.id === _.buyerId)
}
}.join(
db.withCte(Product.select) { prs =>
Purchase.select.join(prs)(_.productId === _.id)
}
)(_._2.id === _._1.shippingInfoId)
.map { case (b, s, (pu, pr)) => (b.name, pr.name) }
-
SELECT subquery0.res_0_name AS res_0, subquery1.res_1_name AS res_1 FROM (WITH cte0 (id, name) AS (SELECT buyer0.id AS id, buyer0.name AS name FROM buyer buyer0), cte1 (id, buyer_id) AS (SELECT shipping_info1.id AS id, shipping_info1.buyer_id AS buyer_id FROM shipping_info shipping_info1) SELECT cte0.name AS res_0_name, cte1.id AS res_1_id FROM cte0 JOIN cte1 ON (cte0.id = cte1.buyer_id)) subquery0 JOIN (WITH cte1 (id, name) AS (SELECT product1.id AS id, product1.name AS name FROM product product1) SELECT purchase2.shipping_info_id AS res_0_shipping_info_id, cte1.name AS res_1_name FROM purchase purchase2 JOIN cte1 ON (purchase2.product_id = cte1.id)) subquery1 ON (subquery0.res_1_id = subquery1.res_0_shipping_info_id) -
Seq[(String, String)]( ("James Bond", "Camera"), ("James Bond", "Skate Board"), ("叉烧包", "Cookie"), ("叉烧包", "Face Mask"), ("叉烧包", "Face Mask"), ("叉烧包", "Guitar"), ("叉烧包", "Socks") )
ExprOps
Operations that can be performed on Expr[T] for any T
ExprOps.numeric.greaterThan
Expr(6) > Expr(2)
-
SELECT (? > ?) AS res -
true
ExprOps.numeric.lessThan
Expr(6) < Expr(2)
-
SELECT (? < ?) AS res -
false
ExprOps.numeric.greaterThanOrEquals
Expr(6) >= Expr(2)
-
SELECT (? >= ?) AS res -
true
ExprOps.numeric.lessThanOrEquals
Expr(6) <= Expr(2)
-
SELECT (? <= ?) AS res -
false
ExprOps.string.greaterThan
Expr("A") > Expr("B")
-
SELECT (? > ?) AS res -
false
ExprOps.string.lessThan
Expr("A") < Expr("B")
-
SELECT (? < ?) AS res -
true
ExprOps.string.greaterThanOrEquals
Expr("A") >= Expr("B")
-
SELECT (? >= ?) AS res -
false
ExprOps.string.lessThanOrEquals
Expr("A") <= Expr("B")
-
SELECT (? <= ?) AS res -
true
ExprOps.boolean.greaterThan
Expr(true) > Expr(false)
-
SELECT (? > ?) AS res -
true
ExprOps.boolean.lessThan
Expr(true) < Expr(true)
-
SELECT (? < ?) AS res -
false
ExprOps.boolean.greaterThanOrEquals
Expr(true) >= Expr(true)
-
SELECT (? >= ?) AS res -
true
ExprOps.boolean.lessThanOrEquals
Expr(true) <= Expr(true)
-
SELECT (? <= ?) AS res -
true
ExprOps.cast.byte
Expr(45.12).cast[Byte]
-
SELECT CAST(? AS INTEGER) AS res -
45: Byte
ExprOps.cast.short
Expr(1234.1234).cast[Short]
-
SELECT CAST(? AS SMALLINT) AS res -
1234: Short
ExprOps.cast.int
Expr(1234.1234).cast[Int]
-
SELECT CAST(? AS INTEGER) AS res -
1234
ExprOps.cast.long
Expr(1234.1234).cast[Long]
-
SELECT CAST(? AS BIGINT) AS res -
1234L
ExprOps.cast.string
Expr(1234.5678).cast[String]
-
SELECT CAST(? AS VARCHAR) AS res -
"1234.5678"
ExprOps.cast.localdate
Expr("2001-02-03").cast[java.time.LocalDate]
-
SELECT CAST(? AS DATE) AS res -
java.time.LocalDate.parse("2001-02-03")
ExprOps.cast.localdatetime
Expr("2023-11-12 03:22:41").cast[java.time.LocalDateTime]
-
SELECT CAST(? AS TIMESTAMP) AS res -
java.time.LocalDateTime.parse("2023-11-12T03:22:41")
ExprOps.cast.utildate
Expr("2023-11-12 03:22:41").cast[java.util.Date]
-
SELECT CAST(? AS TIMESTAMP) AS res -
new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2023-11-12 03:22:41")
ExprOps.cast.instant
Expr("2007-12-03 10:15:30.00").cast[java.time.Instant]
-
SELECT CAST(? AS TIMESTAMP) AS res -
java.time.Instant.parse("2007-12-03T02:15:30.00Z")
ExprOps.cast.castNamed
Expr(1234.5678).castNamed[String](sql"CHAR(3)")
-
SELECT CAST(? AS CHAR(3)) AS res -
"123"
ExprBooleanOps
Operations that can be performed on Expr[Boolean]
ExprBooleanOps.and
Expr(true) && Expr(true)
-
SELECT (? AND ?) AS res -
true
Expr(false) && Expr(true)
-
SELECT (? AND ?) AS res -
false
ExprBooleanOps.or
Expr(false) || Expr(false)
-
SELECT (? OR ?) AS res -
false
!Expr(false)
-
SELECT (NOT ?) AS res -
true
ExprNumericOps
Operations that can be performed on Expr[T] when T is numeric
ExprNumericOps.plus
Expr(6) + Expr(2)
-
SELECT (? + ?) AS res -
8
ExprNumericOps.minus
Expr(6) - Expr(2)
-
SELECT (? - ?) AS res -
4
ExprNumericOps.times
Expr(6) * Expr(2)
-
SELECT (? * ?) AS res -
12
ExprNumericOps.divide
Expr(6) / Expr(2)
-
SELECT (? / ?) AS res -
3
ExprNumericOps.modulo
Expr(6) % Expr(2)
-
SELECT MOD(?, ?) AS res -
0
ExprNumericOps.bitwiseAnd
Expr(6) & Expr(2)
-
SELECT (? & ?) AS res -
2
ExprNumericOps.bitwiseOr
Expr(6) | Expr(3)
-
SELECT (? | ?) AS res -
7
ExprNumericOps.between
Expr(4).between(Expr(2), Expr(6))
-
SELECT ? BETWEEN ? AND ? AS res -
true
ExprNumericOps.unaryPlus
+Expr(-4)
-
SELECT +? AS res -
-4
ExprNumericOps.unaryMinus
-Expr(-4)
-
SELECT -? AS res -
4
ExprNumericOps.unaryTilde
~Expr(-4)
-
SELECT ~? AS res -
3
ExprNumericOps.abs
Expr(-4).abs
-
SELECT ABS(?) AS res -
4
ExprNumericOps.mod
Expr(8).mod(Expr(3))
-
SELECT MOD(?, ?) AS res -
2
ExprNumericOps.ceil
Expr(4.3).ceil
-
SELECT CEIL(?) AS res -
5.0
ExprNumericOps.floor
Expr(4.7).floor
-
SELECT FLOOR(?) AS res -
4.0
ExprNumericOps.precedence
(Expr(2) + Expr(3)) * Expr(4)
-
SELECT ((? + ?) * ?) AS res -
20
ExprNumericOps.sign
Expr(-100).sign
-
SELECT SIGN(?) AS res -
-1
ExprSeqNumericOps
Operations that can be performed on Expr[Seq[T]] where T is numeric
ExprSeqNumericOps.sum
Purchase.select.map(_.count).sum
-
SELECT SUM(purchase0.count) AS res FROM purchase purchase0 -
140
ExprSeqNumericOps.min
Purchase.select.map(_.count).min
-
SELECT MIN(purchase0.count) AS res FROM purchase purchase0 -
3
ExprSeqNumericOps.max
Purchase.select.map(_.count).max
-
SELECT MAX(purchase0.count) AS res FROM purchase purchase0 -
100
ExprSeqNumericOps.avg
Purchase.select.map(_.count).avg
-
SELECT AVG(purchase0.count) AS res FROM purchase purchase0 -
20
ExprSeqOps
Operations that can be performed on Expr[Seq[_]]
ExprSeqOps.size
Purchase.select.size
-
SELECT COUNT(1) AS res FROM purchase purchase0 -
7
ExprSeqOps.sumBy.simple
Purchase.select.sumBy(_.count)
-
SELECT SUM(purchase0.count) AS res FROM purchase purchase0 -
140
ExprSeqOps.sumBy.some
Purchase.select.sumByOpt(_.count)
-
SELECT SUM(purchase0.count) AS res FROM purchase purchase0 -
Option(140)
ExprSeqOps.sumBy.none
Purchase.select.filter(_ => false).sumByOpt(_.count)
-
SELECT SUM(purchase0.count) AS res FROM purchase purchase0 WHERE ? -
Option.empty[Int]
ExprSeqOps.minBy.simple
Purchase.select.minBy(_.count)
-
SELECT MIN(purchase0.count) AS res FROM purchase purchase0 -
3
ExprSeqOps.minBy.some
Purchase.select.minByOpt(_.count)
-
SELECT MIN(purchase0.count) AS res FROM purchase purchase0 -
Option(3)
ExprSeqOps.minBy.none
Purchase.select.filter(_ => false).minByOpt(_.count)
-
SELECT MIN(purchase0.count) AS res FROM purchase purchase0 WHERE ? -
Option.empty[Int]
ExprSeqOps.maxBy.simple
Purchase.select.maxBy(_.count)
-
SELECT MAX(purchase0.count) AS res FROM purchase purchase0 -
100
ExprSeqOps.maxBy.some
Purchase.select.maxByOpt(_.count)
-
SELECT MAX(purchase0.count) AS res FROM purchase purchase0 -
Option(100)
ExprSeqOps.maxBy.none
Purchase.select.filter(_ => false).maxByOpt(_.count)
-
SELECT MAX(purchase0.count) AS res FROM purchase purchase0 WHERE ? -
Option.empty[Int]
ExprSeqOps.avgBy.simple
Purchase.select.avgBy(_.count)
-
SELECT AVG(purchase0.count) AS res FROM purchase purchase0 -
20
ExprSeqOps.avgBy.some
Purchase.select.avgByOpt(_.count)
-
SELECT AVG(purchase0.count) AS res FROM purchase purchase0 -
Option(20)
ExprSeqOps.avgBy.none
Purchase.select.filter(_ => false).avgByOpt(_.count)
-
SELECT AVG(purchase0.count) AS res FROM purchase purchase0 WHERE ? -
Option.empty[Int]
ExprSeqOps.mkString.simple
Buyer.select.map(_.name).mkString()
-
SELECT STRING_AGG(buyer0.name || '', '') AS res FROM buyer buyer0 -
"James Bond叉烧包Li Haoyi"
ExprSeqOps.mkString.sep
Buyer.select.map(_.name).mkString(", ")
-
SELECT STRING_AGG(buyer0.name || '', ?) AS res FROM buyer buyer0 -
"James Bond, 叉烧包, Li Haoyi"
ExprStringOps
Operations that can be performed on Expr[String]
ExprStringOps.plus
Expr("hello") + Expr("world")
-
SELECT (? || ?) AS res -
"helloworld"
ExprStringOps.like
Expr("hello").like("he%")
-
SELECT (? LIKE ?) AS res -
true
ExprStringOps.length
Expr("hello").length
-
SELECT LENGTH(?) AS res -
5
ExprStringOps.octetLength
Expr("叉烧包").octetLength
-
SELECT OCTET_LENGTH(?) AS res -
9
ExprStringOps.position
Expr("hello").indexOf("ll")
-
SELECT POSITION(? IN ?) AS res -
3
ExprStringOps.toLowerCase
Expr("Hello").toLowerCase
-
SELECT LOWER(?) AS res -
"hello"
ExprStringOps.trim
Expr(" Hello ").trim
-
SELECT TRIM(?) AS res -
"Hello"
ExprStringOps.ltrim
Expr(" Hello ").ltrim
-
SELECT LTRIM(?) AS res -
"Hello "
ExprStringOps.rtrim
Expr(" Hello ").rtrim
-
SELECT RTRIM(?) AS res -
" Hello"
ExprStringOps.substring
Expr("Hello").substring(2, 2)
-
SELECT SUBSTRING(?, ?, ?) AS res -
"el"
ExprStringOps.startsWith
Expr("Hello").startsWith("Hel")
-
SELECT (? LIKE ? || '%') AS res -
true
ExprStringOps.endsWith
Expr("Hello").endsWith("llo")
-
SELECT (? LIKE '%' || ?) AS res -
true
ExprStringOps.contains
Expr("Hello").contains("ll")
-
SELECT (? LIKE '%' || ? || '%') AS res -
true
ExprStringOps.replace
Expr("Hello").replace("ll", "rr")
-
SELECT REPLACE(?, ?, ?) AS res -
"Herro"
ExprBlobOps
Operations that can be performed on Expr[Bytes]
ExprBlobOps.plus
Expr(Bytes("hello")) + Expr(Bytes("world"))
-
SELECT (? || ?) AS res -
Bytes("helloworld")
ExprBlobOps.like
Expr(Bytes("hello")).like(Bytes("he%"))
-
SELECT (? LIKE ?) AS res -
true
ExprBlobOps.length
Expr(Bytes("hello")).length
-
SELECT LENGTH(?) AS res -
5
ExprBlobOps.octetLength
Expr(Bytes("叉烧包")).octetLength
-
SELECT OCTET_LENGTH(?) AS res -
9
ExprBlobOps.position
Expr(Bytes("hello")).indexOf(Bytes("ll"))
-
SELECT POSITION(? IN ?) AS res -
3
ExprBlobOps.substring
Expr(Bytes("Hello")).substring(2, 2)
-
SELECT SUBSTRING(?, ?, ?) AS res -
Bytes("el")
ExprBlobOps.startsWith
Expr(Bytes("Hello")).startsWith(Bytes("Hel"))
-
SELECT (? LIKE ? || '%') AS res -
true
ExprBlobOps.endsWith
Expr(Bytes("Hello")).endsWith(Bytes("llo"))
-
SELECT (? LIKE '%' || ?) AS res -
true
ExprBlobOps.contains
Expr(Bytes("Hello")).contains(Bytes("ll"))
-
SELECT (? LIKE '%' || ? || '%') AS res -
true
ExprMathOps
Math operations; supported by H2/Postgres/MySql/MsSql, not supported by Sqlite
ExprMathOps.power
db.power(10, 3)
-
SELECT POWER(?, ?) AS res -
1000.0
ExprMathOps.sqrt
db.sqrt(9)
-
SELECT SQRT(?) AS res -
3.0
ExprMathOps.ln
db.ln(16.0)
-
SELECT LN(?) AS res
ExprMathOps.log
db.log(2, 8)
-
SELECT LOG(?, ?) AS res
ExprMathOps.log10
db.log10(16.0)
-
SELECT LOG10(?) AS res
ExprMathOps.exp
db.exp(16.0)
-
SELECT EXP(?) AS res
ExprMathOps.sin
db.sin(16.0)
-
SELECT SIN(?) AS res
ExprMathOps.cos
db.cos(16.0)
-
SELECT COS(?) AS res
ExprMathOps.tan
db.tan(16.0)
-
SELECT TAN(?) AS res
ExprMathOps.asin
db.asin(1.0)
-
SELECT ASIN(?) AS res
ExprMathOps.acos
db.acos(1.0)
-
SELECT ACOS(?) AS res
ExprMathOps.atan
db.atan(1.0)
-
SELECT ATAN(?) AS res
ExprMathOps.atan2
db.atan2(16.0, 23.0)
-
SELECT ATAN2(?, ?) AS res
ExprMathOps.pi
db.pi
-
SELECT PI() AS res
ExprMathOps.degrees
db.degrees(180)
-
SELECT DEGREES(?) AS res
ExprMathOps.radians
db.radians(180)
-
SELECT RADIANS(?) AS res
DbCountOps
COUNT and COUNT(DISTINCT) aggregations
DbCountOps.countBy
Purchase.select.countBy(_.productId)
-
SELECT COUNT(purchase0.product_id) AS res FROM purchase purchase0 -
7
DbCountOps.countDistinctBy
Purchase.select.countDistinctBy(_.productId)
-
SELECT COUNT(DISTINCT purchase0.product_id) AS res FROM purchase purchase0 -
6
DbCountOps.countExpr
Purchase.select.map(_.productId).count
-
SELECT COUNT(purchase0.product_id) AS res FROM purchase purchase0 -
7
DbCountOps.countDistinctExpr
Purchase.select.map(_.productId).countDistinct
-
SELECT COUNT(DISTINCT purchase0.product_id) AS res FROM purchase purchase0 -
6
DbCountOps.countWithGroupBy
Purchase.select.groupBy(_.shippingInfoId)(agg => agg.countBy(_.productId))
-
SELECT purchase0.shipping_info_id AS res_0, COUNT(purchase0.product_id) AS res_1 FROM purchase purchase0 GROUP BY purchase0.shipping_info_id -
Seq((1, 3), (2, 2), (3, 2))
DbCountOps.countDistinctWithGroupBy
Purchase.select.groupBy(_.shippingInfoId)(agg => agg.countDistinctBy(_.productId))
-
SELECT purchase0.shipping_info_id AS res_0, COUNT(DISTINCT purchase0.product_id) AS res_1 FROM purchase purchase0 GROUP BY purchase0.shipping_info_id -
Seq((1, 3), (2, 2), (3, 2))
DbCountOps.countWithFilter
Purchase.select.filter(_.total > 100).countBy(_.productId)
-
SELECT COUNT(purchase0.product_id) AS res FROM purchase purchase0 WHERE (purchase0.total > ?) -
4
DbCountOps.countDistinctWithFilter
Purchase.select.filter(_.total > 100).countDistinctBy(_.productId)
-
SELECT COUNT(DISTINCT purchase0.product_id) AS res FROM purchase purchase0 WHERE (purchase0.total > ?) -
4
DbCountOps.multipleAggregatesWithCount
Purchase.select.aggregate(agg =>
(agg.countBy(_.productId), agg.countDistinctBy(_.productId), agg.sumBy(_.total))
)
-
SELECT COUNT(purchase0.product_id) AS res_0, COUNT(DISTINCT purchase0.product_id) AS res_1, SUM(purchase0.total) AS res_2 FROM purchase purchase0 -
(7, 6, 12343.2)
DbCountOps.countInJoin
(for {
p <- Purchase.select
pr <- Product.join(_.id === p.productId)
} yield pr).countBy(_.name)
-
SELECT COUNT(product1.name) AS res FROM purchase purchase0 JOIN product product1 ON (product1.id = purchase0.product_id) -
7
DbCountOps.countWithComplexExpressions.arithmetic
Purchase.select.map(_.total * 2).count
-
SELECT COUNT((purchase0.total * ?)) AS res FROM purchase purchase0 -
7
DbCountOps.countWithComplexExpressions.stringConcat
Product.select.map(p => p.name + " - " + p.kebabCaseName).count
-
SELECT COUNT(((product0.name || ?) || product0.kebab_case_name)) AS res FROM product product0 -
6
DbCountOps.countDistinctWithComplexExpressions.arithmetic
Purchase.select.map(p => p.productId + 100).countDistinct
-
SELECT COUNT(DISTINCT (purchase0.product_id + ?)) AS res FROM purchase purchase0 -
6
DbCountOpsOption
COUNT operations with Option types
DbCountOpsOption
OptCols.insert.batched(_.myInt, _.myInt2)(
(None, None),
(Some(1), Some(2)),
(Some(3), None),
(None, Some(4)),
(Some(1), Some(5)),
(Some(2), Some(2))
)
-
6
DbCountOpsOption.countOptionColumn.countBy
OptCols.select.countBy(_.myInt)
-
SELECT COUNT(opt_cols0.my_int) AS res FROM opt_cols opt_cols0 -
4
DbCountOpsOption.countOptionColumn.countDistinctBy
OptCols.select.countDistinctBy(_.myInt)
-
SELECT COUNT(DISTINCT opt_cols0.my_int) AS res FROM opt_cols opt_cols0 -
3
DbCountOpsOption.countExprOption.count
OptCols.select.map(_.myInt2).count
-
SELECT COUNT(opt_cols0.my_int2) AS res FROM opt_cols opt_cols0 -
4
DbCountOpsOption.countExprOption.countDistinct
OptCols.select.map(_.myInt2).countDistinct
-
SELECT COUNT(DISTINCT opt_cols0.my_int2) AS res FROM opt_cols opt_cols0 -
3
DbCountOpsOption.groupByWithOptionCount
OptCols.select
.groupBy(_.myInt)(agg => agg.countBy(_.myInt2))
-
SELECT opt_cols0.my_int AS res_0, COUNT(opt_cols0.my_int2) AS res_1 FROM opt_cols opt_cols0 GROUP BY opt_cols0.my_int -
Seq((None, 1), (Some(1), 2), (Some(2), 1), (Some(3), 0))
DbCountOpsAdvanced
Advanced COUNT operations with edge cases and expressions
DbCountOpsAdvanced.setup
OptCols.insert.batched(_.myInt, _.myInt2)(
(Some(1), Some(1)),
(Some(2), None),
(Some(3), Some(3)),
(None, Some(4)),
(Some(5), Some(5))
)
-
5
DbCountOpsAdvanced.countWithNulls.nonNullCount
OptCols.select.countBy(_.myInt)
-
SELECT COUNT(opt_cols0.my_int) AS res FROM opt_cols opt_cols0 -
4
DbCountOpsAdvanced.countWithNulls.nonNullCountDistinct
OptCols.select.countDistinctBy(_.myInt)
-
SELECT COUNT(DISTINCT opt_cols0.my_int) AS res FROM opt_cols opt_cols0 -
4
DbCountOpsAdvanced.countWithNulls.secondColumnCount
OptCols.select.countBy(_.myInt2)
-
SELECT COUNT(opt_cols0.my_int2) AS res FROM opt_cols opt_cols0 -
4
DbCountOpsAdvanced.countWithNulls.secondColumnCountDistinct
OptCols.select.countDistinctBy(_.myInt2)
-
SELECT COUNT(DISTINCT opt_cols0.my_int2) AS res FROM opt_cols opt_cols0 -
4
DbCountOpsAdvanced.countWithExpressions.countArithmeticExpressions
Purchase.select.map(p => p.productId * 2).count
-
SELECT COUNT((purchase0.product_id * ?)) AS res FROM purchase purchase0 -
7
DbCountOpsAdvanced.countWithExpressions.countDistinctArithmeticExpressions
Purchase.select.map(p => p.productId + p.shippingInfoId).countDistinct
-
SELECT COUNT(DISTINCT (purchase0.product_id + purchase0.shipping_info_id)) AS res FROM purchase purchase0 -
6
DbCountOpsAdvanced.countWithModuloOperations.moduloCount
Purchase.select.map(p => p.productId % 2).countDistinct
-
SELECT COUNT(DISTINCT MOD(purchase0.product_id, ?)) AS res FROM purchase purchase0 -
2
DbCountOpsAdvanced.countWithModuloOperations.moduloWithFilter
Purchase.select.filter(_.productId > 2).map(p => p.productId % 3).countDistinct
-
SELECT COUNT(DISTINCT MOD(purchase0.product_id, ?)) AS res FROM purchase purchase0 WHERE (purchase0.product_id > ?) -
3
DbCountOpsAdvanced.countWithGroupBy.groupByWithCount
Purchase.select.groupBy(_.shippingInfoId)(agg => agg.countBy(_.productId))
-
SELECT purchase0.shipping_info_id AS res_0, COUNT(purchase0.product_id) AS res_1 FROM purchase purchase0 GROUP BY purchase0.shipping_info_id -
Seq((1, 3), (2, 2), (3, 2))
DbCountOpsAdvanced.countWithGroupBy.groupByWithCountDistinct
Purchase.select.groupBy(_.shippingInfoId)(agg => agg.countDistinctBy(_.productId))
-
SELECT purchase0.shipping_info_id AS res_0, COUNT(DISTINCT purchase0.product_id) AS res_1 FROM purchase purchase0 GROUP BY purchase0.shipping_info_id -
Seq((1, 3), (2, 2), (3, 2))
DbCountOpsAdvanced.countWithComplexFilters.countWithRangeFilter
Purchase.select
.filter(p => p.productId >= 2 && p.productId <= 4)
.countBy(_.total)
-
SELECT COUNT(purchase0.total) AS res FROM purchase purchase0 WHERE ((purchase0.product_id >= ?) AND (purchase0.product_id <= ?)) -
3
DbCountOpsAdvanced.countWithComplexFilters.countWithDecimalFilter
Purchase.select
.filter(_.total > 100)
.countDistinctBy(_.productId)
-
SELECT COUNT(DISTINCT purchase0.product_id) AS res FROM purchase purchase0 WHERE (purchase0.total > ?) -
4
DbCountOpsAdvanced.countWithAdvancedPredicates.countWithComplexFilter
Purchase.select
.filter(p => p.productId > 1 && p.shippingInfoId <= 2)
.countDistinctBy(_.productId)
-
SELECT COUNT(DISTINCT purchase0.product_id) AS res FROM purchase purchase0 WHERE ((purchase0.product_id > ?) AND (purchase0.shipping_info_id <= ?)) -
4
DataTypes
Basic operations on all the data types that ScalaSql supports mapping between Database types and Scala types
DataTypes.constant
This example demonstrates a range of different data types being written and read back via ScalaSQL
object MyEnum extends Enumeration {
val foo, bar, baz = Value
implicit def make: String => Value = withName
}
case class DataTypes[T[_]](
myTinyInt: T[Byte],
mySmallInt: T[Short],
myInt: T[Int],
myBigInt: T[Long],
myDouble: T[Double],
myBoolean: T[Boolean],
myLocalDate: T[LocalDate],
myLocalTime: T[LocalTime],
myLocalDateTime: T[LocalDateTime],
myUtilDate: T[Date],
myInstant: T[Instant],
myVarBinary: T[geny.Bytes],
myUUID: T[java.util.UUID],
myEnum: T[MyEnum.Value]
)
object DataTypes extends Table[DataTypes]
val value = DataTypes[Sc](
myTinyInt = 123.toByte,
mySmallInt = 12345.toShort,
myInt = 12345678,
myBigInt = 12345678901L,
myDouble = 3.14,
myBoolean = false,
myLocalDate = LocalDate.parse("2023-12-20"),
myLocalTime = LocalTime.parse("10:15:30"),
myLocalDateTime = LocalDateTime.parse("2011-12-03T10:15:30"),
myUtilDate = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS").parse("2011-12-03T10:15:30.000"),
myInstant = Instant.parse("2011-12-03T10:15:30Z"),
myVarBinary = new geny.Bytes(Array[Byte](1, 2, 3, 4, 5, 6, 7, 8)),
myUUID = new java.util.UUID(1234567890L, 9876543210L),
myEnum = MyEnum.bar
)
val value2 = DataTypes[Sc](
67.toByte,
mySmallInt = 32767.toShort,
myInt = 12345678,
myBigInt = 9876543210L,
myDouble = 2.71,
myBoolean = true,
myLocalDate = LocalDate.parse("2020-02-22"),
myLocalTime = LocalTime.parse("03:05:01"),
myLocalDateTime = LocalDateTime.parse("2021-06-07T02:01:03"),
myUtilDate = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS").parse("2021-06-07T02:01:03.000"),
myInstant = Instant.parse("2021-06-07T02:01:03Z"),
myVarBinary = new geny.Bytes(Array[Byte](9, 8, 7, 6, 5, 4, 3, 2)),
myUUID = new java.util.UUID(9876543210L, 1234567890L),
myEnum = MyEnum.baz
)
db.run(
DataTypes.insert.columns(
_.myTinyInt := value.myTinyInt,
_.mySmallInt := value.mySmallInt,
_.myInt := value.myInt,
_.myBigInt := value.myBigInt,
_.myDouble := value.myDouble,
_.myBoolean := value.myBoolean,
_.myLocalDate := value.myLocalDate,
_.myLocalTime := value.myLocalTime,
_.myLocalDateTime := value.myLocalDateTime,
_.myUtilDate := value.myUtilDate,
_.myInstant := value.myInstant,
_.myVarBinary := value.myVarBinary,
_.myUUID := value.myUUID,
_.myEnum := value.myEnum
)
) ==> 1
db.run(
DataTypes.insert.columns(
_.myTinyInt := value2.myTinyInt,
_.mySmallInt := value2.mySmallInt,
_.myInt := value2.myInt,
_.myBigInt := value2.myBigInt,
_.myDouble := value2.myDouble,
_.myBoolean := value2.myBoolean,
_.myLocalDate := value2.myLocalDate,
_.myLocalTime := value2.myLocalTime,
_.myLocalDateTime := value2.myLocalDateTime,
_.myUtilDate := value2.myUtilDate,
_.myInstant := value2.myInstant,
_.myVarBinary := value2.myVarBinary,
_.myUUID := value2.myUUID,
_.myEnum := value2.myEnum
)
) ==> 1
db.run(DataTypes.select) ==> Seq(value, value2)
DataTypes.nonRoundTrip
In general, databases do not store timezones and offsets together with their timestamps:
"TIMESTAMP WITH TIMEZONE" is a lie and it actually stores UTC and renders to whatever
timezone the client queries it from. Thus values of type OffsetDateTime can preserve
their instant, but cannot be round-tripped preserving the offset.
case class NonRoundTripTypes[T[_]](
myZonedDateTime: T[ZonedDateTime],
myOffsetDateTime: T[OffsetDateTime]
)
object NonRoundTripTypes extends Table[NonRoundTripTypes]
val value = NonRoundTripTypes[Sc](
myZonedDateTime = ZonedDateTime.parse("2011-12-03T10:15:30+01:00[Europe/Paris]"),
myOffsetDateTime = OffsetDateTime.parse("2011-12-03T10:15:30+00:00")
)
def normalize(v: NonRoundTripTypes[Sc]) = v.copy[Sc](
myZonedDateTime = v.myZonedDateTime.withZoneSameInstant(ZoneId.systemDefault),
myOffsetDateTime = v.myOffsetDateTime.withOffsetSameInstant(OffsetDateTime.now.getOffset)
)
db.run(
NonRoundTripTypes.insert.columns(
_.myOffsetDateTime := value.myOffsetDateTime,
_.myZonedDateTime := value.myZonedDateTime
)
) ==> 1
db.run(NonRoundTripTypes.select).map(normalize) ==> Seq(normalize(value))
DataTypes.enclosing
You can nest case classes in other case classes to DRY up common sets of
table columns. These nested case classes have their columns flattened out
into the enclosing case class's columns, such that at the SQL level it is
all flattened out without nesting.
// case class Nested[T[_]](
// fooId: T[Int],
// myBoolean: T[Boolean],
// )
// object Nested extends Table[Nested]
//
// case class Enclosing[T[_]](
// barId: T[Int],
// myString: T[String],
// foo: Nested[T]
// )
// object Enclosing extends Table[Enclosing]
val value1 = Enclosing[Sc](
barId = 1337,
myString = "hello",
foo = Nested[Sc](
fooId = 271828,
myBoolean = true
)
)
val value2 = Enclosing[Sc](
barId = 31337,
myString = "world",
foo = Nested[Sc](
fooId = 1618,
myBoolean = false
)
)
val insertColumns = Enclosing.insert.columns(
_.barId := value1.barId,
_.myString := value1.myString,
_.foo.fooId := value1.foo.fooId,
_.foo.myBoolean := value1.foo.myBoolean
)
db.renderSql(insertColumns) ==>
"INSERT INTO enclosing (bar_id, my_string, foo_id, my_boolean) VALUES (?, ?, ?, ?)"
db.run(insertColumns) ==> 1
val insertValues = Enclosing.insert.values(value2)
db.renderSql(insertValues) ==>
"INSERT INTO enclosing (bar_id, my_string, foo_id, my_boolean) VALUES (?, ?, ?, ?)"
db.run(insertValues) ==> 1
db.renderSql(Enclosing.select) ==> """
SELECT
enclosing0.bar_id AS bar_id,
enclosing0.my_string AS my_string,
enclosing0.foo_id AS foo_id,
enclosing0.my_boolean AS my_boolean
FROM enclosing enclosing0
"""
db.run(Enclosing.select) ==> Seq(value1, value2)
DataTypes.JoinNullable proper type mapping
case class A[T[_]](id: T[Int], bId: T[Option[Int]])
object A extends Table[A]
object Custom extends Enumeration {
val Foo, Bar = Value
implicit def make: String => Value = withName
}
case class B[T[_]](id: T[Int], custom: T[Custom.Value])
object B extends Table[B]
db.run(A.insert.columns(_.id := 1, _.bId := None))
val result = db.run(A.select.leftJoin(B)(_.id === _.id).single)
result._2 ==> None
DataTypes.enclosing - with SimpleTable
You can nest case classes in other case classes to DRY up common sets of
table columns. These nested case classes have their columns flattened out
into the enclosing case class's columns, such that at the SQL level it is
all flattened out without nesting.
Important: When using nested case classes with SimpleTable,
make sure to extend SimpleTable.Nested in the nested class.
// case class Nested(
// fooId: Int,
// myBoolean: Boolean,
// ) extends SimpleTable.Nested
// object Nested extends SimpleTable[Nested]
//
// case class Enclosing(
// barId: Int,
// myString: String,
// foo: Nested
// )
// object Enclosing extends SimpleTable[Enclosing]
val value1 = Enclosing(
barId = 1337,
myString = "hello",
foo = Nested(
fooId = 271828,
myBoolean = true
)
)
val value2 = Enclosing(
barId = 31337,
myString = "world",
foo = Nested(
fooId = 1618,
myBoolean = false
)
)
val insertColumns = Enclosing.insert.columns(
_.barId := value1.barId,
_.myString := value1.myString,
_.foo.fooId := value1.foo.fooId,
_.foo.myBoolean := value1.foo.myBoolean
)
db.renderSql(insertColumns) ==>
"INSERT INTO enclosing (bar_id, my_string, foo_id, my_boolean) VALUES (?, ?, ?, ?)"
db.run(insertColumns) ==> 1
val insertValues = Enclosing.insert.values(value2)
db.renderSql(insertValues) ==>
"INSERT INTO enclosing (bar_id, my_string, foo_id, my_boolean) VALUES (?, ?, ?, ?)"
db.run(insertValues) ==> 1
db.renderSql(Enclosing.select) ==> """
SELECT
enclosing0.bar_id AS bar_id,
enclosing0.my_string AS my_string,
enclosing0.foo_id AS foo_id,
enclosing0.my_boolean AS my_boolean
FROM enclosing enclosing0
"""
db.run(Enclosing.select) ==> Seq(value1, value2)
Optional
Queries using columns that may be NULL, Expr[Option[T]] or Option[T] in Scala
Optional
OptCols.insert.batched(_.myInt, _.myInt2)(
(None, None),
(Some(1), Some(2)),
(Some(3), None),
(None, Some(4))
)
-
4
Optional.selectAll
Nullable columns are modelled as T[Option[V]] fields on your case class,
and are returned to you as Option[V] values when you run a query. These
can be Some or None
OptCols.select
-
SELECT opt_cols0.my_int AS my_int, opt_cols0.my_int2 AS my_int2 FROM opt_cols opt_cols0 -
Seq( OptCols[Sc](None, None), OptCols[Sc](Some(1), Some(2)), OptCols[Sc](Some(3), None), OptCols[Sc](None, Some(4)) )
Optional.groupByMaxGet
Some aggregates return Expr[Option[V]]s, et.c. .maxByOpt
OptCols.select.groupBy(_.myInt)(_.maxByOpt(_.myInt2.get))
-
SELECT opt_cols0.my_int AS res_0, MAX(opt_cols0.my_int2) AS res_1 FROM opt_cols opt_cols0 GROUP BY opt_cols0.my_int -
Seq(None -> Some(4), Some(1) -> Some(2), Some(3) -> None)
Optional.isDefined
.isDefined on Expr[Option[V]] translates to a SQL
IS NOT NULL check
OptCols.select.filter(_.myInt.isDefined)
-
SELECT opt_cols0.my_int AS my_int, opt_cols0.my_int2 AS my_int2 FROM opt_cols opt_cols0 WHERE (opt_cols0.my_int IS NOT NULL) -
Seq(OptCols[Sc](Some(1), Some(2)), OptCols[Sc](Some(3), None))
Optional.isEmpty
.isEmpty on Expr[Option[V]] translates to a SQL
IS NULL check
OptCols.select.filter(_.myInt.isEmpty)
-
SELECT opt_cols0.my_int AS my_int, opt_cols0.my_int2 AS my_int2 FROM opt_cols opt_cols0 WHERE (opt_cols0.my_int IS NULL) -
Seq(OptCols[Sc](None, None), OptCols[Sc](None, Some(4)))
Optional.sqlEquals.nonOptionHit
Backticked = equality in ScalaSQL translates to a raw =
in SQL. This follows SQL NULL semantics, meaning that
None = None returns false rather than true
OptCols.select.filter(_.myInt `=` 1)
-
SELECT opt_cols0.my_int AS my_int, opt_cols0.my_int2 AS my_int2 FROM opt_cols opt_cols0 WHERE (opt_cols0.my_int = ?) -
Seq(OptCols[Sc](Some(1), Some(2)))
Optional.sqlEquals.nonOptionMiss
OptCols.select.filter(_.myInt `=` 2)
-
SELECT opt_cols0.my_int AS my_int, opt_cols0.my_int2 AS my_int2 FROM opt_cols opt_cols0 WHERE (opt_cols0.my_int = ?) -
Seq[OptCols[Sc]]()
Optional.sqlEquals.optionMiss
OptCols.select.filter(_.myInt `=` Option.empty[Int])
-
SELECT opt_cols0.my_int AS my_int, opt_cols0.my_int2 AS my_int2 FROM opt_cols opt_cols0 WHERE (opt_cols0.my_int = ?) -
Seq[OptCols[Sc]]()
Optional.scalaEquals.someHit
=== equality in ScalaSQL translates to a IS NOT DISTINCT in SQL.
This roughly follows Scala == semantics, meaning None === None
returns true
OptCols.select.filter(_.myInt === Option(1))
-
SELECT opt_cols0.my_int AS my_int, opt_cols0.my_int2 AS my_int2 FROM opt_cols opt_cols0 WHERE (opt_cols0.my_int IS NOT DISTINCT FROM ?) -
Seq(OptCols[Sc](Some(1), Some(2)))
Optional.scalaEquals.noneHit
OptCols.select.filter(_.myInt === Option.empty[Int])
-
SELECT opt_cols0.my_int AS my_int, opt_cols0.my_int2 AS my_int2 FROM opt_cols opt_cols0 WHERE (opt_cols0.my_int IS NOT DISTINCT FROM ?) -
Seq(OptCols[Sc](None, None), OptCols[Sc](None, Some(4)))
Optional.scalaEquals.notEqualsSome
OptCols.select.filter(_.myInt !== Option(1))
-
SELECT opt_cols0.my_int AS my_int, opt_cols0.my_int2 AS my_int2 FROM opt_cols opt_cols0 WHERE (opt_cols0.my_int IS DISTINCT FROM ?) -
Seq( OptCols[Sc](None, None), OptCols[Sc](Some(3), None), OptCols[Sc](None, Some(value = 4)) )
Optional.scalaEquals.notEqualsNone
OptCols.select.filter(_.myInt !== Option.empty[Int])
-
SELECT opt_cols0.my_int AS my_int, opt_cols0.my_int2 AS my_int2 FROM opt_cols opt_cols0 WHERE (opt_cols0.my_int IS DISTINCT FROM ?) -
Seq( OptCols[Sc](Some(1), Some(2)), OptCols[Sc](Some(3), None) )
Optional.map
You can use operators like .map and .flatMap to work with
your Expr[Option[V]] values. These roughly follow the semantics
that you would be familiar with from Scala.
OptCols.select.map(d => d.copy[Expr](myInt = d.myInt.map(_ + 10)))
-
SELECT (opt_cols0.my_int + ?) AS my_int, opt_cols0.my_int2 AS my_int2 FROM opt_cols opt_cols0 -
Seq( OptCols[Sc](None, None), OptCols[Sc](Some(11), Some(2)), OptCols[Sc](Some(13), None), OptCols[Sc](None, Some(4)) )
Optional.map2
OptCols.select.map(_.myInt.map(_ + 10))
-
SELECT (opt_cols0.my_int + ?) AS res FROM opt_cols opt_cols0 -
Seq(None, Some(11), Some(13), None)
Optional.flatMap
OptCols.select
.map(d => d.copy[Expr](myInt = d.myInt.flatMap(v => d.myInt2.map(v2 => v + v2 + 10))))
-
SELECT ((opt_cols0.my_int + opt_cols0.my_int2) + ?) AS my_int, opt_cols0.my_int2 AS my_int2 FROM opt_cols opt_cols0 -
Seq( OptCols[Sc](None, None), OptCols[Sc](Some(13), Some(2)), // because my_int2 is added to my_int, and my_int2 is null, my_int becomes null too OptCols[Sc](None, None), OptCols[Sc](None, Some(4)) )
Optional.mapGet
You can use .get to turn an Expr[Option[V]] into an Expr[V]. This follows
SQL semantics, such that NULLs anywhere in that selected column automatically
will turn the whole column None (if it's an Expr[Option[V]] column) or null
(if it's not an optional column)
OptCols.select.map(d => d.copy[Expr](myInt = d.myInt.map(_ + d.myInt2.get + 1)))
-
SELECT ((opt_cols0.my_int + opt_cols0.my_int2) + ?) AS my_int, opt_cols0.my_int2 AS my_int2 FROM opt_cols opt_cols0 -
Seq( OptCols[Sc](None, None), OptCols[Sc](Some(4), Some(2)), // because my_int2 is added to my_int, and my_int2 is null, my_int becomes null too OptCols[Sc](None, None), OptCols[Sc](None, Some(4)) )
Optional.rawGet
OptCols.select.map(d => d.copy[Expr](myInt = d.myInt.get + d.myInt2.get + 1))
-
SELECT ((opt_cols0.my_int + opt_cols0.my_int2) + ?) AS my_int, opt_cols0.my_int2 AS my_int2 FROM opt_cols opt_cols0 -
Seq( OptCols[Sc](None, None), OptCols[Sc](Some(4), Some(2)), // because my_int2 is added to my_int, and my_int2 is null, my_int becomes null too OptCols[Sc](None, None), OptCols[Sc](None, Some(4)) )
Optional.getOrElse
OptCols.select.map(d => d.copy[Expr](myInt = d.myInt.getOrElse(-1)))
-
SELECT COALESCE(opt_cols0.my_int, ?) AS my_int, opt_cols0.my_int2 AS my_int2 FROM opt_cols opt_cols0 -
Seq( OptCols[Sc](Some(-1), None), OptCols[Sc](Some(1), Some(2)), OptCols[Sc](Some(3), None), OptCols[Sc](Some(-1), Some(4)) )
Optional.orElse
OptCols.select.map(d => d.copy[Expr](myInt = d.myInt.orElse(d.myInt2)))
-
SELECT COALESCE(opt_cols0.my_int, opt_cols0.my_int2) AS my_int, opt_cols0.my_int2 AS my_int2 FROM opt_cols opt_cols0 -
Seq( OptCols[Sc](None, None), OptCols[Sc](Some(1), Some(2)), OptCols[Sc](Some(3), None), OptCols[Sc](Some(4), Some(4)) )
Optional.filter
.filter follows normal Scala semantics, and translates to a CASE/WHEN (foo)/ELSE NULL
OptCols.select.map(d => d.copy[Expr](myInt = d.myInt.filter(_ < 2)))
-
SELECT CASE WHEN (opt_cols0.my_int < ?) THEN opt_cols0.my_int ELSE NULL END AS my_int, opt_cols0.my_int2 AS my_int2 FROM opt_cols opt_cols0 -
Seq( OptCols[Sc](None, None), OptCols[Sc](Some(1), Some(2)), OptCols[Sc](None, None), OptCols[Sc](None, Some(4)) )
Optional.sorting.nullsLast
.nullsLast and .nullsFirst translate to SQL NULLS LAST and NULLS FIRST clauses
OptCols.select.sortBy(_.myInt).nullsLast
-
SELECT opt_cols0.my_int AS my_int, opt_cols0.my_int2 AS my_int2 FROM opt_cols opt_cols0 ORDER BY my_int NULLS LAST -
Seq( OptCols[Sc](Some(1), Some(2)), OptCols[Sc](Some(3), None), OptCols[Sc](None, None), OptCols[Sc](None, Some(4)) )
Optional.sorting.nullsFirst
OptCols.select.sortBy(_.myInt).nullsFirst
-
SELECT opt_cols0.my_int AS my_int, opt_cols0.my_int2 AS my_int2 FROM opt_cols opt_cols0 ORDER BY my_int NULLS FIRST -
Seq( OptCols[Sc](None, None), OptCols[Sc](None, Some(4)), OptCols[Sc](Some(1), Some(2)), OptCols[Sc](Some(3), None) )
Optional.sorting.ascNullsLast
OptCols.select.sortBy(_.myInt).asc.nullsLast
-
SELECT opt_cols0.my_int AS my_int, opt_cols0.my_int2 AS my_int2 FROM opt_cols opt_cols0 ORDER BY my_int ASC NULLS LAST -
Seq( OptCols[Sc](Some(1), Some(2)), OptCols[Sc](Some(3), None), OptCols[Sc](None, None), OptCols[Sc](None, Some(4)) )
Optional.sorting.ascNullsFirst
OptCols.select.sortBy(_.myInt).asc.nullsFirst
-
SELECT opt_cols0.my_int AS my_int, opt_cols0.my_int2 AS my_int2 FROM opt_cols opt_cols0 ORDER BY my_int ASC NULLS FIRST -
Seq( OptCols[Sc](None, None), OptCols[Sc](None, Some(4)), OptCols[Sc](Some(1), Some(2)), OptCols[Sc](Some(3), None) )
Optional.sorting.descNullsLast
OptCols.select.sortBy(_.myInt).desc.nullsLast
-
SELECT opt_cols0.my_int AS my_int, opt_cols0.my_int2 AS my_int2 FROM opt_cols opt_cols0 ORDER BY my_int DESC NULLS LAST -
Seq( OptCols[Sc](Some(3), None), OptCols[Sc](Some(1), Some(2)), OptCols[Sc](None, None), OptCols[Sc](None, Some(4)) )
Optional.sorting.descNullsFirst
OptCols.select.sortBy(_.myInt).desc.nullsFirst
-
SELECT opt_cols0.my_int AS my_int, opt_cols0.my_int2 AS my_int2 FROM opt_cols opt_cols0 ORDER BY my_int DESC NULLS FIRST -
Seq( OptCols[Sc](None, None), OptCols[Sc](None, Some(4)), OptCols[Sc](Some(3), None), OptCols[Sc](Some(1), Some(2)) )
Optional.sorting.roundTripOptionalValues
This example demonstrates a range of different data types being written as options, both with Some(v) and None values
object MyEnum extends Enumeration {
val foo, bar, baz = Value
implicit def make: String => Value = withName
}
case class OptDataTypes[T[_]](
myTinyInt: T[Option[Byte]],
mySmallInt: T[Option[Short]],
myInt: T[Option[Int]],
myBigInt: T[Option[Long]],
myDouble: T[Option[Double]],
myBoolean: T[Option[Boolean]],
myLocalDate: T[Option[LocalDate]],
myLocalTime: T[Option[LocalTime]],
myLocalDateTime: T[Option[LocalDateTime]],
myUtilDate: T[Option[Date]],
myInstant: T[Option[Instant]],
myVarBinary: T[Option[geny.Bytes]],
myUUID: T[Option[java.util.UUID]],
myEnum: T[Option[MyEnum.Value]]
)
object OptDataTypes extends Table[OptDataTypes] {
override def tableName: String = "data_types"
}
val rowSome = OptDataTypes[Sc](
myTinyInt = Some(123.toByte),
mySmallInt = Some(12345.toShort),
myInt = Some(12345678),
myBigInt = Some(12345678901L),
myDouble = Some(3.14),
myBoolean = Some(true),
myLocalDate = Some(LocalDate.parse("2023-12-20")),
myLocalTime = Some(LocalTime.parse("10:15:30")),
myLocalDateTime = Some(LocalDateTime.parse("2011-12-03T10:15:30")),
myUtilDate = Some(
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS").parse("2011-12-03T10:15:30.000")
),
myInstant = Some(Instant.parse("2011-12-03T10:15:30Z")),
myVarBinary = Some(new geny.Bytes(Array[Byte](1, 2, 3, 4, 5, 6, 7, 8))),
myUUID = Some(new java.util.UUID(1234567890L, 9876543210L)),
myEnum = Some(MyEnum.bar)
)
val rowSome2 = OptDataTypes[Sc](
myTinyInt = Some(67.toByte),
mySmallInt = Some(32767.toShort),
myInt = Some(23456789),
myBigInt = Some(9876543210L),
myDouble = Some(2.71),
myBoolean = Some(false),
myLocalDate = Some(LocalDate.parse("2020-02-22")),
myLocalTime = Some(LocalTime.parse("03:05:01")),
myLocalDateTime = Some(LocalDateTime.parse("2021-06-07T02:01:03")),
myUtilDate = Some(
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS").parse("2021-06-07T02:01:03.000")
),
myInstant = Some(Instant.parse("2021-06-07T02:01:03Z")),
myVarBinary = Some(new geny.Bytes(Array[Byte](9, 8, 7, 6, 5, 4, 3, 2))),
myUUID = Some(new java.util.UUID(9876543210L, 1234567890L)),
myEnum = Some(MyEnum.baz)
)
val rowNone = OptDataTypes[Sc](
myTinyInt = None,
mySmallInt = None,
myInt = None,
myBigInt = None,
myDouble = None,
myBoolean = None,
myLocalDate = None,
myLocalTime = None,
myLocalDateTime = None,
myUtilDate = None,
myInstant = None,
myVarBinary = None,
myUUID = None,
myEnum = None
)
db.run(
OptDataTypes.insert.values(rowSome, rowSome2, rowNone)
) ==> 3
db.run(OptDataTypes.select) ==> Seq(rowSome, rowSome2, rowNone)
Optional.filter - with SimpleTable
.filter follows normal Scala semantics, and translates to a CASE/WHEN (foo)/ELSE NULL
OptCols.select.map(d => d.updates(_.myInt(_.filter(_ < 2))))
-
SELECT CASE WHEN (opt_cols0.my_int < ?) THEN opt_cols0.my_int ELSE NULL END AS my_int, opt_cols0.my_int2 AS my_int2 FROM opt_cols opt_cols0 -
Seq( OptCols(None, None), OptCols(Some(1), Some(2)), OptCols(None, None), OptCols(None, Some(4)) )
Optional.getOrElse - with SimpleTable
OptCols.select.map(d => d.updates(_.myInt(_.getOrElse(-1))))
-
SELECT COALESCE(opt_cols0.my_int, ?) AS my_int, opt_cols0.my_int2 AS my_int2 FROM opt_cols opt_cols0 -
Seq( OptCols(Some(-1), None), OptCols(Some(1), Some(2)), OptCols(Some(3), None), OptCols(Some(-1), Some(4)) )
Optional.rawGet - with SimpleTable
OptCols.select.map(d => d.updates(_.myInt := d.myInt.get + d.myInt2.get + 1))
-
SELECT ((opt_cols0.my_int + opt_cols0.my_int2) + ?) AS my_int, opt_cols0.my_int2 AS my_int2 FROM opt_cols opt_cols0 -
Seq( OptCols(None, None), OptCols(Some(4), Some(2)), // because my_int2 is added to my_int, and my_int2 is null, my_int becomes null too OptCols(None, None), OptCols(None, Some(4)) )
Optional.orElse - with SimpleTable
OptCols.select.map(d => d.updates(_.myInt(_.orElse(d.myInt2))))
-
SELECT COALESCE(opt_cols0.my_int, opt_cols0.my_int2) AS my_int, opt_cols0.my_int2 AS my_int2 FROM opt_cols opt_cols0 -
Seq( OptCols(None, None), OptCols(Some(1), Some(2)), OptCols(Some(3), None), OptCols(Some(4), Some(4)) )
Optional.flatMap - with SimpleTable
OptCols.select
.map(d => d.updates(_.myInt(_.flatMap(v => d.myInt2.map(v2 => v + v2 + 10)))))
-
SELECT ((opt_cols0.my_int + opt_cols0.my_int2) + ?) AS my_int, opt_cols0.my_int2 AS my_int2 FROM opt_cols opt_cols0 -
Seq( OptCols(None, None), OptCols(Some(13), Some(2)), // because my_int2 is added to my_int, and my_int2 is null, my_int becomes null too OptCols(None, None), OptCols(None, Some(4)) )
Optional.map - with SimpleTable
You can use operators like .map and .flatMap to work with
your Expr[Option[V]] values. These roughly follow the semantics
that you would be familiar with from Scala.
OptCols.select.map(d => d.updates(_.myInt(_.map(_ + 10))))
-
SELECT (opt_cols0.my_int + ?) AS my_int, opt_cols0.my_int2 AS my_int2 FROM opt_cols opt_cols0 -
Seq( OptCols(None, None), OptCols(Some(11), Some(2)), OptCols(Some(13), None), OptCols(None, Some(4)) )
PostgresDialect
Operations specific to working with Postgres Databases
PostgresDialect.distinctOn
ScalaSql's Postgres dialect provides the .distinctOn operator, which translates
into a SQL DISTINCT ON clause
Purchase.select.distinctOn(_.shippingInfoId).sortBy(_.shippingInfoId).desc
-
SELECT DISTINCT ON (purchase0.shipping_info_id) purchase0.id AS id, purchase0.shipping_info_id AS shipping_info_id, purchase0.product_id AS product_id, purchase0.count AS count, purchase0.total AS total FROM purchase purchase0 ORDER BY shipping_info_id DESC -
Seq( Purchase[Sc](6, 3, 1, 5, 44.4), Purchase[Sc](4, 2, 4, 4, 493.8), Purchase[Sc](2, 1, 2, 3, 900.0) )
PostgresDialect.forUpdate
ScalaSql's Postgres dialect provides the .forUpdate operator, which translates
into a SQL SELECT ... FOR UPDATE clause
Invoice.select.filter(_.id === 1).forUpdate
-
SELECT invoice0.id AS id, invoice0.total AS total, invoice0.vendor_name AS vendor_name FROM otherschema.invoice invoice0 WHERE (invoice0.id = ?) FOR UPDATE -
Seq( Invoice[Sc](1, 150.4, "Siemens") )
PostgresDialect.ltrim2
Expr("xxHellox").ltrim("x")
-
SELECT LTRIM(?, ?) AS res -
"Hellox"
PostgresDialect.rtrim2
Expr("xxHellox").rtrim("x")
-
SELECT RTRIM(?, ?) AS res -
"xxHello"
PostgresDialect.reverse
Expr("Hello").reverse
-
SELECT REVERSE(?) AS res -
"olleH"
PostgresDialect.lpad
Expr("Hello").lpad(10, "xy")
-
SELECT LPAD(?, ?, ?) AS res -
"xyxyxHello"
PostgresDialect.rpad
Expr("Hello").rpad(10, "xy")
-
SELECT RPAD(?, ?, ?) AS res -
"Helloxyxyx"
PostgresDialect.concat
db.concat("i ", "am", " cow", 1337)
-
SELECT CONCAT(?, ?, ?, ?) AS res -
"i am cow1337"
PostgresDialect.concatWs
db.concatWs(" ", "i", "am", "cow", 1337)
-
SELECT CONCAT_WS(?, ?, ?, ?, ?) AS res -
"i am cow 1337"
PostgresDialect.format
db.format("i am cow %s hear me moo %s", 1337, 31337)
-
SELECT FORMAT(?, ?, ?) AS res -
"i am cow 1337 hear me moo 31337"
PostgresDialect.random
db.random
-
SELECT RANDOM() AS res
MySqlDialect
Operations specific to working with MySql Databases
MySqlDialect.forUpdate
ScalaSql's MySql dialect provides the .forUpdate operator, which translates
into a SQL SELECT ... FOR UPDATE clause
Buyer.select.filter(_.id === 1).forUpdate
-
SELECT buyer0.id AS id, buyer0.name AS name, buyer0.date_of_birth AS date_of_birth FROM buyer buyer0 WHERE (buyer0.id = ?) FOR UPDATE -
Seq( Buyer[Sc](1, "James Bond", LocalDate.parse("2001-02-03")) )
MySqlDialect.reverse
Expr("Hello").reverse
-
SELECT REVERSE(?) AS res -
"olleH"
MySqlDialect.lpad
Expr("Hello").lpad(10, "xy")
-
SELECT LPAD(?, ?, ?) AS res -
"xyxyxHello"
MySqlDialect.rpad
Expr("Hello").rpad(10, "xy")
-
SELECT RPAD(?, ?, ?) AS res -
"Helloxyxyx"
MySqlDialect.conflict.ignore
Buyer.insert
.columns(
_.name := "test buyer",
_.dateOfBirth := LocalDate.parse("2023-09-09"),
_.id := 1 // This should cause a primary key conflict
)
.onConflictUpdate(x => x.id := x.id)
-
INSERT INTO buyer (name, date_of_birth, id) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE id = buyer.id -
1
MySqlDialect.conflict.update
Buyer.insert
.columns(
_.name := "test buyer",
_.dateOfBirth := LocalDate.parse("2023-09-09"),
_.id := 1 // This should cause a primary key conflict
)
.onConflictUpdate(_.name := "TEST BUYER CONFLICT")
-
INSERT INTO buyer (name, date_of_birth, id) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE name = ? -
2
Buyer.select
-
Seq( Buyer[Sc](1, "TEST BUYER CONFLICT", LocalDate.parse("2001-02-03")), Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")) )
MySqlDialect.conflict.updateComputed
Buyer.insert
.columns(
_.name := "test buyer",
_.dateOfBirth := LocalDate.parse("2023-09-09"),
_.id := 1 // This should cause a primary key conflict
)
.onConflictUpdate(v => v.name := v.name.toUpperCase)
-
INSERT INTO buyer (name, date_of_birth, id) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE name = UPPER(buyer.name) -
2
Buyer.select
-
Seq( Buyer[Sc](1, "JAMES BOND", LocalDate.parse("2001-02-03")), Buyer[Sc](2, "叉烧包", LocalDate.parse("1923-11-12")), Buyer[Sc](3, "Li Haoyi", LocalDate.parse("1965-08-09")) )
MySqlDialect.concat
db.concat("i ", "am", " cow", 1337)
-
SELECT CONCAT(?, ?, ?, ?) AS res -
"i am cow1337"
MySqlDialect.concatWs
db.concatWs(" ", "i", "am", "cow", 1337)
-
SELECT CONCAT_WS(?, ?, ?, ?, ?) AS res -
"i am cow 1337"
MySqlDialect.rand
db.rand
-
SELECT RAND() AS res
SqliteDialect
Operations specific to working with Sqlite Databases
SqliteDialect.ltrim2
Expr("xxHellox").ltrim("x")
-
SELECT LTRIM(?, ?) AS res -
"Hellox"
SqliteDialect.rtrim2
Expr("xxHellox").rtrim("x")
-
SELECT RTRIM(?, ?) AS res -
"xxHello"
SqliteDialect.glob
Expr("*cop*").glob("roflcopter")
-
SELECT GLOB(?, ?) AS res -
true
SqliteDialect.changes
db.changes
-
SELECT CHANGES() AS res
SqliteDialect.totalChanges
db.totalChanges
-
SELECT TOTAL_CHANGES() AS res
SqliteDialect.typeOf
db.typeOf(123)
-
SELECT TYPEOF(?) AS res -
"integer"
SqliteDialect.lastInsertRowId
db.lastInsertRowId
-
SELECT LAST_INSERT_ROWID() AS res
SqliteDialect.char
db.char(108, 111, 108)
-
SELECT CHAR(?, ?, ?) AS res -
"lol"
SqliteDialect.format
db.format("i am cow %s hear me moo %s", 1337, 31337)
-
SELECT FORMAT(?, ?, ?) AS res -
"i am cow 1337 hear me moo 31337"
SqliteDialect.hex
db.hex(new geny.Bytes(Array(1, 10, 100, -127)))
-
SELECT HEX(?) AS res -
"010A6481"
SqliteDialect.unhex
db.unhex("010A6481")
-
SELECT UNHEX(?) AS res -
new geny.Bytes(Array(1, 10, 100, -127))
SqliteDialect.zeroBlob
db.zeroBlob(16)
-
SELECT ZEROBLOB(?) AS res -
new geny.Bytes(new Array[Byte](16))
H2Dialect
Operations specific to working with H2 Databases
H2Dialect.ltrim2
Expr("xxHellox").ltrim("x")
-
SELECT LTRIM(?, ?) AS res -
"Hellox"
H2Dialect.rtrim2
Expr("xxHellox").rtrim("x")
-
SELECT RTRIM(?, ?) AS res -
"xxHello"
H2Dialect.lpad
Expr("Hello").lpad(10, "xy")
-
SELECT LPAD(?, ?, ?) AS res -
"xxxxxHello"
H2Dialect.rpad
Expr("Hello").rpad(10, "xy")
-
SELECT RPAD(?, ?, ?) AS res -
"Helloxxxxx"
H2Dialect.concat
db.concat("i ", "am", " cow", 1337)
-
SELECT CONCAT(?, ?, ?, ?) AS res -
"i am cow1337"
H2Dialect.concatWs
db.concatWs(" ", "i", "am", "cow", 1337)
-
SELECT CONCAT_WS(?, ?, ?, ?, ?) AS res -
"i am cow 1337"
MsSqlDialect
Operations specific to working with Microsoft SQL Databases
MsSqlDialect.top
For ScalaSql's Microsoft SQL dialect provides, the .take(n) operator translates
into a SQL TOP(n) clause
Buyer.select.take(0)
-
SELECT TOP(?) buyer0.id AS id, buyer0.name AS name, buyer0.date_of_birth AS date_of_birth FROM buyer buyer0 -
Seq[Buyer[Sc]]()
MsSqlDialect.bool vs bit
Insert rows with BIT values
db.run(
BoolTypes.insert.columns(
_.nullable := value.nullable,
_.nonNullable := value.nonNullable,
_.a := value.a,
_.b := value.b,
_.comment := value.comment
)
) ==> 1
db.run(
BoolTypes.insert.columns(
_.nullable := value2.nullable,
_.nonNullable := value2.nonNullable,
_.a := value2.a,
_.b := value2.b,
_.comment := value2.comment
)
) ==> 1
MsSqlDialect.uodate BIT
BoolTypes
.update(_.a `=` 1)
.set(_.nonNullable := true)
-
UPDATE bool_types SET non_nullable = ? WHERE (bool_types.a = ?)