7

次の構造のテーブルがあります。

Contents (
  id
  name
  desc
  tdate
  categoryid
  ...
)

このテーブルのデータを使用して統計を行う必要があります。たとえば、グループ化とそのカテゴリの ID によって、同じカテゴリの行数を取得したいとします。nまた、降順で行を制限したいと思います。利用可能なカテゴリが他にもある場合は、それらを「その他」としてマークしたいと思います。これまでのところ、データベースへの2つのクエリが出てきました:

n降順で行を選択します。

SELECT COALESCE(ca.NAME, 'Unknown') AS label
    ,ca.id AS catid
    ,COUNT(c.id) AS data
FROM contents c
LEFT OUTER JOIN category ca ON ca.id = c.categoryid
GROUP BY label
    ,catid
ORDER BY data DESC LIMIT 7

他の行を 1 つに選択:

SELECT 'Others' AS label
    ,COUNT(c.id) AS data
FROM contents c
LEFT OUTER JOIN category ca ON ca.id = c.categoryid
WHERE c.categoryid NOT IN ($INCONDITION)

しかし、db テーブルにカテゴリ グループが残っていない場合でも、「その他」のレコードが表示されます。1つのクエリで作成し、「その他」レコードをオプションにすることは可能ですか?

4

3 に答える 3

4

ここでの具体的な問題SELECT:リストに 1 つ以上の集計関数があり、GROUP BY句がないクエリでは、基になるテーブルに行が見つからない場合でも、1 つの行が生成されます。

WHEREその行を抑制するために句でできることは何もありません。そのような行は、事後、つまりHAVING句内、または外部クエリで除外する必要があります。

ドキュメントごと:

クエリに集約関数の呼び出しが含まれていてもGROUP BY句がない場合でも、グループ化は行われます。結果は単一のグループ行になります (または、単一の行が によって削除された場合は、行がまったくない可能性がありますHAVING)。HAVING集計関数の呼び出しや句がなくても、句が含まれている場合も同様ですGROUP BY

GROUP BY定数式のみを含む節を追加すること (それ以外の場合は完全に無意味です!) も機能することに注意してください。以下の例を参照してください。しかし、たとえそれが短く、安価で、単純であっても、私はむしろそのトリックを使用したくありません。

次のクエリは、1 回のテーブル スキャンのみを必要とし、カウント順に並べられた上位 7 つのカテゴリを返します。さらにカテゴリがある場合 (およびその場合のみ)、残りは「その他」にまとめられます。

WITH cte AS (
   SELECT categoryid, count(*) AS data
        , row_number() OVER (ORDER BY count(*) DESC, categoryid) AS rn
   FROM   contents
   GROUP  BY 1
   )
(  -- parentheses required again
SELECT categoryid, COALESCE(ca.name, 'Unknown') AS label, data
FROM   cte
LEFT   JOIN category ca ON ca.id = cte.categoryid
WHERE  rn <= 7
ORDER  BY rn
)
UNION ALL
SELECT NULL, 'Others', sum(data)
FROM   cte
WHERE  rn > 7         -- only take the rest
HAVING count(*) > 0;  -- only if there actually is a rest
-- or: HAVING  sum(data) > 0
  • 7 位 / 8 位で複数のカテゴリが同じ数になる場合は、タイを破る必要があります。私の例では、小さいカテゴリがそのcategoryidような競争に勝ちます。

  • LIMITor句をクエリORDER BYの個々のレッグに含めるには、括弧が必要です。UNION

  • category上位 7 カテゴリのテーブルに参加するだけです。また、このシナリオでは、最初に集約し、後で結合する方が一般的に安価です。したがって、という名前のCTE (共通テーブル式)cteのベース クエリに参加しないでください。最初SELECTUNIONクエリにのみ参加してください。これは安価です。

  • が必要な理由がわかりませんCOALESCE。fromとcontents.categoryidtocategory.idの両方が定義されている(おそらくそうあるべきであるように) 適切な場所に外部キーがある場合、それは必要ありません。contents.categoryidcategory.nameNOT NULL

奇妙なGROUP BY true

これもうまくいきます:

...

UNION ALL
SELECT NULL , 'Others', sum(data)
FROM   cte
WHERE  rn > 7
GROUP BY true; 

また、わずかに高速なクエリ プランも得られます。しかし、それはかなり奇妙なハックです...

すべてを示すSQL Fiddle 。

UNION ALL/LIMITテクニックの詳細な説明を含む関連回答:

于 2015-06-02T14:26:23.573 に答える
0

ネストされた集計でこれにアプローチできます。内部集計は、連番とともにカウントを計算します。数が 7 以下のすべてを取得し、それ以外のすべてをothersカテゴリに結合します。

SELECT (case when seqnum <= 7 then label else 'others' end) as label,
       (case when seqnum <= 7 then catid end) as catid, sum(cnt)
FROM (SELECT ca.name AS label, ca.id AS catid, COUNT(c.id) AS cnt,
             row_number() over (partition by ca.name, catid order by count(c.id) desc) as seqnum
      FROM contents c LEFT OUTER JOIN
           category ca
           ON ca.id = c.categoryid
      GROUP BY label, catid
     ) t
GROUP BY (case when seqnum <= 7 then label else 'others' end),
         (case when seqnum <= 7 then catid end) 
ORDER BY cnt DESC ;
于 2015-04-10T11:39:29.517 に答える