【问题标题】:Array-aggregation across a link-table in OpaleyeOpaleye 中跨链接表的数组聚合
【发布时间】:2020-07-31 12:30:27
【问题描述】:

我正在尝试构建与以下 SQL 匹配的 Opaleye 查询:

select * ,
    (select array_agg(tags.tagname)
     from articles_tags
     inner join tags on tags.id = articles_tags.tag_fk
         where articles_tags.article_fk = articles.id
    )
from articles

涉及的表(简化)是:

articles: (id, title, content)
articles_tags: (article_fk, tag_fk)
tags: (id, tagname)

我的目标是查询附加了一个或多个标签的文章,并将所有附加标签作为数组检索。

到目前为止,我得到了以下基本查询:

-- | Query all article-tag relations.
allTaggedArticlesQ :: OE.Select TaggedArticleR
allTaggedArticlesQ = OE.selectTable taggedArticlesTable

-- | Query article-tag relations for the given articles.
taggedArticlesQ :: OE.SelectArr PA.ArticleIdField TaggedArticleR
taggedArticlesQ = proc articleId -> do
  ta <- allTaggedArticlesQ -< ()
  OE.restrict -< articleFk ta .=== articleId
  returnA -< ta

-- | Join article-ids and tag names for the given subset of articles.
articleTagNamesQ :: OE.SelectArr PA.ArticleIdField ArticleTagR
articleTagNamesQ = proc articleIds -> do
  ta <- taggedArticlesQ -< articleIds
  tags <- PT.allTagsQ -< ()
  OE.restrict -< PT.tagKey tags .=== tagFk ta
  returnA -< ArticleTag (articleFk ta) (PT.tagName tags)

但是,我无法使聚合工作:以下内容不进行类型检查,并且我不明白如何使用上述查询组合此聚合:

-- | Aggregate all tag names for all given articles
articleTagsQ :: PA.ArticleIdField -> OE.Select (PA.ArticleIdField, F (OE.SqlArray OE.SqlText))
articleTagsQ = OE.aggregate
          ( pArticleTag
              ArticleTag
                { atArticleFk = OE.groupBy,
                  atTagname = OE.arrayAgg
                }
          ) OE.selectTable articleTagNamesQ

在一些博客文章和 GitHub 问题中,我发现聚合不适用于 Product-Profunctors 和 Arrows,因此不能包含在箭头查询中。然而,我对 Haskell 比较陌生,并没有真正理解这两个库背后的理论(似乎没有适合初学者的文档);因此,我无法提出如何将查询与聚合相结合的一般结构。 William Yao here 有一些例子,但我不明白一般概念,所以我无法将这些例子应用于我的问题。

如果有人能提供有关如何在 Opaleye 中使用常规查询组合聚合的见解,我将不胜感激,谢谢!

【问题讨论】:

    标签: haskell arrows opaleye profunctor


    【解决方案1】:

    我对您答案中的代码进行了一些更改,以便将其编译为独立文件:

    • 操作员必须是OE..=== 而不是OE.(.===)
    • arr first 需要删除
    • 我添加了一些数据类型定义、表定义和扩展
    {-# LANGUAGE Arrows #-}
    {-# LANGUAGE FlexibleInstances #-}
    {-# LANGUAGE MultiParamTypeClasses #-}
    {-# LANGUAGE TemplateHaskell #-}
    
    import           Control.Arrow
    import qualified Opaleye as OE
    import qualified Data.Profunctor.Product as PP
    import           Data.Profunctor.Product.TH (makeAdaptorAndInstance')
    
    type F field = OE.Field field
    
    data TaggedArticle a b = TaggedArticle { articleFk :: a, tagFk :: b }
    type TaggedArticleR = TaggedArticle (F OE.SqlInt8) (F OE.SqlInt8)
    
    data Tag a b = Tag { tagKey :: a, tagName :: b }
    type TagR = Tag (F OE.SqlInt8) (F OE.SqlText)
    
    $(makeAdaptorAndInstance' ''TaggedArticle)
    $(makeAdaptorAndInstance' ''Tag)
    
    tagsTable :: OE.Table TagR TagR
    tagsTable = error "Fill in the definition of tagsTable"
    
    taggedArticlesTable :: OE.Table TaggedArticleR TaggedArticleR
    taggedArticlesTable = error "Fill in the definition of taggedArticlesTable"
    
    -- | Query all tags.
    allTagsQ :: OE.Select TagR
    allTagsQ = OE.selectTable tagsTable
    
    -- | Query all article-tag relations.
    allTaggedArticlesQ :: OE.Select TaggedArticleR
    allTaggedArticlesQ = OE.selectTable taggedArticlesTable
    
    -- | Join article-ids and tag names for all articles.
    articleTagNamesQ :: OE.Select (F OE.SqlInt8, F OE.SqlText)
    articleTagNamesQ = proc () -> do
      TaggedArticle {articleFk = aId, tagFk = tFk} <- allTaggedArticlesQ -< ()
      Tag {tagKey = tId, tagName = tn} <- allTagsQ -< ()
      OE.restrict -< tFk OE..=== tId -- INNER JOIN ON
      returnA -< (aId, tn)
    
    -- | Aggregate all tag names for all articles
    articleTagsQ :: OE.Select (F OE.SqlInt8, F (OE.SqlArray OE.SqlText))
    articleTagsQ =
      OE.aggregate (PP.p2 (OE.groupBy, OE.arrayAgg)) articleTagNamesQ
    

    【讨论】:

      【解决方案2】:

      在研究了几个示例之后,这是我最终设法构建和运行的解决方案:

      import           Control.Arrow
      import qualified Opaleye as OE
      import qualified Data.Profunctor.Product as PP
      
      type F field = OE.Field field 
      
      -- | Query all tags.
      allTagsQ :: OE.Select TagR
      allTagsQ = OE.selectTable tagsTable
      
      -- | Query all article-tag relations.
      allTaggedArticlesQ :: OE.Select TaggedArticleR
      allTaggedArticlesQ = OE.selectTable taggedArticlesTable
      
      -- | Join article-ids and tag names for all articles.
      articleTagNamesQ :: OE.Select (F OE.SqlInt8, F OE.SqlText)
      articleTagNamesQ = proc () -> do
        TaggedArticle {articleFk = aId, tagFk = tFk} <- allTaggedArticlesQ -< ()
        Tag {tagKey = tId, tagName = tn} <- allTagsQ -< ()
        OE.restrict -< tFk OE.(.===) tId -- INNER JOIN ON
        returnA -< (aId, tn)
      
      -- | Aggregate all tag names for all articles
      articleTagsQ :: OE.Select (F OE.SqlInt8, F (OE.SqlArray OE.SqlText))
      articleTagsQ =
        OE.aggregate (PP.p2 (OE.groupBy, OE.arrayAgg)) $
          arr (first) <<< articleTagNamesQ
      
      

      articles_tags 表的一行在 Haskell 中由多态 TaggedArticle* Opaleye 类型表示,Tag* 类似地表示标记行。

      关键是选择两个表的所有行,然后进行join,最后进行聚合。因为 Opaleye 中的聚合函数既不是 Arrow 也不是 ProductProfunctor,但 OE.aggregate 函数需要 Select a,所以我不能将聚合作为用箭头表示法编写的查询的一部分。相反,我必须编写一个单独的函数,以 Select a 作为输入。

      请注意,不能对更一般的SelectArr 执行聚合。来自 pacakge 文档:“按照设计,没有 Aggregator b b' -&gt; \S.SelectArr a b -&gt; S.SelectArr a b' 类型的聚合函数。这样的函数将允许违反 SQL 的范围规则并导致无效查询。”

      我上面的代码有些简化。我尝试对键使用多态类型。但是,我不知道如何根据这些新类型包装器编写所有代码;相反,我不得不多次打开并重新包装这些字段。

      我遇到的另一个问题是 JOIN 产生的行类型的定义。最初,我定义了一个新的多态行类型。但是后来我没有设法正确解开该类型的字段,因此我可以将它们输入OE.Aggregator。因此,我选择了上面更详细的元组表示法。

      【讨论】:

      • 您好,Ulrich,它看起来像是一个有效的答案。欢迎您在 Opaleye 问题跟踪器 (github.com/tomjaguarpaw/haskell-opaleye/issues/new) 上提出此类问题,您通常会得到更快的响应和有用的讨论(在 StackOverflow 上通常禁止讨论)。关于包装和展开,请您通过上面的链接在问题跟踪器上解释问题,我会帮助您。
      • (顺便说一句,你可以写arr first,而不是arr (first),实际上和first一样。
      • (但似乎需要删除 first 才能编译它。请参阅我的单独答案。)
      • 感兴趣的朋友可以继续讨论github.com/tomjaguarpaw/haskell-opaleye/issues/479
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-12-26
      • 2012-09-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多