【问题标题】:Select All Posts With All Their Tags选择所有带有所有标签的帖子
【发布时间】:2014-02-13 23:12:28
【问题描述】:

对此有很多问题,但没有足够明确的答案,尤其是在本例中使用 SQL Server。

我有 3 个表格来表示与之相关的博客文章和标签。我想运行一个查询,它将获取所有帖子和每个帖子标签 - 我需要标签 ID 和名称。结果需要很容易地序列化到我的 C# 类中。我也在尝试使用 Dapper.net,但这不是最重要的部分。

显然很多网站都这样做,我想知道最好的方法以及在现实世界中应该如何完成?我可以获取所有帖子,然后为每个帖子运行多个查询,然后返回每个帖子的标签。但肯定有更好的方法吗?

如果我只是想获得一篇文章,那么我只会写多个选择。一个用于帖子,一个用于标签。但我想要所有的帖子。有没有办法在不复制返回的每个标签行的帖子信息的情况下做到这一点?

例如,如果每个帖子的标签都被序列化为一列并用逗号分隔,那么如何才能同时获得 id 和 name?编码字符串?

SELECT * FROM dbo.Posts    
SELECT * FROM dbo.Tags
SELECT * FROM dbo.PostTags

Posts
Id  Title       Content
===============================
1   First Post  First Content
3   Second      Second Content

Tags
Id  Name
============
1   C#
2   SQL
3   IIS
4   Steam
5   OpenID

PostTags
PostId  TagId
=============
1       1
1       2
3       3
3       4

只需使用以下查询连接表:

SELECT p.*, t.Name
FROM dbo.Posts p
LEFT JOIN dbo.PostTags pt ON p.id = pt.PostId
LEFT JOIN dbo.Tags t ON t.id = pt.TagId

通过为与之关联的每个标签重复发布内容来提供大量冗余数据:

Id  Title       Content         Name
======================================
1   First Post  First Post      C#
1   First Post  First Post      SQL
3   Second Post Second Content  IIS
3   Second Post Second Content  Steam

【问题讨论】:

  • 这将是一个非常丑陋的 hack,但我之前已经看到它在生产中使用过......您可以将它们组合成一个列然后在 C# 端解析它们吗?例如,标签列可能看起来像:“{{{1|||C#}}}{{{2|||SQL}}}”,等等(格式为 {{{ID|||名称}}}...)
  • 好吧,我当时在想,我希望看看关于这类事情的最佳实践是什么——我认为这是很常见的事情,不是吗?我还希望使用 Dapper.net(一个微型 ORM)——我认为它不能开箱即用地处理这类事情。
  • 这绝对与最佳实践相去甚远(尤其是当您谈论 .NET 和 OOP 以及 ORM 时),但它有时会在现实世界中使用(就像许多其他非常 hacky事物)由于性能和带宽的考虑。另一种可能更好的方法仍然有些过时但不像上面那样丑陋,是返回多个结果集(不知道 Dapper 是否可以处理)。因此,在第一个结果中,标签列将只有一个以逗号分隔的标签 ID 列表,而第二个结果设置了一个实际的标签列表(以及它们的含义)。
  • 标签ID(并且只有ID)的逗号分隔列表,就像我的第二个例子一样,实际上并不是那么糟糕的做法。我给出的第一个示例是,但如果您遇到其他方法的限制,它可能会作为一个非常快速的解决方案。
  • 并且只是按照标签 ID 和标签名称在不同集合或列中出现的顺序匹配它们?听起来可能比以某种方式编码字符串更好。但是可能仍然需要自定义序列化代码,我猜这不是太大的问题,但是当我已经开始时,现在停止使用 Dapper 很烦人。我想知道堆栈溢出如何获取显示大量问题的主页的数据以及每个问题的标签。他们显然也使用Dapper?这似乎是一个如此简单的问题,但也许不是?

标签: c# sql .net sql-server dapper


【解决方案1】:

这纯粹是一个练习,让我先说一下被复制的数据量很可能不是什么大问题。尽管如果帖子的大小非常大并且数量很多,那么避免重复确实开始变得更有意义。

此外,使用 C# Linq-to-Sql 或实体框架,将为您制定对象关系,并且您的 Post 实体将具有您可以访问的 List<Tag> 属性。

但是,如果您想推出自己的类型的东西,一个只涉及一次数据库往返且不重复数据的选项是编写一个存储过程,让您返回 2 个记录集(2 个单独的选择语句) - 一个带有发布内容,以及一个带有标签内容的内容。

然后创建一个代表 Post 并且只有一个 List<Tag> 的 C# 类并将其从存储的 proc 结果中提取出来会非常简单。

Create Procedure GetPostTags
As

-- We will use the GotTags column here to loop through and get tabs later
Declare @Posts Table (
    PostID varchar(50), 
    PostTitle varchar(50), 
    PostContent varchar(50),
    GotTags bit default 0
)

/* Assuming you care about the ID's, this will get you all of 
   the tags without duplicating any post content */
Declare @PostTags Table (
    PostID int,
    TagID int,
    TagName varchar(50)
)

-- Populate posts from the main table
Insert Into @Posts (PostID, PostTitle, PostContent)
Select * From Posts

-- Now loop through and get the tags for each post. 
Declare @CurrentPostID int
Set @CurrentPostID = (Select Top 1 PostID From @Posts Where GotTags = 0)
While @CurrentPostID Is Not Null
    Begin
        Insert Into @PostTags (PostId, TagID, TagName)
        Select pt.postid, pt.tagid, t.name
        From Tags t 
            Join PostTags pt
                On t.id = pt.tagid
        Where pt.postid = @CurrentPostID

        -- Set next loop
        Update @Posts Set GotTags = 1 Where PostID = @CurrentPostID
        Set @CurrentPostID = (Select Top 1 PostID From @Posts Where GotTags = 0)
    End

-- Return 2 recordsets, which are related by the PostID column found in both sets
Select * from @Posts
Select * From @PostTags

我更喜欢这种类型的解决方案,而不是将字符串连接成一个字符串,然后再拆分它们;它使以这种方式处理数据变得更容易,允许在 C# 中更加面向对象,并让您更轻松地跟踪标签 ID,以防需要从帖子中删除或添加标签,您不需要因为您已经有了 ID,所以按名称查找标签或匹配项。

【讨论】:

    【解决方案2】:

    显然很多网站都这样做,我想知道最好的方法..

    最好的办法:没有。

    在现实世界中应该如何做?

    Entity Framework 将按照您的建议构建查询并具体化您需要的对象。是的,存在重复数据,但通常情况下,重复数据与相关数据相比更好,然后尝试将信息重新关联在一起。优点是代码更易读,更容易用类似 c# 的语言查询,具有相关记录和更改跟踪(默认情况下)。

    Dapper can do the same thing - A parent object with it's children objects。它是faster,但它没有更改跟踪,并且语句不像 c#,它们是(据我所见)直接 SQL,这使得编写动态查询更加困难。

    但肯定有更好的方法吗?

    我不知道什么是更好。是否更高效、内存开销更少、网络数据包/大小更小、更易于维护、更易读?

    有没有办法在不复制返回的每个标签行的帖子信息的情况下做到这一点?

    是的,您可以编写一个存储过程来返回多个记录集,具体化您的对象,然后手动连接它们。

    This sounds like you are trying to optimize something you don't have a problem with..

    【讨论】:

    • 嗯,不,我现在没有问题,这只是一个技术练习——我只是想知道在这个领域是否有一个简单的解决方案或最佳实践可以遵循,但它似乎不是这样,无论哪种效果最好!感谢您提供有用的信息;我想重复数据在低容量时不是问题,如果系统更大,缓存标签将是一种不错的方法。
    【解决方案3】:

    我会编写一个查询来返回多个记录集。在您进行一些性能测试之前,我不会担心过度优化。

    我不确定 Dapper 最近是否支持一对多或多对多查询,但您可能想查看 Insight.Database 4.0 中的新功能。现在在 nuget 中有一个预发布版本。

    查看预发布文档。我希望得到一些反馈。

    https://github.com/jonwagner/Insight.Database/wiki/Proposed-4.0-Changes

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-11-01
      • 2015-08-02
      • 1970-01-01
      • 2015-03-04
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多