2016-03-29 8 views
4

投稿にSQLジョインを追加すると、コメントに投稿された投稿テーブルを照会し、クリックして投票し、各投稿のアクティビティに関する統計を返すようになっています。以下の私の質問は私が使ってきたものです。SQLクエリは各列で同じ値を返します

SELECT 
    p.PostID, 
    p.Title, 
    CASE 
     WHEN COUNT(cm.CommentID) IS NULL THEN 0 
     ELSE COUNT(cm.CommentID) 
    END AS CommentCount, 
    CASE 
     WHEN COUNT(cl.ClickID) IS NULL THEN 0 
     ELSE COUNT(cl.ClickID) 
    END AS ClickCount, 
    CASE 
     WHEN SUM(vt.Value) IS NULL THEN 0 
     ELSE SUM(vt.Value) 
    END AS VoteScore 
FROM 
    Post p 
    LEFT OUTER JOIN Comment cm ON p.PostID = cm.PostID 
    LEFT OUTER JOIN Click cl ON p.PostID = cl.PostID 
    LEFT OUTER JOIN Vote vt ON p.PostID = vt.PostID 
GROUP BY 
    p.PostID, 
    p.Title 

は、次のような結果

| PostID | CommentCount | ClickCount | VoteScore | 
|--------|--------------|------------|-----------| 
| 41  |   60|   60|   60| 
| 50  |   1683|  1683|  1683| 

これをもたらし、私は知って正しくありません。一つが、コメントアウトすべてが参加すると:

SELECT 
    p.PostID 
    ,p.Title 
    ,CASE 
     WHEN COUNT(cm.CommentID) IS NULL THEN 0 
     ELSE COUNT(cm.CommentID) 
    END AS CommentCount 
/* 
    ,CASE 
     WHEN COUNT(cl.ClickID) IS NULL THEN 0 
     ELSE COUNT(cl.ClickID) 
    END AS ClickCount 
    ,CASE 
     WHEN SUM(vt.Value) IS NULL THEN 0 
     ELSE SUM(vt.Value) 
    END AS VoteScore 
*/ 
FROM 
    Post p 
    LEFT OUTER JOIN Comment cm ON p.PostID = cm.PostID 
/* 
    LEFT OUTER JOIN Click cl ON p.PostID = cl.PostID 
    LEFT OUTER JOIN Vote vt ON p.PostID = vt.PostID 
*/ 
GROUP BY 
    p.PostID, 
    p.Title 

私は正しいです

| PostID | CommentCount | 
|--------|--------------| 
| 41  |    3| 

を取得します。私が間違っていたことは何ですか?

ありがとうございました。

+1

あなたがここにCASE式を使用している理由はわかりません。 ISNULL(COUNT(cm.CommendID)、0)は、CommentCountとして多くのクリーナーと簡単です。私の推測では、あなたの結合にいくつかの論理的な問題がありますが、いくつかのddlやサンプルデータがなければ、ここで多くの助けをするのは本当に難しいです。 –

+4

'left join'はおそらく1:たくさんの関係にあります。これは' post'と 'comment'のレコードを複製して' click'や 'vote'の余分なレコードを全て収容するためです。 'Count(Distinct cm.commentid) 'と' Count(Distinct cm.mentid) 'を行うために' Count() '集計を行いましょう。 'Vote(投票)はおそらく最も細かいので、あなたの' Sum() 'はうまくいきますが、それは推測です。 – JNevill

+0

すでに説明したように、重複を数えています。そして、私は 'vote'は常に1であると思うので、' sum'は 'count'と同じ結果になります。そして、countがnullでないので、 'count'の周りの' case'は余計です。 –

答えて

3

ので期待されて返されている結果をクエリーはデカルト(または半デカルト)プロダクトを生成しています。クエリは基本的に、commentclickおよびvoteから返された行に対して「クロスジョイン」操作を実行するようにMySQLに指示しています。

comment(指定されたpostidの場合)から返される各行は、click(同じpostidの場合)から各行に一致します。その結果の各行は、vote(同じpostidの)の各行に一致します。

commentの2つの行とclickの3つの行とvoteの4つの行は合計24(= 2x3x4)行を返します。

これを修正するための通常のパターンは、クロスジョイン操作を避けるためです。

これにはいくつかの方法があります。選択リスト


相関サブクエリあなたが唯一の3つのテーブルのそれぞれから単一の集約(例えばCOUNTまたはSUM)を必要とする場合は、あなたが参加し、削除、およびで相関サブクエリを使用することができますSELECTリスト。そして、括弧でそのクエリをラップし、別のクエリのSELECTリストでそれを参照し、からpostidするための基準に疑問符を置き換える例

SELECT COUNT(1) 
    FROM comment cmt 
    WHERE cmt.postid = ? 

のために、単一postidのカウントを取得するクエリを書きます外部クエリで参照されるテーブル

SELECT p.postid 
    , (SELECT COUNT(1) 
      FROM comment cmt 
      WHERE cmt.postid = p.postid 
     ) AS comment_count 
    FROM post p 

clickvoteから "カウント" を取得するために同じパターンを繰り返します。

このアプローチの欠点は、SELECTリストのサブクエリが、の各サブクエリに対して実行されることです。外側のクエリによって返される行はそれぞれです。外部クエリが多くの行を返す場合、これは高価になる可能性があります。 commentが大きなテーブルの場合、妥当なパフォーマンスを得るには、適切なインデックスがcommentにあることが重要です。


インライン・ビューで事前集計

別のアプローチは、「前凝集」結果インライン図です。 postidのコメント数を返すクエリを作成します。たとえば、

SELECT cmt.postid 
    , COUNT(1) 
    FROM comment cmt 
GROUP BY cmt.postid 

別のクエリのFROM句で括弧を付けて参照すると、エイリアスを割り当てます。インラインビューのクエリは、基本的に外部クエリのテーブルの代わりになります。

SELECT p.postid 
    , cm.postid 
    , cm.comment_count 
    FROM post p 
    LEFT 
    JOIN (SELECT cmt.postid 
       , COUNT(1) AS comment_count 
      FROM comment cmt 
      GROUP BY cmt.postid 
     ) cm 
    ON cm.postid = p.postid 

そしてclickvoteため、同じパターンを繰り返します。ここでのトリックは、重複するpostid値を返さないことを保証するインラインビュークエリのGROUP BY句です。そしてそれにデカルト積(クロス結合)が重複を生成しません。

このアプローチの欠点は、派生テーブルが索引付けされないことです。したがって、多数のpostidの場合、外部クエリで結合を実行するのにはコストがかかる可能性があります。 (より最近のバージョンのMySQLでは、適切なインデックスを自動的に作成することで、この欠点を部分的に解決しています)。

(この制限は、適切なインデックスを持つ一時的なテーブルを作成することで回避できます。特別な単一ステートメントに完全に適しています。しかし、大きなセットをバッチ処理する場合、いくつかの重要なパフォーマンス向上のためには複雑さが増します。


全く異なるアプローチとしてDISTINCT値

によって崩壊直積、それがあるようにクロス結合操作をして、あなたのクエリを残し、そしてMySQLはデカルト積を生成することができます。次に、SELECTリスト内の集計によって重複が除外されます。これには、指定されたpostidのコメントの各行に対して、ユニークなcommentの列(または式が生成されている)が必要です。

SELECT p.postid 
    , COUNT(DISTINCT c.id) AS comment_count 
    FROM post p 
    LEFT 
    JOIN comment c 
    ON c.postid = p.postid 
GROUP BY p.postid 

このアプローチの大きな欠点は、(GROUP BYを満足させるために)「filesortレコードを使用する」操作とし、「崩壊」さ巨大中間結果を生成する可能性を有することです。そして、これは大きなセットではかなり高価になる可能性があります。


これは、返す予定の結果を得るためのすべての可能なクエリパターンの網羅的なリストではありません。ちょうど代表的なサンプリング。

+0

問題自体とそのさまざまな解決策パターンの非常に良い説明。 –

2

は、おそらくこのような何かをしたい:

SELECT 
    p.PostID, 
    p.Title, 
    (SELECT COUNT(*) FROM Comment cm 
    WHERE cm.PostID = p.PostID) AS CommentCount, 
    (SELECT COUNT(*) FROM Click cl 
    WHERE p.PostID = cl.PostID) AS ClickCount , 
    (SELECT SUM(vt.Value) FROM Vote vt 
    WHERE p.PostID = vt.PostID) AS VoteScore 
FROM 
    Post p 

クエリでの問題は、第2、第3のLEFT JOIN操作がレコードを複製することです:最初のLEFT JOINは、たとえば3のために、あなたが持って適用された後、のレコードを投稿はPostID = 41です。 2番目のLEFT JOINはこれらの3つのレコードに結合されるため、PostID = 41が使用されます3番目のは2番目にLEFT JOINです。

1がある場合:(ClickPost)、(CommentPost)間及び(PostVote)多くの関係直接は、その後、上記のクエリは、おそらく何をしたいあなたを与えるだろう。

1

あなたの質問には、あなたが行っていると思われることはありません。このような行を結合してカウントすると、x行の新しいデータセットが作成され、そのデータセットの行が3回カウントされます。したがって、あなたは同じ数を3回得る。

やりたいことだけコメントや、左には、例えば、これらの2つの表にあるデータを結合するクリックの行数をカウントします

SELECT 
    p.PostID 
    ,p.Title 
    ,COUNT(CASE 
     WHEN cm.PostID IS NULL THEN 0 
     ELSE 1 
    END) AS CommentCount 

    ,COUNT(CASE 
    WHEN cl.PostID IS NULL THEN 0 
    ELSE 1 
    END) AS ClickCount 
    ,SUM(CASE 
    WHEN vt.PostID IS NULL THEN 0 
    ELSE ISNULL(vt.Value,0) 
    END) AS VoteScore 

FROM 
    Post p 
    LEFT OUTER JOIN Comment cm ON p.PostID = cm.PostID 
    LEFT OUTER JOIN Click cl ON p.PostID = cl.PostID 
    LEFT OUTER JOIN Vote vt ON p.PostID = vt.PostID 
GROUP BY 
    p.PostID, 
    p.Title 
1

あなたと間違っているものを既に説明しましたクエリ:postid 41の3つのコメント、5つのクリック、4つの投票(それぞれ1の値を持つ)を指定すると、1回目と2回目のカウント式では3x5x4 = 60、合計では3x5x4x1 = 60となります。

いくつかの外を扱う、凝集との組み合わせで合流最初にテーブルを結合し、後で集約するが、集計最初にして凝集体を結合してはいけません。

select 
    p.postid, 
    p.title, 
    coalesce(cm.cnt, 0) as commentcount, 
    coalesce(cl.cnt, 0) as clickcount, 
    coalesce(vt.total, 0) as votescore 
from post p 
left outer join (select postid, count(*) as cnt from comment group by postid) cm 
    on cm.postid = p.postid 
left outer join (select postid, count(*) as cnt from click group by postid) cl 
    on cl.postid = p.postid 
left outer join (select postid, sum(value) as total from vote group by postid) vt 
    on vt.postid = p.postid; 
1

COUNTはNULL以外の値をカウントします。しかし、ヌルを0に設定すると、カウントされます。カウントをSUMに変更し、ケース外に移動して問題が解決すると思います。

EG

SELECT 
    p.PostID, 
    p.Title, 
    SUM(CASE 
     WHEN cm.CommentID IS NULL THEN 0 
     ELSE cm.CommentID 
    END) AS CommentCount, 
    SUM(CASE 
     WHEN cl.ClickID IS NULL THEN 0 
     ELSE cl.ClickID 
    END) AS ClickCount, 
    SUM(CASE 
     WHEN SUM(vt.Value IS NULL THEN 0 
     ELSE SUM(vt.Value 
    END) AS VoteScore 
FROM 
    Post p 
    LEFT OUTER JOIN Comment cm ON p.PostID = cm.PostID 
    LEFT OUTER JOIN Click cl ON p.PostID = cl.PostID 
    LEFT OUTER JOIN Vote vt ON p.PostID = vt.PostID 
GROUP BY 
    p.PostID, 
    p.Title 
関連する問題