2009-07-19 265 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