2009-07-19 267 views
3

我有一个存储过程(称为sprocGetArticles),它返回文章列表中的文章。这个存储过程没有任何参数。sql存储过程中的附加表如何计数?

用户可以为每篇文章发表评论,并将这些评论存储在由文章ID链接的评论表中。

有什么办法可以在sprocGetArticles存储过程中对返回列表中的每个articleid做一次评论计数,所以我只需要对数据库进行一次调用?

我的问题是,我需要文章编号来做计数,我似乎无法申报。

无论如何,这是最好的方法吗?

回答

1

一个选项。这通常比实际计算每次评论的次数要快得多,并且如果您确实需要频繁查询该数字,它可以为您节省大量处理开销!

在SQL Server 2005及更高版本中,您可以在这种情况下创建一个小型存储函数来计算每篇文章的评论数,然后将其作为计算列添加到文章表中。然后,您可以将其用作普通列并相信我 - 这比使用子查询的速度快得多!

CREATE FUNCTION dbo.CountComments(@ArticleID INT) 
RETURNS INT 
WITH SCHEMABINDING 
AS BEGIN 
    DECLARE @ArticleCommentCount INT 

    SELECT @ArticleCommentCount = COUNT(*) 
    FROM dbo.ArticleComments 
    WHERE ArticleID = @ArticleID 

    RETURN @ArticleCommentCount 
END 
GO 

这对你的文章的表添加为列:

ALTER TABLE dbo.Articles 
    ADD CommentCount AS dbo.CountComments(ArticleID) 

,并从那时起,只是用它作为一个正常的列:

SELECT ArticleID, ArticleTitle, ArticlePostDate, CommentCount 
FROM dbo.Articles 

为了使它更快,你可以将这个列作为一个持久列添加到你的表中,然后它真的很有趣! :-)

ALTER TABLE dbo.Articles 
    ADD CommentCount AS dbo.CountComments(ArticleID) PERSISTED 

这是一个有点多的前期工作,但如果你需要这往往和所有的时间,它可能是值得的麻烦!也适用于例如从数据库表中存储的XML列中读出某些信息,并将其公开为常规INT列或其他内容。

强烈推荐!这是SQL Server中经常被忽略的一个特性。

马克

2

嗯,不知道你正在选择和您的一般模式(假设你是至少使用SQL Server 2005:

WITH CommentCounts AS 
(
    SELECT COUNT(*) CommentCount, ac.ArticleID 
    FROM Articles a 
    INNER JOIN ArticleComments ac 
     ON ac.ArticleID = a.ID 
    GROUP BY ac.ArticleID 
) 

SELECT a.*, 
     c.CommentCount 
FROM Articles a 
INNER JOIN CommentCounts c 
    ON a.ID = c.ArticleID 

这是一个公共表表达式或CTE你可以阅读更多关于他们。在这里:http://msdn.microsoft.com/en-us/library/ms190766.aspx

+1

这对您的链接无效CTE语法。 – 2009-07-21 17:43:38

+0

谢谢,不知道我是如何错过的。 – AndyMcKenna 2009-07-21 18:10:11

5

SQL允许整个标subqueries为投射列返回子查询可以correlated与父查询所以很容易在计数对于给定的文章编号评论子查询计数点评:

SELECT a.*, (
    SELECT COUNT(*) 
    FROM Comments c 
    WHERE c.article_id = a.article_id) AS CountComments 
    FROM Articles a; 

请注意,每次计算注释可能会相当昂贵,最好将计数保留为Article属性。

+0

您能否将您的评论进一步解释为将文章属性保留为文章属性?为什么这意味着我不需要每次都评论评论?谢谢。 – Cunners 2009-07-27 04:06:58

+0

谢谢。这就是我一直在寻找的! – David 2010-06-02 16:57:53

1

下面将SQL Server的2005+或Oracle 9i的+上工作:

WITH COMMENT_COUNT AS (
     SELECT ac.article_id 
      COUNT(ac.*) 'numComments' 
     FROM ARTICLE_COMMENTS ac 
    GROUP BY ac.article_id) 
SELECT t.description, 
     cc.numComments 
    FROM ARTICLES t 
    JOIN COMMENT_COUNT cc ON cc.article_id = t.article_id 

的SQL Server称之为公用表表达式(CTE); Oracle称之为子查询分解。

备选:

SELECT t.description, 
     cc.numComments 
    FROM ARTICLES t 
    JOIN (SELECT ac.article_id 
       COUNT(ac.*) 'numComments' 
      FROM ARTICLE_COMMENTS ac 
     GROUP BY ac.article_id) cc ON cc.article_id = t.article_id 

工作,但将执行最糟糕的是建议的事实,它会为每一行执行SELECT语句执行的子查询。

2

也许我错过了一些东西,但所有的子查询和内联视图是什么?为什么不只是做一个简单的左加入,例如:到目前为止,没有人提到将是对你的文章表计算列这将计算的评论的数量

SELECT a.ArticleId 
     , a.ArticleName 
     , (other a columns) 
     , COUNT(*) 
    FROM Articles a 
     LEFT JOIN Comments c 
       ON c.ArticleId = a.ArticleId 
GROUP BY a.ArticleId 
     , a.ArticleName 
     , (other a columns); 
0

关于使用在答复中提到计算列的,我想确认声称使用计算列会产生更好的性能(它没有任何意义,我,但我没有SQL Server大师)。我得到的结果表明,使用计算列的速度确实比慢或慢,比简单的group by或子查询要慢。我跑了一个SQL Server实例我有我自己的电脑上测试 - 这里是方法和结果:

CREATE TABLE smb_header (keycol INTEGER NOT NULL 
         , name1 VARCHAR2(255) 
         , name2 VARCHAR2(255)); 

INSERT INTO smb_header 
    VALUES (1 
     , 'This is column 1' 
     , 'This is column 2' 
     ); 

INSERT INTO smb_header 
    SELECT (SELECT MAX(keycol) 
      FROM smb_header 
     ) + keycol 
     , name1 
     , name2 
    FROM smb_header; 
REM (repeat 20 times to generate ~1 million rows) 

ALTER TABLE smb_header ADD PRIMARY KEY (keycol); 

CREATE TABLE smb_detail (keycol INTEGER 
         , commentno INTEGER 
         , commenttext VARCHAR2(255)); 

INSERT INTO smb_detail 
    SELECT keycol 
     , 1 
     , 'A comment that describes this issue' 
    FROM smb_header; 

ALTER TABLE smb_detail ADD PRIMARY KEY (keycol, commentno); 

ALTER TABLE smb_detail ADD FOREIGN KEY (keycol) 
          REFERENCES smb_header (keycol); 

INSERT INTO smb_detail 
    SELECT keycol 
     , (SELECT MAX(commentno) 
      FROM smb_detail sd2 
      WHERE sd2.keycol = sd1.keycol 
     ) + commentno 
     , 'A comment that follows comment number ' 
      + CAST(sd1.commentno AS VARCHAR(32)) 
    FROM smb_detail sd1 
    WHERE MOD(keycol, 31) = 0; 

REM repeat 5 times, to create some records that have 64 comments 
REM where others have one. 

在这一点上,会出现在头约100万行,1或64每个评论。

现在我创建功能(上述一样的你,只与我的专栏&表名),以及计算列:

alter table dbo.smb_header add CommentCountPersist as dbo.CountComments(keycol) 

顺便说一句,坚持不会为此列工作,因为我在上面的评论中怀疑 - 如果您在函数中引用其他表,那么SQL Server不可能或太难记录哪些行需要更新。使用PERSISTED关键字产生错误:

Msg 4934, Level 16, State 3, Line 1 
Computed column 'CommentCountPersist' in table 'smb_header' cannot be 
persisted because the column does user or system data access. 

这对我来说很有意义 - 我不知道怎么的SQL Server可以决定需要什么行更新时,其他行更改,对于可以实现的任何功能,而不更新过程非常低效。

现在,为测试。我创建了一个临时表#holder来插入行 - 我想确保我的查询运行时,我处理整个结果集,而不仅仅是出现在Mgmt Studio网格控件中的前几行。

SELECT h.keycol 
     , h.name1 
     , CommentCount 
    INTO #holder 
    FROM smb_header h 
    WHERE h.keycol < 0 

这是我的查询结果。首先,计算列:

INSERT 
    INTO #holder 
    SELECT h.keycol 
     , h.name1 
     , CommentCount 
    FROM smb_header h 
    WHERE h.keycol between 5000 and 10000 

SQL Server parse and compile time: 
    CPU time = 0 ms, elapsed time = 0 ms. 
Table 'Worktable'. Scan count 1, logical reads 10160, physical reads 0, 
        read-ahead reads 0, lob logical reads 0, 
        lob physical reads 0, lob read-ahead reads 0. 
Table 'smb_header'. Scan count 1, logical reads 44, physical reads 0, 
        read-ahead reads 0, lob logical reads 0, 
        lob physical reads 0, lob read-ahead reads 0. 

SQL Server Execution Times: 
    CPU time = 265 ms, elapsed time = 458 ms. 

(5001 row(s) affected) 
SQL Server parse and compile time: 
    CPU time = 0 ms, elapsed time = 0 ms. 

SQL Server Execution Times: 
    CPU time = 0 ms, elapsed time = 0 ms. 

现在GROUP BY版本,计算列:

INSERT 
    INTO #holder 
    SELECT h.keycol 
     , h.name1 
     , COUNT(*) 
    FROM smb_header h 
     , smb_detail d 
    WHERE h.keycol between 5000 and 10000 
    AND h.keycol = d.keycol 
GROUP BY h.keycol, h.name1 

SQL Server parse and compile time: 
    CPU time = 0 ms, elapsed time = 0 ms. 
Table 'smb_header'. Scan count 1, logical reads 44, physical reads 0, 
        read-ahead reads 0, lob logical reads 0, 
        lob physical reads 0, lob read-ahead reads 0. 
Table 'smb_detail'. Scan count 1, logical reads 366, physical reads 0, 
        read-ahead reads 0, lob logical reads 0, 
        lob physical reads 0, lob read-ahead reads 0. 

SQL Server Execution Times: 
    CPU time = 15 ms, elapsed time = 13 ms. 

(5001 row(s) affected) 
SQL Server parse and compile time: 
    CPU time = 0 ms, elapsed time = 0 ms. 

SQL Server Execution Times: 
    CPU time = 0 ms, elapsed time = 0 ms. 

写作与子查询的查询中的SELECT子句中莱姆斯上面那样产生同样的计划&性能作为GROUP BY(这是预期的)。

正如你所看到的,计算列执行版本显著恶化。这对我来说很有意义,因为优化器被迫调用函数并为头中的每一行执行count(*),而不是使用更复杂的方法来解析两组数据。

这可能是我在这里做得不对。我会对marc_s感兴趣,贡献他的发现。

0

史蒂夫 - 我做了整个演习您的设置我的本地机器(台式PC,没有服务器)上,和我跑的比较选择几次 - 一旦选择与功能第一,一旦对方一个第一,一旦只是其中一个获得这个选择的数字,一个是另一个。

SELECT h.keycol 
     , h.name1 
     , COUNT(*) 
    FROM smb_header h 
     , smb_detail d 
    WHERE h.keycol between 5000 and 10000 
    AND h.keycol = d.keycol 
GROUP BY h.keycol, h.name1 

SELECT h.keycol 
     , h.name1 
     , CommentCount 
    FROM smb_header h 
    WHERE h.keycol between 5000 and 10000 

它归结为这样的结果:我得到25%的选择与功能,75%为一个与加盟。具有该功能的人快3倍。

output of actual SQL Server 2008 execution plan http://i29.tinypic.com/140cl79.jpg

我有一个标准运行的设施,工厂的戴尔台式机,Vista商业版64位SP1的,4 GB的RAM时,SQL Server 2008开发版。

猜测:我不知道足够的SQL Server内部真正知道这一点,但如何对这种思想:当你有一个计算列像在这种情况下,SQL Server需要真正去计算子记录的数量。如果SQL Server将缓存这些结果并重新使用它们,如果同样的“keycol”被一次又一次地计数,会怎么样?而不是真的要和他们(在使用JOIN或相关子查询的情况下,它可能将不得不)再次计数,SQL服务器可以从计算同一组的子记录的时间X-数,而不是仅仅回饶本身返回缓存计数。这听起来是否可行/合理?

Marc