webdevqa.jp.net

MySQL-グループ化されていない列の選択

既存のアプリケーションに機能を追加しようとしていますが、次のようなMySQLビューに遭遇しました。

SELECT
     AVG(table_name.col1),
     AVG(table_name.col2),
     AVG(table_name.col3),
     table_name.personID,
     table_name.col4
FROM table_name
GROUP BY table_name.personID;

わかりましたので、いくつかの集計関数があります。グループ化しているため、personIDを選択できます。ただし、集計関数内になく、GROUP BY句の一部ではない列も選択されます。これはどのようにして可能ですか?値はグループごとに一意ではないため、ランダムな値を選択するだけですか?

私の出身地(MSSQLサーバー)はエラーです。誰かがこの動作を私に説明できますか、なぜそれがMySQLで許可されているのですか?

45
colithium

この機能はいくつかのあいまいなクエリを許可し、その列から選択された任意の値を含む結果セットを静かに返します。実際には、最初に物理的に格納されるのは、グループ内の行の値になる傾向があります。

GROUP BY条件の列に機能的に依存する列のみを選択する場合、これらのクエリがあいまいではありません。つまり、グループを定義する値ごとに「あいまい」列の個別値が1つしか存在しない場合、問題はありません。 Microsoft SQL Server(およびANSI SQL)では、論理的に曖昧さをもたらすことはできませんが、このクエリは不正です。

SELECT AVG(table1.col1), table1.personID, persons.col4
FROM table1 JOIN persons ON (table1.personID = persons.id)
GROUP BY table1.personID;

また、MySQLには標準に従って動作するSQLモードがあります。 ONLY_FULL_GROUP_BY

FWIW、SQLiteはこれらのあいまいなGROUP BY句も許可しますが、グループ内のlast行から値を選択します。


少なくとも私がテストしたバージョンでは。 arbitraryとは、MySQLまたはSQLiteのいずれかが将来的に実装を変更し、動作が異なる可能性があることを意味します。したがって、現在のようなあいまいな場合のように、動作をそのままにしておくべきではありません。クエリは、確定的で曖昧でないように書き換える方が適切です。これが、MySQL 5.7がデフォルトでONLY_FULL_GROUP_BYを有効にする理由です。

47
Bill Karwin

私はもう少しグーグルでいるべきだった...見つけたようです 私の答え

MySQLはGROUP BYの使用を拡張しているため、GROUP BY句に表示されないSELECTリストで非集計列または計算を使用できます。この機能を使用すると、不要な列のソートとグループ化を回避することにより、パフォーマンスを向上させることができます。たとえば、次のクエリではcustomer.nameでグループ化する必要はありません

標準SQLでは、customer.nameをGROUP BY句に追加する必要があります。 MySQLでは、この名前は冗長です。

それでも、それはちょうど間違っているようです...

11
colithium

次のようなクエリがあるとします。

SELECT g, v 
FROM t
GROUP BY g;

この場合、gの可能な値ごとに、mysqlはvの対応する値の1つを選択します。

ただし、どちらを選択するかは、状況によって異なります。

Gのグループごとに、レコードがテーブルvに挿入された順序で、tの最初の値が保持されることをどこかで読みました。

テーブルのレコードはsetとして扱う必要があるため、これは非常に醜い要素です。この場合、要素の順序は重要ではありません。これはとても "mysql-ish"です...

保持するvの値を決定する場合は、次のようにtの副選択を適用する必要があります。

SELECT g, v 
FROM (
    SELECT * 
        FROM t 
        ORDER BY g, v DESC
) q
GROUP BY g;

このようにして、サブクエリのレコードが外部クエリによって処理される順序を定義します。これにより、vのどの値がgの個々の値として選択されるかを信頼できます。

ただし、WHERE条件が必要な場合は、十分に注意してください。 WHERE条件をサブクエリに追加すると、動作が維持され、期待した値が常に返されます。

SELECT g, v 
FROM (
    SELECT * 
        FROM t 
        WHERE g = '737a8783-110c-447e-b4c2-1cbb7c6b72c9' 
        ORDER BY g, v DESC
) q
GROUP BY g;

これは期待どおりの結果であり、副選択はテーブルをフィルタリングして順序付けします。 gが指定された値を持つレコードを保持し、外部クエリはそのgvの最初の値を返します。

ただし、同じWHERE条件を外部クエリに追加すると、非決定的な結果が得られます。

SELECT g, v 
FROM (
    SELECT * 
        FROM t 
        -- WHERE g = '737a8783-110c-447e-b4c2-1cbb7c6b72c9' 
        ORDER BY g, v DESC
) q
WHERE g = '737a8783-110c-447e-b4c2-1cbb7c6b72c9'
GROUP BY g;

驚いたことに、同じクエリを何度も実行すると、vの値が異なる場合があります。これは奇妙です。予想される動作は、サブクエリから適切な順序ですべてのレコードを取得し、外部クエリでそれらをフィルタリングしてから、前の例で選択したものと同じものを選択することです。しかし、そうではありません。

vの値をランダムに選択します。同じクエリでvに異なる値が返されましたが、実行回数が20回を超えたにもかかわらず、分布が均一ではありませんでした。

外部のWHEREを追加する代わりに、次のようなHAVING条件を指定します。

SELECT g, v 
FROM (
    SELECT * 
        FROM t1 
        -- WHERE g = '737a8783-110c-447e-b4c2-1cbb7c6b72c9' 
        ORDER BY g, v DESC
) q
-- WHERE g = '737a8783-110c-447e-b4c2-1cbb7c6b72c9'
GROUP BY g
HAVING g = '737a8783-110c-447e-b4c2-1cbb7c6b72c9';

その後、再び一貫した動作が得られます。

結論:この手法にまったく依存しないことをお勧めします。本当に必要な場合は、外部クエリでのWHERE条件を避けてください。可能であれば内部クエリで使用するか、外部クエリでHAVING句を使用します。

私はこのデータでそれをテストしました:

CREATE TABLE t1 (
    v INT,
    g VARCHAR(36)
);

INSERT INTO t1 VALUES (1, '737a8783-110c-447e-b4c2-1cbb7c6b72c9');
INSERT INTO t1 VALUES (2, '737a8783-110c-447e-b4c2-1cbb7c6b72c9');

mysql 5.6.41。

たぶん、それは新しいバージョンで修正されるバグである可能性があります。新しいバージョンの経験がある場合はフィードバックをお寄せください。

0
Csongor Halmai
select * from personel where p_id IN(select
min(dbo.personel.p_id)
FROM
personel
GROUP BY dbo.personel.p_adi)
0
Salih Kiraz