web-dev-qa-db-fra.com

Conversion de requêtes SELECT DISTINCT ON de Postgresql à MySQL

J'utilise PostgreSQL et migre maintenant vers MySQL.

Dans mes requêtes, j'utilise PostgreSQL's SELECT DISTINCT ON (col1, col2, col3), je me demandais s'il y avait une contrepartie de cette déclaration dans MySQL.

28

Il n'y a pas d'équivalent exact pour convertir une requête Postgresql qui utilise SELECT DISTINCT ON en MySQL.

Postgresql SELECT DISTINCT ON

Dans Postgresql, la requête suivante éliminera toutes les lignes où les expressions (col1, col2, col3) correspond, et il ne conservera que la "première ligne col4, col5" pour chaque ensemble de lignes correspondantes:

SELECT DISTINCT ON (col1, col2, col3) col4, col5
FROM tablename

Donc, si votre table est comme ça:

col1 | col2 | col3 | col4 | col5
--------------------------------
1    | 2    | 3    | 777  | 888
1    | 2    | 3    | 888  | 999
3    | 3    | 3    | 555  | 555

notre requête gardera une seule ligne pour (1,2,3) et une ligne pour (3,3,3). Les lignes résultantes seront alors:

col4 | col5
-----------
777  | 888
555  | 555

veuillez noter que la "première ligne" de chaque ensemble est imprévisible, notre première ligne peut également être (888, 999), sauf si nous spécifions un ORDER BY:

SELECT DISTINCT ON (col1, col2, col3) col4, col5
FROM tablename
ORDER BY col1, col2, col3, col4

(les expressions DISTINCT on doivent correspondre aux expressions ORDER BY les plus à gauche, mais ORDER BY peut contenir des expressions supplémentaires).

Extension MySQL vers GROUP BY

MySQL étend l'utilisation de GROUP BY afin que nous puissions sélectionner des colonnes non agrégées non nommées dans la clause GROUP BY. Chaque fois que nous sélectionnons des colonnes non agrégées, le serveur est libre de choisir n'importe quelle valeur de chaque groupe de cette colonne, de sorte que les valeurs résultantes seront indéterminées.

Donc, cette requête Postgresql:

SELECT DISTINCT ON (col1, col2, col3) col4, col5
FROM tablename

peut être considéré comme équivalent à cette requête MySQL:

SELECT col4, col5
FROM tablename
GROUP BY col1, col2, col3

postgresql et MySQL renverront la "première ligne" pour chacun (col1, col2, col3), et dans les deux cas, la ligne retournée est imprévisible car nous n'avons pas spécifié et ordonné par clause.

Beaucoup de gens seraient très tentés de convertir cette requête Postgresql avec un ORDER BY:

SELECT DISTINCT ON (col1, col2, col3) col4, col5
FROM tablename
ORDER BY col1, col2, col3, col4

avec celui-ci:

SELECT col4, col5
FROM (
  SELECT col1, col2, col3, col4, col5
  FROM tablename
  ORDER BY col1, col2, col3, col4
) s
GROUP BY col1, col2, col3

l'idée ici est d'appliquer un ORDER BY à une sous-requête afin que lorsque MySQL regroupe par col1, col2, col3, il conserve la première valeur rencontrée pour col4 et col5. L'idée est bonne, mais c'est faux! MySQL est libre de choisir n'importe quelle valeur pour col4 et col5, et nous ne savons pas quelles sont les premières valeurs rencontré, cela dépend de l'optimiseur. Je le corrigerais donc:

SELECT t1.col4, t1.col5
FROM tablename t1 INNER JOIN (SELECT col1, col2, col3, MIN(col4) as m_col4
                              FROM tablename
                              GROUP BY col1, col2, col3) s
     ON t1.col1=s.col1
        AND t1.col2=s.col2
        AND t1.col3=s.col3
        AND t1.col4=s.m_col4
GROUP BY
  t1.col1, t1.col2, t1.col3, t1.col4

mais cela commence à devenir plus compliqué.

Conclusion

En règle générale, il n'y a pas de moyen exact de convertir une requête Postgresql en une requête MySQL, mais il existe de nombreuses solutions de contournement, la requête résultante peut être aussi simple que celle d'origine ou elle peut devenir très compliquée, mais cela dépend de la requête elle-même.

37
fthiella

Utilisez une sous-requête pour déterminer l'ordre et une requête externe pour les regrouper.

Comme le souligne @a_horse_with_no_name, cela fonctionne parce que MySQL autorise group by, contrairement aux autres SGBD.

Par exemple:

CREATE TABLE customer_order
    (`customer` varchar(5), `item` varchar(6), `date` datetime)
;

INSERT INTO customer_order
    (`customer`, `item`, `date`)
VALUES
    ('alice', 'widget', '2000-01-05 00:00:00'),
    ('bob', 'widget', '2000-01-02 00:00:00'),
    ('alice', 'widget', '2000-01-01 00:00:00'),
    ('alice', 'wodget', '2000-01-06 00:00:00')
;

Requête pour la première commande de chaque client:

select *
from
  (select customer, item, date
  from customer_order
  order by date) c
group by customer

Résultat:

| CUSTOMER |   ITEM |                           DATE |
|----------|--------|--------------------------------|
|    alice | widget | January, 01 2000 00:00:00+0000 |
|      bob | widget | January, 02 2000 00:00:00+0000 |

http://sqlfiddle.com/#!2/6cbbe/1

0
Alex Wittig