【问题标题】:Join and Group multiple tables加入和分组多个表
【发布时间】:2017-04-22 16:45:02
【问题描述】:

假设我在我的 MSSQL 2016(后来的 Azure SQL)上有这种关系

[ForumBoards] 1-n [ForumThreads] 1-n [ForumPosts] n-1 [Users]

我们有:50 个版块、20 万个线程、100 万个帖子和 5 万个用户

现在的目标是

  • 所有带有 ID 和名称的板
  • 板内线程数
  • 板内的帖子数
  • 板内的最新帖子
  • 最新帖子的用户 ID 和名称

我的第一个节目是

SELECT 
    Boards.Id AS BoardsId,
    Board.Name AS BoardsName,
    LP.*
        ThreadsCount = (SELECT Count(*) FROM ForumBoards AS SubB 
                        JOIN ForumThreads AS SubT ON SubB.Id = SubT.BoardId 
                        WHERE SubB.Id = Boards.Id AND SubT.BoardId = SubB.Id),  
        PostsCount = (SELECT Count(*) FROM ForumBoards AS SubB 
                        JOIN ForumThreads AS SubT ON SubB.Id = SubT.BoardId 
                        JOIN ForumPosts AS SubP ON SubT.Id = SubP.ThreadId 
                      WHERE SubB.Id = Boards.Id AND SubT.BoardId = SubB.Id AND SubP.ThreadId = SubT.Id)
FROM ForumBoards as Boards 

OUTER APPLY(
    SELECT 
    TOP 1   SubP.Id AS LatestPostId,
        SubP.PostedOn AS LatestPostPostedOn,
        SubP.ThreadId AS LatestPostThreadId,
        SubT.Topic AS LatestPostThreadTopic,
        SubU.Id AS LatestPostUserId,
        SubU.Username AS LatestPostUsername 
        FROM ForumBoards AS SubB 
             JOIN ForumThreads AS SubT ON SubB.Id = SubT.BoardId 
             JOIN ForumPosts AS SubP ON SubT.Id = SubP.ThreadId 
             JOIN Users AS SubU ON SubP.UserId = SubU.Id 
        WHERE SubB.Id = Boards.Id AND SubT.BoardId = SubB.Id AND SubP.ThreadId = SubT.Id AND SubU.Id = SubP.UserId 
        ORDER BY SubP.PostedOn DESC) AS LP

性能非常差。

没有

WHERE SubB.Id = Boards.Id AND SubT.BoardId = SubB.Id AND SubP.ThreadId = SubT.Id AND SubU.Id = SubP.UserId 

花费 45 毫秒,大约 6 秒。

另一个节目是这个

SELECT  
    B.Id,
    B.Name as BoardName,
    Count(*) as ThreadsCount,
    (SELECT Count(*)
    FROM
        ForumBoards Boards, ForumThreads Threads, ForumPosts Posts
        WHERE Boards.Id = Threads.BoardId AND Threads.Id = Posts.ThreadId AND Boards.Id = B.Id) AS PostsCount

FROM    ForumBoards B, ForumThreads T

WHERE B.Id = T.BoardId

GROUP   BY B.Id, B.Name

没关系,大约 172 毫秒 - 但没有最新的帖子。

但我认为我在对冲的错误一边。并提示我如何实现目标?

【问题讨论】:

  • 您是否已经在连接操作中使用的列上创建了非聚集索引(它也与相关查询有关)?
  • @AliaksandrBortnik MSSQL 查询计划建议创建CREATE NONCLUSTERED INDEX IX_ForumThreads_BoardRelation ON [dbo].[ForumThreads] ([BoardId]) INCLUDE ([Id]) 已完成,是的。
  • 表中的 ID 是否有聚集索引?
  • @AliaksandrBortnik 在所有 PK 上是的
  • 您使用的是哪个 SQL 服务器?

标签: sql sql-server join sql-server-2016


【解决方案1】:

好的,这似乎是一个类似论坛的项目,所以首先要记住:你的阅读量要比在数据库上写的多。

避免每次有人显示您的首页时都应运行的复杂查询,这不是一个好主意,您只会以缓慢的数据库结束。

这种问题是触发器可以神奇的地方。

board 上添加 5 个新列:

  • nb_threads
  • nb_posts
  • last_post_id
  • last_user_id
  • last_user_name

并添加以下触发器:

  • ForumThreads.trgAddThread => +1 给家长ForumBoards.nb_threads
  • ForumThreads.trgDeleteThread => -1 给父 ForumBoards.nb_threads
  • ForumPosts.trgAddPost => +1 到父 ForumBoards.nb_posts,将当前 post.id 设置为 last_post_id,将 user.id 设置为 last_user_id 并获取 user.name 以设置 last_user_name
  • ForumPosts.trgDeletePost => -1 到父 ForumBoards.nb_posts 并获取最后一个帖子以更新以前的数据

如果你不能使用触发器(正如你在评论中解释的那样),这个查询应该在 400 毫秒内完成:

SELECT 
    Boards.Id AS BoardsId,
    Boards.Name AS BoardsName,
    coalesce(nbThread.ThreadsCount, 0) ThreadsCount, 
    coalesce(LP.nbPost, 0) nbPost,
    LP.*,
FROM ForumBoards AS Boards
LEFT JOIN (
    SELECT BoardId, Count(*) ThreadsCount
    FROM ForumThreads
    GROUP BY ForumThreads.BoardId
) AS nbThread
    ON nbThread.BoardId = Boards.Id
LEFT JOIN (
    SELECT   
        t.BoardId,
        t.nbPost,
        ForumPosts.Id AS LatestPostId,
        ForumPosts.PostedOn AS LatestPostPostedOn,
        ForumPosts.ThreadId AS LatestPostThreadId,
        ForumThreads.Topic AS LatestPostThreadTopic,
        Users.Id AS LatestPostUserId,
        Users.Username AS LatestPostUsername 
        FROM (
            select 
                ForumThreads.BoardId, 
                MAX(ForumPosts.Id) Id, 
                Count(*) nbPost
            from ForumPosts
            JOIN ForumThreads
                ON ForumThreads.Id = ForumPosts.ThreadId
            GROUP BY ForumThreads.BoardId
        ) AS t 
        INNER JOIN ForumPosts  
            ON t.Id = ForumPosts.Id 
        INNER JOIN ForumThreads
            ON ForumThreads.Id = ForumPosts.ThreadId
        INNER JOIN Users
            ON Users.Id = ForumPosts.UserId        
) AS LP
    ON LP.BoardId = Boards.Id

【讨论】:

  • 在Board本身添加上一篇帖子相关数据其实是最好的主意!点赞! :)
  • 感谢您的回答,但我们仅在没有可用的高性能解决方案时才需要触发器。至少需要几十个触发器,比如从板 A 到 B 的线程移动、隐藏的帖子等……
  • 我们不想把我们的BL放入数据库
  • @Ben no performant solution available => 为每个视图浏览 1M 行的查询是我所说的“非高性能解决方案”,触发器将更多执行。而且你不必做十几个触发器,其中 6 个就足以处理所有可能发生的情况
  • @Ben 我正在处理多达数十亿行的数据库,如果您希望随着数量的增加将性能保持在一个不错的水平,那么您必须执行数据库中的业务逻辑。 (顺便说一句,这不是真正的 BL,这就是我们所说的“计算字段”,它只是在需要时为每个视图发出一次您想要做的请求)
猜你喜欢
  • 1970-01-01
  • 2019-07-27
  • 2011-08-24
  • 1970-01-01
  • 2013-10-17
  • 1970-01-01
  • 1970-01-01
  • 2012-04-11
  • 1970-01-01
相关资源
最近更新 更多