在SQL中从连接表中添加多行的附加结果列的最佳方法

时间:2014-05-06 10:19:55

标签: sql-server tsql azure-sql-database

假设一个与Stackoverflow类似的示例。您有一个内容项(问题或答案),您可以获取有关每个内容项的数据。每个数据都由以下数据表示:

  • 标题
  • 细节
  • 得分

然后您还要附加表示与单个内容项相关的用户交互的其他列:

  • 用户已投票
  • 用户投票
  • 用户评论
  • 用户标记的垃圾邮件项目

这使我有可能指出用户与哪些内容项进行了互动以及该互动是什么。现在,内容项存储在Posts表中,而操作(投票,评论)记录在单独的表PostActions中,并带有列:

  • PostId
  • UserId
  • ActionType(投票/投票,评论,垃圾邮件,关闭等)
  • CreationDate

因此,对于每个项目,此表中有几行与每个项目相关。用户可能已对同一项目执行了多项操作。

我可以执行即。

select p.*, pa.ActionType
from Posts p
    left join PostActions pa
    on ((pa.PostId = p.PostId) and (pa.UserId = @UserId))

但这会导致与同一内容项相关的多行。为了将它们全部放在一个结果行中,我可以:

  1. 对每个操作单独使用left join PostActions几次

    select
        p.*,
        iif(paUp.CreationDate is null, 0, 1) as VotedUp,
        iif(paDown.CreationDate isnull, 0, 1) as VotedDown,
        ...
    from Posts p
        left join PostActions paUp
        on ((paUp.PostId = p.PostId) and (paUp.UserId = @UserId) and (paUp.ActionId = @UpVoteType))
        left join PostActions paDown
        on ((paUp.PostId = p.PostId) and (paUp.UserId = @UserId) and (paUp.ActionId = @DownVoteType))
        ...
    

    但最终会有许多left join到同一张桌子

  2. 我可以left join PostActions并使用stuff函数连接所有现有操作,然后在中间层解析

  3. 无论如何 - 即按PostActions.PostIdPostActions.UserId进行分组,然后从群组中获取该信息(如果可能的话,可以过滤具有多种不同条件的同一群组)。

  4. 使用pivotapply获取我的数据

  5. 问题

    主要问题是哪种方法(上述或其他方法之一)在性能方面最佳?你会建议什么?

1 个答案:

答案 0 :(得分:0)

最终实施

我试验了一下,与我的上层可能性相关的是这个结果:

  1. 只有多个连接完全不可能。还必须实施分组,这会导致查询过于复杂且难以维护

  2. 需要上层后处理来解析连锁数据。 Haven没有实现它,因为它需要更多的上层代码更改,这些更改也会比数据层更加受限制。

  3. 我准备了一个CTE,它生成按内容项和用户分组的内容交互数据,并将结果连接到原始查询。运行良好,按预期工作。棘手的部分是过滤每组记录中的数据,只计算特定的交互(见下文)。

  4. 与#3类似的方法是使用pivot关系运算符,我也实现了这一点,尽管预先准备好的数据也需要事先分组以获得每个项目一条记录

  5. 在#3和#4之间,我决定选择#3,因为它有更好的执行计划。

    在记录组解决方案中过滤

    如#3(最终实现)中所述,为了使我的查询执行,我应该只进行一次分组,然后过滤每组记录中的数据以获取有关各个内容交互的信息。

    因为我只需要指示特定用户是否在内容项上进行了交互,所有这些都返回布尔值(SQL bit类型)。最好的部分是在count聚合中使用函数,以便仅使用组内的特定记录。

    with Interactions (Id, UpVoted, DownVoted)
    as (
        select
            p.Id,
            cast(count(iif(pa.ActionTypeId = @UpVoteType, 1, null)) as bit),
            cast(count(iif(pa.ActionTypeId = @DownVoteType, 1, null)) as bit)
        from PostActions pa
            join Posts p
            on (p.Id = pa.PostId)
        where pa.UserId = @UserId
        group by p.Id
    )
    select
        ...
        isnull(i.UpVoted, 0) as UpVoted,
        isnull(i.DownVoted, 0) as DownVoted
    from Posts p
        ...
        left join Interactions i
        on (p.Id = i.Id)
    where ...
    order by ...
    

    count的好处是它只计算非空记录,因此我利用这一事实来过滤掉与内容项相关的同一组内的各个特定用户交互。

    COUNT是我的新爱。 :)