web-dev-qa-db-fra.com

Stocker la requête courante sous forme de colonne?

En utilisant PostgreSQL, j'ai un certain nombre de requêtes qui ressemblent à ceci:

SELECT <col 1>, <col 2>
     , (SELECT sum(<col x>)
        FROM   <otherTable> 
        WHERE  <other table foreignkeyCol>=<this table keycol>) AS <col 3>
FROM   <tbl>

Étant donné que la sous-sélection sera identique dans tous les cas, existe-t-il un moyen de stocker cette sous-sélection en tant que pseudo-colonne dans le tableau? Essentiellement, je veux pouvoir sélectionner une colonne du tableau A qui est la somme d'une colonne spécifique du tableau B où les enregistrements sont liés. Est-ce possible?

37
ibrewster

Existe-t-il un moyen de stocker cette sous-sélection en tant que pseudo-colonne dans le tableau?

Un VIEW comme il a été conseillé est une solution parfaitement valable. Mais il existe un autre moyen qui correspond encore plus à votre question. Vous pouvez écrire une fonction qui prend le type de table comme paramètre pour émuler a "champ calculé" ou "colonne générée" .

Considérez ce cas de test, dérivé de votre description:

CREATE TABLE tbl_a (a_id int, col1 int, col2 int);
INSERT INTO tbl_a VALUES (1,1,1), (2,2,2), (3,3,3), (4,4,4);

CREATE TABLE tbl_b (b_id int, a_id int, colx int);
INSERT INTO tbl_b VALUES
 (1,1,5),  (2,1,5),  (3,1,1)
,(4,2,8),  (5,2,8),  (6,2,6)
,(7,3,11), (8,3,11), (9,3,11);

Créer une fonction qui émule col3:

CREATE FUNCTION col3(tbl_a)
  RETURNS int8 AS
$func$
    SELECT sum(colx)
    FROM   tbl_b b
    WHERE  b.a_id = $1.a_id
$func$ LANGUAGE SQL STABLE;

Vous pouvez maintenant interroger:

SELECT a_id, col1, col2, tbl_a.col3
FROM   tbl_a;

Ou même:

SELECT *, a.col3 FROM tbl_a a;

Notez comment j'ai écrit tbl_a.col3/a.col3, Pas seulement col3. C'est essentiel.

Contrairement à un "colonne virtuelle" dans Oracle c'est pas inclus automatiquement dans un SELECT * FROM tbl_a. Vous pouvez utiliser un VIEW pour cela.

Pourquoi ça marche?

La manière courante de référencer une colonne de table est avec la notation d'attribut :

SÉLECTIONNER tbl_a.col1 FROM tbl_a;

La façon courante d'appeler une fonction est avec la notation fonctionnelle :

SÉLECTIONNER col3 (tbl_a);

En règle générale, il est préférable de s'en tenir à ces méthodes canoniques , qui sont conformes à la norme SQL.

Mais dans PostgreSQL, la notation fonctionnelle et la notation d'attribut sont équivalentes. Donc ça marche aussi:

SÉLECTIONNER col1 (tbl_a) FROM tbl_a; 
 SELECT tbl_a.col3;

Plus à ce sujet dans le manuel.
Vous voyez probablement maintenant où cela va. Ceci semble comme si vous ajoutiez une colonne supplémentaire de la table tbl_a Tandis que col3() est en fait une fonction qui prend la ligne actuelle de tbl_a (ou son alias) comme argument de type ligne et calcule une valeur.

SELECT *, a.col3
FROM   tbl_a AS a;

S'il existe une colonne réelle col3, Elle est prioritaire et le système ne recherche pas une fonction de ce nom en prenant la ligne tbl_a Comme paramètre.

La beauté de celui-ci: vous pouvez ajouter ou supprimer des colonnes de tbl_a Et la dernière requête retournera dynamiquement toutes les colonnes actuelles, où une vue ne retournerait que les colonnes qui existaient au moment de la création (liaison anticipée vs liaison tardive de *).
Bien sûr, vous devez supprimer la fonction dépendante avant de pouvoir supprimer la table maintenant. Et vous devez faire attention à ne pas invalider la fonction lorsque vous apportez des modifications à la table.

73
Erwin Brandstetter

Il y a trois réponses à ce jour, qui fonctionnent toutes. N'importe lequel d'entre eux pourrait être une "meilleure solution" selon les circonstances. Avec de petites tables, les performances devraient être assez proches, mais aucune d'entre elles n'est susceptible de bien évoluer vers des tables avec des millions de lignes. Le moyen le plus rapide d'obtenir les résultats souhaités avec un grand ensemble de données serait probablement (en utilisant la configuration d'Erwin):

SELECT a_id, col1, col2, sum(colx)
FROM tbl_a LEFT JOIN tbl_b b using(a_id)
GROUP BY a_id, col1, col2;

Si a_id est déclaré comme clé primaire, et ceci est exécuté sous 9.1 ou version ultérieure, le GROUP BY la clause peut être simplifiée car col1 et col2 sont fonctionnellement dépendants sur a_id.

SELECT a_id, col1, col2, sum(colx)
FROM tbl_a LEFT JOIN tbl_b b using(a_id)
GROUP BY a_id;

La vue pourrait être définie de cette façon et elle évoluerait, mais je ne pense pas pensez que tous les mêmes chemins d'exécution seront pris en compte pour les approches utilisant des fonctions, donc le chemin d'exécution le plus rapide pourrait ne pas être utilisé.

2
kgrittn

Apparemment, cela est géré avec des vues, selon le commentaire du lion. Donc dans mon cas, j'ai utilisé la commande:

CREATE VIEW <viewname> AS
SELECT *, (SELECT sum(<col x>)
FROM   <otherTable
WHERE  <otherTable foreignkeyCol>=<thisTable keycol>) AS <col 3>
FROM   <tablename>

ce qui me donne essentiellement un autre tableau comprenant la colonne souhaitée.

2
ibrewster

Outre une vue, vous pouvez créer une fonction pour la somme.

CREATE FUNCTION sum_other_table( key type_of_key ) RETURNS bigint
AS $$ SELECT sum( col_x ) FROM table_1 where table_1.key = key $$ LANGUAGE SQL;

puis utilisez-le comme agrégateur:

SELECT col_1, col_2, sum_other_table( key ) AS col_3
FROM table_2 WHERE table_2.key = key;

Notez que le type de retour de sum_other_table () dépend du type de la colonne que vous résumez.

2
Johann Oskarsson