Question

J'essaie d'ajouter des fonctionnalités à une application préexistante et je suis tombé sur une vue MySQL qui ressemble à ceci:

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;

OK, il y a donc quelques fonctions d'agrégat. Vous pouvez sélectionner personID parce que vous le regroupez. Mais il sélectionne également une colonne qui ne fait pas partie d'une fonction d'agrégat et ne fait pas partie de la clause GROUP BY. Comment est-ce possible??? Choisit-il simplement une valeur aléatoire parce que les valeurs ne sont certainement pas uniques par groupe?

D'où je viens (serveur MSSQL), c'est une erreur. Quelqu'un peut-il m'expliquer ce comportement et pourquoi il est autorisé dans MySQL?

Était-ce utile?

La solution

Il est vrai que cette fonctionnalité autorise certaines requêtes ambiguës et renvoie en mode silencieux un ensemble de résultats avec une valeur arbitraire choisie dans cette colonne. En pratique, il s'agit généralement de la valeur de la ligne du groupe qui est stockée physiquement en premier.

Ces requêtes ne sont pas ambiguës si vous ne choisissez que des colonnes fonctionnellement dépendantes de la (des) colonne (s) du critère GROUP BY. En d’autres termes, s’il ne peut y avoir qu’une seule valeur distincte de la valeur "ambiguë" colonne par valeur qui définit le groupe, il n'y a pas de problème. Cette requête serait illégale dans Microsoft SQL Server (et ANSI SQL), même si elle ne peut logiquement aboutir à une ambiguïté:

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

De plus, MySQL utilise un mode SQL pour se comporter conformément au standard: ONLY_FULL_GROUP_BY

FWIW, SQLite autorise également ces clauses GROUP BY ambiguës, mais il choisit la valeur dans la dernière ligne du groupe.

Au moins dans la version que j'ai testée. Ce que cela signifie d'être arbitraire , c'est que MySQL ou SQLite pourraient changer leur implémentation à l'avenir et avoir un comportement différent. Par conséquent, vous ne devez pas vous fier au comportement qui prévaut dans les cas ambigus comme celui-ci. Il est préférable de réécrire vos requêtes de manière déterministe et non ambiguë. C'est pourquoi MySQL 5.7 active maintenant ONLY_FULL_GROUP_BY par défaut.

Autres conseils

J'aurais dû googler un peu plus longtemps ... Il semble que j'ai trouvé ma réponse .

  

MySQL étend l'utilisation de GROUP BY afin   que vous pouvez utiliser des colonnes non agrégées   ou des calculs dans la liste SELECT   qui n'apparaissent pas dans le GROUP BY   clause. Vous pouvez utiliser cette fonctionnalité pour   obtenir de meilleures performances en évitant   tri inutile des colonnes et   regroupement. Par exemple, vous n'avez pas besoin   grouper sur customer.name dans le   requête suivante

     

En SQL standard, il faudrait ajouter   customer.name à la clause GROUP BY.   Dans MySQL, le nom est redondant.

Pourtant, cela semble juste… faux.

select * from personel where p_id IN(select
min(dbo.personel.p_id)
FROM
personel
GROUP BY dbo.personel.p_adi)

Disons que vous avez une requête comme celle-ci:

SELECT g, v 
FROM t
GROUP BY g;

Dans ce cas, pour chaque valeur possible pour g , mysql choisit l'une des valeurs correspondantes de v .

Cependant, celui qui est choisi dépend de certaines circonstances.

J'ai lu quelque part que pour chaque groupe de g, la première valeur de v est conservée, dans l'ordre dans lequel les enregistrements ont été insérés dans la table t .

C’est assez moche car les enregistrements d’une table doivent être traités comme un ensemble où l’ordre des éléments ne devrait pas avoir d’importance. C’est tellement "mysql-ish" ...

Si vous souhaitez déterminer la valeur à conserver pour v , vous devez appliquer une sous-sélection pour t comme ceci:

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

Ainsi, vous définissez l’ordre dans lequel les requêtes de la sous-requête traitent les enregistrements de la sous-requête. Ainsi, vous pouvez faire confiance à la valeur de v choisie pour les valeurs individuelles de g .

Cependant, si vous avez besoin de conditions WHERE, soyez très prudent. Si vous ajoutez la condition WHERE à la sous-requête, le comportement sera conservé, il retournera toujours la valeur attendue:

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

C’est ce à quoi vous vous attendez, la sous-sélection filtre et ordonne la table. Il conserve les enregistrements où g a la valeur donnée et la requête externe retourne que g et la première valeur de v .

Cependant, si vous ajoutez la même condition WHERE à la requête externe, vous obtenez un résultat non déterministe:

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;

Étonnamment, vous pouvez obtenir des valeurs différentes pour v lors de l'exécution répétée de la même requête, ce qui est ... étrange. Le comportement attendu consiste à extraire tous les enregistrements de la sous-requête dans le bon ordre, en les filtrant dans la requête externe, puis en sélectionnant la même chose que dans l'exemple précédent. Mais ce n'est pas le cas.

Il sélectionne une valeur pour v apparemment aléatoire. La même requête a renvoyé des valeurs différentes pour v si j'ai exécuté plus de temps (~ 20) mais que la distribution n'était pas uniforme.

Si, au lieu d'ajouter un WHERE externe, spécifiez une condition HAVING comme ceci:

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';

Ensuite, vous obtenez à nouveau un comportement cohérent.

CONCLUSION: Je suggérerais de ne pas utiliser cette technique du tout. Si vous voulez vraiment / devez alors éviter les conditions WHERE dans la requête externe. Utilisez-le dans la requête interne si vous le pouvez ou utilisez une clause HAVING dans la requête externe.

Je l'ai testé avec ces données:

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');

dans mysql 5.6.41.

Peut-être qu’il s’agit simplement d’un bogue corrigé / corrigé dans les versions les plus récentes. Merci de nous faire part de vos commentaires si vous avez de l’expérience avec les versions les plus récentes.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top