web-dev-qa-db-fra.com

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

J'utilise Postgres, qui impose la contrainte que toutes les colonnes d'un SELECT ... GROUP BY doivent apparaître dans la clause GROUP BY ou être utilisées dans une fonction d'agrégation.

Disons que je modélise les voitures des gens, et je veux déterminer le nom, le numéro de licence et le nombre de voitures d'une personne. Voici mon exemple en tant que SQL Fiddle .

J'aurais le schéma suivant:

CREATE TABLE person(
  id integer PRIMARY KEY,
  name text
);

CREATE TABLE license(
  person_id integer REFERENCES person(id),
  expiry_date date
);

CREATE TABLE car(
  owner_id integer REFERENCES person(id),
  registration_number TEXT
);

Voici la requête:

SELECT person.name, person.id, license.expiry_date, COUNT(car) FROM person
  JOIN license ON license.person_id = person.id
  JOIN car ON car.owner_id = person.id
WHERE person.name = 'Charles Bannerman'
GROUP BY person.id;

Je sais, en raison de ma propre logique métier, que une personne ne peut avoir qu'une seule licence , donc quand je m'inscris à la personne, même si c'est GROUP BY'd, je devrais pouvoir trouver son numéro de licence. Cependant, car il ne fait pas partie de l'instruction GROUP BY et n'utilise pas de fonction d'agrégation, je ne peux pas y accéder.

Comment dois-je écrire cette requête de manière idiomatique. J'ai vu des choses vagues sur les JOINTS LATÉRAUX, les fonctions de fenêtre et les sous-requêtes, mais je ne suis pas sûr de la meilleure façon de répondre à cette question.

6
Migwell

Vous pourriez GROUP BY avant de vous joindre ... Ce qui signifie, inversez votre requête.

Ceci est votre ancienne requête

SELECT person.name, person.id, license.expiry_date, COUNT(car) FROM person
  JOIN license ON license.person_id = person.id
  JOIN car ON car.owner_id = person.id
WHERE person.name = 'Charles Bannerman'
GROUP BY person.id;

Ceci est votre NOUVELLE requête

WITH vals AS (
  SELECT owner_id, COUNT(car) AS cars FROM car GROUP BY owner_id
) SELECT
  person.name, person.id, license.expiry_date, vals.cars
FROM cars
JOIN person ON cars.owner_id = person.id
JOIN license ON person.id = license.person_id
WHERE person.name = 'Charles Bannerman';

Cette solution n'est peut-être pas aussi efficace que vous le souhaitez ... Une autre solution potentielle serait d'écrire une fonction plpgsql pour renvoyer le nombre de voitures avec un id_personne.

La solution que je choisirais dépendrait du cas d'utilisation de la requête. La requête sera-t-elle TOUJOURS utilisée pour renvoyer une seule ligne de données (pour une personne spécifique)? .. Ou sera-t-elle utilisée dans un rapport pour afficher plusieurs lignes ? ..

1
Joishi Bodio

seharusnya seperti ini (censé être comme ça):

SELECT person.name, person.id, license.expiry_date, COUNT(car) FROM person
  JOIN license ON license.person_id = person.id
  JOIN car ON car.owner_id = person.id
WHERE person.name = 'Charles Bannerman'
GROUP BY person.name, person.id, license.expiry_date, car.car;
1
Hamba
SELECT person.name, 
       person.id, 
       license.expiry_date, COUNT(car) OVER (Partition by Person.Id)
FROM person
  JOIN license ON license.person_id = person.id
  JOIN car ON car.owner_id = person.id
WHERE person.name = 'Charles Bannerman';

Vous pouvez utiliser OVER (partion by <grouping column(s)>). Cela agrégera les données sans avoir à utiliser un groupe par.

Le seul inconvénient est que s'il y a Person.id Et license.expiry_date En double, vous obtiendrez 2 résultats. Mais vous pouvez ajouter d'autres données sans qu'elles fassent partie du group by. Juste une pensée.

0
SPC