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.
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)
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
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]
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?
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.