Comment utilisez-vous les variables de script dans psql?


133

Dans MS SQL Server, je crée mes scripts pour utiliser des variables personnalisables:

DECLARE @somevariable int  
SELECT @somevariable = -1

INSERT INTO foo VALUES ( @somevariable )

Je changerai alors la valeur de @somevariable au moment de l'exécution, en fonction de la valeur que je souhaite dans la situation particulière. Comme il se trouve en haut du script, il est facile à voir et à retenir.

Comment faire de même avec le client PostgreSQL psql?


5
FWIW, l'opérateur \ set semble être lié à l'outil de ligne de commande psql, pas au langage de traitement par lots pgsql. Je peux me tromper.
Daniel Yankowsky

1
Sur quelle version de Postgres êtes-vous?
Kuberchaun

Réponses:


181

Les variables Postgres sont créées via la commande \ set, par exemple ...

\set myvariable value

... et peut ensuite être remplacé, par exemple, par ...

SELECT * FROM :myvariable.table1;

... ou ...

SELECT * FROM table1 WHERE :myvariable IS NULL;

edit: Depuis psql 9.1, les variables peuvent être développées entre guillemets comme dans:

\set myvariable value 

SELECT * FROM table1 WHERE column1 = :'myvariable';

Dans les anciennes versions du client psql:

... Si vous souhaitez utiliser la variable comme valeur dans une requête de chaîne conditionnelle, telle que ...

SELECT * FROM table1 WHERE column1 = ':myvariable';

... alors vous devez inclure les guillemets dans la variable elle-même car ce qui précède ne fonctionnera pas. Définissez plutôt votre variable en tant que telle ...

\set myvariable 'value'

Cependant, si, comme moi, vous rencontriez une situation dans laquelle vous vouliez créer une chaîne à partir d'une variable existante, j'ai trouvé que l'astuce était la suivante ...

\set quoted_myvariable '\'' :myvariable '\''

Vous avez maintenant à la fois une variable entre guillemets et sans guillemets de la même chaîne! Et vous pouvez faire quelque chose comme ça ...

INSERT INTO :myvariable.table1 SELECT * FROM table2 WHERE column1 = :quoted_myvariable;

66
\setest uniquement pour l' psqloutil, vous ne pouvez pas l'utiliser dans les procédures stockées!
sorin le

6
@SorinSbarnea, l'OP a posé des questions sur le script , pas sur la procédure
Daniel Serodio

35
Cette réponse mélange les psqlméta-commandes \setavec les commandes PostgreSQL d'une manière déroutante.
Erwin Brandstetter

20
Depuis postgresql 9.1, dans psql vous pouvez maintenant utiliser: 'variable' pour qu'il soit correctement cité comme valeur pour vous, ou: "variable" pour l'utiliser comme identifiant.
HitScan

9
\set myvariable 'value'ne pas inclure une citation dans la variable, contrairement à ce que dit cette réponse. En cas de doute, utilisez \echo :myvariabledans psql pour afficher la valeur indépendamment de toute requête.
Daniel Vérité

61

Un dernier mot sur les variables PSQL:

  1. Ils ne se développent pas si vous les mettez entre guillemets simples dans l'instruction SQL. Ainsi cela ne fonctionne pas:

    SELECT * FROM foo WHERE bar = ':myvariable'
  2. Pour développer une chaîne littérale dans une instruction SQL, vous devez inclure les guillemets dans le jeu de variables. Cependant, la valeur de la variable doit déjà être placée entre guillemets, ce qui signifie que vous avez besoin d'un deuxième ensemble de guillemets et que l'ensemble interne doit être échappé. Il vous faut donc:

    \set myvariable '\'somestring\''  
    SELECT * FROM foo WHERE bar = :myvariable

    EDIT : à partir de PostgreSQL 9.1, vous pouvez écrire à la place:

    \set myvariable somestring
    SELECT * FROM foo WHERE bar = :'myvariable'

12
À :'myvariable'
bientôt

Exactement ce que je cherchais!
KeniSteward

47

Vous pouvez essayer d'utiliser une clause WITH .

WITH vars AS (SELECT 42 AS answer, 3.14 AS appr_pi)
SELECT t.*, vars.answer, t.radius*vars.appr_pi
FROM table AS t, vars;

Cette méthode est particulièrement pratique lorsque vous utilisez les mêmes valeurs calculées plusieurs fois dans votre requête.
skaurus

2
Contrairement au rapport de Bryce, cela semble bien fonctionner pour moi. CREATE TABLE test (name VARCHAR, age INT); INSERT INTO test (name, age) VALUES ('Jack', 21), ('Jill', 20); WITH vars AS (SELECT N'Jack' AS name, 21 AS age) SELECT test.* FROM test, vars WHERE test.name = vars.name and test.age = vars.age; Ouputs l'âge de Jack et Jack, comme prévu.
Joshua

2
Pour de nombreuses utilisations, en particulier dans le contexte d'un framework d'application Web comme Python Flask, c'est la meilleure solution pour réutiliser des valeurs calculées complexes dans une seule requête.
Sera

1
Quelqu'un peut-il suggérer comment cela pourrait fonctionner dans un insert?
Stoopkid

@Stoopkidcreate table t(x integer); insert into t(x) with sub as (select 999 as num) select num from sub; select * from t;
JL_SO

33

Plus précisément pour psql, vous pouvez également transmettre des psqlvariables à partir de la ligne de commande; vous pouvez les transmettre avec -v. Voici un exemple d'utilisation:

$ psql -v filepath=/path/to/my/directory/mydatafile.data regress
regress=> SELECT :'filepath';
               ?column?                
---------------------------------------
 /path/to/my/directory/mydatafile.data
(1 row)

Notez que les deux points ne sont pas entre guillemets, puis le nom de la variable lui-même est cité. Syntaxe étrange, je sais. Cela ne fonctionne que dans psql; cela ne fonctionnera pas dans (disons) PgAdmin-III.

Cette substitution se produit pendant le traitement d'entrée dans psql, vous ne pouvez donc pas (par exemple) définir une fonction qui utilise :'filepath'et s'attend à ce que la valeur de :'filepath'change d'une session à l'autre. Il sera remplacé une fois, lorsque la fonction sera définie, puis sera une constante après cela. C'est utile pour les scripts mais pas pour l'utilisation à l'exécution.


les variables psql, par exemple: 'filepath', vous avez fait remarquer: "Notez que le signe deux-points n'est pas entre guillemets, alors le nom de la variable lui-même est entre guillemets." Remercier! Tu! J'ai déjà mis un tas de bosses en forme de front dans mon bureau en essayant de faire fonctionner cela, et vous venez de m'en sauver une tonne de plus. Exactement ce dont j'avais besoin pour certains scripts.
Jason

13

FWIW, le vrai problème était que j'avais inclus un point-virgule à la fin de ma commande \ set:

\ set owner_password 'thepassword';

Le point-virgule a été interprété comme un caractère réel dans la variable:

\ echo: mot de passe_propriétaire le mot de passe;

Alors quand j'ai essayé de l'utiliser:

CREATE ROLE monrole LOGIN MOT DE PASSE NON CRYPTÉ: mot de passe_propriétaire NOINHERIT CREATEDB CREATEROLE VALIDE JUSQU'À 'infini';

... j'ai ceci:

CRÉER UN RÔLE myrole LOGIN MOT DE PASSE NON CRYPTÉ thepassword; NOINHERIT CREATEDB CREATEROLE VALID JUSQU'À 'infini';

Cela a non seulement échoué à placer les guillemets autour du littéral, mais a également divisé la commande en 2 parties (dont la seconde était invalide car elle commençait par "NOINHERIT").

La morale de cette histoire: les «variables» PostgreSQL sont en réalité des macros utilisées dans l'expansion de texte, pas de vraies valeurs. Je suis sûr que cela est utile, mais c'est délicat au début.


12

Vous devez utiliser l'un des langages procéduraux tels que PL / pgSQL et non le langage SQL proc. Dans PL / pgSQL, vous pouvez utiliser vars directement dans les instructions SQL. Pour les guillemets simples, vous pouvez utiliser la fonction littérale quote.


5
Cela ne peut pas être fait dans postgres lui-même, mais cela peut être fait dans l'application client PSQL.
Philluminati

1
plpgsql peut (maintenant) être utilisé dans postgres (depuis la version 9.0)) postgresql.org/docs/9.0/static/sql-do.html
Jasen

11

postgres (depuis la version 9.0) autorise les blocs anonymes dans l'un des langages de script côté serveur pris en charge

DO '
DECLARE somevariable int = -1;
BEGIN
INSERT INTO foo VALUES ( somevariable );
END
' ;

http://www.postgresql.org/docs/current/static/sql-do.html

Comme tout est à l'intérieur d'une chaîne, les variables de chaîne externes remplacées devront être échappées et entre guillemets deux fois. L'utilisation de guillemets en dollars à la place n'offrira pas une protection complète contre l'injection SQL.


5

Une autre approche consiste à (ab) utiliser le mécanisme PostgreSQL GUC pour créer des variables. Voir cette réponse précédente pour des détails et des exemples.

Vous déclarez le GUC in postgresql.conf, puis changez sa valeur au moment de l'exécution avec des SETcommandes et obtenez sa valeur avec current_setting(...).

Je ne le recommande pas pour une utilisation générale, mais cela pourrait être utile dans des cas restreints comme 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.


4

Je l'ai résolu avec une table temporaire.

CREATE TEMP TABLE temp_session_variables (
    "sessionSalt" TEXT
);
INSERT INTO temp_session_variables ("sessionSalt") VALUES (current_timestamp || RANDOM()::TEXT);

De cette façon, j'avais une "variable" que je pourrais utiliser sur plusieurs requêtes, qui est unique pour la session. J'en avais besoin pour générer des "noms d'utilisateur" uniques tout en n'ayant toujours pas de collisions lors de l'importation d'utilisateurs avec le même nom d'utilisateur.


Cela semble être la seule méthode de travail dans les outils visuels comme Heidi SQL.
Altair7852

2

J'ai trouvé cette question et les réponses extrêmement utiles, mais aussi déroutantes. J'ai eu beaucoup de mal à faire fonctionner les variables citées, alors voici comment je l'ai fait fonctionner:

\set deployment_user username    -- username
\set deployment_pass '\'string_password\''
ALTER USER :deployment_user WITH PASSWORD :deployment_pass;

De cette façon, vous pouvez définir la variable dans une seule instruction. Lorsque vous l'utilisez, des guillemets simples seront incorporés dans la variable.

REMARQUE! Quand j'ai mis un commentaire après la variable citée, il a été aspiré dans le cadre de la variable lorsque j'ai essayé certaines des méthodes dans d'autres réponses. Cela me dérangeait vraiment pendant un moment. Avec cette méthode, les commentaires semblent être traités comme prévu.


\set deployment_pass 'string_password' ALTER USER :deployment_user WITH PASSWORD :'deployment_pass';
Jasen

\ set n'est pas SQL, c'est une commande intégrée psql Les commentaires sql ne sont pas pris en charge.
Jasen

2

Cette fonctionnalité me manque vraiment. La seule façon d'obtenir quelque chose de similaire est d'utiliser des fonctions.

Je l'ai utilisé de deux manières:

  • fonctions perl utilisant la variable $ _SHARED
  • stocker vos variables dans un tableau

Version Perl:

   CREATE FUNCTION var(name text, val text) RETURNS void AS $$
        $_SHARED{$_[0]} = $_[1];
   $$ LANGUAGE plperl;
   CREATE FUNCTION var(name text) RETURNS text AS $$
        return $_SHARED{$_[0]};
   $$ LANGUAGE plperl;

Version de table:

CREATE TABLE var (
  sess bigint NOT NULL,
  key varchar NOT NULL,
  val varchar,
  CONSTRAINT var_pkey PRIMARY KEY (sess, key)
);
CREATE FUNCTION var(key varchar, val anyelement) RETURNS void AS $$
  DELETE FROM var WHERE sess = pg_backend_pid() AND key = $1;
  INSERT INTO var (sess, key, val) VALUES (sessid(), $1, $2::varchar);
$$ LANGUAGE 'sql';

CREATE FUNCTION var(varname varchar) RETURNS varchar AS $$
  SELECT val FROM var WHERE sess = pg_backend_pid() AND key = $1;
$$ LANGUAGE 'sql';

Remarques:

  • plperlu est plus rapide que perl
  • pg_backend_pid n'est pas la meilleure identification de session, envisagez d'utiliser pid combiné avec backend_start de pg_stat_activity
  • cette version de table est également mauvaise car vous devez effacer cela de temps en temps (et ne pas supprimer les variables de session de travail en cours)

1

Variables dans psql sucer. Si vous voulez déclarer un entier, vous devez entrer l'entier, puis faire un retour chariot, puis terminer l'instruction par un point-virgule. Observer:

Disons que je veux déclarer une variable entière my_varet l'insérer dans une table test:

Exemple de tableau test:

thedatabase=# \d test;
                         Table "public.test"
 Column |  Type   |                     Modifiers                     
--------+---------+---------------------------------------------------
 id     | integer | not null default nextval('test_id_seq'::regclass)
Indexes:
    "test_pkey" PRIMARY KEY, btree (id)

Clairement, rien dans ce tableau pour le moment:

thedatabase=# select * from test;
 id 
----
(0 rows)

Nous déclarons une variable. Remarquez comment le point-virgule est sur la ligne suivante!

thedatabase=# \set my_var 999
thedatabase=# ;

Maintenant, nous pouvons insérer. Nous devons utiliser cette étrange :''syntaxe " ":

thedatabase=# insert into test(id) values (:'my_var');
INSERT 0 1

Ça a marché!

thedatabase=# select * from test;
 id  
-----
 999
(1 row)

Explication:

Alors ... que se passe-t-il si nous n'avons pas le point-virgule sur la ligne suivante? La variable? Regarde:

Nous déclarons my_varsans la nouvelle ligne.

thedatabase=# \set my_var 999;

Sélectionnons my_var.

thedatabase=# select :'my_var';
 ?column? 
----------
 999;
(1 row)

WTF est-ce? Ce n'est pas un entier , c'est une chaîne 999; !

thedatabase=# select 999;
 ?column? 
----------
      999
(1 row)

5
La raison pour laquelle le point-virgule fait des choses inattendues pour vous est qu'un point-virgule termine une instruction SQL, mais vous tapez une commande psql, \ set, qui n'est pas SQL et ne prend PAS de point-virgule de fin. Mettre un point-virgule sur la ligne suivante ne fera pas de mal, mais ne fait absolument rien. C'est une déclaration vide.
volkerk

1

J'ai publié une nouvelle solution pour cela sur un autre fil .

Il utilise une table pour stocker les variables et peut être mis à jour à tout moment. Une fonction getter immuable statique est créée dynamiquement (par une autre fonction), déclenchée par la mise à jour de votre table. Vous obtenez un bon rangement sur la table, ainsi que les vitesses ultra-rapides d'un getter immuable.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.