web-dev-qa-db-fra.com

PostgreSQL GROUP BY différent de MySQL?

J'ai migré certaines de mes requêtes MySQL vers PostgreSQL pour utiliser Heroku. La plupart de mes requêtes fonctionnent correctement, mais je continue d'avoir une erreur récurrente similaire lorsque j'utilise group by:

ERREUR: la colonne "XYZ" doit apparaître dans la clause GROUP BY ou être utilisée dans une fonction d'agrégation

Quelqu'un pourrait-il me dire ce que je fais mal?


MySQL qui fonctionne à 100%:

SELECT `availables`.*
FROM `availables`
INNER JOIN `rooms` ON `rooms`.id = `availables`.room_id
WHERE (rooms.hotel_id = 5056 AND availables.bookdate BETWEEN '2009-11-22' AND '2009-11-24')
GROUP BY availables.bookdate
ORDER BY availables.updated_at


Erreur PostgreSQL:

ActiveRecord :: StatementInvalid: PGError: ERROR: la colonne "availables.id" doit apparaître dans la clause GROUP BY ou être utilisée dans une fonction d'agrégation:
. 21 'ET E'2009-10-23') GROUPE PAR availables.bookdate COMMANDEZ par availables.updated_at


Code Ruby générant le SQL:

expiration = Available.find(:all,
    :joins => [ :room ],
    :conditions => [ "rooms.hotel_id = ? AND availables.bookdate BETWEEN ? AND ?", hostel_id, date.to_s, (date+days-1).to_s ],
    :group => 'availables.bookdate',
    :order => 'availables.updated_at')  


Sortie attendue (à partir d'une requête MySQL fonctionnelle):

 + ----- + ------- + ------- + ------------ + --------- + --------------- + --------------- + 
 | id | prix | taches | bookdate | room_id | created_at | updated_at | 
 + ----- + ------- + ------- + ------------ + ------- - + --------------- + --------------- + 
 | 414 | 38,0 | 1 | 2009-11-22 | 1762 | 2009-11-20 ... | 2009-11-20 ... | 
 | 415 | 38,0 | 1 | 2009-11-23 | 1762 | 2009-11-20 ... | 2009-11-20 ... | 
 | 416 | 38,0 | 2 | 2009-11-24 | 1762 | 2009-11-20 ... | 2009-11-20 ... | 
 + ----- + ------- + ------- + ------------ + --------- + --------------- + --------------- + 
 3 lignes dans ensemble
69
holden

Totalement non conforme aux normes de MySQL GROUP BY peut être émulé par Postgres 'DISTINCT ON. Considère ceci:

MySQL:

SELECT a,b,c,d,e FROM table GROUP BY a

Cela fournit 1 ligne par valeur de a (laquelle, vous ne savez pas vraiment). Eh bien, en fait, vous pouvez le deviner, car MySQL ne connaît pas les agrégats de hachage, il utilisera donc probablement un tri ... mais il ne triera que sur a, donc l'ordre des lignes pourrait être aléatoire. À moins qu'il n'utilise un index multicolonne au lieu du tri. De toute façon, ce n'est pas spécifié par la requête.

Postgres:

SELECT DISTINCT ON (a) a,b,c,d,e FROM table ORDER BY a,b,c

Cela fournit 1 ligne par valeur de a, cette ligne sera la première du tri selon le ORDER BY spécifié par la requête. Facile.

Notez qu'ici, ce n'est pas un agrégat que je calcule. Donc GROUP BY n'a en fait aucun sens. DISTINCT ON est beaucoup plus logique.

Rails est marié à MySQL, donc je ne suis pas surpris qu'il génère du SQL qui ne fonctionne pas dans Postgres.

107
peufeu

PostgreSQL est plus conforme à SQL que MySQL. Tous les champs - à l'exception du champ calculé avec fonction d'agrégation - dans la sortie doivent être présents dans la clause GROUP BY.

16
Erlock

GROUP BY de MySQL peut être utilisé sans fonction d'agrégation (ce qui est contraire à la norme SQL) et renvoie la première ligne du groupe (je ne sais pas en fonction de quels critères), tandis que PostgreSQL doit avoir une fonction d'agrégation (MAX, SUM, etc.) sur la colonne sur laquelle la clause GROUP BY est émise.

8
Bozho

Correct, la solution pour résoudre ce problème est d'utiliser: sélectionnez et sélectionnez chaque champ avec lequel vous souhaitez décorer l'objet résultant et regroupez-les.

Nasty - mais c'est la façon dont group by should fonctionne par opposition à la façon dont MySQL fonctionne avec lui en devinant ce que vous voulez dire si vous ne collez pas les champs de votre groupe by.

4
Omar Qureshi

Si je me souviens bien, dans PostgreSQL, vous devez ajouter chaque colonne que vous récupérez de la table où la clause GROUP BY s'applique to la clause GROUP BY.

3
Franz

Ce n'est pas la plus jolie solution, mais changer le paramètre de groupe pour afficher chaque colonne dans le modèle fonctionne dans PostgreSQL:

expiration = Available.find(:all,
:joins => [ :room ],
:conditions => [ "rooms.hotel_id = ? AND availables.bookdate BETWEEN ? AND ?", hostel_id, date.to_s, (date+days-1).to_s ],
:group => Available.column_names.collect{|col| "availables.#{col}"},
:order => 'availables.updated_at')
2
Ilia

Pour ceux qui recherchent un moyen de commander par n'importe quel champ, y compris le champ joint, dans postgresql, utilisez une sous-requête:

SELECT * FROM(
SELECT DISTINCT ON(availables.bookdate) `availables`.* 
FROM `availables` INNER JOIN `rooms` ON `rooms`.id = `availables`.room_id 
WHERE (rooms.hotel_id = 5056 
AND availables.bookdate BETWEEN '2009-11-22' AND '2009-11-24')
) AS distinct_selected
ORDER BY availables.updated_at

or arel:

subquery = SomeRecord.select("distinct on(xx.id) xx.*, jointable.order_field")
      .where("").joins(")
result = SomeRecord.select("*").from("(#{subquery.to_sql}) AS distinct_selected").order(" xx.order_field ASC, jointable.order_field ASC")
1
riley

Selon le "Debuking GROUP BY Myths" de MySQL http://dev.mysql.com/tech-resources/articles/debunking-group-by-myths.html . SQL (version 2003 de la norme) n'exige pas que les colonnes référencées dans la liste SELECT d'une requête apparaissent également dans la clause GROUP BY.

1
Leonel Galán

Je pense que .uniq [1] résoudra votre problème.

[1] Available.select('...').uniq

Jetez un œil à http://guides.rubyonrails.org/active_record_querying.html#selecting-specific-fields

0
Lucas D'Avila