查询 M:N 关系中的所有 N 个元素
Query for all N elements in an M:N relation
假设我有以下 table 模型标签附加到文章:
articles (article_id, title, created_at, content)
tags (tag_id, tagname)
articles_tags (article_fk, tag_fk)
检索 n
最新文章及其所有附加标签名称的惯用方法是什么?这似乎是一个标准问题,但我是 SQL 的新手,不知道如何优雅地解决这个问题。
从应用程序的角度来看,我想编写一个 returns 形式为 [title, content, [tags]]
的记录列表的函数,即文章的所有标签都将包含在可变长度列表。 SQL 关系没那么灵活;到目前为止,我只能考虑一个查询来连接 table 那个 returns 每个 article/tag 组合的新行,然后我需要以编程方式将其压缩成上面的形式。
或者,我可以想到一个解决方案,我发出两个查询:首先,对于文章;其次,link table 和标签 table 上的 inner join
。然后,在应用程序中,我可以过滤每个 article_id
的结果集以获得给定文章的所有标签?后者似乎是一个相当冗长和低效的解决方案。
我错过了什么吗?是否有规范的方式来制定单个查询?还是单个查询加上较小的后处理?
除了简单的 SQL 问题之外,Opaleye DSL 中的相应查询会是什么样子?也就是说,是否可以翻译?
您通常会使用行限制查询来选择文章并按降序日期对它们进行排序,并使用带有聚合函数的连接或相关子查询来生成标签列表。
以下查询为您提供了 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
您已经转换了大部分 successfully to Opaleye in 。这是 Opaleye 中的 fully-working 版本。
以后欢迎大家在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))
假设我有以下 table 模型标签附加到文章:
articles (article_id, title, created_at, content)
tags (tag_id, tagname)
articles_tags (article_fk, tag_fk)
检索 n
最新文章及其所有附加标签名称的惯用方法是什么?这似乎是一个标准问题,但我是 SQL 的新手,不知道如何优雅地解决这个问题。
从应用程序的角度来看,我想编写一个 returns 形式为 [title, content, [tags]]
的记录列表的函数,即文章的所有标签都将包含在可变长度列表。 SQL 关系没那么灵活;到目前为止,我只能考虑一个查询来连接 table 那个 returns 每个 article/tag 组合的新行,然后我需要以编程方式将其压缩成上面的形式。
或者,我可以想到一个解决方案,我发出两个查询:首先,对于文章;其次,link table 和标签 table 上的 inner join
。然后,在应用程序中,我可以过滤每个 article_id
的结果集以获得给定文章的所有标签?后者似乎是一个相当冗长和低效的解决方案。
我错过了什么吗?是否有规范的方式来制定单个查询?还是单个查询加上较小的后处理?
除了简单的 SQL 问题之外,Opaleye DSL 中的相应查询会是什么样子?也就是说,是否可以翻译?
您通常会使用行限制查询来选择文章并按降序日期对它们进行排序,并使用带有聚合函数的连接或相关子查询来生成标签列表。
以下查询为您提供了 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
您已经转换了大部分
以后欢迎大家在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))