web-dev-qa-db-ja.com

SUM()のMAX()が間違った出力を出すのはなぜですか?

私はテーブルの従業員がいます

id    name   salary  city
1     ram    50000   c1
2     sham   20000   c2
3     jadu   80000   c1
4     madhu  90000   c4
5     hari   10000   c2
6     gopal  34000   c3
7     komal  55000   c3
8     bappa  98000   c4

クエリは、最も高い収入を得ている都市です。私は試した

SELECT city, SUM(salary) AS maxSalary 
FROM employee GROUP BY city ORDER BY salary DESC LIMIT 1;

それは正常に動作しますが、最大収入都市が複数ある場合、他の最大都市は出力されず、最初の都市のみが出力されます。

だから私はこのクエリを試しました

SELECT city, MAX(totalSalary) maxSalary 
FROM( SELECT city, SUM(salary) AS totalSalary FROM employee GROUP BY city  ) AS tempTable

それは与えています

city  max
c1    188000

しかし正しいです

city  max
c4    188000

これは、c1であるテーブルの最初の都市名を出力していますが、c4である正しい最大収入都市名ではありません。正しいクエリは何ですか?

2
babu

間違ったクエリをデータベースに送信しました。 manual で説明されているmysql拡張をヒットしました。次のようなクエリ

SELECT city, MAX(salary) 
from employee

標準SQLでは機能しません。 Oracle (エラーメッセージ: "ORA-00937:単一グループグループ関数ではありません")でエラーを発生させます MSSqlServer 2012 (エラーメッセージ: "Column 'employee .city 'は、集約関数またはGROUP BY句のいずれにも含まれていないため、選択リストでは無効です ")または postgresql (エラーメッセージ:"エラー:列 "employee.city"が必要ですGROUP BY句で使用するか、集計関数で使用します」)。

選択リストの標準SQL式では、group by句でも使用される列の式と集計関数のみを使用できます。 group by句で使用される列の値は、これらのグループのすべての行で同じです。集計もグループごとに一意です。 selectは、グループに対してこの一意に定義された値を含むグループごとに1行を返します。

集約クエリにgroup by句がない場合、それらは行の1つのグループにすぎません。

標準では、その値が一意に定義されていないため、集計クエリの選択リストで任意の列を使用することは許可されていません。列はクエリの行に対して異なる値を持っているため、クエリによってこのグループに返される値はどれですか?

Mysqlには、group by句にない式(集約関数式ではない)の列がある場合、このグループのselectステートメントによって返される値が、このグループの任意の行。

したがって、クエリ

SELECT city, MAX(salary) 
from employee

すべての従業員の給与の合計と、これらの行の1つの市を返します。ただし、クエリは集約クエリであり、従業員テーブルのすべてのレコードを含むグループが1つしかないため、正確に1つのreowを返します。

クエリ

SELECT city, MAX(salary) 
from employee
group by city

各都市の都市と各都市の給与の行を返します。

クエリ

SELECT city, salary
from employee
group by city

この都市の任意の従業員のsalatyを持つ各都市の行を返します。サーバーは、都市のどの従業員を選択するかを決定します。

クエリ

SELECT city, zipcode
from employee
group by city

また、この都市の郵便番号を含む各都市の行を返します。すべての都市が1つの都市で定義された各グループの行よりも1つだけ郵便番号を持っていると仮定すると、同じ郵便番号が含まれます。そのため、行とは関係なく、サーバーは返された郵便番号を常に都市の郵便番号として選択します。

標準SQLでは、このクエリは次のように記述されます

SELECT city, zipcode
from employee
group by city, zipcode 

期待される結果を生み出す

group by city,zipcodeおよびgroup city zipcdoeと都市の間の1対1の対応のため、同じグループを定義します。


クエリ

SELECT city, SUM(salary) 
from employee
group by city

必要な行だけでなく、その他の行も含まれます。

このクエリから必要な行を除外する方法は次のとおりです

SELECT city, SUM(salary) 
from employee
group by city 
having SUM(salary) >= all (select SUM(salary)  city_salary 
     from employee
     group by city)

フィルタリングする他の方法があります。

最大の給与額は次のクエリで見つけることができます

select MAX(city_salary)  
   from (select SUM(salary)  city_salary 
     from employee
     group by city) tab

(注:Oracleでも同じことを実現できます。

select MAX(SUM(salary))  city_salary 
  from employee
  group by city

これを使用して、必要なものを除外できます。

SELECT city, SUM(salary) 
from employee
group by city 
having SUM(salary) = (select MAX(city_salary)  
   from (select SUM(salary)  city_salary 
     from employee
     group by city) tab) 

このソリューションは、@ Mihaiが comment ですでに提示しています。

5
miracle173

グループ化基準を提供せずに、tempTable結果セットに対してmax()集約関数を使用しています。グループなしで集約関数を使用すると、すべての行が1つのグループと見なされ、結果は次のようになります。都市のような不定の順序は不定の順序で返されます。max()は結果セットからの最大値を保証しますが、関連する列の値を保証しません(都市が関連する列の場合)。 c4の代わりに都市c1を取得しています。

ただし、tempTableの結果セットの値と一致するようにすべての行に対してサブクエリを実行する必要があるため、あまり効率的ではないサブクエリを使用して回答を達成しました。クロス結合を使用することをお勧めします集計関数の結果を一致させるためにHAVINGを使用するサブクエリの代わりに副選択を使用

サンプルデータ

INSERT INTO employee
    (`id`, `name`, `salary`, `city`)
VALUES
    (1, 'ram', 50000, 'c1'),
    (2, 'sham', 20000, 'c2'),
    (3, 'jadu', 80000, 'c1'),
    (4, 'madhu', 90000, 'c4'),
    (5, 'hari', 10000, 'c2'),
    (6, 'gopal', 34000, 'c3'),
    (7, 'komal', 55000, 'c3'),
    (8, 'bappa', 98000, 'c4')
;

クエリ

SELECT 
e.city,
SUM(e.salary) AS totalSalary ,
e1.maxSalary
FROM employee e
CROSS JOIN (SELECT city, SUM(salary) AS maxSalary
            FROM employee
            GROUP BY city
            ORDER BY maxSalary
            DESC LIMIT 1
           ) e1
GROUP BY e.city
HAVING totalSalary = e1.maxSalary

結果の例

CITY    TOTALSALARY MAXSALARY
c4       188000     188000

Fiddle Demo 1

副選択でクロス結合を使用する利点は、クロス結合のサブクエリが回答のサブクエリと比較して1回だけ評価され、クロス結合のサブ選択では、降順で並べることによって従業員の最大合計給与を取得するだけです。制限1の方法では、最高の給与合計が得られ、クエリの合計結果では、having句のクロス結合の副選択によって計算されたmaxsalaryが比較されます。

フィドルデモ1の最大給与は188000だったので、2人の従業員が同じ最大給与を持っている場合は、両方を返すので、(9, 'test', 188000, 'c5')と同じ最高給与を持つ別の従業員がもう1行あるサンプルデータを少し変更しました。フィドルデモ2が同じクエリを使用して、最高の給与が同じ2人の従業員を返すかどうかを評価していることがわかります

サンプルデータ

INSERT INTO employee
    (`id`, `name`, `salary`, `city`)
VALUES
    (1, 'ram', 50000, 'c1'),
    (2, 'sham', 20000, 'c2'),
    (3, 'jadu', 80000, 'c1'),
    (4, 'madhu', 90000, 'c4'),
    (5, 'hari', 10000, 'c2'),
    (6, 'gopal', 34000, 'c3'),
    (7, 'komal', 55000, 'c3'),
    (8, 'bappa', 98000, 'c4'),
    (9, 'test', 188000, 'c5')
;

結果セットの例

CITY    TOTALSALARY MAXSALARY
c4      188000      188000
c5      188000      188000

Fiddle Demo 2

4
M Khalid Junaid

私は答えを見つけたと思います。正解しない理由は、試してみたのと同じ理由

SELECT name, MAX(salary) FROM employee

これは機能しません。これはnameの最初のレコードのみを出力するためです。ただし、最大の給与が得られるため、給与フィールドと最大の給与を比較する必要があります。

SELECT name, salary FROM employee
WHERE salary = (SELECT MAX(salary) FROM employee)

だから私の質問の実際のクエリは

SELECT city, totalSalary
FROM( SELECT city, SUM(salary) AS totalSalary FROM employee GROUP BY city  ) AS tempTable
WHERE totalSalary =
(SELECT MAX(totalSalary) FROM ( SELECT city, SUM(salary) AS totalSalary FROM employee GROUP BY city  ) AS tempTable)

tempTableを再度宣言する必要があります。そうしないと、tempTableが存在しないと表示されます。

2
babu