GROUP BY句で使用するか、集計関数で使用する必要があります


276

この発信者「makerar」のようなテーブルがあります

 cname  | wmname |          avg           
--------+-------------+------------------------
 canada | zoro   |     2.0000000000000000
 spain  | luffy  | 1.00000000000000000000
 spain  | usopp  |     5.0000000000000000

そして、各cnameの最大平均を選択します。

SELECT cname, wmname, MAX(avg)  FROM makerar GROUP BY cname;

エラーになりますが

ERROR:  column "makerar.wmname" must appear in the GROUP BY clause or be used in an   aggregate function 
LINE 1: SELECT cname, wmname, MAX(avg)  FROM makerar GROUP BY cname;

だから私はこれをします

SELECT cname, wmname, MAX(avg)  FROM makerar GROUP BY cname, wmname;

ただし、これでは意図した結果が得られず、以下の誤った出力が表示されます。

 cname  | wmname |          max           
--------+--------+------------------------
 canada | zoro   |     2.0000000000000000
 spain  | luffy  | 1.00000000000000000000
 spain  | usopp  |     5.0000000000000000

実際の結果は

 cname  | wmname |          max           
--------+--------+------------------------
 canada | zoro   |     2.0000000000000000
 spain  | usopp  |     5.0000000000000000

この問題を解決するにはどうすればよいですか?

注:この表は、前の操作で作成されたVIEWです。



分かりません。なぜwmname="usopp"期待されているのwmname="luffy"ですか?
AndreKR

回答:


226

はい、これは一般的な集計の問題です。SQL3(1999)より前のバージョンでは、選択したフィールドはGROUP BY句[*]に含まれている必要があります。

この問題を回避するには、サブクエリで集計を計算し、それをそれと結合して、表示する必要がある追加の列を取得する必要があります。

SELECT m.cname, m.wmname, t.mx
FROM (
    SELECT cname, MAX(avg) AS mx
    FROM makerar
    GROUP BY cname
    ) t JOIN makerar m ON m.cname = t.cname AND t.mx = m.avg
;

 cname  | wmname |          mx           
--------+--------+------------------------
 canada | zoro   |     2.0000000000000000
 spain  | usopp  |     5.0000000000000000

しかし、よりシンプルに見えるウィンドウ関数を使用することもできます。

SELECT cname, wmname, MAX(avg) OVER (PARTITION BY cname) AS mx
FROM makerar
;

このメソッドの唯一の点は、すべてのレコードを表示することです(ウィンドウ関数はグループ化しません)。しかし、それは各行の国の正しい(つまり、最大cnameレベル)MAXを表示するので、それはあなた次第です。

 cname  | wmname |          mx           
--------+--------+------------------------
 canada | zoro   |     2.0000000000000000
 spain  | luffy  |     5.0000000000000000
 spain  | usopp  |     5.0000000000000000

(cname, wmname)最大値に一致するタプルのみを表示するソリューションは、おそらくあまりエレガントではありませんが、次のとおりです。

SELECT DISTINCT /* distinct here matters, because maybe there are various tuples for the same max value */
    m.cname, m.wmname, t.avg AS mx
FROM (
    SELECT cname, wmname, avg, ROW_NUMBER() OVER (PARTITION BY avg DESC) AS rn 
    FROM makerar
) t JOIN makerar m ON m.cname = t.cname AND m.wmname = t.wmname AND t.rn = 1
;


 cname  | wmname |          mx           
--------+--------+------------------------
 canada | zoro   |     2.0000000000000000
 spain  | usopp  |     5.0000000000000000

[*]:興味深いことに、仕様ではグループ化されていないフィールドを選択できるようになっていますが、主要なエンジンではあまり好きではないようです。OracleとSQLServerはこれをまったく許可しません。Mysqlはデフォルトでこれを許可していましたが、5.7以降、管理者はONLY_FULL_GROUP_BYこの機能をサポートするためにサーバー設定でこのオプション()を手動で有効にする必要があります...


1
ありがとう構文は正しいですが、参加するときはmxとavgの値を比較する必要があります
RandomGuy 2013年

1
はい、構文は正しく、重複を排除しますが、意図した結果を得るには最後に(JOINGを記述した後)m.avg = t.mxが必要です
RandomGuy

1
@Sebas参加せずに実行できますMAX(@ypercubeの回答を参照してください。私の回答には別の解決策もあります)。ただし、それを行う方法はありません。予想される出力を確認します。
zero323 2013年

1
@Sebasソリューションは列(MAX avgあたりcname)を追加するだけですが、(OPが望むように)結果の行を制限しません。実際の結果は質問の段落である必要がありますを参照してください。
ypercubeᵀᴹ

1
ターニングオフ ONLY_FULL_GROUP_BY列から省略することができたときのMySQL 5.7には、方法SQL標準指定を活性化しないgroup by(またはPostgresのようにMySQLの振る舞いになります)。代わりに、MySQLがランダム(=「不確定」)の結果を返すという古い動作に戻ります。
a_horse_with_no_name 2015年

126

Postgresでは、特別なDISTINCT ON (expression)構文を使用することもできます。

SELECT DISTINCT ON (cname) 
    cname, wmname, avg
FROM 
    makerar 
ORDER BY 
    cname, avg DESC ;

5
avgのように列をソートしたい場合、期待どおりに機能しません
amenzhinsky

@amenzhinskyどういう意味ですか?結果セットを別の順序でソートしたい場合はBY cname
ypercubeᵀᴹ

@ypercube、実際にはpsqlが最初にソートしてから、DISTINCTを適用します。平均で並べ替える場合、並べ替えの方向に応じて、すべての行の最小値と最大値に対して異なる結果が得られます
amenzhinsky

3
もちろん。私が投稿したクエリを実行しないと、異なる結果が得られます。「期待どおりに動作しません」それは...と同じではありません
ypercubeᵀᴹ

1
@Batfan thnx。これは非常にクールでコンパクトで簡単に書くことができますが、この種のクエリにとって最も効率的な方法とは限りません。
ypercubeᵀᴹ

27

group byselectでグループ化されていない、集計されていないフィールドを指定する場合の問題は、エンジンがこの場合にどのレコードのフィールドを返すべきかを知る方法がないことです。最初ですか?最後ですか?自然(集計結果に対応していることを何も記録は通常ありませんminし、max例外ですが)。

ただし、回避策があります。必須フィールドも集計するようにします。posgresでは、これはうまくいくはずです:

SELECT cname, (array_agg(wmname ORDER BY avg DESC))[1], MAX(avg)
FROM makerar GROUP BY cname;

これは、avgで並べられたすべてのwnameの配列を作成し、最初の要素を返します(postgresの配列は1から始まります)。


いい視点ね。ただし、DBが外部結合を実行して、各行の非集計フィールドを、その行が寄与した集計結果にリンクする可能性はあります。彼らがそのためのオプションがない理由を私はしばしば知りました。私はこのオプションを知らないだけかもしれませんが:)
Ben Simmons

16
SELECT t1.cname, t1.wmname, t2.max
FROM makerar t1 JOIN (
    SELECT cname, MAX(avg) max
    FROM makerar
    GROUP BY cname ) t2
ON t1.cname = t2.cname AND t1.avg = t2.max;

rank() ウィンドウ関数の使用:

SELECT cname, wmname, avg
FROM (
    SELECT cname, wmname, avg, rank() 
    OVER (PARTITION BY cname ORDER BY avg DESC)
    FROM makerar) t
WHERE rank = 1;

注意

どちらでも、グループごとに複数の最大値が保持されます。avgがmaxに等しいレコードが複数ある場合でも、グループごとに1つのレコードのみが必要な場合は、@ ypercubeの回答を確認してください。


16

私にとって、それは「一般的な集計問題」ではなく、誤ったSQLクエリに関するものです。「各cnameの最大平均を選択...」の単一の正解は

SELECT cname, MAX(avg) FROM makerar GROUP BY cname;

結果は次のようになります。

 cname  |      MAX(avg)
--------+---------------------
 canada | 2.0000000000000000
 spain  | 5.0000000000000000

この結果は一般に、「各グループの最良の結果は何ですか?」という質問に答えます。。スペインの最良の結果は5であり、カナダの最良の結果は2であることがわかります。これは真であり、エラーはありません。wmnameも表示する必要がある場合は、「結果セットからwmnameを選択するためのルールは何ですか?」という質問に答える必要があります。間違いを明確にするために、入力データを少し変更してみましょう。

  cname | wmname |        avg           
--------+--------+-----------------------
 spain  | zoro   |  1.0000000000000000
 spain  | luffy  |  5.0000000000000000
 spain  | usopp  |  5.0000000000000000

このクエリを実行すると、どの結果が予想されますSELECT cname, wmname, MAX(avg) FROM makerar GROUP BY cname;か?それか、spain+luffyそれともspain+usopp?どうして?いくつかのものが適切である場合、クエリで「より良い」wmnameを選択する方法は決定されないため、結果も決定されません。そのため、SQLインタープリターはエラーを返します-クエリが正しくありません。

つまり、「グループで一番の人はspain?」という質問に対する正解はありません。。usoppは「スコア」が同じであるため、ルフィはusoppよりも優れていません。


この解決策も私にとってうまくいきました。ORMに関連する主キーも含まれていたためにクエリの問題があり、次の誤ったクエリがSELECT cname, id, MAX(avg) FROM makerar GROUP BY cname;発生しました。
ロベルト

1

これも機能するようです

SELECT *
FROM makerar m1
WHERE m1.avg = (SELECT MAX(avg)
                FROM makerar m2
                WHERE m1.cname = m2.cname
               )

0

最近case when、を使用してカウントしようとしたときにこの問題に遭遇し、whichand countステートメントの順序を変更すると問題が解決することがわかりました。

SELECT date(dateday) as pick_day,
COUNT(CASE WHEN (apples = 'TRUE' OR oranges 'TRUE') THEN fruit END)  AS fruit_counter

FROM pickings

GROUP BY 1

使用する代わりに-後者では、リンゴとオレンジが集計関数に表示されるはずであるというエラーが発生しました

CASE WHEN ((apples = 'TRUE' OR oranges 'TRUE') THEN COUNT(*) END) END AS fruit_counter

1
which声明?
ヒラリーサンダーズ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.