【问题标题】:Query for all N elements in an M:N relation查询 M:N 关系中的所有 N 个元素
【发布时间】:2020-11-19 13:47:02
【问题描述】:

假设我有以下表格,这些表格将模型标签附加到文章:

articles (article_id, title, created_at, content)
tags (tag_id, tagname)
articles_tags (article_fk, tag_fk)

检索n 最新文章及其所有附加标签名的惯用方法是什么?这似乎是一个标准问题,但我是 SQL 新手,不知道如何优雅地解决这个问题。

从应用程序的角度来看,我想编写一个函数,它返回[title, content, [tags]] 形式的记录列表,即,附加到文章的所有标签都将包含在可变长度列表中。 SQL 关系没有那么灵活。到目前为止,我只能考虑一个查询来连接为每个文章/标签组合返回一个新行的表,然后我需要以编程方式将其压缩为上述形式。

另外,我可以想到一个解决方案,我发出两个查询:首先,对于文章;第二,链接表和标签表上的inner join。然后,在应用程序中,我可以过滤每个article_id 的结果集以获取给定文章的所有标签吗?后者似乎是一个相当冗长和低效的解决方案。

我错过了什么吗?是否有规范的方法来制定单个查询?还是单个查询加上较小的后处理?

除了简单的 SQL 问题,在 Opaleye DSL 中对应的查询会是什么样子?也就是说,能不能翻译?

【问题讨论】:

  • 请用您正在运行的数据库标记您的问题:mysql、oracle、postgresql...?

标签: sql postgresql join subquery opaleye


【解决方案1】:

您通常会使用限制行的查询来选择文章并按日期降序排列,并使用带有聚合函数的连接或相关子查询来生成标签列表。

以下查询为您提供了 10 篇最新文章,以及数组中相关标签的名称:

select 
    a.*,
    (
        select array_agg(t.tagname) 
        from article_tags art
        inner join tags t on t.tag_id = art.tag_fk
        where art.article_fk = a.article_id
    ) tags
from articles
order by a.created_at desc
limit 10

【讨论】:

  • 打败了我.. 我打算建议阅读有关 LISTAGG 的内容-尽管我认为这不是最佳选择,因为它会强制进行一些后期处理
  • 非常感谢。我想 array_agg 是特定于 PostgreSQL 的?标准 SQL 中有没有等价的表达式?
  • @UlrichSchuster:字符串(resp。数组)聚合函数是高度特定于数据库的......我不知道ANSI SQL在这方面指定了什么(可能是listagg()的字符串聚合),但是肯定不会被数据库供应商广泛实施。
【解决方案2】:

您已成功将大部分GMB's answer 转换为your answer to your subsequent question 中的Opaleye。这是 Opaleye 中的完整版本。

以后欢迎您在Opaleye's issue tracker 上提出此类问题。您可能会在那里得到更快的响应。

{-# LANGUAGE Arrows #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TemplateHaskell #-}

import           Control.Arrow
import qualified Opaleye as OE
import qualified Data.Profunctor as P
import           Data.Profunctor.Product.TH (makeAdaptorAndInstance')

type F field = OE.Field field

data TaggedArticle a b c =
  TaggedArticle { articleFk :: a, tagFk :: b, createdAt :: c}
type TaggedArticleR = TaggedArticle (F OE.SqlInt8) (F OE.SqlInt8) (F OE.SqlDate)

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, F OE.SqlDate)
articleTagNamesQ = proc () -> do
  ta <- allTaggedArticlesQ -< ()
  t  <- allTagsQ -< ()
  OE.restrict -< tagFk ta OE..=== tagKey t -- INNER JOIN ON
  returnA -< (articleFk ta, tagName t, createdAt ta)

-- | Aggregate all tag names for all articles
articleTagsQ :: OE.Select (F OE.SqlInt8, F (OE.SqlArray OE.SqlText))
articleTagsQ =
  OE.aggregate ((,) <$> P.lmap (\(i, _, _) -> i) OE.groupBy
                    <*> P.lmap (\(_, t, _) -> t) OE.arrayAgg)
      (OE.limit 10 (OE.orderBy (OE.desc (\(_, _, ca) -> ca)) articleTagNamesQ))

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-02-26
    • 1970-01-01
    • 1970-01-01
    • 2022-01-01
    • 1970-01-01
    • 2012-10-18
    • 2013-03-24
    • 1970-01-01
    相关资源
    最近更新 更多