11

私が現在データベースのコースをたどっているときに読んでいるこの本では、集計演算子を使用した不正なクエリの次の例が示されています

最年長の船乗りの名前と年齢を見つけてください。

この質問に答える次の試みを検討してください。

SELECT S.sname, MAX(S.age)
FROM Sailors S

このクエリは、最大年齢だけでなく、その年齢の船員の名前も返すことを目的としています。ただし、このクエリはSQLでは無効です。SELECT句で集計操作を使用する場合、クエリにGROUP BY句が含まれていない限り、集計操作のみを使用する必要があります。

しばらくして、MySQLを使用して演習を行っているときに、同様の問題に直面し、前述の問題と同様の間違いを犯しました。しかし、MySQLは文句を言わず、いくつかのテーブルを吐き出しましたが、後で必要なものではないことが判明しました。

上記のクエリはSQLでは本当に違法ですが、MySQLでは合法ですか?もしそうなら、それはなぜですか?どのような状況でそのようなクエリを実行する必要がありますか?

質問のさらなる詳細:

問題は、SELECTで言及されているすべての属性をGROUPBYでも言及する必要があるかどうかではありません。これが、MySQLでGROUP BYを使用せずに、属性に対する集計操作とともに属性を使用する上記のクエリが有効である理由です。

セーラーテーブルが次のようになっているとしましょう。

+----------+------+
| sname    | age  |
+----------+------+
| John Doe |   30 |
| Jane Doe |   50 |
+----------+------+

クエリは次を返します。

+----------+------------+
| sname    | MAX(S.age) |
+----------+------------+
| John Doe |         50 |
+----------+------------+

今、誰がそれを必要としますか?John Doeは50歳ではなく、30歳です。本からの引用で述べられているように、これは最年長の船乗り、この例では50歳のジェーンドゥの名前と年齢を取得する最初の試みです。

SQLはこのクエリは違法であると言いますが、MySQLはただ続行して「ゴミ」を吐き出します。誰がこの種の結果を必要とするでしょうか?MySQLがこの小さなトラップを新規参入者に許可するのはなぜですか?

4

4 に答える 4

11

ちなみに、これはデフォルトのMySQLの動作です。my.iniただし、ファイルまたはセッションでONLY_FULL_GROUP_BYサーバーモードを設定することで変更できます-

SET sql_mode = 'ONLY_FULL_GROUP_BY';
SELECT * FROM sakila.film_actor GROUP BY actor_id;

Error: 'sakila.film_actor.film_id' isn't in GROUP BY

ONLY_FULL_GROUP_BY-選択リストがGROUPBY句で指定されていない非集計列を参照しているクエリを許可しません。

于 2012-10-11T15:46:22.000 に答える
6

上記のクエリはSQLでは本当に違法ですが、MySQLでは合法ですか

はい

もしそうなら、それはなぜですか

MySQLで設計を決定した理由はわかりませんが、実際の関連データは、集計の元と同じ行(たとえば、MAXまたはMIN)からわずかに多くの作業で取得できることを考えると、任意の行から追加の列データを返すことに利点はありません。

私はMySQLのこの「機能」が非常に嫌いで、MySQLで集計を学習してから別のデータベース管理システムに移動する多くの人をつまずかせ、突然、自分が何をしているのかまったくわからないことに気付きます。

于 2012-10-11T15:38:45.600 に答える
6

コメントでa_horse_with_no_nameが提供したリンクに基づいて、私は自分の答えにたどり着きました。

とにかく他の含まれている列に機能的に依存している場合、GROUP BY句から列を除外できるようにするために、MySQLのGROUPBYの使用方法はSQLの方法とは異なるようです。

銀行口座のアクティビティを表示するテーブルがあるとします。それはあまり考え抜かれたテーブルではありませんが、私たちが持っているのはそれだけであり、それが必要になるでしょう。金額を追跡する代わりに、アカウントが「0」から始まり、そのアカウントへのすべてのトランザクションが代わりに記録されると想像します。したがって、金額はトランザクションの合計です。テーブルは次のようになります。

+------------+----------+-------------+
| costumerID | name     | transaction |
+------------+----------+-------------+
|       1337 | h4x0r    |         101 |
|         42 | John Doe |         500 |
|       1337 | h4x0r    |        -101 |
|         42 | John Doe |        -200 |
|         42 | John Doe |         500 |
|         42 | John Doe |        -200 |
+------------+----------+-------------+

「名前」が「costumerID」に機能的に依存していることは明らかです。(この例では、逆の方法も可能です。)

各顧客のcostumerID、名前、現在の金額を知りたい場合はどうなりますか?

このような状況では、2つの非常によく似たクエリが次の正しい結果を返します。

+------------+----------+--------+
| costumerID | name     | amount |
+------------+----------+--------+
|         42 | John Doe |    600 |
|       1337 | h4x0r    |      0 |
+------------+----------+--------+

このクエリはMySQLで実行でき、SQLに従って有効です。

SELECT costumerID, name, SUM(transaction) AS amount
FROM Activity
GROUP BY costumerID, name

このクエリはMySQLで実行でき、SQLによると合法ではありません。

SELECT costumerID, name, SUM(transaction) AS amount
FROM Activity
GROUP BY costumerID

次の行では、集計操作とGROUP BYを使用するSQLの方法に従う必要があるため、代わりにクエリが返され、エラーが発生します。

SET sql_mode = 'ONLY_FULL_GROUP_BY';

MySQLで2番目のクエリを許可するための引数は、SELECTで言及されているが、GROUP BYでは言及されていないすべての列が、集計操作内で使用されているか('transaction'の場合)、または含まれている他の列に機能的に依存しています(「name」の場合)。'name'の場合、機能的に'costumerID'に依存しているため、すべてのグループエントリに正しい'name'が選択されていることを確認できます。したがって、costumerIDの各グループに名前が1つしかない可能性があります。

GROUP BYを使用するこの方法は、GROUP BY句から除外されているものをさらにチェックしないため、欠陥があるように見えます。特定の列を含めたり除外したりしても意味がない場合でも、SELECTステートメントから列を選択して、適切と思われるGROUPBY句を挿入できます。

セーラーの例は、この欠陥を非常によく示しています。集計演算子を使用する場合(おそらくGROUP BYと組み合わせて)、返されるセットの各グループエントリには、その列ごとに1つの値しかありません。セーラーの場合、GROUP BY句が省略されているため、テーブル全体が1つのグループエントリにまとめられます。このエントリには、名前と最大年齢が必要です。MAX(S.age)は1つの値しか返さないため、このエントリの最大年齢を選択するのは簡単です。ただし、S.snameの場合、SELECTでのみ言及されているため、Sailorテーブル全体(この場合はJohnとJane Doeの2つ)にある一意のsnameと同じ数の選択肢があります。MySQLには何もありませんどちらを選択するかについての手がかり、私たちはそれを与えませんでした、そしてそれは時間内にブレーキをかけなかったので、それは最初に来るものを選ぶだけです(ジェーンドゥ)。2つの行を入れ替えると、実際には誤って「正しい答え」が返されます。MySQLでこのようなことが許可されていること、GROUP BY句で何かが省略されている場合、GROUP BYを使用したクエリの結果がテーブルの順序に潜在的に依存する可能性があることは、まったく馬鹿げているようです。どうやら、それはMySQLが転がる方法です。しかし、それでも、「欠陥のある」クエリのために何をしているのか見当がつかないときに、少なくとも警告の礼儀を持っていることができませんでしたか?確かに、プログラムに間違った指示を与えた場合、それはおそらくあなたが望むように行わない(またはすべきではない)でしょうが、あなたが不明確な指示を与える場合、私は確かにそうしません

于 2012-10-11T20:16:34.630 に答える
2

MySQLでは、この非標準のSQL構文が許可されています。これは、SQLを名目上記述しやすくする特定のケースが少なくとも1つあるためです。その場合は、PRIMARY / FOREIGN KEY関係(データベースによって強制されるかどうかに関係なく)を持つ2つのテーブルを結合し、FOREIGNKEY側からの集計値とPRIMARYKEY側からの複数の列が必要な場合です。

CustomerOrdersテーブルを備えたシステムを考えてみましょう。顧客テーブルのすべてAmountのフィールドと、テーブルのフィールドの合計が必要だとしOrdersます。標準SQLでは、次のように記述します。

 SELECT C.CustomerID, C.FirstName, C.LastName, C.Address, C.City, C.State, C.Zip, SUM(O.Amount)
    FROM Customer C INNER JOIN Orders O ON C.CustomerID = O.CustomerID
    GROUP BY C.CustomerID, C.FirstName, C.LastName, C.Address, C.City, C.State, C.Zip

扱いにくいGROUPBY句に注意し、顧客から必要な列がさらにあるとしたらどうなるか想像してみてください。

MySQLでは、次のように書くことができます。

 SELECT C.CustomerID, C.FirstName, C.LastName, C.Address, C.City, C.State, C.Zip, SUM(O.Amount)
    FROM Customer C INNER JOIN Orders O ON C.CustomerID = O.CustomerID
    GROUP BY C.CustomerID

または(私はそれを試していないと思います):

 SELECT C.*, SUM(O.Amount)
    FROM Customer C INNER JOIN Orders O ON C.CustomerID = O.CustomerID
    GROUP BY C.CustomerID

書くのがはるかに簡単です。この特定のケースでは、テーブルの1行のみCustomerが各グループに寄与することがわかっているので(CustomerIDがPRIMARYまたはUNIQUE KEYであると想定)、これも安全です。

個人的には、標準のSQL構文に対するこの例外の大ファンではありません(この構文を使用してグループ内の特定の行から値を取得することに依存するのは安全ではない場合が多いため)が、どこにあるかはわかります特定の種類のクエリをより簡単にし、(私の2番目のMySQLの例の場合)可能にします。

于 2012-10-11T16:11:13.120 に答える