基本查询
编辑教程基本查询
本篇介绍 Slick 的基本查询,比如选择,插入,更新,删除记录等。
排序和过滤
Slick 提供了多种方法可以用来排序和过滤,比如:
val q = Album.filter(_.albumid === 101)
//select `AlbumId`, `Title`, `ArtistId`
//from `Album` where `AlbumId` = 101
val q = Album.drop(10).take(5)
//select .`AlbumId` as `AlbumId`, .`Title` as `Title`,
// .`ArtistId` as `ArtistId` from `Album` limit 10,5
val q = Album.sortBy(_.title.desc)
//select `AlbumId`, `Title`, `ArtistId`
//from `Album` order by `Title` desc
Join 和 Zipping
Join 指多表查询,可以有两种不同的方法来实现多表查询,一种是通过明确调用支持多表连接的方法(比如 innerJoin 方法)返回一个多元组,另外一种为隐含连接( implicit join ),它不直接使用这些连接方法(比如 LeftJoin 方法)。
一个隐含的 cross-Join 为 Query 的 flatMap 操作(在 for 表达式中使用多个生成式),例如:
val q = for{a <- Album
b <- Artist
} yield( a.title, b.name)
//select x2.`Title`, x3.`Name` from `Album` x2, `Artist` x3
如果添加一个条件过滤表达式,它就变成隐含的 inner join,例如:
val q = for{a <- Album
b <- Artist
if a.artistid === b.artistid
} yield( a.title, b.name)
//select x2.`Title`, x3.`Name` from `Album` x2, `Artist` x3
//where x2.`ArtistId` = x3.`ArtistId`
明确的多表连接则使用 innerJoin,leftJoin,rightJoin,outerJoin 方法,例如:
val explicitCrossJoin = = for {
(a,b) <- Album innerJoin Artist
} yield( a.title, b.name)
//select x2.x3, x4.x5 from (select x6.`Title` as x3 from `Album` x6)
//x2 inner join (select x7.`Name` as x5 from `Artist` x7) x4 on 1=1
val explicitInnerJoin = for {
(a,b) <- Album innerJoin Artist on (_.artistid === _.artistid)
} yield( a.title, b.name)
//select x2.x3, x4.x5 from (select x6.`Title` as x3, x6.`ArtistId` as x7 from `Album` x6) x2
//inner join (select x8.`ArtistId` as x9, x8.`Name` as x5 from `Artist` x8) x4 on x2.x7 = x4.x9
val explicitLeftOuterJoin = for {
(a,b) <- Album leftJoin Artist on (_.artistid === _.artistid)
} yield( a.title, b.name.?)
//select x2.x3, x4.x5 from (select x6.`Title` as x3, x6.`ArtistId` as x7 from `Album` x6) x2
//left outer join (select x8.`ArtistId` as x9, x8.`Name` as x5 from `Artist` x8) x4 on x2.x7 = x4.x9
val explicitRightOuterJoin = for {
(a,b) <- Album rightJoin Artist on (_.artistid === _.artistid)
} yield( a.title.?, b.name)
//select x2.x3, x4.x5 from (select x6.`Title` as x3, x6.`ArtistId` as x7 from `Album` x6) x2
//right outer join (select x8.`ArtistId` as x9, x8.`Name` as x5 from `Artist` x8) x4 on x2.x7 = x4.x9
注意 leftJoin 和 rightJoin 中的 b.name.? 和 a.title.? 的”.?” 这是因为外部查询时会产生额外的 NULL 值,你必须保证返回 Option 类型的值。
除了通常的 InnerJoin,LeftJoin,RightJoin 之外,Scala 还提供了 Zip 方法,它的语法类似于 Scala 的集合类型,比如:
val zipJoinQuery = for {
(a,b) <- Album zip Artist
} yield( a.title.?, b.name)
此外,还有一个 zipWithIndex,可以把一个表的行和一个从 0 开始的整数序列 Zip 操作,相当于给行添加序号,比如
val zipWithIndexJoin = for {
(a,idx) <- Album.zipWithIndex
} yield( a.title, idx)
Union
两个查询的结果可以通过 ++ (或者 unionAll ) 和 union 操作联合起来:
val q1= Album.filter(_.artistid <10)
val q2 = Album.filter(_.artistid > 15)
val unionQuery = q1 union q2
val unionAllQuery = q1 ++ q2
union 操作会去掉重复的结果,而 unionAll 只是简单的把两个查询结果连接起来(通常来说比较高效)。
Aggregation
和 SQL 一样,Slick 也有 min,max,sum,avg 等集合操作
val q = Album.map(_.artistid)
val q1 = q.max
val q2 = q.min
val q3 = q.avg
val q4 = q.sum
注意:这里 q.max,min,avg,sum 返回结果类型为 Column[Option[T]],要得到最好的 scalar 类型的值 T,可以调用 run,得到 Option[T],然后再调用 Option 的 get 或 getOrDefault,比如:
val q = Album.map(_.artistid)
val q1 = q.max
println(q1.run.get)
得到打印的结果:275
其它的 Aggregation 操作还有 length,exists,比如:
val q1 = Album.length
val q2 = Album.exists
分组使用 groupBy 操作,类似于 Scala 集合类型的 groupBy 操作:
val q= (for {
a <- Album
b <- Artist
if a.artistid === b.artistid
} yield (b.artistid,b.name)
).groupBy(_._2)
val q1 = q.map { case (name, records) =>
(records.map(_._1).avg, name,records.length)}
q1 foreach println
这段代码使用两个查询,给出 Album 根据艺术家出的专辑的统计,其中中间查询 q,包含一个嵌套的 Query,目前 Scala 不支持直接查询嵌套的 Query,因此我们需要分两次查询,打印出的部分结果如下:
(Some(230),Some(Aaron Copland & London Symphony Orchestra),1)
(Some(202),Some(Aaron Goldberg),1)
(Some(1),Some(AC/DC),2)
(Some(214),Some(Academy of St. Martin in the Fields & Sir Neville Marriner),1)
(Some(215),Some(Academy of St. Martin in the Fields Chamber Ensemble & Sir Neville Marriner),1)
(Some(222),Some(Academy of St. Martin in the Fields, John Birch, Sir Neville Marriner & Sylvia McNair),1)
(Some(257),Some(Academy of St. Martin in the Fields, Sir Neville Marriner & Thurston Dart),1)
(Some(2),Some(Accept),2)
(Some(260),Some(Adrian Leaper & Doreen de Feis),1)
(Some(3),Some(Aerosmith),1)
(Some(197),Some(Aisha Duo),1)
(Some(4),Some(Alanis Morissette),1)
(Some(206),Some(Alberto Turco & Nova Schola Gregoriana),1)
(Some(5),Some(Alice In Chains),1)
(Some(252),Some(Amy Winehouse),2)
...
Slick 的查询实际上是执行由 Invoker(无参数时为 UnitInvoker ) Trait 定义的方法,Slick 定义了一个从 Query 隐含的变换,使得你可以直接执行查询操作,最常用的一个情况是把整个查询结果存放到一个 Scala 集合类型中(比如使用 list 方法)
val l = q.list
val v = q.buildColl[Vector]
val invoker = q.invoker
val statement = q.selectStatement
所有的查询方法都定义了一个隐含参数 Session,如果你愿意,你也可以直接传入一个 session 参数:
val l = q.list()(session)
如果你只需要单个查询结果,你可以使用 first 或 firstOption 方法,而方法 foreach, foldLeft 和 elements 方法可以用来遍历查询结果而不需要先把结果复制到另外一个 Scala 集合对象中。
Deleting
删除数据和查询很类似,你首先写一个选择查询,然后调用它的 delete 方法,同样 Slick 也定义一个从 Query 到 DeleteInvoker 的隐含转换,DeleteInvoker 定义了 delete 方法
val affectedRowsCount = q.delete
val invoker = q.deleteInvoker
val statement = q.deleteStatement
定义用来删除记录的查询时只能使用单个表格。
Inserting
插入操作基于单个表定义的字段映射,当你直接使用某个表来插入数据时,这个操作基于表类型中定义的“*”,如果你省略某些字段,那么插入这些省略的字段会使用缺省值,所有的插入操作方法定义在 InsertInvoker 和 FullInsertInvoker。
coffees += ("Colombian", 101, 7.99, 0, 0)
coffees ++= Seq(
("French_Roast", 49, 8.99, 0, 0),
("Espresso", 150, 9.99, 0, 0)
)
// "sales" and "total" will use the default value 0:
coffees.map(c => (c.name, c.supID, c.price)) += ("Colombian_Decaf", 101, 8.99)
val statement = coffees.insertStatement
val invoker = coffees.insertInvoker
// compiles to SQL:
// INSERT INTO "COFFEES" ("COF_NAME","SUP_ID","PRICE","SALES","TOTAL") VALUES (?,?,?,?,?)
如果你的插入操作定义了自动增一的字段,该字段会自动忽略,由数据库本身来插入该字段的值。缺省情况 += 返回受影响的行数(通常总为 1),而 ++ 操作给出总计的行数(以 Option 类型给出),你可以使用 returning 修改返回的值,比如返回插入的行的主键:
val userId =
(users returning users.map(_.id)) += User(None, "Stefan", "Zeiger")
要注意的是很多数据库只支持返回自动增一的作为主键的那个字段,如果想返回其它字段,可能会抛出 SlickException 异常。
除了上面的插入记录的方法,还可以使用服务器端表达式的方发插入数据:
class Users2(tag: Tag) extends Table[(Int, String)](tag, "users2") {
def id = column[Int]("id", O.PrimaryKey)
def name = column[String]("name")
def * = (id, name)
}
val users2 = TableQuery[Users2]
users2.ddl.create
users2 insert (users.map { u => (u.id, u.first ++ " " ++ u.last) })
users2 insertExpr (users.length + 1, "admin")
Updating
更新记录也是先写查询,然后调用 update 方法,比如:
val q = for { c <- coffees if c.name === "Espresso" } yield c.price
q.update(10.49)
val statement = q.updateStatement
val invoker = q.updateInvoker
update 方法定义在 UpdateInvoker Trait 中。
Compiled Queries
数据库查询时,通常需要定义一些查询参数,比如根据 ID 查找对应的记录。
可以定义一个带参数的函数来定义查询对象,但每次调用该函数时都要重新编译这个查询语句,系统消耗有些大,Slick 支持预编译这个带参数的查询函数,例如:
def userNameByIDRange(min: Column[Int], max: Column[Int]) =
for {
u <- users if u.id >= min && u.id < max
} yield u.first
val userNameByIDRangeCompiled = Compiled(userNameByIDRange _)
// The query will be compiled only once:
val names1 = userNameByIDRangeCompiled(2, 5).run
val names2 = userNameByIDRangeCompiled(1, 3).run
这种方法支持查询,更新和删除数据。
选择支付方式:
备注:
转账时请填写正确的金额和备注信息,到账由人工处理,可能需要较长时间