``` 骨架教程 ```
Esqueleto Tutorial

原始链接: https://entropicthoughts.com/esqueleto-tutorial

这段摘录演示了使用SQL和Haskell库Esqueleto查询数据库,以获取最短的曲目及其专辑。目标是检索五首最短曲目的曲目名称和专辑标题。 SQL查询`SELECT t.name, a.title FROM tracks t INNER JOIN albums a ON a.albumid = t.albumid ORDER BY t.milliseconds LIMIT 5;`通过基于各自的ID连接`tracks`和`albums`表,按曲目长度(`milliseconds`)对结果进行排序,并将输出限制为前五条来实现这一点。 Esqueleto提供了一种函数式的方法来构建相同的查询。它使用一系列函数应用(`.`、`on`、`innerJoin`)来构造连接条件,并最终将其转换为等效的SQL。关键概念包括使用简短的表标识符(如`t`和`a`)以提高清晰度,`:&`构造函数来绑定连接的表对象,以及模式匹配来访问这些对象中的字段。该示例仔细地内联函数,以演示如何逐步构建复杂的查询,最终回到原始的SQL等效形式。 查询结果返回五首曲目及其对应的专辑标题,展示了成功的连接和排序。

黑客新闻 新 | 过去 | 评论 | 提问 | 展示 | 招聘 | 提交 登录 Esqueleto 教程 (entropicthoughts.com) 8 分,作者 ibobev 4 小时前 | 隐藏 | 过去 | 收藏 | 讨论 帮助 指南 | 常见问题 | 列表 | API | 安全 | 法律 | 申请 YC | 联系 搜索:
相关文章

原文

Let’s say we want to know the track title and album name for the shortest tracks that belong to an album in our database.

printResults "select t.name, a.title from tracks t inner join albums a on a.albumid = t.albumid order by t.milliseconds limit 5;" $
  select $ do
    (t :& a) <- from $
      innerJoin (table @Track) . on (table @Album) $ \(t :& a) ->
        t^.TrackAlbumId ==. just (a^.AlbumId)
    limit 5
    orderBy [asc (t^.TrackMilliseconds)]
    pure (t^.TrackName, a^.AlbumTitle)
# select t.name, a.title from tracks t inner join albums a on a.albumid = t.albumid order by t.milliseconds limit 5;
       1. (Value "\201 Uma Partida De Futebol", Value "O Samba Pocon\233")
       2. (Value "Now Sports", Value "Body Count")
       3. (Value "A Statistic", Value "Body Count")
       4. (Value "Oprah", Value "Body Count")
       5. (Value "Commercial 1", Value "House of Pain")

At this point, we finally see why we have been giving our tables short identifiers (t, c, i, a etc.) in both sql and Haskell code. When we are juggling multiple tables at once in a join, we can access fields from either table by choosing the right prefix, and it’s clear where the data is coming from.

The new part in this query is the join. I’ve written it this way:

(t :& a) <- from $
  innerJoin (table @Track) . on (table @Album) $ \(t :& a) ->
    t^.TrackAlbumId ==. just (a^.AlbumId)

The way you’ll find it written by other people is using infix backticks.

(t :& a) <- from $
  table @Track
  `innerJoin` table @Album
  `on` (\(t :& a) -> t^.TrackAlbumId ==. just (a^.AlbumId))

However such a from clause is written, its innermost expression is going to be a function that produces a sql conditional expression indicating how to join rows from both tables with each other. In our case, the function is

let
  joinRowsOn (t :& a) =
    t^.TrackAlbumId ==. just (a^.AlbumId)
in
  -- ...

The :& data constructor serves practically the same purpuse as a tuple – it binds together two table objects that are joined in a query, and by deconstructing that pair we can access each table object individually, as we do here when asserting equivalence between album ids.11 Note also the just function, required because TrackAlbumId is nullable.

This function is paired up with the second table we are joining using the on function, as in

let
  joinRowsOn (t :& a) =
    t^.TrackAlbumId ==. just (a^.AlbumId)
  hasOnClause =
    on (table @Album) joinRowsOn
in
  -- ...

Then the innerJoin function takes the first table and the result of the on function and produces an object that can be passed to the Esqueleto from function.

let
  joinRowsOn (t :& a) =
    t^.TrackAlbumId ==. just (a^.AlbumId)
  hasOnClause =
    on (table @Album) joinRowsOn
  completeJoinExpression =
    innerJoin (table @Track) hasOnClause
in
  select $ do
    joinedTables <- from completeJoinExpression
    -- ...

This will get translated to an inner join sql query.

To get the identifiers of the individual tables out of the joinedTables variable, we can add a pattern match on it, since it is one of those :& pairs that we can deconstruct.

let
  joinRowsOn (t :& a) =
    t^.TrackAlbumId ==. just (a^.AlbumId)
  hasOnClause =
    on (table @Album) joinRowsOn
  completeJoinExpression =
    innerJoin (table @Track) hasOnClause
in
  select $ do
    joinedTables <- from completeJoinExpression
    case joinedTables of
      t :& a -> do
        -- ...

Going backwards again, we can now inline the pattern match:

let
  joinRowsOn (t :& a) =
    t^.TrackAlbumId ==. just (a^.AlbumId)
  hasOnClause =
    on (table @Album) joinRowsOn
  completeJoinExpression =
    innerJoin (table @Track) hasOnClause
in
  select $ do
    (t :& a) <- from completeJoinExpression

and then inline the completeJoinExpression.

let
  joinRowsOn (t :& a) =
    t^.TrackAlbumId ==. just (a^.AlbumId)
  hasOnClause =
    on (table @Album) joinRowsOn
in
  select $ do
    (t :& a) <- from $
      innerJoin (table @Track) hasOnClause

From here, we continue to inline the hasOnClause variable.

let
  joinRowsOn (t :& a) =
    t^.TrackAlbumId ==. just (a^.AlbumId)
in
  select $ do
    (t :& a) <- from $
      innerJoin (table @Track)
      . on (table @Album)
      $ joinRowsOn

We inline the last variable too.

select $ do
  (t :& a) <- from $
    innerJoin (table @Track)
    . on (table @Album)
    $ \(t :& a) -> t^.TrackAlbumId ==. just (a^.AlbumId)

Now we’re back at the query we started with. Hopefully that gives a sense of how these joins are constructed.

联系我们 contact @ memedata.com