web-dev-qa-db-fra.com

Transmettre les paramètres "WHERE" à PostgreSQL View?

J'ai une requête assez compliquée sur ma base de données PostgreSQL couvrant 4 tables via une série de sous-requêtes imbriquées. Cependant, malgré l'apparence et la configuration légèrement délicates, il renverra finalement deux colonnes (de la même table, si cela aide la situation) sur la base de la correspondance de deux paramètres externes (deux chaînes doivent correspondre aux champs de différentes tables). La conception de bases de données dans PostgreSQL ™ étant relativement nouvelle pour moi, je sais que cette chose apparemment magique appelée Views existe, et il semble que cela pourrait m'aider ici, mais peut-être pas.

Existe-t-il un moyen de déplacer ma requête complexe dans une vue et de lui transmettre en quelque sorte les deux valeurs nécessaires? Cela simplifierait grandement mon code sur le front-end (en déplaçant les complexités vers la structure de la base de données). Je peux créer une vue qui englobe mon exemple de requête statique et qui fonctionne parfaitement, mais qui ne fonctionne que pour une paire de valeurs de chaîne. Je dois pouvoir l'utiliser avec une variété de valeurs différentes.

Ma question est donc la suivante: est-il possible de passer des paramètres dans une vue par ailleurs statique et de la rendre "dynamique"? Ou peut-être qu'une vue n'est pas la bonne façon de l'aborder. S'il y a autre chose qui fonctionnerait mieux, je suis tout ouïe!

* Edit: * Comme demandé dans les commentaires, voici ma requête telle qu'elle est actuellement:

SELECT   param_label, param_graphics_label
  FROM   parameters
 WHERE   param_id IN 
         (SELECT param_id 
            FROM parameter_links
           WHERE region_id = 
                 (SELECT region_id
                    FROM regions
                   WHERE region_label = '%PARAMETER 1%' AND model_id =
                         (SELECT model_id FROM models WHERE model_label = '%PARAMETER 2%')
                 )
         ) AND active = 'TRUE'
ORDER BY param_graphics_label;

Les paramètres sont définis par les symboles de pourcentage ci-dessus.

36
Devin

Vous pouvez utiliser une fonction de retour définie:

create or replace function label_params(parm1 text, parm2 text)
  returns table (param_label text, param_graphics_label text)
as
$body$
  select ...
  WHERE region_label = $1 
     AND model_id = (SELECT model_id FROM models WHERE model_label = $2)
  ....
$body$
language sql;

Ensuite, vous pouvez faire:

select *
from label_params('foo', 'bar')

Btw: êtes-vous sûr de vouloir: 

AND model_id = (SELECT model_id FROM models WHERE model_label = $2)

si model_label n'est pas unique (ou la clé primaire), une erreur sera éventuellement générée. Vous voulez probablement:

AND model_id IN (SELECT model_id FROM models WHERE model_label = $2)
47

En plus de ce que @a_horse a déjà clarifié, vous pouvez simplifier votre SQL en utilisant la syntaxe JOIN au lieu de sous-requêtes imbriquées. Les performances seront similaires, mais la syntaxe est beaucoup plus courte et facile à gérer.

CREATE OR REPLACE FUNCTION param_labels(_region_label text, _model_label text)
  RETURNS TABLE (param_label text, param_graphics_label text) AS
$func$
    SELECT p.param_label, p.param_graphics_label
    FROM   parameters      p 
    JOIN   parameter_links l USING (param_id)
    JOIN   regions         r USING (region_id)
    JOIN   models          m USING (model_id)
    WHERE  p.active
    AND    r.region_label = $1 
    AND    m.model_label = $2
    ORDER  BY p.param_graphics_label;
$func$ LANGUAGE sql;
  • Si model_label n'est pas unique ou si quelque chose d'autre dans la requête génère des lignes en double, vous pouvez créer ce SELECT DISTINCT p.param_graphics_label, p.param_label - avec une clause ORDER BY correspondante pour des performances optimales. Ou utilisez une clause GROUP BY.

  • Depuis Postgres 9.2, vous pouvez utiliser les noms de paramètres déclarés à la place de $1 et $2 dans les fonctions SQL. (Cela a été possible pour les fonctions PL/pgSQL pendant longtemps).

  • Il faut veiller à éviter les conflits de noms. C'est pourquoi je prends l'habitude de préfixer les noms de paramètre dans la déclaration (ceux-ci sont visibles presque partout dans la fonction) et de qualifier les noms de colonne dans le corps.

  • J'ai simplifié WHERE p.active = 'TRUE' à WHERE p.active, car la colonne active devrait très probablement être de type boolean, pas text.

  • USING ne fonctionne que si les noms de colonne ne sont pas ambigus dans toutes les tables situées à gauche de JOIN. Sinon, vous devez utiliser la syntaxe plus explicite:
    ON l.param_id = p.param_id

25
Erwin Brandstetter

Dans la plupart des cas, la fonction retour-ensemble est la voie à suivre, mais dans le cas où vous souhaitez lire et écrire dans l'ensemble, une vue peut être plus appropriée. Et il est possible pour une vue de lire un paramètre de session:

CREATE VIEW widget_sb AS SELECT * FROM widget WHERE column = cast(current_setting('mydomain.myparam') as int)

SET mydomain.myparam = 0
select * from widget_sb
[results]

SET mydomain.myparam = 1
select * from widget_sb
[distinct results]
20
Drew

Je ne pense pas qu'une vue "dynamique" comme vous l'avez dit soit possible.

Pourquoi ne pas écrire une procédure stockée qui prend 2 arguments à la place?

2
Jin Kim

Je reformulerais la requête comme suit:

SELECT   p.param_label, p.param_graphics_label
  FROM   parameters p
where exists (
    select 1
    from parameter_links pl
    where pl.parameter_id = p.id
    and exists (select 1 from regions r where r.region_id = pl.region_id
) and p.active = 'TRUE'
order by p.param_graphics_label;

En supposant que vous ayez des index sur les différentes colonnes id, cette requête devrait être beaucoup plus rapide que d'utiliser l'opérateur IN; les paramètres existants ici n’utiliseront que les valeurs d’index sans même toucher à la table de données, sauf pour obtenir les données finales de la table de paramètres.

0
Monty