web-dev-qa-db-fra.com

Erreur «la colonne n'existe pas» dans une requête SELECT avec requête JOIN et GROUP BY

J'utilise PostgreSQL 9.1 avec une application Ruby on Rails.

J'essaie de lister la dernière version de chaque "charge" (dans ma table d'historique: hist_version_charges) appartenant au même identifiant de projet (proj_sous_projet_id = 2).

Cela me fait utiliser la fonction d'agrégation max () et appliquer le résultat à une fonction JOIN sur la même table que PostgreSQL n'autorise pas à utiliser les colonnes de la clause SELECT si elles n'apparaissent pas dans la clause GROUP BY, ALTHOUGH utilisant un max () signifie évidemment que je suis intéressé par la ligne contenant les valeurs maximales!

Voici ma requête:

SELECT h_v_charges.*, 
       max(last_v.version) as lv 
FROM hist_versions_charges h_v_charges 
    JOIN hist_versions_charges last_v 
      ON h_v_charges.version = lv 
    AND h_v_charges.proj_charge_id = last_v.proj_charge_id 
GROUP BY last_v.proj_sous_projet_id, 
         last_v.proj_charge_id 
HAVING last_v.proj_sous_projet_id = 2 
ORDER BY h_v_charges.proj_charge_id ASC;

Le message d'erreur que j'ai reçu:

ERROR:  column "lv" does not exist
LINE 1: ..._versions_charges last_v ON h_v_charges.version = lv AND h_v...
                                                             ^
********** Error **********

ERROR: column "lv" does not exist
SQL state: 42703
Character: 147

J'ai également essayé avec "last_v.lv" mais l'erreur reste la même.

Si quelqu'un a une idée de ce qui ne va pas, elle est plus que bienvenue.

=== MISE À JOUR ===

Selon * a_horse_with_no_name * et les réponses de Colin 't Hart , je me suis finalement retrouvé avec la requête suivante:

SELECT *
FROM (
    SELECT *, max(version) OVER (PARTITION BY proj_charge_id) AS lv
    FROM hist_versions_charges
    WHERE proj_sous_projet_id = 2) AS hv
WHERE hv.lv = hv.version
ORDER BY hv.proj_charge_id ASC;

C'est un peu plus rapide avec un seul ORDER BY.

J'ai également essayé la requête avec une clause WITH. Bien que "plus agréable", il crée des frais de traitement supplémentaires. Comme je sais que je ne réutiliserai pas à l'avenir la sous-requête deux fois ou plus dans la même requête principale, je suis d'accord pour utiliser une simple sous-requête.

Merci quand même à * a_horse_with_no_name * et Colin 't Hart . J'ai appris beaucoup de choses!

7
Douglas

Vous voulez probablement quelque chose comme ça:

SELECT h_v_charges.*, 
       last_v.last_version
FROM hist_versions_charges h_v_charges 
  JOIN (select proj_charge_id, 
               max(version) as last_version
        from hist_versions_charges 
        where proj_sous_projet_id = 2  
        group by proj_charge_id
  ) last_v  
  ON h_v_charges.version = last_v.last_version
 AND h_v_charges.proj_charge_id = last_v.proj_charge_id 
ORDER BY h_v_charges.proj_charge_id ASC;

Une solution éventuellement (car aucune jointure n'est requise) plus rapide serait:

select *
from (
   select hvc.*, 
          row_number() over (partition by proj_charge_id order by version desc) as rn
   from hist_versions_charges as hvc
   where proj_sous_projet_id = 2  
) as hv
where rn = 1
order by hv.proj_charge_id ASC;

Comme l'a souligné Colin, cela peut également s'écrire:

with hv as (
  select hvc.*, 
         row_number() over (partition by proj_charge_id order by version desc) as rn
  from hist_versions_charges as hvc
  where proj_sous_projet_id = 2  
) 
select *
from hv
where rn = 1
order by hv.proj_charge_id ASC;
9