web-dev-qa-db-fra.com

Est-il possible de définir une constante nommée dans une requête PostgreSQL?

Est-il possible de définir une constante nommée dans une requête PostgreSQL? Par exemple:

MY_ID = 5;
SELECT * FROM users WHERE id = MY_ID;
37
Ajedi32

Cette question a déjà été posée ( Comment utilisez-vous les variables de script dans PostgreSQL? ). Cependant, il y a une astuce que j'utilise parfois pour les requêtes:

with const as (
    select 1 as val
)
select . . .
from const cross join
     <more tables>

C’est-à-dire que je définis un CTE appelé const dans lequel les constantes sont définies. Je peux ensuite joindre cela à ma requête, autant de fois que nécessaire, à n'importe quel niveau. J'ai trouvé cela particulièrement utile lorsque je traite des dates et que je dois gérer les constantes de date dans de nombreuses sous-requêtes.

37
Gordon Linoff

PostgreSQL n'a pas de moyen intégré pour définir des variables (globales) comme MySQL ou Oracle. (Il existe une solution de contournement limitée utilisant "options personnalisées" ). Selon ce que vous voulez exactement, il y a d'autres moyens:

Pour la requête one

Vous pouvez fournir des valeurs en haut d'une requête dans un CTE comme @Gordon déjà fourni .

Globale, constante persistante:

Vous pouvez créer une simple fonction IMMUTABLE pour cela:

CREATE FUNCTION public.f_myid()
  RETURNS int IMMUTABLE LANGUAGE SQL AS
'SELECT 5';

Il doit vivre dans un schéma qui est visible pour l'utilisateur actuel, c'est-à-dire dans le search_path correspondant. Comme le schéma public, par défaut. Si la sécurité est un problème, assurez-vous que c'est le premier schéma dans search_path ou qualifiez-le dans votre appel:

SELECT public.f_myid();

Visible pour tous les utilisateurs de la base de données (autorisés à accéder au schéma public).

Plusieurs valeurs pour session en cours:

CREATE TEMP TABLE val (val_id int PRIMARY KEY, val text);
INSERT INTO val(val_id, val) VALUES
  (  1, 'foo')
, (  2, 'bar')
, (317, 'baz');

CREATE FUNCTION f_val(_id int)
  RETURNS text STABLE LANGUAGE SQL AS
'SELECT val FROM val WHERE val_id = $1';

SELECT f_val(2);  -- returns 'baz'

Puisque plpgsql vérifie l'existence d'une table lors de la création, vous devez créer une table (temporaire) val avant de pouvoir créer la fonction, même si une table temporaire est supprimée à la fin de la session tant que cette fonction est conservée. La fonction lève une exception si la table sous-jacente n’est pas trouvée au moment de l’appel.

Le schéma actuel pour les objets temporaires précède le reste de votre search_path par défaut - à moins d'indication contraire explicite. Vous ne pouvez pas exclure le schéma temporaire du search_path, mais vous pouvez d'abord placer d'autres schémas.
Les créatures maléfiques de la nuit (avec les privilèges nécessaires) pourraient bricoler le search_path et placer un autre objet du même nom devant:

CREATE TABLE myschema.val (val_id int PRIMARY KEY, val text);
INSERT INTO val(val_id, val) VALUES (2, 'wrong');

SET search_path = myschema, pg_temp;

SELECT f_val(2);  -- returns 'wrong'

Ce n'est pas une menace, car seuls les utilisateurs privilégiés peuvent modifier les paramètres globaux. Les autres utilisateurs ne peuvent le faire que pour leur propre session. Pour éviter que cela ne se produise du tout, définissez le search_path pour votre fonction et qualifiez-le par le schéma dans l'appel:

CREATE FUNCTION f_val(_id int)
  RETURNS text STABLE LANGUAGE SQL AS
'SELECT val FROM val WHERE val_id = $1'
SET search_path = pg_temp;

Ou utilisez plutôt:

... SET search_path = pg_temp, param;

Cela vous permettrait (ou à toute personne disposant des privilèges nécessaires) de fournir des valeurs par défaut globales (persistantes) dans une table param.val ...

Examinez le chapitre correspondant du manuel sur création de fonctions avec SECURITY DEFINER .

Cependant, cette fonction super sécurisée ne peut pas être "intégrée" et peut exécuter plus lentement qu'une alternative plus simple avec un schéma câblé:

CREATE FUNCTION f_val(_id int)
  RETURNS text STABLE LANGUAGE SQL AS
'SELECT val FROM pg_temp.val WHERE val_id = $1';

Réponses associées avec plus d'options:

36

Outre les options judicieuses déjà mentionnées par Gordon et Erwin (tables temporaires, fonctions à renvoi constant, CTE, etc.), vous pouvez également utiliser le mécanisme GUC de PostgreSQL pour créer des variables de niveau global, de session et de transaction.

Voir cet article précédent qui présente l'approche en détail. 

Je ne le recommande pas pour une utilisation générale, mais cela pourrait être utile dans des cas aussi restreints que celui mentionné dans la question liée, où l'affiche voulait un moyen de fournir le nom d'utilisateur au niveau de l'application aux déclencheurs et aux fonctions.

5
Craig Ringer

J'ai trouvé cette solution:

with vars as (
    SELECT * FROM (values(5)) as t(MY_ID)
)
SELECT * FROM users WHERE id = (SELECT MY_ID FROM vars)
2

J'ai trouvé un mélange des approches disponibles pour être le meilleur:

  • Stockez vos variables dans un tableau:
CREATE TABLE vars (
  id INT NOT NULL PRIMARY KEY DEFAULT 1,
  zipcode INT NOT NULL DEFAULT 90210,
  -- etc..
  CHECK (id = 1)
);
  • Créez une fonction dynamique, qui charge le contenu de votre table et l'utilise pour:
    • Re/Créer une autre fonction getter immuable statique distincte.
CREATE FUNCTION generate_var_getter()
RETURNS VOID AS $$
DECLARE
  var_name TEXT;
  var_value TEXT;
  new_rows TEXT[];
  new_sql TEXT;
BEGIN
  FOR var_name IN (
    SELECT columns.column_name
    FROM information_schema.columns
    WHERE columns.table_schema = 'public'
      AND columns.table_name = 'vars'
    ORDER BY columns.ordinal_position ASC
  ) LOOP
    EXECUTE
      FORMAT('SELECT %I FROM vars LIMIT 1', var_name)
      INTO var_value;

    new_rows := ARRAY_APPEND(
      new_rows,
      FORMAT('(''%s'', %s)', var_name, var_value)
    );
  END LOOP;

  new_sql := FORMAT($sql$
    CREATE OR REPLACE FUNCTION var_get(key_in TEXT)
    RETURNS TEXT AS $config$
    DECLARE
      result NUMERIC;
    BEGIN
      result := (
        SELECT value FROM (VALUES %s)
        AS vars_tmp (key, value)
        WHERE key = key_in
      );
      RETURN result;
    END;
    $config$ LANGUAGE plpgsql IMMUTABLE;
  $sql$, ARRAY_TO_STRING(new_rows, ','));

  EXECUTE new_sql;
  RETURN;
END;
$$ LANGUAGE plpgsql;
  • Ajoutez un déclencheur de mise à jour à votre table afin que, après avoir modifié l'une de vos variables, generate_var_getter() soit appelé et que la fonction immuable var_get() soit recréée.
CREATE FUNCTION vars_regenerate_update()
RETURNS TRIGGER AS $$
BEGIN
  PERFORM generate_var_getter();
  RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trigger_vars_regenerate_change
  AFTER INSERT OR UPDATE ON vars
  EXECUTE FUNCTION vars_regenerate_update();

Désormais, vous pouvez facilement conserver vos variables dans un tableau, mais également obtenir un accès immuable à toute vitesse. Le meilleur des deux mondes:

INSERT INTO vars DEFAULT VALUES;
-- INSERT 0 1

SELECT var_get('zipcode')::INT; 
-- 90210

UPDATE vars SET zipcode = 84111;
-- UPDATE 1

SELECT var_get('zipcode')::INT;
-- 84111
0
Brev