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
Totalement non conforme aux normes de MySQL GROUP BY
peut être émulé par Postgres 'DISTINCT ON
. Considère ceci:
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.
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.
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.
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.
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.
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.
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')
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")
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.
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