<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://wiki.postgresql.org/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Okbobcz</id>
	<title>PostgreSQL wiki - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.postgresql.org/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Okbobcz"/>
	<link rel="alternate" type="text/html" href="https://wiki.postgresql.org/wiki/Special:Contributions/Okbobcz"/>
	<updated>2026-06-09T20:03:46Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.39.17</generator>
	<entry>
		<id>https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=42499</id>
		<title>Implementation of declarative catalog session variables</title>
		<link rel="alternate" type="text/html" href="https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=42499"/>
		<updated>2026-01-01T07:50:21Z</updated>

		<summary type="html">&lt;p&gt;Okbobcz: /* Introduction */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Session variables are database objects that can hold a value across multiple queries inside a session. The design of session variables varies widely, and many databases implement more than one type of session variable.&lt;br /&gt;
&lt;br /&gt;
The SQL/PSM standard introduces modules and module-level variables. However, this part of the standard has not been implemented in any widely used product. As a result, each database system has developed its own proprietary design, syntax, and feature set.&lt;br /&gt;
&lt;br /&gt;
Session variables are often part of the stored procedure environment, but there are exceptions --- for example, MySQL session variables or client-side session variables in tools like psql. In most systems, session variables do not participate in transactions: once set, their value persists until explicitly changed, even if the surrounding transaction is rolled back. Conceptually, they behave much like variables in general-purpose programming languages.&lt;br /&gt;
&lt;br /&gt;
Because there is no common design, comparisons across systems are difficult. Each implementation has its own advantages and disadvantages, and in some cases a single system supports more than one kind of session variable.&lt;br /&gt;
&lt;br /&gt;
Kinds of session variables:&lt;br /&gt;
* client side (psql, pgadmin, sql plus, ...) - mostly similar to shell variables (based on string operations)&lt;br /&gt;
* server side&lt;br /&gt;
** declarative&lt;br /&gt;
*** created only for batch (T-SQL)&lt;br /&gt;
*** created as an database object (or part of database object) (Oracle, DB2)&lt;br /&gt;
** created by usage (MySQL)&lt;br /&gt;
** with priprietary syntax for identifiers (MySQL, T-SQL)&lt;br /&gt;
** with ANSI SQL syntax for identifiers (Oracle, DB2)&lt;br /&gt;
** Inside only PL (Oracle)&lt;br /&gt;
** Inside db engine (MySQL, T-SQL, DB2)&lt;br /&gt;
&lt;br /&gt;
Another classification:&lt;br /&gt;
* external (are not part of SQL)&lt;br /&gt;
** server side (Oracle package variables - part of PL for stored procedures (PL/SQL))&lt;br /&gt;
** client side (psql, sql plus)&lt;br /&gt;
* native (are part of SQL)&lt;br /&gt;
** persistent (DB2)&lt;br /&gt;
** declarative (T-SQL)&lt;br /&gt;
** implicit (MySQL)&lt;br /&gt;
&lt;br /&gt;
Possible uses of session variables include:&lt;br /&gt;
&lt;br /&gt;
* Acting as global variables in PL code (for tracing, debugging, or quick hacks),&lt;br /&gt;
* Storing configuration values for PL routines,&lt;br /&gt;
* Keeping frequently used data handy in an interactive session,&lt;br /&gt;
* Storing credentials for Row-Level Security (RLS) and other data-protection mechanisms,&lt;br /&gt;
* Assisting with migrations from other systems (especially Oracle),&lt;br /&gt;
* Enabling parameterization of anonymous code blocks (specific to PostgreSQL).&lt;br /&gt;
* Session variables are writable even on read-only hot standby servers in PostgreSQL.&lt;br /&gt;
&lt;br /&gt;
They behave somewhat like temporary tables, but there are important differences:&lt;br /&gt;
&lt;br /&gt;
* Variables hold single values, while tables hold sets of rows.&lt;br /&gt;
* The syntax for using variables is shorter and more convenient for interactive work.&lt;br /&gt;
* Access (read/write) to variables is generally faster.&lt;br /&gt;
* The contents of session variables are typically non-transactional, whereas temporary tables are fully transactional.&lt;br /&gt;
* A session variable stores only a single value or NULL (no MVCC, no VACUUM).&lt;br /&gt;
* Temporary tables follow MVCC rules; repeated updates within a transaction generate many dead tuples, making updates more expensive.&lt;br /&gt;
* Temporary tables are not cleaned up by autovacuum, and it is not possible to run VACUUM inside a function.&lt;br /&gt;
* PostgreSQL does not support global temporary tables, so using a temporary table just to store a single value is inefficient and can lead to catalog bloat.&lt;br /&gt;
&lt;br /&gt;
= SQL/PSM =&lt;br /&gt;
&lt;br /&gt;
The SQL standard introduces the concept of modules, which can be associated with schemas (partially). Variables in modules behave like PL/pgSQL variables but are only local. The only objects allowed at the module level are temporary tables, which can be accessed only from routines assigned to that module. Modules can encapsulate private objects (functions, procedure, temp tables), that are not visible outside the module. Inside the module, a  private objects shadows other objects. What I know the modules (and generally SQL/PSM) doesn&#039;t cover a deployment of code, so there is not similarity with PostgreSQL extensions. SQL/PSM Modules are modules like modules from modular languages like Modula2 or Oberon. There is some similarity with Postgres schemas (both are containers) with significant differences (there is not a concept of SEARCH_PATH (on PSM side) or there is not a concept of private or public objects (on Postgres schema side)).&lt;br /&gt;
&lt;br /&gt;
SQL/PSM variables are not transactional (they are used exactly like in PL/pgSQL), and cannot be declared outside routines. The precedence between variables and columns is not specified.&lt;br /&gt;
&lt;br /&gt;
= Implementation Challenges =&lt;br /&gt;
&lt;br /&gt;
* Possible collisions between session variables and other database objects.&lt;br /&gt;
* Memory cleanup after dropping a session variable, especially when DDL operations can be reverted (Postgres has not support for global temp objects).&lt;br /&gt;
* Memory invalidation: if a variable is dropped by one session, other sessions should not continue using it (Postgres has not support for global temp objects).&lt;br /&gt;
&lt;br /&gt;
= Proposed Implementations =&lt;br /&gt;
&lt;br /&gt;
It is clear that no single design is perfect for all use cases. MySQL’s approach is convenient and useful for interactive work, but it cannot be used for storing sensitive data and is considered unsafe. PostgreSQL GUCs are excellent for configuration, but they are not well suited for interactive use and are very limited when used as global variables in PL code.&lt;br /&gt;
&lt;br /&gt;
Because of these trade-offs, no single design is sufficient. In practice, having multiple designs within a single system could support a broader range of use cases more effectively.&lt;br /&gt;
&lt;br /&gt;
== Design session variables as database global temporary typed objects with ANSI SQL syntax for identifiers ==&lt;br /&gt;
&lt;br /&gt;
Author: Pavel Stehule &amp;lt;pavel.stehule@gmail.com&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I searched for a solution that could work well with the specific PostgreSQL environment:&lt;br /&gt;
&lt;br /&gt;
* PostgreSQL has more than one widely used stored procedure language: PL/pgSQL, PL/Python, or PL/Perl.&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL is a reduced version of PL/SQL, and PL/SQL is a modified ADA language. I am pretty happy with the current complexity of PL/pgSQL --- it is a domain-specific language that works well. It is very simple and easy to learn. PL/pgSQL has no packages or modules; instead PostgreSQL schemas are used. But schemas are database objects in their own right, independent of PL.&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL has not own data types, own operators library (language library), own dependency tracking. This is main difference from PL/SQL. PL/pgSQL is just very simple interpret that fully use types, functions, operators, dependency tracking implemented by Postgres for SQL support. Is not possible to implement variables with larger scope than transactions without Postgres dependency tracking. &lt;br /&gt;
&lt;br /&gt;
* PostgreSQL already has a code container --- the schema. I don&#039;t want to introduce another container object (modules), as this would overlap heavily with schemas. Implementing modules for PL/pgSQL could be useful, but doing it properly would be quite complex. The main problem is the concept of public and private objects --- PostgreSQL does not have this. Don&#039;t forget: every PL/pgSQL expression is a SQL expression, so public/private visibility would first need to be implemented internally in PostgreSQL. That would be redundant (and possibly in conflict) with SEARCH_PATH.&lt;br /&gt;
&lt;br /&gt;
So I wanted a design that supports multiple PL languages, does not add complexity to the relatively simple PL/pgSQL, and does not duplicate functionality already available.  &lt;br /&gt;
&lt;br /&gt;
I wrote a larger application in PL/pgSQL. To maintain it, I created plpgsql_lint (later renamed plpgsql_check). So I am always looking for solutions that do not block static code analysis. Static analysis requires persistent objects, which naturally leads to designing session variables as database objects (with their own system catalog).  &lt;br /&gt;
When session variables are database objects, ACLs can be applied naturally. Oracle package variables are well protected by package scope. PostgreSQL, however, has no schema scope --- schema locality is controlled by the SEARCH_PATH GUC, and introducing another mechanism would be messy. But with ACLs, variable contents can also be secured:&lt;br /&gt;
&lt;br /&gt;
    CREATE TEMP VARIABLE x AS int;&lt;br /&gt;
     &lt;br /&gt;
    LET x = 10;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(x);&lt;br /&gt;
    END;&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    SELECT VARIABLE(x);&lt;br /&gt;
&lt;br /&gt;
==== Collisions between column and variable identifiers ====&lt;br /&gt;
&lt;br /&gt;
There is a risk of identifier collisions between variables and columns. With ANSI SQL syntax for session variable identifiers, the problem is how to distinguish variables from columns. This is a known issue in PL/pgSQL and PL/SQL and can be solved either by prioritization or by prohibiting collisions.  &lt;br /&gt;
&lt;br /&gt;
Prohibiting collisions has largely solved this problem in PL/pgSQL. Unfortunately, session variables are global objects, so collisions can occur in any query. A poorly named variable could break queries unexpectedly. Prioritization also has problems --- a variable or a column could be used silently when the other was intended.&lt;br /&gt;
&lt;br /&gt;
    CREATE TABLE tab(a int);&lt;br /&gt;
    CREATE VARIABLE a AS int;&lt;br /&gt;
    &lt;br /&gt;
    SELECT a FROM tab; -- with disallowed collisions, this query fails when a variable named &amp;quot;a&amp;quot; exists&lt;br /&gt;
    &lt;br /&gt;
    LET a = 1;&lt;br /&gt;
    DELETE FROM tab WHERE a = 1; -- with higher priority for variables, all rows could be deleted&lt;br /&gt;
    SELECT a FROM tab; -- with higher priority for columns, the variable is ignored&lt;br /&gt;
&lt;br /&gt;
Even with collision prohibition, mistakes are still possible:&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE _id AS int;&lt;br /&gt;
    LET _id = 10;&lt;br /&gt;
    CREATE TABLE foo(id int);&lt;br /&gt;
    DELETE FROM foo WHERE _id = 10; -- just a typo, but all rows are deleted&lt;br /&gt;
&lt;br /&gt;
These problems are not new. Developers in PL/pgSQL or PL/SQL know them, but the scope of risk is normally limited to stored procedures. After many discussions and designs, I introduced &amp;quot;variable fences&amp;quot; (the syntax VARIABLE(varname)). Inside any query, variables can be used only inside a variable fence. This completely solves collisions and reduces the chance of human error. Currently, fences are required everywhere in queries and expressions. In the future, they might be made optional inside PL/pgSQL, to reduce overhead when porting Oracle applications. This could be controlled by a new plpgsql.extra_checks setting.&lt;br /&gt;
&lt;br /&gt;
A variable fence can collide with a user-defined function named `variable`. This is similar to keyword conflicts such as CUBE. It can be resolved by schema-qualifying or quoting:&lt;br /&gt;
&lt;br /&gt;
    SELECT public.variable(...)&lt;br /&gt;
    SELECT &amp;quot;variable&amp;quot;(...)&lt;br /&gt;
&lt;br /&gt;
Why not use T-SQL (MSSQL, MySQL) syntax for session variables? There are several reasons – some objective, one subjective:&lt;br /&gt;
&lt;br /&gt;
* There is a collision with custom operators. Operators `@` and `@@` are already used, and `@@` is common:&lt;br /&gt;
&lt;br /&gt;
    (2025-09-28 06:57:30) postgres=# create table tab(a int);&lt;br /&gt;
    CREATE TABLE&lt;br /&gt;
    (2025-09-28 06:57:42) postgres=# insert into tab values(10);&lt;br /&gt;
    INSERT 0 1&lt;br /&gt;
    (2025-09-28 06:57:50) postgres=# select @a from tab;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │       10 │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
We could safely use only `:` as a prefix, but that would conflict with psql variables. Similarly, `$` would conflict with query parameters.&lt;br /&gt;
&lt;br /&gt;
* T-SQL variable names look strange in PostgreSQL (in queries and in PL/pgSQL). PostgreSQL uses only ANSI/SQL identifiers, and PL/pgSQL follows the same rules. PostgreSQL is consistent here, and I see little motivation to introduce a new, non-standard syntax (especially since it could cause compatibility issues).&lt;br /&gt;
&lt;br /&gt;
Note: There was also a proposal to use table syntax for variables (similar to T-SQL in-memory table variables, but restricted to a single row). This is very effective for avoiding collisions, but I dislike it. It complicates simple expressions, adds inconsistency, and looks odd for scalar variables (though it could be useful for composite variables). This design does not solve every problem --- for example, variables can still be shadowed by tables because of SEARCH_PATH (a known issue).&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(var); -- variable is not a table&lt;br /&gt;
    END&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      -- variable as a table (I don&#039;t like it)&lt;br /&gt;
      -- can we use DML? How many rows can it hold?&lt;br /&gt;
      -- is the value a row or not?&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, (SELECT var FROM var);&lt;br /&gt;
    END&lt;br /&gt;
    $$&lt;br /&gt;
&lt;br /&gt;
I am not against introducing in-memory tables --- or more correctly, global temporary tables. A well-designed implementation could be very useful. But this is a different feature and should be designed separately.  &lt;br /&gt;
&lt;br /&gt;
There are also performance considerations: PL/pgSQL can use simple expression evaluation when no tables are referenced. This optimization would need to change, which touches sensitive code. It would also be inconsistent.  &lt;br /&gt;
&lt;br /&gt;
I would like global temporary tables (which PostgreSQL currently lacks). Tables should behave like tables, and variables should behave like variables. The natural mental model for session variables comes from variables in PL languages. PostgreSQL internally supports *transition tables*, and I can imagine allowing these outside of triggers. That would be valid, but again, it is a different feature.  &lt;br /&gt;
&lt;br /&gt;
Implementing declared tables inside PL/pgSQL would be simpler from a high-level design perspective (interaction between SQL tables and PL/pgSQL is already well defined), but difficult from a low-level perspective (statistics, optimizer data, etc. --- the same issues as with global temporary tables, unless we add fake proxy local temp tables).&lt;br /&gt;
&lt;br /&gt;
   CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
   AS $$&lt;br /&gt;
   -- not implemented yet, not part of this proposal&lt;br /&gt;
   DECLARE t TABLE(a int, b int); -- this table is invisible outside fx&lt;br /&gt;
   BEGIN&lt;br /&gt;
     INSERT INTO t VALUES(10,20);&lt;br /&gt;
     INSERT INTO t VALUES(30,40);&lt;br /&gt;
     RAISE NOTICE &#039;%&#039;, (SELECT count(*) FROM t);&lt;br /&gt;
   END;&lt;br /&gt;
   $$ LANGUAGE plpgsql;&lt;br /&gt;
&lt;br /&gt;
Some data languages define relational variables, but this concept has no overlap with the common understanding of session variables.&lt;br /&gt;
&lt;br /&gt;
Inside PL/pgSQL it would be possible to configure the parser to allow the use of session variables without a variable fence (similar to ordinary PL/pgSQL variables) in expressions. This is not supported yet, but implementation should not be problematic. Outside PL, variable fences could in principle be optional when there is no risk of collisions (for example, in expressions without subselects). This has not been implemented yet.&lt;br /&gt;
&lt;br /&gt;
==== Assign Command LET ====&lt;br /&gt;
&lt;br /&gt;
The value of a session variable can be assigned either by a dedicated LET command or by the SELECT INTO construct. In most systems where a special command is needed, the keyword SET is used (MySQL, MSSQL, SQL/PSM, DB2). In PostgreSQL, SET could be extended to support a_expr at the parser level, but this would require partially rewriting the current implementation of the SET command. Technically, this should not be a problem.&lt;br /&gt;
&lt;br /&gt;
The main issue is the potential for collisions between GUCs and session variables. GUCs are not declared in advance, are not associated with a schema (the prefix for a custom GUC can be any non-empty string), and access is not controlled by SEARCH_PATH. A variable fence could also solve this issue, but in this case the risk is always present, so fences could not be optional. Some languages use the keyword LET for assignments. Introducing LET in PostgreSQL provides a clean way to eliminate collisions between GUCs and session variables.&lt;br /&gt;
&lt;br /&gt;
It would also be possible to allow assignment to session variables in PL code using the usual PL assignment syntax (not yet implemented). This would be beneficial when porting from PL/SQL (Oracle) and would provide a natural syntax when using session variables as PL global variables. There do not appear to be technical obstacles to implementing this. However, one important issue arises: in PL/pgSQL, only declared variables can be targets of assignment statements. If session variables were allowed here, either (a) this check could not be performed, or (b) PL functions would gain a strong dependency on session variables, similar to the dependency on types today.&lt;br /&gt;
&lt;br /&gt;
    (2025-10-02 07:42:01) postgres=# CREATE OR REPLACE FUNCTION fx() &lt;br /&gt;
    returns void as $$&lt;br /&gt;
    begin&lt;br /&gt;
      declare var mydomain;&lt;br /&gt;
    begin&lt;br /&gt;
      var := 10;&lt;br /&gt;
    end&lt;br /&gt;
    $$ language plpgsql;&lt;br /&gt;
    ERROR:  type &amp;quot;mydomain&amp;quot; does not exist&lt;br /&gt;
    LINE 4:   declare var mydomain;&lt;br /&gt;
                          ^&lt;br /&gt;
&lt;br /&gt;
While such dependencies may not be a problem (the plan cache can be invalidated correctly), they raise new questions about temporary variables. To avoid these complications, the LET command was introduced, meaning the PL/pgSQL assignment mechanism does not need to be modified.&lt;br /&gt;
&lt;br /&gt;
A major consideration is performance, both in general and when LET is used inside PL/pgSQL.&lt;br /&gt;
&lt;br /&gt;
The LET command is a PostgreSQL utility command. Like other utility commands (e.g., CREATE TABLE AS SELECT), it can be prepared. Patches exist to implement PREPARE and EXECUTE for LET (these are not included in the reduced patch set). Even though the plan cache can be used, LET may still perform worse than a PL/pgSQL assignment for two reasons:&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL supports simple expression evaluation (also supported by LET in the enhanced patch set)&lt;br /&gt;
* PL/pgSQL variables are stored in arrays and accessed via an integer offset, while session variables are stored in a hash table and accessed via a hash lookup, which is slower. This slowdown is visible only in worst-case benchmarks:&lt;br /&gt;
&lt;br /&gt;
   DO $$ declare locvar int DEFAULT 0; begin for i in 1..1000000 loop locvar := locvar + 1; end loop; end $$ &lt;br /&gt;
   DO $$ begin for i in 1..100000 loop LET sesvar := sesvar + 1; end loop; end $$&lt;br /&gt;
&lt;br /&gt;
In the reduced patch set, the LET implementation is significantly slower because simple expression evaluation is not used (although it is implemented in the extended patch set). Nevertheless, even with the slowdown, execution remains much faster than queries that touch real tables, so performance should not be an issue in practice.&lt;br /&gt;
&lt;br /&gt;
==== Notes on Implementing Session Variables as Global Temporary Objects ====&lt;br /&gt;
&lt;br /&gt;
The core of this proposal is to design session variables as global temporary objects. Unfortunately, PostgreSQL currently has no support for this type of object. Surprisingly non-trivial is the implementation of non-system objects that have a catalogue entry, but unlike other objects, keep the data purely in memory. The problem is in memory cleaning. There is no hook that could be used to catch the event when the object was 100% deleted. We can (and must - there is nothing else) catch the sinval message, however, it is often delivered as a false alarm.&lt;br /&gt;
&lt;br /&gt;
Two main issues arise with this design:&lt;br /&gt;
&lt;br /&gt;
* the possibility of using invalid values (values stored in valid memory but no longer valid in the catalog),&lt;br /&gt;
* and memory cleanup happening too early or too late.&lt;br /&gt;
&lt;br /&gt;
Each session variable is identified by name (and possibly by an OID from pg_variable). A variable may have its own data type. Values stored in memory are kept in native binary format and remain unchanged until reassigned. Important note: an OID can be reused over time --- OIDs are unique only at a single moment, and there is no guarantee that an OID will not later be assigned to a different object. This creates a real possibility of the following issue:&lt;br /&gt;
&lt;br /&gt;
* session A: CREATE VARIABLE v AS t;&lt;br /&gt;
* session B: LET v = t &#039;value&#039;;&lt;br /&gt;
* session A: DROP VARIABLE v;&lt;br /&gt;
* session B: no activity -- it got lot of sinval messages (signals), but no command is executed, and then is not possibility to handle sinval&lt;br /&gt;
* session A: CREATE VARIABLE v AS nt; -- theoretically I can get same varid and typid like in first step, although t != nt&lt;br /&gt;
* session B: SELECT v; -- crash&lt;br /&gt;
&lt;br /&gt;
To prevent crashes, each stored value must be checked carefully before use. Checking only varid and typid is not sufficient. To ensure correctness, each variable can store its creation LSN (createlsn), which is unique throughout the lifetime of the database. Before using a value, the system checks that value.varid = catalog.varid and value.createlsn = catalog.createlsn. Type changes (ALTER VARIABLE ... ALTER TYPE) are not allowed. Dependencies ensure that the value type always matches the catalog type.&lt;br /&gt;
&lt;br /&gt;
A second problem is deciding when to clean memory. Memory cannot be freed immediately after DROP VARIABLE because the command can be rolled back:&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    BEGIN;&lt;br /&gt;
    LET var := 1;&lt;br /&gt;
    DROP VARIABLE var;&lt;br /&gt;
    ROLLBACK;&lt;br /&gt;
    SELECT VARIABLE(var); -- expected 1&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    BEGIN;&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    LET var := 1;&lt;br /&gt;
    ROLLBACK;&lt;br /&gt;
    -- var should be removed from memory&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    BEGIN;&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    SAVEPOINT s1;&lt;br /&gt;
    LET var := 1;&lt;br /&gt;
    DROP VARIABLE var;&lt;br /&gt;
    ROLLBACK TO s1;&lt;br /&gt;
    COMMIT;&lt;br /&gt;
    SELECT VARIABLE(var); -- expected 1&lt;br /&gt;
&lt;br /&gt;
The simplest solution is to mark the variable as dropped and delay memory cleanup until the next transaction where variables are validated. This approach is simple and robust, although it means memory may only be freed after some delay. While this might look messy when monitoring memory usage, the catalog is transactional, and validation against the catalog ensures correctness.&lt;br /&gt;
&lt;br /&gt;
= Variabes in PostgreSQL (as of v18) =&lt;br /&gt;
&lt;br /&gt;
PostgreSQL provides relatively advanced client-side session variables in psql --- these variables use the `:` prefix. The implementation is pretty much straightforward: `psql`&#039;s parser reads tokens from input and, when it encounters a token related to a variable, replaces it with the variable&#039;s value. The syntax supports literal and identifier escaping, and variables can be used as arguments of the `\bind` command.&lt;br /&gt;
&lt;br /&gt;
`psql` variables are typeless client-side variables, available only inside `psql` or `pgbench`.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:40:18) postgres=# \set myvar ahoj&lt;br /&gt;
    (2025-09-25 09:40:33) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:14) postgres=# select :&#039;myvar&#039;;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:24) postgres=# select $1 \bind :myvar \g&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
The `\set` command allows only simple string concatenation, but it also supports execution of shell commands. When a query is executed with the `\gset` command, its result can be stored in a `psql` variable.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:41:40) postgres=# \set ctime `date`&lt;br /&gt;
    (2025-09-25 09:43:55) postgres=# \echo :ctime&lt;br /&gt;
    Thu Sep 25 09:43:55 CEST 2025&lt;br /&gt;
&lt;br /&gt;
The `\set` command is fairly limited, making complex operations unreadable or non-portable.&lt;br /&gt;
&lt;br /&gt;
This mechanism is good enough for writing tests, but it is not generally available (most users do not need it), and it is not accessible from the server side (e.g., from stored procedures). There is no simple way to access `psql` variables from an anonymous block --- a proxy custom GUC variable must be used:&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:58:38) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    (2025-09-25 09:58:43) postgres=# select set_config(&#039;myvars.myvar&#039;, :&#039;myvar&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ ahoj       │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 10:00:27) postgres=# do $$ begin raise notice &#039;%&#039;, current_setting(&#039;myvars.myvar&#039;); end $$;&lt;br /&gt;
    NOTICE:  ahoj&lt;br /&gt;
    DO&lt;br /&gt;
&lt;br /&gt;
PostgreSQL also has configuration variables (GUC - Grand Unified Configuration). These are primarily designed for PostgreSQL and extension configuration, but they can be used as session variables. GUC variables can be set with the `SET` command or the `set_config` function, and read with `SHOW` or `current_setting`.&lt;br /&gt;
&lt;br /&gt;
GUC variables are meant for holding configuration parameters. When created through the internal API, they can be restricted to superusers, hidden from regular users, and assigned specific types (boolean, memory, number, interval, string, enum) with validation. This type system is separate from the SQL type system and is initialized before PostgreSQL’s SQL types.&lt;br /&gt;
&lt;br /&gt;
Variables created from SQL must be &amp;quot;custom&amp;quot; variables, using a prefix in their name --- these are always strings. Such custom GUCs are often used as a workaround for missing server-side session variables in PostgreSQL, but they are typeless, unprotected, and unsafe.&lt;br /&gt;
&lt;br /&gt;
A notable feature of PostgreSQL GUCs (built-in or custom) is that they can have different scopes: transaction, session, or function execution. They can also be assigned default values at specific events --- configuration loading, role login, database login, or function start. These features are useful for configuration, but problematic when GUCs are repurposed as session variables.&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
    RETURNS void AS $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, current_setting(&#039;my.x&#039;);&lt;br /&gt;
    END $$ LANGUAGE plpgsql SET my.x = 20;&lt;br /&gt;
    CREATE FUNCTION&lt;br /&gt;
    (2025-09-27 06:10:37) postgres=# SET my.x = 0; SELECT fx();&lt;br /&gt;
    SET&lt;br /&gt;
    NOTICE:  20&lt;br /&gt;
    ┌────┐&lt;br /&gt;
    │ fx │&lt;br /&gt;
    ╞════╡&lt;br /&gt;
    │    │&lt;br /&gt;
    └────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    (2025-09-27 06:10:39) postgres=# SHOW my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 0    │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
GUC variables are transactional, and this behavior cannot be disabled.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-27 06:17:31) postgres=# set my.x = 10;&lt;br /&gt;
    SET&lt;br /&gt;
    (2025-09-27 06:19:42) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:19:46) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, true);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:19:55) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:01) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:20:11) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:13) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:20:38) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:45) postgres=# rollback;&lt;br /&gt;
    ROLLBACK&lt;br /&gt;
    (2025-09-27 06:20:52) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:55) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:22:36) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:22:39) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:22:42) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
As a result, there is no reliable way to store details such as error information in custom GUCs.&lt;br /&gt;
&lt;br /&gt;
= Session Variables in Other Systems =&lt;br /&gt;
&lt;br /&gt;
This section surveys how session variables are implemented in several widely used database systems. Each subsection includes code examples to illustrate syntax, behaviour, and scope.&lt;br /&gt;
&lt;br /&gt;
== MySQL ==&lt;br /&gt;
&lt;br /&gt;
Session variables in MySQL have syntax similar to the better-known T-SQL variables (though other details are MySQL-specific). MySQL distinguishes two kinds of variables:&lt;br /&gt;
&lt;br /&gt;
* system variables - referenced using the `@@` prefix (or `@@scope.name`)&lt;br /&gt;
* user-defined variables - referenced using the `@` prefix&lt;br /&gt;
&lt;br /&gt;
System variables are modified with the `SET` statement. The `SET` statement accepts the modifiers `GLOBAL`, `SESSION`, or `PERSIST`. Some variables can only be set using the `GLOBAL` or `SESSION` modifier; when the variable name is specified with the `@@` prefix, it is not necessary to explicitly indicate the scope. System variables are defined by MySQL or by MySQL plugins.&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL some_config = 100;&lt;br /&gt;
    SET @@same_config = 100;&lt;br /&gt;
    &lt;br /&gt;
    SET SESSION sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@SESSION.sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
&lt;br /&gt;
Persistent values are stored in `mysqld-auto.cnf` (in the server data directory).&lt;br /&gt;
&lt;br /&gt;
Resetting system variables can be done by assigning DEFAULT or by copying from the GLOBAL namespace:&lt;br /&gt;
&lt;br /&gt;
    SET @@sql_mode = DEFAULT;&lt;br /&gt;
    SET @@sql_mode = @@GLOBAL.sql_mode&lt;br /&gt;
&lt;br /&gt;
PostgreSQL equivalents:&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL ; -- there is not similar command in Postgres&lt;br /&gt;
    SET SESSION ; -- SET&lt;br /&gt;
    SET PERSIST ; -- ALTER SYSTEM; SELECT pg_reload_conf();&lt;br /&gt;
    SELECT @@var; -- SELECT current_setting(&#039;var);&lt;br /&gt;
&lt;br /&gt;
User-defined variables are created by assignment:&lt;br /&gt;
&lt;br /&gt;
    SET @var = expr [, @var2 = expr [ ... ]];&lt;br /&gt;
&lt;br /&gt;
User-defined variables can only be of type integer, decimal, float, binary, non-binary string, or NULL. Casting between these types is implicit and hidden. User-defined variables cannot be used as table or column names.&lt;br /&gt;
&lt;br /&gt;
An advantage of the T-SQL-style syntax (with special prefixes) is that it provides a simple solution for avoiding identifier collisions. MySQL variables are convenient, and the user experience can be good for people already familiar with T-SQL (MSSQL or Sybase). On the other hand, prefix-based syntax can be confusing for users coming from systems other than MSSQL. The type system is perhaps too simple and can be unsafe. In general, the performance of MySQL (or MariaDB) stored procedures is not good, and stored procedures are therefore not frequently used. There is no protection against typographical errors. Unassigned user variables can be used without error (they default to NULL), which makes static checking of stored procedures impossible in this area.&lt;br /&gt;
&lt;br /&gt;
There is no way to restrict access to user-defined variables in MySQL. They cannot be protected against unintended usage. The only possible safeguard is a naming convention, since all user-defined variables share a single namespace.&lt;br /&gt;
&lt;br /&gt;
== Oracle ==&lt;br /&gt;
&lt;br /&gt;
Oracle PL/SQL allows the use of package variables. PL/SQL is based on the Ada language, and package variables act as &amp;quot;global&amp;quot; variables. They are not directly visible from SQL, but Oracle provides a reduced syntax for functions without arguments, so you typically write a wrapper:&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE PACKAGE my_package&lt;br /&gt;
    AS&lt;br /&gt;
      FUNCTION get_a RETURN NUMBER;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    /&lt;br /&gt;
    &lt;br /&gt;
    CREATE OR REPLACE PACKAGE BODY my_package&lt;br /&gt;
    AS&lt;br /&gt;
      a  NUMBER(20);&lt;br /&gt;
      &lt;br /&gt;
      FUNCTION get_a&lt;br /&gt;
      RETURN NUMBER&lt;br /&gt;
      IS&lt;br /&gt;
      BEGIN&lt;br /&gt;
        RETURN a;&lt;br /&gt;
      END get_a;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    &lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
Inside SQL, SQL identifiers take precedence. Inside non-SQL commands (such as CALL or PL/SQL blocks), package identifiers take precedence.&lt;br /&gt;
&lt;br /&gt;
Oracle allows both syntaxes for calling a function with zero arguments:&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a() FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
This reduces the risk of naming collisions. Package variables persist for the duration of a session.&lt;br /&gt;
&lt;br /&gt;
Another possibility is to use variables in SQL*Plus (similar to `psql` variables, but with the ability to define the type on the server side).&lt;br /&gt;
&lt;br /&gt;
A variable is declared with the `VARIABLE` command and can be accessed in the session using the `:varname` syntax (a prior declaration step may be optional):&lt;br /&gt;
&lt;br /&gt;
    VARIABLE bv_variable_name VARCHAR2(30)&lt;br /&gt;
    BEGIN&lt;br /&gt;
      :bv_variable_name := &#039;Some Value&#039;;&lt;br /&gt;
    END;&lt;br /&gt;
    &lt;br /&gt;
    SELECT column_name&lt;br /&gt;
      FROM   table_name&lt;br /&gt;
     WHERE  column_name = :bv_variable_name;&lt;br /&gt;
&lt;br /&gt;
== DB2 ==&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;user-defined global variables&amp;quot; in DB2 are similar to the proposed PostgreSQL feature. The main difference is in the access rights: DB2 uses `READ` and `WRITE`, while PostgreSQL uses `SELECT` and `UPDATE`. Because PostgreSQL already uses the `SET` command for GUCs, the `LET` command was introduced in the proposal (DB2 uses `SET`).&lt;br /&gt;
&lt;br /&gt;
Variables are visible in all sessions, but their values are private to each session. Variables are not transactional. Their usage is broader than in the proposal: they can be changed by `SET`, `SELECT INTO`, or used as OUT parameters of procedures. The search path (or a similar mechanism) applies to variables as well, but variables have lower priority than tables or columns.&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE myCounter INT DEFAULT 01;&lt;br /&gt;
    SELECT EMPNO, LASTNAME, CASE WHEN myCounter = 1 THEN SALARY ELSE NULL END&lt;br /&gt;
    FROM EMPLOYEE WHERE WORKDEPT = &#039;A00&#039;;&lt;br /&gt;
    SET myCounter = 29;&lt;br /&gt;
&lt;br /&gt;
There also appear to be other kinds of variables, accessed using the function `GETVARIABLE(&#039;name&#039;, &#039;default&#039;)`. These are very similar to PostgreSQL GUCs and the `current_setting` function. Such variables can be set via the connection string, are of type `VARCHAR`, and up to 10 values are allowed. Built-in session variables (configuration) can also be accessed using `GETVARIABLE`.&lt;br /&gt;
&lt;br /&gt;
== MSSQL (T-SQL) ==&lt;br /&gt;
&lt;br /&gt;
MSSQL provides two types of variables:&lt;br /&gt;
&lt;br /&gt;
* Global variables — accessed with the `@@varname` syntax. These are similar to GUCs and provide state information such as `@@ERROR`, `@@ROWCOUNT`, or `@@IDENTITY`. &lt;br /&gt;
* Local variables — accessed with the `@varname` syntax. These must be declared with the `DECLARE` command before use. Their scope is limited to the batch, procedure, or function where they are executed.&lt;br /&gt;
&lt;br /&gt;
    DECLARE @TestVariable AS VARCHAR(100)&lt;br /&gt;
    SET @TestVariable = &#039;Think Green&#039;&lt;br /&gt;
    GO&lt;br /&gt;
    PRINT @TestVariable&lt;br /&gt;
&lt;br /&gt;
This script fails because `PRINT` is executed in a different batch. Therefore, it seems MSSQL does not truly support session variables.&lt;br /&gt;
&lt;br /&gt;
There are also mechanisms similar to PostgreSQL&#039;s custom GUCs and the use of `current_setting` and `set_config` functions. In general, MSSQL is fairly primitive in this area.&lt;br /&gt;
&lt;br /&gt;
    EXEC sp_set_session_context &#039;user_id&#039;, 4;&lt;br /&gt;
    SELECT SESSION_CONTEXT(N&#039;user_id&#039;);&lt;br /&gt;
&lt;br /&gt;
= ToDo =&lt;br /&gt;
&lt;br /&gt;
* SECURITY LABEL support — enhance the `dummy_seclabel` module and the `sepgsql` extension. Update PostgreSQL documentation and ensure `SecLabelSupportsObjectType` includes session variables.&lt;/div&gt;</summary>
		<author><name>Okbobcz</name></author>
	</entry>
	<entry>
		<id>https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=42498</id>
		<title>Implementation of declarative catalog session variables</title>
		<link rel="alternate" type="text/html" href="https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=42498"/>
		<updated>2026-01-01T07:40:51Z</updated>

		<summary type="html">&lt;p&gt;Okbobcz: /* SQL/PSM */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Session variables are database objects that can hold a value across multiple queries inside a session. The design of session variables varies widely, and many databases implement more than one type of session variable.&lt;br /&gt;
&lt;br /&gt;
The SQL/PSM standard introduces modules and module-level variables. However, this part of the standard has not been implemented in any widely used product. As a result, each database system has developed its own proprietary design, syntax, and feature set.&lt;br /&gt;
&lt;br /&gt;
Session variables are often part of the stored procedure environment, but there are exceptions --- for example, MySQL session variables or client-side session variables in tools like psql. In most systems, session variables do not participate in transactions: once set, their value persists until explicitly changed, even if the surrounding transaction is rolled back. Conceptually, they behave much like variables in general-purpose programming languages.&lt;br /&gt;
&lt;br /&gt;
Because there is no common design, comparisons across systems are difficult. Each implementation has its own advantages and disadvantages, and in some cases a single system supports more than one kind of session variable.&lt;br /&gt;
&lt;br /&gt;
Kinds of session variables:&lt;br /&gt;
* client side (psql, pgadmin, sql plus, ...) - mostly similar to shell variables (based on string operations)&lt;br /&gt;
* server side&lt;br /&gt;
** declarative&lt;br /&gt;
*** created only for batch (T-SQL)&lt;br /&gt;
*** created as an database object (or part of database object) (Oracle, DB2)&lt;br /&gt;
** created by usage (MySQL)&lt;br /&gt;
** with priprietary syntax for identifiers (MySQL, T-SQL)&lt;br /&gt;
** with ANSI SQL syntax for identifiers (Oracle, DB2)&lt;br /&gt;
** Inside only PL (Oracle)&lt;br /&gt;
** Inside db engine (MySQL, T-SQL, DB2)&lt;br /&gt;
&lt;br /&gt;
Another classification:&lt;br /&gt;
* external (are not part of SQL)&lt;br /&gt;
** server side (Oracle package variables)&lt;br /&gt;
** client side (psql, sql plus)&lt;br /&gt;
* native (are part of SQL)&lt;br /&gt;
** persistent (DB2)&lt;br /&gt;
** declarative (T-SQL)&lt;br /&gt;
** implicit (MySQL)&lt;br /&gt;
&lt;br /&gt;
Possible uses of session variables include:&lt;br /&gt;
&lt;br /&gt;
* Acting as global variables in PL code (for tracing, debugging, or quick hacks),&lt;br /&gt;
* Storing configuration values for PL routines,&lt;br /&gt;
* Keeping frequently used data handy in an interactive session,&lt;br /&gt;
* Storing credentials for Row-Level Security (RLS) and other data-protection mechanisms,&lt;br /&gt;
* Assisting with migrations from other systems (especially Oracle),&lt;br /&gt;
* Enabling parameterization of anonymous code blocks (specific to PostgreSQL).&lt;br /&gt;
* Session variables are writable even on read-only hot standby servers in PostgreSQL.&lt;br /&gt;
&lt;br /&gt;
They behave somewhat like temporary tables, but there are important differences:&lt;br /&gt;
&lt;br /&gt;
* Variables hold single values, while tables hold sets of rows.&lt;br /&gt;
* The syntax for using variables is shorter and more convenient for interactive work.&lt;br /&gt;
* Access (read/write) to variables is generally faster.&lt;br /&gt;
* The contents of session variables are typically non-transactional, whereas temporary tables are fully transactional.&lt;br /&gt;
* A session variable stores only a single value or NULL (no MVCC, no VACUUM).&lt;br /&gt;
* Temporary tables follow MVCC rules; repeated updates within a transaction generate many dead tuples, making updates more expensive.&lt;br /&gt;
* Temporary tables are not cleaned up by autovacuum, and it is not possible to run VACUUM inside a function.&lt;br /&gt;
* PostgreSQL does not support global temporary tables, so using a temporary table just to store a single value is inefficient and can lead to catalog bloat.&lt;br /&gt;
&lt;br /&gt;
= SQL/PSM =&lt;br /&gt;
&lt;br /&gt;
The SQL standard introduces the concept of modules, which can be associated with schemas (partially). Variables in modules behave like PL/pgSQL variables but are only local. The only objects allowed at the module level are temporary tables, which can be accessed only from routines assigned to that module. Modules can encapsulate private objects (functions, procedure, temp tables), that are not visible outside the module. Inside the module, a  private objects shadows other objects. What I know the modules (and generally SQL/PSM) doesn&#039;t cover a deployment of code, so there is not similarity with PostgreSQL extensions. SQL/PSM Modules are modules like modules from modular languages like Modula2 or Oberon. There is some similarity with Postgres schemas (both are containers) with significant differences (there is not a concept of SEARCH_PATH (on PSM side) or there is not a concept of private or public objects (on Postgres schema side)).&lt;br /&gt;
&lt;br /&gt;
SQL/PSM variables are not transactional (they are used exactly like in PL/pgSQL), and cannot be declared outside routines. The precedence between variables and columns is not specified.&lt;br /&gt;
&lt;br /&gt;
= Implementation Challenges =&lt;br /&gt;
&lt;br /&gt;
* Possible collisions between session variables and other database objects.&lt;br /&gt;
* Memory cleanup after dropping a session variable, especially when DDL operations can be reverted (Postgres has not support for global temp objects).&lt;br /&gt;
* Memory invalidation: if a variable is dropped by one session, other sessions should not continue using it (Postgres has not support for global temp objects).&lt;br /&gt;
&lt;br /&gt;
= Proposed Implementations =&lt;br /&gt;
&lt;br /&gt;
It is clear that no single design is perfect for all use cases. MySQL’s approach is convenient and useful for interactive work, but it cannot be used for storing sensitive data and is considered unsafe. PostgreSQL GUCs are excellent for configuration, but they are not well suited for interactive use and are very limited when used as global variables in PL code.&lt;br /&gt;
&lt;br /&gt;
Because of these trade-offs, no single design is sufficient. In practice, having multiple designs within a single system could support a broader range of use cases more effectively.&lt;br /&gt;
&lt;br /&gt;
== Design session variables as database global temporary typed objects with ANSI SQL syntax for identifiers ==&lt;br /&gt;
&lt;br /&gt;
Author: Pavel Stehule &amp;lt;pavel.stehule@gmail.com&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I searched for a solution that could work well with the specific PostgreSQL environment:&lt;br /&gt;
&lt;br /&gt;
* PostgreSQL has more than one widely used stored procedure language: PL/pgSQL, PL/Python, or PL/Perl.&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL is a reduced version of PL/SQL, and PL/SQL is a modified ADA language. I am pretty happy with the current complexity of PL/pgSQL --- it is a domain-specific language that works well. It is very simple and easy to learn. PL/pgSQL has no packages or modules; instead PostgreSQL schemas are used. But schemas are database objects in their own right, independent of PL.&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL has not own data types, own operators library (language library), own dependency tracking. This is main difference from PL/SQL. PL/pgSQL is just very simple interpret that fully use types, functions, operators, dependency tracking implemented by Postgres for SQL support. Is not possible to implement variables with larger scope than transactions without Postgres dependency tracking. &lt;br /&gt;
&lt;br /&gt;
* PostgreSQL already has a code container --- the schema. I don&#039;t want to introduce another container object (modules), as this would overlap heavily with schemas. Implementing modules for PL/pgSQL could be useful, but doing it properly would be quite complex. The main problem is the concept of public and private objects --- PostgreSQL does not have this. Don&#039;t forget: every PL/pgSQL expression is a SQL expression, so public/private visibility would first need to be implemented internally in PostgreSQL. That would be redundant (and possibly in conflict) with SEARCH_PATH.&lt;br /&gt;
&lt;br /&gt;
So I wanted a design that supports multiple PL languages, does not add complexity to the relatively simple PL/pgSQL, and does not duplicate functionality already available.  &lt;br /&gt;
&lt;br /&gt;
I wrote a larger application in PL/pgSQL. To maintain it, I created plpgsql_lint (later renamed plpgsql_check). So I am always looking for solutions that do not block static code analysis. Static analysis requires persistent objects, which naturally leads to designing session variables as database objects (with their own system catalog).  &lt;br /&gt;
When session variables are database objects, ACLs can be applied naturally. Oracle package variables are well protected by package scope. PostgreSQL, however, has no schema scope --- schema locality is controlled by the SEARCH_PATH GUC, and introducing another mechanism would be messy. But with ACLs, variable contents can also be secured:&lt;br /&gt;
&lt;br /&gt;
    CREATE TEMP VARIABLE x AS int;&lt;br /&gt;
     &lt;br /&gt;
    LET x = 10;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(x);&lt;br /&gt;
    END;&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    SELECT VARIABLE(x);&lt;br /&gt;
&lt;br /&gt;
==== Collisions between column and variable identifiers ====&lt;br /&gt;
&lt;br /&gt;
There is a risk of identifier collisions between variables and columns. With ANSI SQL syntax for session variable identifiers, the problem is how to distinguish variables from columns. This is a known issue in PL/pgSQL and PL/SQL and can be solved either by prioritization or by prohibiting collisions.  &lt;br /&gt;
&lt;br /&gt;
Prohibiting collisions has largely solved this problem in PL/pgSQL. Unfortunately, session variables are global objects, so collisions can occur in any query. A poorly named variable could break queries unexpectedly. Prioritization also has problems --- a variable or a column could be used silently when the other was intended.&lt;br /&gt;
&lt;br /&gt;
    CREATE TABLE tab(a int);&lt;br /&gt;
    CREATE VARIABLE a AS int;&lt;br /&gt;
    &lt;br /&gt;
    SELECT a FROM tab; -- with disallowed collisions, this query fails when a variable named &amp;quot;a&amp;quot; exists&lt;br /&gt;
    &lt;br /&gt;
    LET a = 1;&lt;br /&gt;
    DELETE FROM tab WHERE a = 1; -- with higher priority for variables, all rows could be deleted&lt;br /&gt;
    SELECT a FROM tab; -- with higher priority for columns, the variable is ignored&lt;br /&gt;
&lt;br /&gt;
Even with collision prohibition, mistakes are still possible:&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE _id AS int;&lt;br /&gt;
    LET _id = 10;&lt;br /&gt;
    CREATE TABLE foo(id int);&lt;br /&gt;
    DELETE FROM foo WHERE _id = 10; -- just a typo, but all rows are deleted&lt;br /&gt;
&lt;br /&gt;
These problems are not new. Developers in PL/pgSQL or PL/SQL know them, but the scope of risk is normally limited to stored procedures. After many discussions and designs, I introduced &amp;quot;variable fences&amp;quot; (the syntax VARIABLE(varname)). Inside any query, variables can be used only inside a variable fence. This completely solves collisions and reduces the chance of human error. Currently, fences are required everywhere in queries and expressions. In the future, they might be made optional inside PL/pgSQL, to reduce overhead when porting Oracle applications. This could be controlled by a new plpgsql.extra_checks setting.&lt;br /&gt;
&lt;br /&gt;
A variable fence can collide with a user-defined function named `variable`. This is similar to keyword conflicts such as CUBE. It can be resolved by schema-qualifying or quoting:&lt;br /&gt;
&lt;br /&gt;
    SELECT public.variable(...)&lt;br /&gt;
    SELECT &amp;quot;variable&amp;quot;(...)&lt;br /&gt;
&lt;br /&gt;
Why not use T-SQL (MSSQL, MySQL) syntax for session variables? There are several reasons – some objective, one subjective:&lt;br /&gt;
&lt;br /&gt;
* There is a collision with custom operators. Operators `@` and `@@` are already used, and `@@` is common:&lt;br /&gt;
&lt;br /&gt;
    (2025-09-28 06:57:30) postgres=# create table tab(a int);&lt;br /&gt;
    CREATE TABLE&lt;br /&gt;
    (2025-09-28 06:57:42) postgres=# insert into tab values(10);&lt;br /&gt;
    INSERT 0 1&lt;br /&gt;
    (2025-09-28 06:57:50) postgres=# select @a from tab;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │       10 │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
We could safely use only `:` as a prefix, but that would conflict with psql variables. Similarly, `$` would conflict with query parameters.&lt;br /&gt;
&lt;br /&gt;
* T-SQL variable names look strange in PostgreSQL (in queries and in PL/pgSQL). PostgreSQL uses only ANSI/SQL identifiers, and PL/pgSQL follows the same rules. PostgreSQL is consistent here, and I see little motivation to introduce a new, non-standard syntax (especially since it could cause compatibility issues).&lt;br /&gt;
&lt;br /&gt;
Note: There was also a proposal to use table syntax for variables (similar to T-SQL in-memory table variables, but restricted to a single row). This is very effective for avoiding collisions, but I dislike it. It complicates simple expressions, adds inconsistency, and looks odd for scalar variables (though it could be useful for composite variables). This design does not solve every problem --- for example, variables can still be shadowed by tables because of SEARCH_PATH (a known issue).&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(var); -- variable is not a table&lt;br /&gt;
    END&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      -- variable as a table (I don&#039;t like it)&lt;br /&gt;
      -- can we use DML? How many rows can it hold?&lt;br /&gt;
      -- is the value a row or not?&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, (SELECT var FROM var);&lt;br /&gt;
    END&lt;br /&gt;
    $$&lt;br /&gt;
&lt;br /&gt;
I am not against introducing in-memory tables --- or more correctly, global temporary tables. A well-designed implementation could be very useful. But this is a different feature and should be designed separately.  &lt;br /&gt;
&lt;br /&gt;
There are also performance considerations: PL/pgSQL can use simple expression evaluation when no tables are referenced. This optimization would need to change, which touches sensitive code. It would also be inconsistent.  &lt;br /&gt;
&lt;br /&gt;
I would like global temporary tables (which PostgreSQL currently lacks). Tables should behave like tables, and variables should behave like variables. The natural mental model for session variables comes from variables in PL languages. PostgreSQL internally supports *transition tables*, and I can imagine allowing these outside of triggers. That would be valid, but again, it is a different feature.  &lt;br /&gt;
&lt;br /&gt;
Implementing declared tables inside PL/pgSQL would be simpler from a high-level design perspective (interaction between SQL tables and PL/pgSQL is already well defined), but difficult from a low-level perspective (statistics, optimizer data, etc. --- the same issues as with global temporary tables, unless we add fake proxy local temp tables).&lt;br /&gt;
&lt;br /&gt;
   CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
   AS $$&lt;br /&gt;
   -- not implemented yet, not part of this proposal&lt;br /&gt;
   DECLARE t TABLE(a int, b int); -- this table is invisible outside fx&lt;br /&gt;
   BEGIN&lt;br /&gt;
     INSERT INTO t VALUES(10,20);&lt;br /&gt;
     INSERT INTO t VALUES(30,40);&lt;br /&gt;
     RAISE NOTICE &#039;%&#039;, (SELECT count(*) FROM t);&lt;br /&gt;
   END;&lt;br /&gt;
   $$ LANGUAGE plpgsql;&lt;br /&gt;
&lt;br /&gt;
Some data languages define relational variables, but this concept has no overlap with the common understanding of session variables.&lt;br /&gt;
&lt;br /&gt;
Inside PL/pgSQL it would be possible to configure the parser to allow the use of session variables without a variable fence (similar to ordinary PL/pgSQL variables) in expressions. This is not supported yet, but implementation should not be problematic. Outside PL, variable fences could in principle be optional when there is no risk of collisions (for example, in expressions without subselects). This has not been implemented yet.&lt;br /&gt;
&lt;br /&gt;
==== Assign Command LET ====&lt;br /&gt;
&lt;br /&gt;
The value of a session variable can be assigned either by a dedicated LET command or by the SELECT INTO construct. In most systems where a special command is needed, the keyword SET is used (MySQL, MSSQL, SQL/PSM, DB2). In PostgreSQL, SET could be extended to support a_expr at the parser level, but this would require partially rewriting the current implementation of the SET command. Technically, this should not be a problem.&lt;br /&gt;
&lt;br /&gt;
The main issue is the potential for collisions between GUCs and session variables. GUCs are not declared in advance, are not associated with a schema (the prefix for a custom GUC can be any non-empty string), and access is not controlled by SEARCH_PATH. A variable fence could also solve this issue, but in this case the risk is always present, so fences could not be optional. Some languages use the keyword LET for assignments. Introducing LET in PostgreSQL provides a clean way to eliminate collisions between GUCs and session variables.&lt;br /&gt;
&lt;br /&gt;
It would also be possible to allow assignment to session variables in PL code using the usual PL assignment syntax (not yet implemented). This would be beneficial when porting from PL/SQL (Oracle) and would provide a natural syntax when using session variables as PL global variables. There do not appear to be technical obstacles to implementing this. However, one important issue arises: in PL/pgSQL, only declared variables can be targets of assignment statements. If session variables were allowed here, either (a) this check could not be performed, or (b) PL functions would gain a strong dependency on session variables, similar to the dependency on types today.&lt;br /&gt;
&lt;br /&gt;
    (2025-10-02 07:42:01) postgres=# CREATE OR REPLACE FUNCTION fx() &lt;br /&gt;
    returns void as $$&lt;br /&gt;
    begin&lt;br /&gt;
      declare var mydomain;&lt;br /&gt;
    begin&lt;br /&gt;
      var := 10;&lt;br /&gt;
    end&lt;br /&gt;
    $$ language plpgsql;&lt;br /&gt;
    ERROR:  type &amp;quot;mydomain&amp;quot; does not exist&lt;br /&gt;
    LINE 4:   declare var mydomain;&lt;br /&gt;
                          ^&lt;br /&gt;
&lt;br /&gt;
While such dependencies may not be a problem (the plan cache can be invalidated correctly), they raise new questions about temporary variables. To avoid these complications, the LET command was introduced, meaning the PL/pgSQL assignment mechanism does not need to be modified.&lt;br /&gt;
&lt;br /&gt;
A major consideration is performance, both in general and when LET is used inside PL/pgSQL.&lt;br /&gt;
&lt;br /&gt;
The LET command is a PostgreSQL utility command. Like other utility commands (e.g., CREATE TABLE AS SELECT), it can be prepared. Patches exist to implement PREPARE and EXECUTE for LET (these are not included in the reduced patch set). Even though the plan cache can be used, LET may still perform worse than a PL/pgSQL assignment for two reasons:&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL supports simple expression evaluation (also supported by LET in the enhanced patch set)&lt;br /&gt;
* PL/pgSQL variables are stored in arrays and accessed via an integer offset, while session variables are stored in a hash table and accessed via a hash lookup, which is slower. This slowdown is visible only in worst-case benchmarks:&lt;br /&gt;
&lt;br /&gt;
   DO $$ declare locvar int DEFAULT 0; begin for i in 1..1000000 loop locvar := locvar + 1; end loop; end $$ &lt;br /&gt;
   DO $$ begin for i in 1..100000 loop LET sesvar := sesvar + 1; end loop; end $$&lt;br /&gt;
&lt;br /&gt;
In the reduced patch set, the LET implementation is significantly slower because simple expression evaluation is not used (although it is implemented in the extended patch set). Nevertheless, even with the slowdown, execution remains much faster than queries that touch real tables, so performance should not be an issue in practice.&lt;br /&gt;
&lt;br /&gt;
==== Notes on Implementing Session Variables as Global Temporary Objects ====&lt;br /&gt;
&lt;br /&gt;
The core of this proposal is to design session variables as global temporary objects. Unfortunately, PostgreSQL currently has no support for this type of object. Surprisingly non-trivial is the implementation of non-system objects that have a catalogue entry, but unlike other objects, keep the data purely in memory. The problem is in memory cleaning. There is no hook that could be used to catch the event when the object was 100% deleted. We can (and must - there is nothing else) catch the sinval message, however, it is often delivered as a false alarm.&lt;br /&gt;
&lt;br /&gt;
Two main issues arise with this design:&lt;br /&gt;
&lt;br /&gt;
* the possibility of using invalid values (values stored in valid memory but no longer valid in the catalog),&lt;br /&gt;
* and memory cleanup happening too early or too late.&lt;br /&gt;
&lt;br /&gt;
Each session variable is identified by name (and possibly by an OID from pg_variable). A variable may have its own data type. Values stored in memory are kept in native binary format and remain unchanged until reassigned. Important note: an OID can be reused over time --- OIDs are unique only at a single moment, and there is no guarantee that an OID will not later be assigned to a different object. This creates a real possibility of the following issue:&lt;br /&gt;
&lt;br /&gt;
* session A: CREATE VARIABLE v AS t;&lt;br /&gt;
* session B: LET v = t &#039;value&#039;;&lt;br /&gt;
* session A: DROP VARIABLE v;&lt;br /&gt;
* session B: no activity -- it got lot of sinval messages (signals), but no command is executed, and then is not possibility to handle sinval&lt;br /&gt;
* session A: CREATE VARIABLE v AS nt; -- theoretically I can get same varid and typid like in first step, although t != nt&lt;br /&gt;
* session B: SELECT v; -- crash&lt;br /&gt;
&lt;br /&gt;
To prevent crashes, each stored value must be checked carefully before use. Checking only varid and typid is not sufficient. To ensure correctness, each variable can store its creation LSN (createlsn), which is unique throughout the lifetime of the database. Before using a value, the system checks that value.varid = catalog.varid and value.createlsn = catalog.createlsn. Type changes (ALTER VARIABLE ... ALTER TYPE) are not allowed. Dependencies ensure that the value type always matches the catalog type.&lt;br /&gt;
&lt;br /&gt;
A second problem is deciding when to clean memory. Memory cannot be freed immediately after DROP VARIABLE because the command can be rolled back:&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    BEGIN;&lt;br /&gt;
    LET var := 1;&lt;br /&gt;
    DROP VARIABLE var;&lt;br /&gt;
    ROLLBACK;&lt;br /&gt;
    SELECT VARIABLE(var); -- expected 1&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    BEGIN;&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    LET var := 1;&lt;br /&gt;
    ROLLBACK;&lt;br /&gt;
    -- var should be removed from memory&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    BEGIN;&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    SAVEPOINT s1;&lt;br /&gt;
    LET var := 1;&lt;br /&gt;
    DROP VARIABLE var;&lt;br /&gt;
    ROLLBACK TO s1;&lt;br /&gt;
    COMMIT;&lt;br /&gt;
    SELECT VARIABLE(var); -- expected 1&lt;br /&gt;
&lt;br /&gt;
The simplest solution is to mark the variable as dropped and delay memory cleanup until the next transaction where variables are validated. This approach is simple and robust, although it means memory may only be freed after some delay. While this might look messy when monitoring memory usage, the catalog is transactional, and validation against the catalog ensures correctness.&lt;br /&gt;
&lt;br /&gt;
= Variabes in PostgreSQL (as of v18) =&lt;br /&gt;
&lt;br /&gt;
PostgreSQL provides relatively advanced client-side session variables in psql --- these variables use the `:` prefix. The implementation is pretty much straightforward: `psql`&#039;s parser reads tokens from input and, when it encounters a token related to a variable, replaces it with the variable&#039;s value. The syntax supports literal and identifier escaping, and variables can be used as arguments of the `\bind` command.&lt;br /&gt;
&lt;br /&gt;
`psql` variables are typeless client-side variables, available only inside `psql` or `pgbench`.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:40:18) postgres=# \set myvar ahoj&lt;br /&gt;
    (2025-09-25 09:40:33) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:14) postgres=# select :&#039;myvar&#039;;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:24) postgres=# select $1 \bind :myvar \g&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
The `\set` command allows only simple string concatenation, but it also supports execution of shell commands. When a query is executed with the `\gset` command, its result can be stored in a `psql` variable.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:41:40) postgres=# \set ctime `date`&lt;br /&gt;
    (2025-09-25 09:43:55) postgres=# \echo :ctime&lt;br /&gt;
    Thu Sep 25 09:43:55 CEST 2025&lt;br /&gt;
&lt;br /&gt;
The `\set` command is fairly limited, making complex operations unreadable or non-portable.&lt;br /&gt;
&lt;br /&gt;
This mechanism is good enough for writing tests, but it is not generally available (most users do not need it), and it is not accessible from the server side (e.g., from stored procedures). There is no simple way to access `psql` variables from an anonymous block --- a proxy custom GUC variable must be used:&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:58:38) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    (2025-09-25 09:58:43) postgres=# select set_config(&#039;myvars.myvar&#039;, :&#039;myvar&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ ahoj       │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 10:00:27) postgres=# do $$ begin raise notice &#039;%&#039;, current_setting(&#039;myvars.myvar&#039;); end $$;&lt;br /&gt;
    NOTICE:  ahoj&lt;br /&gt;
    DO&lt;br /&gt;
&lt;br /&gt;
PostgreSQL also has configuration variables (GUC - Grand Unified Configuration). These are primarily designed for PostgreSQL and extension configuration, but they can be used as session variables. GUC variables can be set with the `SET` command or the `set_config` function, and read with `SHOW` or `current_setting`.&lt;br /&gt;
&lt;br /&gt;
GUC variables are meant for holding configuration parameters. When created through the internal API, they can be restricted to superusers, hidden from regular users, and assigned specific types (boolean, memory, number, interval, string, enum) with validation. This type system is separate from the SQL type system and is initialized before PostgreSQL’s SQL types.&lt;br /&gt;
&lt;br /&gt;
Variables created from SQL must be &amp;quot;custom&amp;quot; variables, using a prefix in their name --- these are always strings. Such custom GUCs are often used as a workaround for missing server-side session variables in PostgreSQL, but they are typeless, unprotected, and unsafe.&lt;br /&gt;
&lt;br /&gt;
A notable feature of PostgreSQL GUCs (built-in or custom) is that they can have different scopes: transaction, session, or function execution. They can also be assigned default values at specific events --- configuration loading, role login, database login, or function start. These features are useful for configuration, but problematic when GUCs are repurposed as session variables.&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
    RETURNS void AS $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, current_setting(&#039;my.x&#039;);&lt;br /&gt;
    END $$ LANGUAGE plpgsql SET my.x = 20;&lt;br /&gt;
    CREATE FUNCTION&lt;br /&gt;
    (2025-09-27 06:10:37) postgres=# SET my.x = 0; SELECT fx();&lt;br /&gt;
    SET&lt;br /&gt;
    NOTICE:  20&lt;br /&gt;
    ┌────┐&lt;br /&gt;
    │ fx │&lt;br /&gt;
    ╞════╡&lt;br /&gt;
    │    │&lt;br /&gt;
    └────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    (2025-09-27 06:10:39) postgres=# SHOW my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 0    │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
GUC variables are transactional, and this behavior cannot be disabled.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-27 06:17:31) postgres=# set my.x = 10;&lt;br /&gt;
    SET&lt;br /&gt;
    (2025-09-27 06:19:42) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:19:46) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, true);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:19:55) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:01) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:20:11) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:13) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:20:38) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:45) postgres=# rollback;&lt;br /&gt;
    ROLLBACK&lt;br /&gt;
    (2025-09-27 06:20:52) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:55) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:22:36) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:22:39) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:22:42) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
As a result, there is no reliable way to store details such as error information in custom GUCs.&lt;br /&gt;
&lt;br /&gt;
= Session Variables in Other Systems =&lt;br /&gt;
&lt;br /&gt;
This section surveys how session variables are implemented in several widely used database systems. Each subsection includes code examples to illustrate syntax, behaviour, and scope.&lt;br /&gt;
&lt;br /&gt;
== MySQL ==&lt;br /&gt;
&lt;br /&gt;
Session variables in MySQL have syntax similar to the better-known T-SQL variables (though other details are MySQL-specific). MySQL distinguishes two kinds of variables:&lt;br /&gt;
&lt;br /&gt;
* system variables - referenced using the `@@` prefix (or `@@scope.name`)&lt;br /&gt;
* user-defined variables - referenced using the `@` prefix&lt;br /&gt;
&lt;br /&gt;
System variables are modified with the `SET` statement. The `SET` statement accepts the modifiers `GLOBAL`, `SESSION`, or `PERSIST`. Some variables can only be set using the `GLOBAL` or `SESSION` modifier; when the variable name is specified with the `@@` prefix, it is not necessary to explicitly indicate the scope. System variables are defined by MySQL or by MySQL plugins.&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL some_config = 100;&lt;br /&gt;
    SET @@same_config = 100;&lt;br /&gt;
    &lt;br /&gt;
    SET SESSION sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@SESSION.sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
&lt;br /&gt;
Persistent values are stored in `mysqld-auto.cnf` (in the server data directory).&lt;br /&gt;
&lt;br /&gt;
Resetting system variables can be done by assigning DEFAULT or by copying from the GLOBAL namespace:&lt;br /&gt;
&lt;br /&gt;
    SET @@sql_mode = DEFAULT;&lt;br /&gt;
    SET @@sql_mode = @@GLOBAL.sql_mode&lt;br /&gt;
&lt;br /&gt;
PostgreSQL equivalents:&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL ; -- there is not similar command in Postgres&lt;br /&gt;
    SET SESSION ; -- SET&lt;br /&gt;
    SET PERSIST ; -- ALTER SYSTEM; SELECT pg_reload_conf();&lt;br /&gt;
    SELECT @@var; -- SELECT current_setting(&#039;var);&lt;br /&gt;
&lt;br /&gt;
User-defined variables are created by assignment:&lt;br /&gt;
&lt;br /&gt;
    SET @var = expr [, @var2 = expr [ ... ]];&lt;br /&gt;
&lt;br /&gt;
User-defined variables can only be of type integer, decimal, float, binary, non-binary string, or NULL. Casting between these types is implicit and hidden. User-defined variables cannot be used as table or column names.&lt;br /&gt;
&lt;br /&gt;
An advantage of the T-SQL-style syntax (with special prefixes) is that it provides a simple solution for avoiding identifier collisions. MySQL variables are convenient, and the user experience can be good for people already familiar with T-SQL (MSSQL or Sybase). On the other hand, prefix-based syntax can be confusing for users coming from systems other than MSSQL. The type system is perhaps too simple and can be unsafe. In general, the performance of MySQL (or MariaDB) stored procedures is not good, and stored procedures are therefore not frequently used. There is no protection against typographical errors. Unassigned user variables can be used without error (they default to NULL), which makes static checking of stored procedures impossible in this area.&lt;br /&gt;
&lt;br /&gt;
There is no way to restrict access to user-defined variables in MySQL. They cannot be protected against unintended usage. The only possible safeguard is a naming convention, since all user-defined variables share a single namespace.&lt;br /&gt;
&lt;br /&gt;
== Oracle ==&lt;br /&gt;
&lt;br /&gt;
Oracle PL/SQL allows the use of package variables. PL/SQL is based on the Ada language, and package variables act as &amp;quot;global&amp;quot; variables. They are not directly visible from SQL, but Oracle provides a reduced syntax for functions without arguments, so you typically write a wrapper:&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE PACKAGE my_package&lt;br /&gt;
    AS&lt;br /&gt;
      FUNCTION get_a RETURN NUMBER;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    /&lt;br /&gt;
    &lt;br /&gt;
    CREATE OR REPLACE PACKAGE BODY my_package&lt;br /&gt;
    AS&lt;br /&gt;
      a  NUMBER(20);&lt;br /&gt;
      &lt;br /&gt;
      FUNCTION get_a&lt;br /&gt;
      RETURN NUMBER&lt;br /&gt;
      IS&lt;br /&gt;
      BEGIN&lt;br /&gt;
        RETURN a;&lt;br /&gt;
      END get_a;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    &lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
Inside SQL, SQL identifiers take precedence. Inside non-SQL commands (such as CALL or PL/SQL blocks), package identifiers take precedence.&lt;br /&gt;
&lt;br /&gt;
Oracle allows both syntaxes for calling a function with zero arguments:&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a() FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
This reduces the risk of naming collisions. Package variables persist for the duration of a session.&lt;br /&gt;
&lt;br /&gt;
Another possibility is to use variables in SQL*Plus (similar to `psql` variables, but with the ability to define the type on the server side).&lt;br /&gt;
&lt;br /&gt;
A variable is declared with the `VARIABLE` command and can be accessed in the session using the `:varname` syntax (a prior declaration step may be optional):&lt;br /&gt;
&lt;br /&gt;
    VARIABLE bv_variable_name VARCHAR2(30)&lt;br /&gt;
    BEGIN&lt;br /&gt;
      :bv_variable_name := &#039;Some Value&#039;;&lt;br /&gt;
    END;&lt;br /&gt;
    &lt;br /&gt;
    SELECT column_name&lt;br /&gt;
      FROM   table_name&lt;br /&gt;
     WHERE  column_name = :bv_variable_name;&lt;br /&gt;
&lt;br /&gt;
== DB2 ==&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;user-defined global variables&amp;quot; in DB2 are similar to the proposed PostgreSQL feature. The main difference is in the access rights: DB2 uses `READ` and `WRITE`, while PostgreSQL uses `SELECT` and `UPDATE`. Because PostgreSQL already uses the `SET` command for GUCs, the `LET` command was introduced in the proposal (DB2 uses `SET`).&lt;br /&gt;
&lt;br /&gt;
Variables are visible in all sessions, but their values are private to each session. Variables are not transactional. Their usage is broader than in the proposal: they can be changed by `SET`, `SELECT INTO`, or used as OUT parameters of procedures. The search path (or a similar mechanism) applies to variables as well, but variables have lower priority than tables or columns.&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE myCounter INT DEFAULT 01;&lt;br /&gt;
    SELECT EMPNO, LASTNAME, CASE WHEN myCounter = 1 THEN SALARY ELSE NULL END&lt;br /&gt;
    FROM EMPLOYEE WHERE WORKDEPT = &#039;A00&#039;;&lt;br /&gt;
    SET myCounter = 29;&lt;br /&gt;
&lt;br /&gt;
There also appear to be other kinds of variables, accessed using the function `GETVARIABLE(&#039;name&#039;, &#039;default&#039;)`. These are very similar to PostgreSQL GUCs and the `current_setting` function. Such variables can be set via the connection string, are of type `VARCHAR`, and up to 10 values are allowed. Built-in session variables (configuration) can also be accessed using `GETVARIABLE`.&lt;br /&gt;
&lt;br /&gt;
== MSSQL (T-SQL) ==&lt;br /&gt;
&lt;br /&gt;
MSSQL provides two types of variables:&lt;br /&gt;
&lt;br /&gt;
* Global variables — accessed with the `@@varname` syntax. These are similar to GUCs and provide state information such as `@@ERROR`, `@@ROWCOUNT`, or `@@IDENTITY`. &lt;br /&gt;
* Local variables — accessed with the `@varname` syntax. These must be declared with the `DECLARE` command before use. Their scope is limited to the batch, procedure, or function where they are executed.&lt;br /&gt;
&lt;br /&gt;
    DECLARE @TestVariable AS VARCHAR(100)&lt;br /&gt;
    SET @TestVariable = &#039;Think Green&#039;&lt;br /&gt;
    GO&lt;br /&gt;
    PRINT @TestVariable&lt;br /&gt;
&lt;br /&gt;
This script fails because `PRINT` is executed in a different batch. Therefore, it seems MSSQL does not truly support session variables.&lt;br /&gt;
&lt;br /&gt;
There are also mechanisms similar to PostgreSQL&#039;s custom GUCs and the use of `current_setting` and `set_config` functions. In general, MSSQL is fairly primitive in this area.&lt;br /&gt;
&lt;br /&gt;
    EXEC sp_set_session_context &#039;user_id&#039;, 4;&lt;br /&gt;
    SELECT SESSION_CONTEXT(N&#039;user_id&#039;);&lt;br /&gt;
&lt;br /&gt;
= ToDo =&lt;br /&gt;
&lt;br /&gt;
* SECURITY LABEL support — enhance the `dummy_seclabel` module and the `sepgsql` extension. Update PostgreSQL documentation and ensure `SecLabelSupportsObjectType` includes session variables.&lt;/div&gt;</summary>
		<author><name>Okbobcz</name></author>
	</entry>
	<entry>
		<id>https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=42496</id>
		<title>Implementation of declarative catalog session variables</title>
		<link rel="alternate" type="text/html" href="https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=42496"/>
		<updated>2026-01-01T07:38:08Z</updated>

		<summary type="html">&lt;p&gt;Okbobcz: /* Introduction */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Session variables are database objects that can hold a value across multiple queries inside a session. The design of session variables varies widely, and many databases implement more than one type of session variable.&lt;br /&gt;
&lt;br /&gt;
The SQL/PSM standard introduces modules and module-level variables. However, this part of the standard has not been implemented in any widely used product. As a result, each database system has developed its own proprietary design, syntax, and feature set.&lt;br /&gt;
&lt;br /&gt;
Session variables are often part of the stored procedure environment, but there are exceptions --- for example, MySQL session variables or client-side session variables in tools like psql. In most systems, session variables do not participate in transactions: once set, their value persists until explicitly changed, even if the surrounding transaction is rolled back. Conceptually, they behave much like variables in general-purpose programming languages.&lt;br /&gt;
&lt;br /&gt;
Because there is no common design, comparisons across systems are difficult. Each implementation has its own advantages and disadvantages, and in some cases a single system supports more than one kind of session variable.&lt;br /&gt;
&lt;br /&gt;
Kinds of session variables:&lt;br /&gt;
* client side (psql, pgadmin, sql plus, ...) - mostly similar to shell variables (based on string operations)&lt;br /&gt;
* server side&lt;br /&gt;
** declarative&lt;br /&gt;
*** created only for batch (T-SQL)&lt;br /&gt;
*** created as an database object (or part of database object) (Oracle, DB2)&lt;br /&gt;
** created by usage (MySQL)&lt;br /&gt;
** with priprietary syntax for identifiers (MySQL, T-SQL)&lt;br /&gt;
** with ANSI SQL syntax for identifiers (Oracle, DB2)&lt;br /&gt;
** Inside only PL (Oracle)&lt;br /&gt;
** Inside db engine (MySQL, T-SQL, DB2)&lt;br /&gt;
&lt;br /&gt;
Another classification:&lt;br /&gt;
* external (are not part of SQL)&lt;br /&gt;
** server side (Oracle package variables)&lt;br /&gt;
** client side (psql, sql plus)&lt;br /&gt;
* native (are part of SQL)&lt;br /&gt;
** persistent (DB2)&lt;br /&gt;
** declarative (T-SQL)&lt;br /&gt;
** implicit (MySQL)&lt;br /&gt;
&lt;br /&gt;
Possible uses of session variables include:&lt;br /&gt;
&lt;br /&gt;
* Acting as global variables in PL code (for tracing, debugging, or quick hacks),&lt;br /&gt;
* Storing configuration values for PL routines,&lt;br /&gt;
* Keeping frequently used data handy in an interactive session,&lt;br /&gt;
* Storing credentials for Row-Level Security (RLS) and other data-protection mechanisms,&lt;br /&gt;
* Assisting with migrations from other systems (especially Oracle),&lt;br /&gt;
* Enabling parameterization of anonymous code blocks (specific to PostgreSQL).&lt;br /&gt;
* Session variables are writable even on read-only hot standby servers in PostgreSQL.&lt;br /&gt;
&lt;br /&gt;
They behave somewhat like temporary tables, but there are important differences:&lt;br /&gt;
&lt;br /&gt;
* Variables hold single values, while tables hold sets of rows.&lt;br /&gt;
* The syntax for using variables is shorter and more convenient for interactive work.&lt;br /&gt;
* Access (read/write) to variables is generally faster.&lt;br /&gt;
* The contents of session variables are typically non-transactional, whereas temporary tables are fully transactional.&lt;br /&gt;
* A session variable stores only a single value or NULL (no MVCC, no VACUUM).&lt;br /&gt;
* Temporary tables follow MVCC rules; repeated updates within a transaction generate many dead tuples, making updates more expensive.&lt;br /&gt;
* Temporary tables are not cleaned up by autovacuum, and it is not possible to run VACUUM inside a function.&lt;br /&gt;
* PostgreSQL does not support global temporary tables, so using a temporary table just to store a single value is inefficient and can lead to catalog bloat.&lt;br /&gt;
&lt;br /&gt;
= SQL/PSM =&lt;br /&gt;
&lt;br /&gt;
The SQL standard introduces the concept of modules, which can be associated with schemas (partially). Variables in modules behave like PL/pgSQL variables but are only local. The only objects allowed at the module level are temporary tables, which can be accessed only from routines assigned to that module. Modules can encapsulate private objects (functions, procedure, temp tables), that are not visible outside the module. Inside the module, a  private objects shadows other objects. What I know the modules (and generally SQL/PSM) doesn&#039;t cover a deployment of code, so there is not similarity with PostgreSQL extensions. SQL/PSM Modules are modules like modules from modular languages like Modula2 or Oberon. There is some similarity with Postgres schemas (both are containers) with significant differences (there is not a concept of SEARCH_PATH (on PSM side) or there is not a concept of private or public objects (on Postgres schema side)).&lt;br /&gt;
&lt;br /&gt;
SQL/PSM variables are not transactional (they are used exactly like in PL/pgSQL). The precedence between variables and columns is not specified.&lt;br /&gt;
&lt;br /&gt;
= Implementation Challenges =&lt;br /&gt;
&lt;br /&gt;
* Possible collisions between session variables and other database objects.&lt;br /&gt;
* Memory cleanup after dropping a session variable, especially when DDL operations can be reverted (Postgres has not support for global temp objects).&lt;br /&gt;
* Memory invalidation: if a variable is dropped by one session, other sessions should not continue using it (Postgres has not support for global temp objects).&lt;br /&gt;
&lt;br /&gt;
= Proposed Implementations =&lt;br /&gt;
&lt;br /&gt;
It is clear that no single design is perfect for all use cases. MySQL’s approach is convenient and useful for interactive work, but it cannot be used for storing sensitive data and is considered unsafe. PostgreSQL GUCs are excellent for configuration, but they are not well suited for interactive use and are very limited when used as global variables in PL code.&lt;br /&gt;
&lt;br /&gt;
Because of these trade-offs, no single design is sufficient. In practice, having multiple designs within a single system could support a broader range of use cases more effectively.&lt;br /&gt;
&lt;br /&gt;
== Design session variables as database global temporary typed objects with ANSI SQL syntax for identifiers ==&lt;br /&gt;
&lt;br /&gt;
Author: Pavel Stehule &amp;lt;pavel.stehule@gmail.com&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I searched for a solution that could work well with the specific PostgreSQL environment:&lt;br /&gt;
&lt;br /&gt;
* PostgreSQL has more than one widely used stored procedure language: PL/pgSQL, PL/Python, or PL/Perl.&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL is a reduced version of PL/SQL, and PL/SQL is a modified ADA language. I am pretty happy with the current complexity of PL/pgSQL --- it is a domain-specific language that works well. It is very simple and easy to learn. PL/pgSQL has no packages or modules; instead PostgreSQL schemas are used. But schemas are database objects in their own right, independent of PL.&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL has not own data types, own operators library (language library), own dependency tracking. This is main difference from PL/SQL. PL/pgSQL is just very simple interpret that fully use types, functions, operators, dependency tracking implemented by Postgres for SQL support. Is not possible to implement variables with larger scope than transactions without Postgres dependency tracking. &lt;br /&gt;
&lt;br /&gt;
* PostgreSQL already has a code container --- the schema. I don&#039;t want to introduce another container object (modules), as this would overlap heavily with schemas. Implementing modules for PL/pgSQL could be useful, but doing it properly would be quite complex. The main problem is the concept of public and private objects --- PostgreSQL does not have this. Don&#039;t forget: every PL/pgSQL expression is a SQL expression, so public/private visibility would first need to be implemented internally in PostgreSQL. That would be redundant (and possibly in conflict) with SEARCH_PATH.&lt;br /&gt;
&lt;br /&gt;
So I wanted a design that supports multiple PL languages, does not add complexity to the relatively simple PL/pgSQL, and does not duplicate functionality already available.  &lt;br /&gt;
&lt;br /&gt;
I wrote a larger application in PL/pgSQL. To maintain it, I created plpgsql_lint (later renamed plpgsql_check). So I am always looking for solutions that do not block static code analysis. Static analysis requires persistent objects, which naturally leads to designing session variables as database objects (with their own system catalog).  &lt;br /&gt;
When session variables are database objects, ACLs can be applied naturally. Oracle package variables are well protected by package scope. PostgreSQL, however, has no schema scope --- schema locality is controlled by the SEARCH_PATH GUC, and introducing another mechanism would be messy. But with ACLs, variable contents can also be secured:&lt;br /&gt;
&lt;br /&gt;
    CREATE TEMP VARIABLE x AS int;&lt;br /&gt;
     &lt;br /&gt;
    LET x = 10;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(x);&lt;br /&gt;
    END;&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    SELECT VARIABLE(x);&lt;br /&gt;
&lt;br /&gt;
==== Collisions between column and variable identifiers ====&lt;br /&gt;
&lt;br /&gt;
There is a risk of identifier collisions between variables and columns. With ANSI SQL syntax for session variable identifiers, the problem is how to distinguish variables from columns. This is a known issue in PL/pgSQL and PL/SQL and can be solved either by prioritization or by prohibiting collisions.  &lt;br /&gt;
&lt;br /&gt;
Prohibiting collisions has largely solved this problem in PL/pgSQL. Unfortunately, session variables are global objects, so collisions can occur in any query. A poorly named variable could break queries unexpectedly. Prioritization also has problems --- a variable or a column could be used silently when the other was intended.&lt;br /&gt;
&lt;br /&gt;
    CREATE TABLE tab(a int);&lt;br /&gt;
    CREATE VARIABLE a AS int;&lt;br /&gt;
    &lt;br /&gt;
    SELECT a FROM tab; -- with disallowed collisions, this query fails when a variable named &amp;quot;a&amp;quot; exists&lt;br /&gt;
    &lt;br /&gt;
    LET a = 1;&lt;br /&gt;
    DELETE FROM tab WHERE a = 1; -- with higher priority for variables, all rows could be deleted&lt;br /&gt;
    SELECT a FROM tab; -- with higher priority for columns, the variable is ignored&lt;br /&gt;
&lt;br /&gt;
Even with collision prohibition, mistakes are still possible:&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE _id AS int;&lt;br /&gt;
    LET _id = 10;&lt;br /&gt;
    CREATE TABLE foo(id int);&lt;br /&gt;
    DELETE FROM foo WHERE _id = 10; -- just a typo, but all rows are deleted&lt;br /&gt;
&lt;br /&gt;
These problems are not new. Developers in PL/pgSQL or PL/SQL know them, but the scope of risk is normally limited to stored procedures. After many discussions and designs, I introduced &amp;quot;variable fences&amp;quot; (the syntax VARIABLE(varname)). Inside any query, variables can be used only inside a variable fence. This completely solves collisions and reduces the chance of human error. Currently, fences are required everywhere in queries and expressions. In the future, they might be made optional inside PL/pgSQL, to reduce overhead when porting Oracle applications. This could be controlled by a new plpgsql.extra_checks setting.&lt;br /&gt;
&lt;br /&gt;
A variable fence can collide with a user-defined function named `variable`. This is similar to keyword conflicts such as CUBE. It can be resolved by schema-qualifying or quoting:&lt;br /&gt;
&lt;br /&gt;
    SELECT public.variable(...)&lt;br /&gt;
    SELECT &amp;quot;variable&amp;quot;(...)&lt;br /&gt;
&lt;br /&gt;
Why not use T-SQL (MSSQL, MySQL) syntax for session variables? There are several reasons – some objective, one subjective:&lt;br /&gt;
&lt;br /&gt;
* There is a collision with custom operators. Operators `@` and `@@` are already used, and `@@` is common:&lt;br /&gt;
&lt;br /&gt;
    (2025-09-28 06:57:30) postgres=# create table tab(a int);&lt;br /&gt;
    CREATE TABLE&lt;br /&gt;
    (2025-09-28 06:57:42) postgres=# insert into tab values(10);&lt;br /&gt;
    INSERT 0 1&lt;br /&gt;
    (2025-09-28 06:57:50) postgres=# select @a from tab;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │       10 │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
We could safely use only `:` as a prefix, but that would conflict with psql variables. Similarly, `$` would conflict with query parameters.&lt;br /&gt;
&lt;br /&gt;
* T-SQL variable names look strange in PostgreSQL (in queries and in PL/pgSQL). PostgreSQL uses only ANSI/SQL identifiers, and PL/pgSQL follows the same rules. PostgreSQL is consistent here, and I see little motivation to introduce a new, non-standard syntax (especially since it could cause compatibility issues).&lt;br /&gt;
&lt;br /&gt;
Note: There was also a proposal to use table syntax for variables (similar to T-SQL in-memory table variables, but restricted to a single row). This is very effective for avoiding collisions, but I dislike it. It complicates simple expressions, adds inconsistency, and looks odd for scalar variables (though it could be useful for composite variables). This design does not solve every problem --- for example, variables can still be shadowed by tables because of SEARCH_PATH (a known issue).&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(var); -- variable is not a table&lt;br /&gt;
    END&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      -- variable as a table (I don&#039;t like it)&lt;br /&gt;
      -- can we use DML? How many rows can it hold?&lt;br /&gt;
      -- is the value a row or not?&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, (SELECT var FROM var);&lt;br /&gt;
    END&lt;br /&gt;
    $$&lt;br /&gt;
&lt;br /&gt;
I am not against introducing in-memory tables --- or more correctly, global temporary tables. A well-designed implementation could be very useful. But this is a different feature and should be designed separately.  &lt;br /&gt;
&lt;br /&gt;
There are also performance considerations: PL/pgSQL can use simple expression evaluation when no tables are referenced. This optimization would need to change, which touches sensitive code. It would also be inconsistent.  &lt;br /&gt;
&lt;br /&gt;
I would like global temporary tables (which PostgreSQL currently lacks). Tables should behave like tables, and variables should behave like variables. The natural mental model for session variables comes from variables in PL languages. PostgreSQL internally supports *transition tables*, and I can imagine allowing these outside of triggers. That would be valid, but again, it is a different feature.  &lt;br /&gt;
&lt;br /&gt;
Implementing declared tables inside PL/pgSQL would be simpler from a high-level design perspective (interaction between SQL tables and PL/pgSQL is already well defined), but difficult from a low-level perspective (statistics, optimizer data, etc. --- the same issues as with global temporary tables, unless we add fake proxy local temp tables).&lt;br /&gt;
&lt;br /&gt;
   CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
   AS $$&lt;br /&gt;
   -- not implemented yet, not part of this proposal&lt;br /&gt;
   DECLARE t TABLE(a int, b int); -- this table is invisible outside fx&lt;br /&gt;
   BEGIN&lt;br /&gt;
     INSERT INTO t VALUES(10,20);&lt;br /&gt;
     INSERT INTO t VALUES(30,40);&lt;br /&gt;
     RAISE NOTICE &#039;%&#039;, (SELECT count(*) FROM t);&lt;br /&gt;
   END;&lt;br /&gt;
   $$ LANGUAGE plpgsql;&lt;br /&gt;
&lt;br /&gt;
Some data languages define relational variables, but this concept has no overlap with the common understanding of session variables.&lt;br /&gt;
&lt;br /&gt;
Inside PL/pgSQL it would be possible to configure the parser to allow the use of session variables without a variable fence (similar to ordinary PL/pgSQL variables) in expressions. This is not supported yet, but implementation should not be problematic. Outside PL, variable fences could in principle be optional when there is no risk of collisions (for example, in expressions without subselects). This has not been implemented yet.&lt;br /&gt;
&lt;br /&gt;
==== Assign Command LET ====&lt;br /&gt;
&lt;br /&gt;
The value of a session variable can be assigned either by a dedicated LET command or by the SELECT INTO construct. In most systems where a special command is needed, the keyword SET is used (MySQL, MSSQL, SQL/PSM, DB2). In PostgreSQL, SET could be extended to support a_expr at the parser level, but this would require partially rewriting the current implementation of the SET command. Technically, this should not be a problem.&lt;br /&gt;
&lt;br /&gt;
The main issue is the potential for collisions between GUCs and session variables. GUCs are not declared in advance, are not associated with a schema (the prefix for a custom GUC can be any non-empty string), and access is not controlled by SEARCH_PATH. A variable fence could also solve this issue, but in this case the risk is always present, so fences could not be optional. Some languages use the keyword LET for assignments. Introducing LET in PostgreSQL provides a clean way to eliminate collisions between GUCs and session variables.&lt;br /&gt;
&lt;br /&gt;
It would also be possible to allow assignment to session variables in PL code using the usual PL assignment syntax (not yet implemented). This would be beneficial when porting from PL/SQL (Oracle) and would provide a natural syntax when using session variables as PL global variables. There do not appear to be technical obstacles to implementing this. However, one important issue arises: in PL/pgSQL, only declared variables can be targets of assignment statements. If session variables were allowed here, either (a) this check could not be performed, or (b) PL functions would gain a strong dependency on session variables, similar to the dependency on types today.&lt;br /&gt;
&lt;br /&gt;
    (2025-10-02 07:42:01) postgres=# CREATE OR REPLACE FUNCTION fx() &lt;br /&gt;
    returns void as $$&lt;br /&gt;
    begin&lt;br /&gt;
      declare var mydomain;&lt;br /&gt;
    begin&lt;br /&gt;
      var := 10;&lt;br /&gt;
    end&lt;br /&gt;
    $$ language plpgsql;&lt;br /&gt;
    ERROR:  type &amp;quot;mydomain&amp;quot; does not exist&lt;br /&gt;
    LINE 4:   declare var mydomain;&lt;br /&gt;
                          ^&lt;br /&gt;
&lt;br /&gt;
While such dependencies may not be a problem (the plan cache can be invalidated correctly), they raise new questions about temporary variables. To avoid these complications, the LET command was introduced, meaning the PL/pgSQL assignment mechanism does not need to be modified.&lt;br /&gt;
&lt;br /&gt;
A major consideration is performance, both in general and when LET is used inside PL/pgSQL.&lt;br /&gt;
&lt;br /&gt;
The LET command is a PostgreSQL utility command. Like other utility commands (e.g., CREATE TABLE AS SELECT), it can be prepared. Patches exist to implement PREPARE and EXECUTE for LET (these are not included in the reduced patch set). Even though the plan cache can be used, LET may still perform worse than a PL/pgSQL assignment for two reasons:&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL supports simple expression evaluation (also supported by LET in the enhanced patch set)&lt;br /&gt;
* PL/pgSQL variables are stored in arrays and accessed via an integer offset, while session variables are stored in a hash table and accessed via a hash lookup, which is slower. This slowdown is visible only in worst-case benchmarks:&lt;br /&gt;
&lt;br /&gt;
   DO $$ declare locvar int DEFAULT 0; begin for i in 1..1000000 loop locvar := locvar + 1; end loop; end $$ &lt;br /&gt;
   DO $$ begin for i in 1..100000 loop LET sesvar := sesvar + 1; end loop; end $$&lt;br /&gt;
&lt;br /&gt;
In the reduced patch set, the LET implementation is significantly slower because simple expression evaluation is not used (although it is implemented in the extended patch set). Nevertheless, even with the slowdown, execution remains much faster than queries that touch real tables, so performance should not be an issue in practice.&lt;br /&gt;
&lt;br /&gt;
==== Notes on Implementing Session Variables as Global Temporary Objects ====&lt;br /&gt;
&lt;br /&gt;
The core of this proposal is to design session variables as global temporary objects. Unfortunately, PostgreSQL currently has no support for this type of object. Surprisingly non-trivial is the implementation of non-system objects that have a catalogue entry, but unlike other objects, keep the data purely in memory. The problem is in memory cleaning. There is no hook that could be used to catch the event when the object was 100% deleted. We can (and must - there is nothing else) catch the sinval message, however, it is often delivered as a false alarm.&lt;br /&gt;
&lt;br /&gt;
Two main issues arise with this design:&lt;br /&gt;
&lt;br /&gt;
* the possibility of using invalid values (values stored in valid memory but no longer valid in the catalog),&lt;br /&gt;
* and memory cleanup happening too early or too late.&lt;br /&gt;
&lt;br /&gt;
Each session variable is identified by name (and possibly by an OID from pg_variable). A variable may have its own data type. Values stored in memory are kept in native binary format and remain unchanged until reassigned. Important note: an OID can be reused over time --- OIDs are unique only at a single moment, and there is no guarantee that an OID will not later be assigned to a different object. This creates a real possibility of the following issue:&lt;br /&gt;
&lt;br /&gt;
* session A: CREATE VARIABLE v AS t;&lt;br /&gt;
* session B: LET v = t &#039;value&#039;;&lt;br /&gt;
* session A: DROP VARIABLE v;&lt;br /&gt;
* session B: no activity -- it got lot of sinval messages (signals), but no command is executed, and then is not possibility to handle sinval&lt;br /&gt;
* session A: CREATE VARIABLE v AS nt; -- theoretically I can get same varid and typid like in first step, although t != nt&lt;br /&gt;
* session B: SELECT v; -- crash&lt;br /&gt;
&lt;br /&gt;
To prevent crashes, each stored value must be checked carefully before use. Checking only varid and typid is not sufficient. To ensure correctness, each variable can store its creation LSN (createlsn), which is unique throughout the lifetime of the database. Before using a value, the system checks that value.varid = catalog.varid and value.createlsn = catalog.createlsn. Type changes (ALTER VARIABLE ... ALTER TYPE) are not allowed. Dependencies ensure that the value type always matches the catalog type.&lt;br /&gt;
&lt;br /&gt;
A second problem is deciding when to clean memory. Memory cannot be freed immediately after DROP VARIABLE because the command can be rolled back:&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    BEGIN;&lt;br /&gt;
    LET var := 1;&lt;br /&gt;
    DROP VARIABLE var;&lt;br /&gt;
    ROLLBACK;&lt;br /&gt;
    SELECT VARIABLE(var); -- expected 1&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    BEGIN;&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    LET var := 1;&lt;br /&gt;
    ROLLBACK;&lt;br /&gt;
    -- var should be removed from memory&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    BEGIN;&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    SAVEPOINT s1;&lt;br /&gt;
    LET var := 1;&lt;br /&gt;
    DROP VARIABLE var;&lt;br /&gt;
    ROLLBACK TO s1;&lt;br /&gt;
    COMMIT;&lt;br /&gt;
    SELECT VARIABLE(var); -- expected 1&lt;br /&gt;
&lt;br /&gt;
The simplest solution is to mark the variable as dropped and delay memory cleanup until the next transaction where variables are validated. This approach is simple and robust, although it means memory may only be freed after some delay. While this might look messy when monitoring memory usage, the catalog is transactional, and validation against the catalog ensures correctness.&lt;br /&gt;
&lt;br /&gt;
= Variabes in PostgreSQL (as of v18) =&lt;br /&gt;
&lt;br /&gt;
PostgreSQL provides relatively advanced client-side session variables in psql --- these variables use the `:` prefix. The implementation is pretty much straightforward: `psql`&#039;s parser reads tokens from input and, when it encounters a token related to a variable, replaces it with the variable&#039;s value. The syntax supports literal and identifier escaping, and variables can be used as arguments of the `\bind` command.&lt;br /&gt;
&lt;br /&gt;
`psql` variables are typeless client-side variables, available only inside `psql` or `pgbench`.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:40:18) postgres=# \set myvar ahoj&lt;br /&gt;
    (2025-09-25 09:40:33) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:14) postgres=# select :&#039;myvar&#039;;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:24) postgres=# select $1 \bind :myvar \g&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
The `\set` command allows only simple string concatenation, but it also supports execution of shell commands. When a query is executed with the `\gset` command, its result can be stored in a `psql` variable.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:41:40) postgres=# \set ctime `date`&lt;br /&gt;
    (2025-09-25 09:43:55) postgres=# \echo :ctime&lt;br /&gt;
    Thu Sep 25 09:43:55 CEST 2025&lt;br /&gt;
&lt;br /&gt;
The `\set` command is fairly limited, making complex operations unreadable or non-portable.&lt;br /&gt;
&lt;br /&gt;
This mechanism is good enough for writing tests, but it is not generally available (most users do not need it), and it is not accessible from the server side (e.g., from stored procedures). There is no simple way to access `psql` variables from an anonymous block --- a proxy custom GUC variable must be used:&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:58:38) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    (2025-09-25 09:58:43) postgres=# select set_config(&#039;myvars.myvar&#039;, :&#039;myvar&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ ahoj       │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 10:00:27) postgres=# do $$ begin raise notice &#039;%&#039;, current_setting(&#039;myvars.myvar&#039;); end $$;&lt;br /&gt;
    NOTICE:  ahoj&lt;br /&gt;
    DO&lt;br /&gt;
&lt;br /&gt;
PostgreSQL also has configuration variables (GUC - Grand Unified Configuration). These are primarily designed for PostgreSQL and extension configuration, but they can be used as session variables. GUC variables can be set with the `SET` command or the `set_config` function, and read with `SHOW` or `current_setting`.&lt;br /&gt;
&lt;br /&gt;
GUC variables are meant for holding configuration parameters. When created through the internal API, they can be restricted to superusers, hidden from regular users, and assigned specific types (boolean, memory, number, interval, string, enum) with validation. This type system is separate from the SQL type system and is initialized before PostgreSQL’s SQL types.&lt;br /&gt;
&lt;br /&gt;
Variables created from SQL must be &amp;quot;custom&amp;quot; variables, using a prefix in their name --- these are always strings. Such custom GUCs are often used as a workaround for missing server-side session variables in PostgreSQL, but they are typeless, unprotected, and unsafe.&lt;br /&gt;
&lt;br /&gt;
A notable feature of PostgreSQL GUCs (built-in or custom) is that they can have different scopes: transaction, session, or function execution. They can also be assigned default values at specific events --- configuration loading, role login, database login, or function start. These features are useful for configuration, but problematic when GUCs are repurposed as session variables.&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
    RETURNS void AS $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, current_setting(&#039;my.x&#039;);&lt;br /&gt;
    END $$ LANGUAGE plpgsql SET my.x = 20;&lt;br /&gt;
    CREATE FUNCTION&lt;br /&gt;
    (2025-09-27 06:10:37) postgres=# SET my.x = 0; SELECT fx();&lt;br /&gt;
    SET&lt;br /&gt;
    NOTICE:  20&lt;br /&gt;
    ┌────┐&lt;br /&gt;
    │ fx │&lt;br /&gt;
    ╞════╡&lt;br /&gt;
    │    │&lt;br /&gt;
    └────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    (2025-09-27 06:10:39) postgres=# SHOW my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 0    │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
GUC variables are transactional, and this behavior cannot be disabled.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-27 06:17:31) postgres=# set my.x = 10;&lt;br /&gt;
    SET&lt;br /&gt;
    (2025-09-27 06:19:42) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:19:46) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, true);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:19:55) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:01) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:20:11) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:13) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:20:38) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:45) postgres=# rollback;&lt;br /&gt;
    ROLLBACK&lt;br /&gt;
    (2025-09-27 06:20:52) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:55) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:22:36) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:22:39) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:22:42) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
As a result, there is no reliable way to store details such as error information in custom GUCs.&lt;br /&gt;
&lt;br /&gt;
= Session Variables in Other Systems =&lt;br /&gt;
&lt;br /&gt;
This section surveys how session variables are implemented in several widely used database systems. Each subsection includes code examples to illustrate syntax, behaviour, and scope.&lt;br /&gt;
&lt;br /&gt;
== MySQL ==&lt;br /&gt;
&lt;br /&gt;
Session variables in MySQL have syntax similar to the better-known T-SQL variables (though other details are MySQL-specific). MySQL distinguishes two kinds of variables:&lt;br /&gt;
&lt;br /&gt;
* system variables - referenced using the `@@` prefix (or `@@scope.name`)&lt;br /&gt;
* user-defined variables - referenced using the `@` prefix&lt;br /&gt;
&lt;br /&gt;
System variables are modified with the `SET` statement. The `SET` statement accepts the modifiers `GLOBAL`, `SESSION`, or `PERSIST`. Some variables can only be set using the `GLOBAL` or `SESSION` modifier; when the variable name is specified with the `@@` prefix, it is not necessary to explicitly indicate the scope. System variables are defined by MySQL or by MySQL plugins.&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL some_config = 100;&lt;br /&gt;
    SET @@same_config = 100;&lt;br /&gt;
    &lt;br /&gt;
    SET SESSION sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@SESSION.sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
&lt;br /&gt;
Persistent values are stored in `mysqld-auto.cnf` (in the server data directory).&lt;br /&gt;
&lt;br /&gt;
Resetting system variables can be done by assigning DEFAULT or by copying from the GLOBAL namespace:&lt;br /&gt;
&lt;br /&gt;
    SET @@sql_mode = DEFAULT;&lt;br /&gt;
    SET @@sql_mode = @@GLOBAL.sql_mode&lt;br /&gt;
&lt;br /&gt;
PostgreSQL equivalents:&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL ; -- there is not similar command in Postgres&lt;br /&gt;
    SET SESSION ; -- SET&lt;br /&gt;
    SET PERSIST ; -- ALTER SYSTEM; SELECT pg_reload_conf();&lt;br /&gt;
    SELECT @@var; -- SELECT current_setting(&#039;var);&lt;br /&gt;
&lt;br /&gt;
User-defined variables are created by assignment:&lt;br /&gt;
&lt;br /&gt;
    SET @var = expr [, @var2 = expr [ ... ]];&lt;br /&gt;
&lt;br /&gt;
User-defined variables can only be of type integer, decimal, float, binary, non-binary string, or NULL. Casting between these types is implicit and hidden. User-defined variables cannot be used as table or column names.&lt;br /&gt;
&lt;br /&gt;
An advantage of the T-SQL-style syntax (with special prefixes) is that it provides a simple solution for avoiding identifier collisions. MySQL variables are convenient, and the user experience can be good for people already familiar with T-SQL (MSSQL or Sybase). On the other hand, prefix-based syntax can be confusing for users coming from systems other than MSSQL. The type system is perhaps too simple and can be unsafe. In general, the performance of MySQL (or MariaDB) stored procedures is not good, and stored procedures are therefore not frequently used. There is no protection against typographical errors. Unassigned user variables can be used without error (they default to NULL), which makes static checking of stored procedures impossible in this area.&lt;br /&gt;
&lt;br /&gt;
There is no way to restrict access to user-defined variables in MySQL. They cannot be protected against unintended usage. The only possible safeguard is a naming convention, since all user-defined variables share a single namespace.&lt;br /&gt;
&lt;br /&gt;
== Oracle ==&lt;br /&gt;
&lt;br /&gt;
Oracle PL/SQL allows the use of package variables. PL/SQL is based on the Ada language, and package variables act as &amp;quot;global&amp;quot; variables. They are not directly visible from SQL, but Oracle provides a reduced syntax for functions without arguments, so you typically write a wrapper:&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE PACKAGE my_package&lt;br /&gt;
    AS&lt;br /&gt;
      FUNCTION get_a RETURN NUMBER;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    /&lt;br /&gt;
    &lt;br /&gt;
    CREATE OR REPLACE PACKAGE BODY my_package&lt;br /&gt;
    AS&lt;br /&gt;
      a  NUMBER(20);&lt;br /&gt;
      &lt;br /&gt;
      FUNCTION get_a&lt;br /&gt;
      RETURN NUMBER&lt;br /&gt;
      IS&lt;br /&gt;
      BEGIN&lt;br /&gt;
        RETURN a;&lt;br /&gt;
      END get_a;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    &lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
Inside SQL, SQL identifiers take precedence. Inside non-SQL commands (such as CALL or PL/SQL blocks), package identifiers take precedence.&lt;br /&gt;
&lt;br /&gt;
Oracle allows both syntaxes for calling a function with zero arguments:&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a() FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
This reduces the risk of naming collisions. Package variables persist for the duration of a session.&lt;br /&gt;
&lt;br /&gt;
Another possibility is to use variables in SQL*Plus (similar to `psql` variables, but with the ability to define the type on the server side).&lt;br /&gt;
&lt;br /&gt;
A variable is declared with the `VARIABLE` command and can be accessed in the session using the `:varname` syntax (a prior declaration step may be optional):&lt;br /&gt;
&lt;br /&gt;
    VARIABLE bv_variable_name VARCHAR2(30)&lt;br /&gt;
    BEGIN&lt;br /&gt;
      :bv_variable_name := &#039;Some Value&#039;;&lt;br /&gt;
    END;&lt;br /&gt;
    &lt;br /&gt;
    SELECT column_name&lt;br /&gt;
      FROM   table_name&lt;br /&gt;
     WHERE  column_name = :bv_variable_name;&lt;br /&gt;
&lt;br /&gt;
== DB2 ==&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;user-defined global variables&amp;quot; in DB2 are similar to the proposed PostgreSQL feature. The main difference is in the access rights: DB2 uses `READ` and `WRITE`, while PostgreSQL uses `SELECT` and `UPDATE`. Because PostgreSQL already uses the `SET` command for GUCs, the `LET` command was introduced in the proposal (DB2 uses `SET`).&lt;br /&gt;
&lt;br /&gt;
Variables are visible in all sessions, but their values are private to each session. Variables are not transactional. Their usage is broader than in the proposal: they can be changed by `SET`, `SELECT INTO`, or used as OUT parameters of procedures. The search path (or a similar mechanism) applies to variables as well, but variables have lower priority than tables or columns.&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE myCounter INT DEFAULT 01;&lt;br /&gt;
    SELECT EMPNO, LASTNAME, CASE WHEN myCounter = 1 THEN SALARY ELSE NULL END&lt;br /&gt;
    FROM EMPLOYEE WHERE WORKDEPT = &#039;A00&#039;;&lt;br /&gt;
    SET myCounter = 29;&lt;br /&gt;
&lt;br /&gt;
There also appear to be other kinds of variables, accessed using the function `GETVARIABLE(&#039;name&#039;, &#039;default&#039;)`. These are very similar to PostgreSQL GUCs and the `current_setting` function. Such variables can be set via the connection string, are of type `VARCHAR`, and up to 10 values are allowed. Built-in session variables (configuration) can also be accessed using `GETVARIABLE`.&lt;br /&gt;
&lt;br /&gt;
== MSSQL (T-SQL) ==&lt;br /&gt;
&lt;br /&gt;
MSSQL provides two types of variables:&lt;br /&gt;
&lt;br /&gt;
* Global variables — accessed with the `@@varname` syntax. These are similar to GUCs and provide state information such as `@@ERROR`, `@@ROWCOUNT`, or `@@IDENTITY`. &lt;br /&gt;
* Local variables — accessed with the `@varname` syntax. These must be declared with the `DECLARE` command before use. Their scope is limited to the batch, procedure, or function where they are executed.&lt;br /&gt;
&lt;br /&gt;
    DECLARE @TestVariable AS VARCHAR(100)&lt;br /&gt;
    SET @TestVariable = &#039;Think Green&#039;&lt;br /&gt;
    GO&lt;br /&gt;
    PRINT @TestVariable&lt;br /&gt;
&lt;br /&gt;
This script fails because `PRINT` is executed in a different batch. Therefore, it seems MSSQL does not truly support session variables.&lt;br /&gt;
&lt;br /&gt;
There are also mechanisms similar to PostgreSQL&#039;s custom GUCs and the use of `current_setting` and `set_config` functions. In general, MSSQL is fairly primitive in this area.&lt;br /&gt;
&lt;br /&gt;
    EXEC sp_set_session_context &#039;user_id&#039;, 4;&lt;br /&gt;
    SELECT SESSION_CONTEXT(N&#039;user_id&#039;);&lt;br /&gt;
&lt;br /&gt;
= ToDo =&lt;br /&gt;
&lt;br /&gt;
* SECURITY LABEL support — enhance the `dummy_seclabel` module and the `sepgsql` extension. Update PostgreSQL documentation and ensure `SecLabelSupportsObjectType` includes session variables.&lt;/div&gt;</summary>
		<author><name>Okbobcz</name></author>
	</entry>
	<entry>
		<id>https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=42495</id>
		<title>Implementation of declarative catalog session variables</title>
		<link rel="alternate" type="text/html" href="https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=42495"/>
		<updated>2026-01-01T07:37:11Z</updated>

		<summary type="html">&lt;p&gt;Okbobcz: /* Introduction */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Session variables are database objects that can hold a value across multiple queries inside a session. The design of session variables varies widely, and many databases implement more than one type of session variable.&lt;br /&gt;
&lt;br /&gt;
The SQL/PSM standard introduces modules and module-level variables. However, this part of the standard has not been implemented in any widely used product. As a result, each database system has developed its own proprietary design, syntax, and feature set.&lt;br /&gt;
&lt;br /&gt;
Session variables are often part of the stored procedure environment, but there are exceptions --- for example, MySQL session variables or client-side session variables in tools like psql. In most systems, session variables do not participate in transactions: once set, their value persists until explicitly changed, even if the surrounding transaction is rolled back. Conceptually, they behave much like variables in general-purpose programming languages.&lt;br /&gt;
&lt;br /&gt;
Because there is no common design, comparisons across systems are difficult. Each implementation has its own advantages and disadvantages, and in some cases a single system supports more than one kind of session variable.&lt;br /&gt;
&lt;br /&gt;
Kinds of session variables:&lt;br /&gt;
* client side (psql, pgadmin, sql plus, ...) - mostly similar to shell variables (based on string operations)&lt;br /&gt;
* server side&lt;br /&gt;
** declarative&lt;br /&gt;
*** created only for batch (T-SQL)&lt;br /&gt;
*** created as an database object (or part of database object) (Oracle, DB2)&lt;br /&gt;
** created by usage (MySQL)&lt;br /&gt;
** with priprietary syntax for identifiers (MySQL, T-SQL)&lt;br /&gt;
** with ANSI SQL syntax for identifiers (Oracle, DB2)&lt;br /&gt;
** Inside only PL (Oracle)&lt;br /&gt;
** Inside db engine (MySQL, T-SQL, DB2)&lt;br /&gt;
&lt;br /&gt;
Another classification:&lt;br /&gt;
* external (are not part of SQL)&lt;br /&gt;
** server side (Oracle package variables)&lt;br /&gt;
** client side (psql, sql plus)&lt;br /&gt;
* native (are part of SQL)&lt;br /&gt;
** dedicated kind of database objects (DB2)&lt;br /&gt;
** declarative (T-SQL)&lt;br /&gt;
** implicit (MySQL)&lt;br /&gt;
&lt;br /&gt;
Possible uses of session variables include:&lt;br /&gt;
&lt;br /&gt;
* Acting as global variables in PL code (for tracing, debugging, or quick hacks),&lt;br /&gt;
* Storing configuration values for PL routines,&lt;br /&gt;
* Keeping frequently used data handy in an interactive session,&lt;br /&gt;
* Storing credentials for Row-Level Security (RLS) and other data-protection mechanisms,&lt;br /&gt;
* Assisting with migrations from other systems (especially Oracle),&lt;br /&gt;
* Enabling parameterization of anonymous code blocks (specific to PostgreSQL).&lt;br /&gt;
* Session variables are writable even on read-only hot standby servers in PostgreSQL.&lt;br /&gt;
&lt;br /&gt;
They behave somewhat like temporary tables, but there are important differences:&lt;br /&gt;
&lt;br /&gt;
* Variables hold single values, while tables hold sets of rows.&lt;br /&gt;
* The syntax for using variables is shorter and more convenient for interactive work.&lt;br /&gt;
* Access (read/write) to variables is generally faster.&lt;br /&gt;
* The contents of session variables are typically non-transactional, whereas temporary tables are fully transactional.&lt;br /&gt;
* A session variable stores only a single value or NULL (no MVCC, no VACUUM).&lt;br /&gt;
* Temporary tables follow MVCC rules; repeated updates within a transaction generate many dead tuples, making updates more expensive.&lt;br /&gt;
* Temporary tables are not cleaned up by autovacuum, and it is not possible to run VACUUM inside a function.&lt;br /&gt;
* PostgreSQL does not support global temporary tables, so using a temporary table just to store a single value is inefficient and can lead to catalog bloat.&lt;br /&gt;
&lt;br /&gt;
= SQL/PSM =&lt;br /&gt;
&lt;br /&gt;
The SQL standard introduces the concept of modules, which can be associated with schemas (partially). Variables in modules behave like PL/pgSQL variables but are only local. The only objects allowed at the module level are temporary tables, which can be accessed only from routines assigned to that module. Modules can encapsulate private objects (functions, procedure, temp tables), that are not visible outside the module. Inside the module, a  private objects shadows other objects. What I know the modules (and generally SQL/PSM) doesn&#039;t cover a deployment of code, so there is not similarity with PostgreSQL extensions. SQL/PSM Modules are modules like modules from modular languages like Modula2 or Oberon. There is some similarity with Postgres schemas (both are containers) with significant differences (there is not a concept of SEARCH_PATH (on PSM side) or there is not a concept of private or public objects (on Postgres schema side)).&lt;br /&gt;
&lt;br /&gt;
SQL/PSM variables are not transactional (they are used exactly like in PL/pgSQL). The precedence between variables and columns is not specified.&lt;br /&gt;
&lt;br /&gt;
= Implementation Challenges =&lt;br /&gt;
&lt;br /&gt;
* Possible collisions between session variables and other database objects.&lt;br /&gt;
* Memory cleanup after dropping a session variable, especially when DDL operations can be reverted (Postgres has not support for global temp objects).&lt;br /&gt;
* Memory invalidation: if a variable is dropped by one session, other sessions should not continue using it (Postgres has not support for global temp objects).&lt;br /&gt;
&lt;br /&gt;
= Proposed Implementations =&lt;br /&gt;
&lt;br /&gt;
It is clear that no single design is perfect for all use cases. MySQL’s approach is convenient and useful for interactive work, but it cannot be used for storing sensitive data and is considered unsafe. PostgreSQL GUCs are excellent for configuration, but they are not well suited for interactive use and are very limited when used as global variables in PL code.&lt;br /&gt;
&lt;br /&gt;
Because of these trade-offs, no single design is sufficient. In practice, having multiple designs within a single system could support a broader range of use cases more effectively.&lt;br /&gt;
&lt;br /&gt;
== Design session variables as database global temporary typed objects with ANSI SQL syntax for identifiers ==&lt;br /&gt;
&lt;br /&gt;
Author: Pavel Stehule &amp;lt;pavel.stehule@gmail.com&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I searched for a solution that could work well with the specific PostgreSQL environment:&lt;br /&gt;
&lt;br /&gt;
* PostgreSQL has more than one widely used stored procedure language: PL/pgSQL, PL/Python, or PL/Perl.&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL is a reduced version of PL/SQL, and PL/SQL is a modified ADA language. I am pretty happy with the current complexity of PL/pgSQL --- it is a domain-specific language that works well. It is very simple and easy to learn. PL/pgSQL has no packages or modules; instead PostgreSQL schemas are used. But schemas are database objects in their own right, independent of PL.&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL has not own data types, own operators library (language library), own dependency tracking. This is main difference from PL/SQL. PL/pgSQL is just very simple interpret that fully use types, functions, operators, dependency tracking implemented by Postgres for SQL support. Is not possible to implement variables with larger scope than transactions without Postgres dependency tracking. &lt;br /&gt;
&lt;br /&gt;
* PostgreSQL already has a code container --- the schema. I don&#039;t want to introduce another container object (modules), as this would overlap heavily with schemas. Implementing modules for PL/pgSQL could be useful, but doing it properly would be quite complex. The main problem is the concept of public and private objects --- PostgreSQL does not have this. Don&#039;t forget: every PL/pgSQL expression is a SQL expression, so public/private visibility would first need to be implemented internally in PostgreSQL. That would be redundant (and possibly in conflict) with SEARCH_PATH.&lt;br /&gt;
&lt;br /&gt;
So I wanted a design that supports multiple PL languages, does not add complexity to the relatively simple PL/pgSQL, and does not duplicate functionality already available.  &lt;br /&gt;
&lt;br /&gt;
I wrote a larger application in PL/pgSQL. To maintain it, I created plpgsql_lint (later renamed plpgsql_check). So I am always looking for solutions that do not block static code analysis. Static analysis requires persistent objects, which naturally leads to designing session variables as database objects (with their own system catalog).  &lt;br /&gt;
When session variables are database objects, ACLs can be applied naturally. Oracle package variables are well protected by package scope. PostgreSQL, however, has no schema scope --- schema locality is controlled by the SEARCH_PATH GUC, and introducing another mechanism would be messy. But with ACLs, variable contents can also be secured:&lt;br /&gt;
&lt;br /&gt;
    CREATE TEMP VARIABLE x AS int;&lt;br /&gt;
     &lt;br /&gt;
    LET x = 10;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(x);&lt;br /&gt;
    END;&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    SELECT VARIABLE(x);&lt;br /&gt;
&lt;br /&gt;
==== Collisions between column and variable identifiers ====&lt;br /&gt;
&lt;br /&gt;
There is a risk of identifier collisions between variables and columns. With ANSI SQL syntax for session variable identifiers, the problem is how to distinguish variables from columns. This is a known issue in PL/pgSQL and PL/SQL and can be solved either by prioritization or by prohibiting collisions.  &lt;br /&gt;
&lt;br /&gt;
Prohibiting collisions has largely solved this problem in PL/pgSQL. Unfortunately, session variables are global objects, so collisions can occur in any query. A poorly named variable could break queries unexpectedly. Prioritization also has problems --- a variable or a column could be used silently when the other was intended.&lt;br /&gt;
&lt;br /&gt;
    CREATE TABLE tab(a int);&lt;br /&gt;
    CREATE VARIABLE a AS int;&lt;br /&gt;
    &lt;br /&gt;
    SELECT a FROM tab; -- with disallowed collisions, this query fails when a variable named &amp;quot;a&amp;quot; exists&lt;br /&gt;
    &lt;br /&gt;
    LET a = 1;&lt;br /&gt;
    DELETE FROM tab WHERE a = 1; -- with higher priority for variables, all rows could be deleted&lt;br /&gt;
    SELECT a FROM tab; -- with higher priority for columns, the variable is ignored&lt;br /&gt;
&lt;br /&gt;
Even with collision prohibition, mistakes are still possible:&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE _id AS int;&lt;br /&gt;
    LET _id = 10;&lt;br /&gt;
    CREATE TABLE foo(id int);&lt;br /&gt;
    DELETE FROM foo WHERE _id = 10; -- just a typo, but all rows are deleted&lt;br /&gt;
&lt;br /&gt;
These problems are not new. Developers in PL/pgSQL or PL/SQL know them, but the scope of risk is normally limited to stored procedures. After many discussions and designs, I introduced &amp;quot;variable fences&amp;quot; (the syntax VARIABLE(varname)). Inside any query, variables can be used only inside a variable fence. This completely solves collisions and reduces the chance of human error. Currently, fences are required everywhere in queries and expressions. In the future, they might be made optional inside PL/pgSQL, to reduce overhead when porting Oracle applications. This could be controlled by a new plpgsql.extra_checks setting.&lt;br /&gt;
&lt;br /&gt;
A variable fence can collide with a user-defined function named `variable`. This is similar to keyword conflicts such as CUBE. It can be resolved by schema-qualifying or quoting:&lt;br /&gt;
&lt;br /&gt;
    SELECT public.variable(...)&lt;br /&gt;
    SELECT &amp;quot;variable&amp;quot;(...)&lt;br /&gt;
&lt;br /&gt;
Why not use T-SQL (MSSQL, MySQL) syntax for session variables? There are several reasons – some objective, one subjective:&lt;br /&gt;
&lt;br /&gt;
* There is a collision with custom operators. Operators `@` and `@@` are already used, and `@@` is common:&lt;br /&gt;
&lt;br /&gt;
    (2025-09-28 06:57:30) postgres=# create table tab(a int);&lt;br /&gt;
    CREATE TABLE&lt;br /&gt;
    (2025-09-28 06:57:42) postgres=# insert into tab values(10);&lt;br /&gt;
    INSERT 0 1&lt;br /&gt;
    (2025-09-28 06:57:50) postgres=# select @a from tab;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │       10 │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
We could safely use only `:` as a prefix, but that would conflict with psql variables. Similarly, `$` would conflict with query parameters.&lt;br /&gt;
&lt;br /&gt;
* T-SQL variable names look strange in PostgreSQL (in queries and in PL/pgSQL). PostgreSQL uses only ANSI/SQL identifiers, and PL/pgSQL follows the same rules. PostgreSQL is consistent here, and I see little motivation to introduce a new, non-standard syntax (especially since it could cause compatibility issues).&lt;br /&gt;
&lt;br /&gt;
Note: There was also a proposal to use table syntax for variables (similar to T-SQL in-memory table variables, but restricted to a single row). This is very effective for avoiding collisions, but I dislike it. It complicates simple expressions, adds inconsistency, and looks odd for scalar variables (though it could be useful for composite variables). This design does not solve every problem --- for example, variables can still be shadowed by tables because of SEARCH_PATH (a known issue).&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(var); -- variable is not a table&lt;br /&gt;
    END&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      -- variable as a table (I don&#039;t like it)&lt;br /&gt;
      -- can we use DML? How many rows can it hold?&lt;br /&gt;
      -- is the value a row or not?&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, (SELECT var FROM var);&lt;br /&gt;
    END&lt;br /&gt;
    $$&lt;br /&gt;
&lt;br /&gt;
I am not against introducing in-memory tables --- or more correctly, global temporary tables. A well-designed implementation could be very useful. But this is a different feature and should be designed separately.  &lt;br /&gt;
&lt;br /&gt;
There are also performance considerations: PL/pgSQL can use simple expression evaluation when no tables are referenced. This optimization would need to change, which touches sensitive code. It would also be inconsistent.  &lt;br /&gt;
&lt;br /&gt;
I would like global temporary tables (which PostgreSQL currently lacks). Tables should behave like tables, and variables should behave like variables. The natural mental model for session variables comes from variables in PL languages. PostgreSQL internally supports *transition tables*, and I can imagine allowing these outside of triggers. That would be valid, but again, it is a different feature.  &lt;br /&gt;
&lt;br /&gt;
Implementing declared tables inside PL/pgSQL would be simpler from a high-level design perspective (interaction between SQL tables and PL/pgSQL is already well defined), but difficult from a low-level perspective (statistics, optimizer data, etc. --- the same issues as with global temporary tables, unless we add fake proxy local temp tables).&lt;br /&gt;
&lt;br /&gt;
   CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
   AS $$&lt;br /&gt;
   -- not implemented yet, not part of this proposal&lt;br /&gt;
   DECLARE t TABLE(a int, b int); -- this table is invisible outside fx&lt;br /&gt;
   BEGIN&lt;br /&gt;
     INSERT INTO t VALUES(10,20);&lt;br /&gt;
     INSERT INTO t VALUES(30,40);&lt;br /&gt;
     RAISE NOTICE &#039;%&#039;, (SELECT count(*) FROM t);&lt;br /&gt;
   END;&lt;br /&gt;
   $$ LANGUAGE plpgsql;&lt;br /&gt;
&lt;br /&gt;
Some data languages define relational variables, but this concept has no overlap with the common understanding of session variables.&lt;br /&gt;
&lt;br /&gt;
Inside PL/pgSQL it would be possible to configure the parser to allow the use of session variables without a variable fence (similar to ordinary PL/pgSQL variables) in expressions. This is not supported yet, but implementation should not be problematic. Outside PL, variable fences could in principle be optional when there is no risk of collisions (for example, in expressions without subselects). This has not been implemented yet.&lt;br /&gt;
&lt;br /&gt;
==== Assign Command LET ====&lt;br /&gt;
&lt;br /&gt;
The value of a session variable can be assigned either by a dedicated LET command or by the SELECT INTO construct. In most systems where a special command is needed, the keyword SET is used (MySQL, MSSQL, SQL/PSM, DB2). In PostgreSQL, SET could be extended to support a_expr at the parser level, but this would require partially rewriting the current implementation of the SET command. Technically, this should not be a problem.&lt;br /&gt;
&lt;br /&gt;
The main issue is the potential for collisions between GUCs and session variables. GUCs are not declared in advance, are not associated with a schema (the prefix for a custom GUC can be any non-empty string), and access is not controlled by SEARCH_PATH. A variable fence could also solve this issue, but in this case the risk is always present, so fences could not be optional. Some languages use the keyword LET for assignments. Introducing LET in PostgreSQL provides a clean way to eliminate collisions between GUCs and session variables.&lt;br /&gt;
&lt;br /&gt;
It would also be possible to allow assignment to session variables in PL code using the usual PL assignment syntax (not yet implemented). This would be beneficial when porting from PL/SQL (Oracle) and would provide a natural syntax when using session variables as PL global variables. There do not appear to be technical obstacles to implementing this. However, one important issue arises: in PL/pgSQL, only declared variables can be targets of assignment statements. If session variables were allowed here, either (a) this check could not be performed, or (b) PL functions would gain a strong dependency on session variables, similar to the dependency on types today.&lt;br /&gt;
&lt;br /&gt;
    (2025-10-02 07:42:01) postgres=# CREATE OR REPLACE FUNCTION fx() &lt;br /&gt;
    returns void as $$&lt;br /&gt;
    begin&lt;br /&gt;
      declare var mydomain;&lt;br /&gt;
    begin&lt;br /&gt;
      var := 10;&lt;br /&gt;
    end&lt;br /&gt;
    $$ language plpgsql;&lt;br /&gt;
    ERROR:  type &amp;quot;mydomain&amp;quot; does not exist&lt;br /&gt;
    LINE 4:   declare var mydomain;&lt;br /&gt;
                          ^&lt;br /&gt;
&lt;br /&gt;
While such dependencies may not be a problem (the plan cache can be invalidated correctly), they raise new questions about temporary variables. To avoid these complications, the LET command was introduced, meaning the PL/pgSQL assignment mechanism does not need to be modified.&lt;br /&gt;
&lt;br /&gt;
A major consideration is performance, both in general and when LET is used inside PL/pgSQL.&lt;br /&gt;
&lt;br /&gt;
The LET command is a PostgreSQL utility command. Like other utility commands (e.g., CREATE TABLE AS SELECT), it can be prepared. Patches exist to implement PREPARE and EXECUTE for LET (these are not included in the reduced patch set). Even though the plan cache can be used, LET may still perform worse than a PL/pgSQL assignment for two reasons:&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL supports simple expression evaluation (also supported by LET in the enhanced patch set)&lt;br /&gt;
* PL/pgSQL variables are stored in arrays and accessed via an integer offset, while session variables are stored in a hash table and accessed via a hash lookup, which is slower. This slowdown is visible only in worst-case benchmarks:&lt;br /&gt;
&lt;br /&gt;
   DO $$ declare locvar int DEFAULT 0; begin for i in 1..1000000 loop locvar := locvar + 1; end loop; end $$ &lt;br /&gt;
   DO $$ begin for i in 1..100000 loop LET sesvar := sesvar + 1; end loop; end $$&lt;br /&gt;
&lt;br /&gt;
In the reduced patch set, the LET implementation is significantly slower because simple expression evaluation is not used (although it is implemented in the extended patch set). Nevertheless, even with the slowdown, execution remains much faster than queries that touch real tables, so performance should not be an issue in practice.&lt;br /&gt;
&lt;br /&gt;
==== Notes on Implementing Session Variables as Global Temporary Objects ====&lt;br /&gt;
&lt;br /&gt;
The core of this proposal is to design session variables as global temporary objects. Unfortunately, PostgreSQL currently has no support for this type of object. Surprisingly non-trivial is the implementation of non-system objects that have a catalogue entry, but unlike other objects, keep the data purely in memory. The problem is in memory cleaning. There is no hook that could be used to catch the event when the object was 100% deleted. We can (and must - there is nothing else) catch the sinval message, however, it is often delivered as a false alarm.&lt;br /&gt;
&lt;br /&gt;
Two main issues arise with this design:&lt;br /&gt;
&lt;br /&gt;
* the possibility of using invalid values (values stored in valid memory but no longer valid in the catalog),&lt;br /&gt;
* and memory cleanup happening too early or too late.&lt;br /&gt;
&lt;br /&gt;
Each session variable is identified by name (and possibly by an OID from pg_variable). A variable may have its own data type. Values stored in memory are kept in native binary format and remain unchanged until reassigned. Important note: an OID can be reused over time --- OIDs are unique only at a single moment, and there is no guarantee that an OID will not later be assigned to a different object. This creates a real possibility of the following issue:&lt;br /&gt;
&lt;br /&gt;
* session A: CREATE VARIABLE v AS t;&lt;br /&gt;
* session B: LET v = t &#039;value&#039;;&lt;br /&gt;
* session A: DROP VARIABLE v;&lt;br /&gt;
* session B: no activity -- it got lot of sinval messages (signals), but no command is executed, and then is not possibility to handle sinval&lt;br /&gt;
* session A: CREATE VARIABLE v AS nt; -- theoretically I can get same varid and typid like in first step, although t != nt&lt;br /&gt;
* session B: SELECT v; -- crash&lt;br /&gt;
&lt;br /&gt;
To prevent crashes, each stored value must be checked carefully before use. Checking only varid and typid is not sufficient. To ensure correctness, each variable can store its creation LSN (createlsn), which is unique throughout the lifetime of the database. Before using a value, the system checks that value.varid = catalog.varid and value.createlsn = catalog.createlsn. Type changes (ALTER VARIABLE ... ALTER TYPE) are not allowed. Dependencies ensure that the value type always matches the catalog type.&lt;br /&gt;
&lt;br /&gt;
A second problem is deciding when to clean memory. Memory cannot be freed immediately after DROP VARIABLE because the command can be rolled back:&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    BEGIN;&lt;br /&gt;
    LET var := 1;&lt;br /&gt;
    DROP VARIABLE var;&lt;br /&gt;
    ROLLBACK;&lt;br /&gt;
    SELECT VARIABLE(var); -- expected 1&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    BEGIN;&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    LET var := 1;&lt;br /&gt;
    ROLLBACK;&lt;br /&gt;
    -- var should be removed from memory&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    BEGIN;&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    SAVEPOINT s1;&lt;br /&gt;
    LET var := 1;&lt;br /&gt;
    DROP VARIABLE var;&lt;br /&gt;
    ROLLBACK TO s1;&lt;br /&gt;
    COMMIT;&lt;br /&gt;
    SELECT VARIABLE(var); -- expected 1&lt;br /&gt;
&lt;br /&gt;
The simplest solution is to mark the variable as dropped and delay memory cleanup until the next transaction where variables are validated. This approach is simple and robust, although it means memory may only be freed after some delay. While this might look messy when monitoring memory usage, the catalog is transactional, and validation against the catalog ensures correctness.&lt;br /&gt;
&lt;br /&gt;
= Variabes in PostgreSQL (as of v18) =&lt;br /&gt;
&lt;br /&gt;
PostgreSQL provides relatively advanced client-side session variables in psql --- these variables use the `:` prefix. The implementation is pretty much straightforward: `psql`&#039;s parser reads tokens from input and, when it encounters a token related to a variable, replaces it with the variable&#039;s value. The syntax supports literal and identifier escaping, and variables can be used as arguments of the `\bind` command.&lt;br /&gt;
&lt;br /&gt;
`psql` variables are typeless client-side variables, available only inside `psql` or `pgbench`.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:40:18) postgres=# \set myvar ahoj&lt;br /&gt;
    (2025-09-25 09:40:33) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:14) postgres=# select :&#039;myvar&#039;;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:24) postgres=# select $1 \bind :myvar \g&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
The `\set` command allows only simple string concatenation, but it also supports execution of shell commands. When a query is executed with the `\gset` command, its result can be stored in a `psql` variable.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:41:40) postgres=# \set ctime `date`&lt;br /&gt;
    (2025-09-25 09:43:55) postgres=# \echo :ctime&lt;br /&gt;
    Thu Sep 25 09:43:55 CEST 2025&lt;br /&gt;
&lt;br /&gt;
The `\set` command is fairly limited, making complex operations unreadable or non-portable.&lt;br /&gt;
&lt;br /&gt;
This mechanism is good enough for writing tests, but it is not generally available (most users do not need it), and it is not accessible from the server side (e.g., from stored procedures). There is no simple way to access `psql` variables from an anonymous block --- a proxy custom GUC variable must be used:&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:58:38) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    (2025-09-25 09:58:43) postgres=# select set_config(&#039;myvars.myvar&#039;, :&#039;myvar&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ ahoj       │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 10:00:27) postgres=# do $$ begin raise notice &#039;%&#039;, current_setting(&#039;myvars.myvar&#039;); end $$;&lt;br /&gt;
    NOTICE:  ahoj&lt;br /&gt;
    DO&lt;br /&gt;
&lt;br /&gt;
PostgreSQL also has configuration variables (GUC - Grand Unified Configuration). These are primarily designed for PostgreSQL and extension configuration, but they can be used as session variables. GUC variables can be set with the `SET` command or the `set_config` function, and read with `SHOW` or `current_setting`.&lt;br /&gt;
&lt;br /&gt;
GUC variables are meant for holding configuration parameters. When created through the internal API, they can be restricted to superusers, hidden from regular users, and assigned specific types (boolean, memory, number, interval, string, enum) with validation. This type system is separate from the SQL type system and is initialized before PostgreSQL’s SQL types.&lt;br /&gt;
&lt;br /&gt;
Variables created from SQL must be &amp;quot;custom&amp;quot; variables, using a prefix in their name --- these are always strings. Such custom GUCs are often used as a workaround for missing server-side session variables in PostgreSQL, but they are typeless, unprotected, and unsafe.&lt;br /&gt;
&lt;br /&gt;
A notable feature of PostgreSQL GUCs (built-in or custom) is that they can have different scopes: transaction, session, or function execution. They can also be assigned default values at specific events --- configuration loading, role login, database login, or function start. These features are useful for configuration, but problematic when GUCs are repurposed as session variables.&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
    RETURNS void AS $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, current_setting(&#039;my.x&#039;);&lt;br /&gt;
    END $$ LANGUAGE plpgsql SET my.x = 20;&lt;br /&gt;
    CREATE FUNCTION&lt;br /&gt;
    (2025-09-27 06:10:37) postgres=# SET my.x = 0; SELECT fx();&lt;br /&gt;
    SET&lt;br /&gt;
    NOTICE:  20&lt;br /&gt;
    ┌────┐&lt;br /&gt;
    │ fx │&lt;br /&gt;
    ╞════╡&lt;br /&gt;
    │    │&lt;br /&gt;
    └────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    (2025-09-27 06:10:39) postgres=# SHOW my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 0    │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
GUC variables are transactional, and this behavior cannot be disabled.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-27 06:17:31) postgres=# set my.x = 10;&lt;br /&gt;
    SET&lt;br /&gt;
    (2025-09-27 06:19:42) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:19:46) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, true);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:19:55) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:01) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:20:11) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:13) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:20:38) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:45) postgres=# rollback;&lt;br /&gt;
    ROLLBACK&lt;br /&gt;
    (2025-09-27 06:20:52) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:55) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:22:36) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:22:39) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:22:42) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
As a result, there is no reliable way to store details such as error information in custom GUCs.&lt;br /&gt;
&lt;br /&gt;
= Session Variables in Other Systems =&lt;br /&gt;
&lt;br /&gt;
This section surveys how session variables are implemented in several widely used database systems. Each subsection includes code examples to illustrate syntax, behaviour, and scope.&lt;br /&gt;
&lt;br /&gt;
== MySQL ==&lt;br /&gt;
&lt;br /&gt;
Session variables in MySQL have syntax similar to the better-known T-SQL variables (though other details are MySQL-specific). MySQL distinguishes two kinds of variables:&lt;br /&gt;
&lt;br /&gt;
* system variables - referenced using the `@@` prefix (or `@@scope.name`)&lt;br /&gt;
* user-defined variables - referenced using the `@` prefix&lt;br /&gt;
&lt;br /&gt;
System variables are modified with the `SET` statement. The `SET` statement accepts the modifiers `GLOBAL`, `SESSION`, or `PERSIST`. Some variables can only be set using the `GLOBAL` or `SESSION` modifier; when the variable name is specified with the `@@` prefix, it is not necessary to explicitly indicate the scope. System variables are defined by MySQL or by MySQL plugins.&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL some_config = 100;&lt;br /&gt;
    SET @@same_config = 100;&lt;br /&gt;
    &lt;br /&gt;
    SET SESSION sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@SESSION.sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
&lt;br /&gt;
Persistent values are stored in `mysqld-auto.cnf` (in the server data directory).&lt;br /&gt;
&lt;br /&gt;
Resetting system variables can be done by assigning DEFAULT or by copying from the GLOBAL namespace:&lt;br /&gt;
&lt;br /&gt;
    SET @@sql_mode = DEFAULT;&lt;br /&gt;
    SET @@sql_mode = @@GLOBAL.sql_mode&lt;br /&gt;
&lt;br /&gt;
PostgreSQL equivalents:&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL ; -- there is not similar command in Postgres&lt;br /&gt;
    SET SESSION ; -- SET&lt;br /&gt;
    SET PERSIST ; -- ALTER SYSTEM; SELECT pg_reload_conf();&lt;br /&gt;
    SELECT @@var; -- SELECT current_setting(&#039;var);&lt;br /&gt;
&lt;br /&gt;
User-defined variables are created by assignment:&lt;br /&gt;
&lt;br /&gt;
    SET @var = expr [, @var2 = expr [ ... ]];&lt;br /&gt;
&lt;br /&gt;
User-defined variables can only be of type integer, decimal, float, binary, non-binary string, or NULL. Casting between these types is implicit and hidden. User-defined variables cannot be used as table or column names.&lt;br /&gt;
&lt;br /&gt;
An advantage of the T-SQL-style syntax (with special prefixes) is that it provides a simple solution for avoiding identifier collisions. MySQL variables are convenient, and the user experience can be good for people already familiar with T-SQL (MSSQL or Sybase). On the other hand, prefix-based syntax can be confusing for users coming from systems other than MSSQL. The type system is perhaps too simple and can be unsafe. In general, the performance of MySQL (or MariaDB) stored procedures is not good, and stored procedures are therefore not frequently used. There is no protection against typographical errors. Unassigned user variables can be used without error (they default to NULL), which makes static checking of stored procedures impossible in this area.&lt;br /&gt;
&lt;br /&gt;
There is no way to restrict access to user-defined variables in MySQL. They cannot be protected against unintended usage. The only possible safeguard is a naming convention, since all user-defined variables share a single namespace.&lt;br /&gt;
&lt;br /&gt;
== Oracle ==&lt;br /&gt;
&lt;br /&gt;
Oracle PL/SQL allows the use of package variables. PL/SQL is based on the Ada language, and package variables act as &amp;quot;global&amp;quot; variables. They are not directly visible from SQL, but Oracle provides a reduced syntax for functions without arguments, so you typically write a wrapper:&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE PACKAGE my_package&lt;br /&gt;
    AS&lt;br /&gt;
      FUNCTION get_a RETURN NUMBER;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    /&lt;br /&gt;
    &lt;br /&gt;
    CREATE OR REPLACE PACKAGE BODY my_package&lt;br /&gt;
    AS&lt;br /&gt;
      a  NUMBER(20);&lt;br /&gt;
      &lt;br /&gt;
      FUNCTION get_a&lt;br /&gt;
      RETURN NUMBER&lt;br /&gt;
      IS&lt;br /&gt;
      BEGIN&lt;br /&gt;
        RETURN a;&lt;br /&gt;
      END get_a;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    &lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
Inside SQL, SQL identifiers take precedence. Inside non-SQL commands (such as CALL or PL/SQL blocks), package identifiers take precedence.&lt;br /&gt;
&lt;br /&gt;
Oracle allows both syntaxes for calling a function with zero arguments:&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a() FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
This reduces the risk of naming collisions. Package variables persist for the duration of a session.&lt;br /&gt;
&lt;br /&gt;
Another possibility is to use variables in SQL*Plus (similar to `psql` variables, but with the ability to define the type on the server side).&lt;br /&gt;
&lt;br /&gt;
A variable is declared with the `VARIABLE` command and can be accessed in the session using the `:varname` syntax (a prior declaration step may be optional):&lt;br /&gt;
&lt;br /&gt;
    VARIABLE bv_variable_name VARCHAR2(30)&lt;br /&gt;
    BEGIN&lt;br /&gt;
      :bv_variable_name := &#039;Some Value&#039;;&lt;br /&gt;
    END;&lt;br /&gt;
    &lt;br /&gt;
    SELECT column_name&lt;br /&gt;
      FROM   table_name&lt;br /&gt;
     WHERE  column_name = :bv_variable_name;&lt;br /&gt;
&lt;br /&gt;
== DB2 ==&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;user-defined global variables&amp;quot; in DB2 are similar to the proposed PostgreSQL feature. The main difference is in the access rights: DB2 uses `READ` and `WRITE`, while PostgreSQL uses `SELECT` and `UPDATE`. Because PostgreSQL already uses the `SET` command for GUCs, the `LET` command was introduced in the proposal (DB2 uses `SET`).&lt;br /&gt;
&lt;br /&gt;
Variables are visible in all sessions, but their values are private to each session. Variables are not transactional. Their usage is broader than in the proposal: they can be changed by `SET`, `SELECT INTO`, or used as OUT parameters of procedures. The search path (or a similar mechanism) applies to variables as well, but variables have lower priority than tables or columns.&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE myCounter INT DEFAULT 01;&lt;br /&gt;
    SELECT EMPNO, LASTNAME, CASE WHEN myCounter = 1 THEN SALARY ELSE NULL END&lt;br /&gt;
    FROM EMPLOYEE WHERE WORKDEPT = &#039;A00&#039;;&lt;br /&gt;
    SET myCounter = 29;&lt;br /&gt;
&lt;br /&gt;
There also appear to be other kinds of variables, accessed using the function `GETVARIABLE(&#039;name&#039;, &#039;default&#039;)`. These are very similar to PostgreSQL GUCs and the `current_setting` function. Such variables can be set via the connection string, are of type `VARCHAR`, and up to 10 values are allowed. Built-in session variables (configuration) can also be accessed using `GETVARIABLE`.&lt;br /&gt;
&lt;br /&gt;
== MSSQL (T-SQL) ==&lt;br /&gt;
&lt;br /&gt;
MSSQL provides two types of variables:&lt;br /&gt;
&lt;br /&gt;
* Global variables — accessed with the `@@varname` syntax. These are similar to GUCs and provide state information such as `@@ERROR`, `@@ROWCOUNT`, or `@@IDENTITY`. &lt;br /&gt;
* Local variables — accessed with the `@varname` syntax. These must be declared with the `DECLARE` command before use. Their scope is limited to the batch, procedure, or function where they are executed.&lt;br /&gt;
&lt;br /&gt;
    DECLARE @TestVariable AS VARCHAR(100)&lt;br /&gt;
    SET @TestVariable = &#039;Think Green&#039;&lt;br /&gt;
    GO&lt;br /&gt;
    PRINT @TestVariable&lt;br /&gt;
&lt;br /&gt;
This script fails because `PRINT` is executed in a different batch. Therefore, it seems MSSQL does not truly support session variables.&lt;br /&gt;
&lt;br /&gt;
There are also mechanisms similar to PostgreSQL&#039;s custom GUCs and the use of `current_setting` and `set_config` functions. In general, MSSQL is fairly primitive in this area.&lt;br /&gt;
&lt;br /&gt;
    EXEC sp_set_session_context &#039;user_id&#039;, 4;&lt;br /&gt;
    SELECT SESSION_CONTEXT(N&#039;user_id&#039;);&lt;br /&gt;
&lt;br /&gt;
= ToDo =&lt;br /&gt;
&lt;br /&gt;
* SECURITY LABEL support — enhance the `dummy_seclabel` module and the `sepgsql` extension. Update PostgreSQL documentation and ensure `SecLabelSupportsObjectType` includes session variables.&lt;/div&gt;</summary>
		<author><name>Okbobcz</name></author>
	</entry>
	<entry>
		<id>https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=42273</id>
		<title>Implementation of declarative catalog session variables</title>
		<link rel="alternate" type="text/html" href="https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=42273"/>
		<updated>2025-11-25T05:21:16Z</updated>

		<summary type="html">&lt;p&gt;Okbobcz: /* Design session variables as database global temporary typed objects with ANSI SQL syntax for identifiers */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Session variables are database objects that can hold a value across multiple queries inside a session. The design of session variables varies widely, and many databases implement more than one type of session variable.&lt;br /&gt;
&lt;br /&gt;
The SQL/PSM standard introduces modules and module-level variables. However, this part of the standard has not been implemented in any widely used product. As a result, each database system has developed its own proprietary design, syntax, and feature set.&lt;br /&gt;
&lt;br /&gt;
Session variables are often part of the stored procedure environment, but there are exceptions --- for example, MySQL session variables or client-side session variables in tools like psql. In most systems, session variables do not participate in transactions: once set, their value persists until explicitly changed, even if the surrounding transaction is rolled back. Conceptually, they behave much like variables in general-purpose programming languages.&lt;br /&gt;
&lt;br /&gt;
Because there is no common design, comparisons across systems are difficult. Each implementation has its own advantages and disadvantages, and in some cases a single system supports more than one kind of session variable.&lt;br /&gt;
&lt;br /&gt;
Kinds of session variables:&lt;br /&gt;
* client side (psql, pgadmin, sql plus, ...) - mostly similar to shell variables (based on string operations)&lt;br /&gt;
* server side&lt;br /&gt;
** declarative&lt;br /&gt;
*** created only for batch (T-SQL)&lt;br /&gt;
*** created as an database object (or part of database object) (Oracle, DB2)&lt;br /&gt;
** created by usage (MySQL)&lt;br /&gt;
** with priprietary syntax for identifiers (MySQL, T-SQL)&lt;br /&gt;
** with ANSI SQL syntax for identifiers (Oracle, DB2)&lt;br /&gt;
** Inside only PL (Oracle)&lt;br /&gt;
** Inside db engine (MySQL, T-SQL, DB2)&lt;br /&gt;
&lt;br /&gt;
Possible uses of session variables include:&lt;br /&gt;
&lt;br /&gt;
* Acting as global variables in PL code (for tracing, debugging, or quick hacks),&lt;br /&gt;
* Storing configuration values for PL routines,&lt;br /&gt;
* Keeping frequently used data handy in an interactive session,&lt;br /&gt;
* Storing credentials for Row-Level Security (RLS) and other data-protection mechanisms,&lt;br /&gt;
* Assisting with migrations from other systems (especially Oracle),&lt;br /&gt;
* Enabling parameterization of anonymous code blocks (specific to PostgreSQL).&lt;br /&gt;
* Session variables are writable even on read-only hot standby servers in PostgreSQL.&lt;br /&gt;
&lt;br /&gt;
They behave somewhat like temporary tables, but there are important differences:&lt;br /&gt;
&lt;br /&gt;
* Variables hold single values, while tables hold sets of rows.&lt;br /&gt;
* The syntax for using variables is shorter and more convenient for interactive work.&lt;br /&gt;
* Access (read/write) to variables is generally faster.&lt;br /&gt;
* The contents of session variables are typically non-transactional, whereas temporary tables are fully transactional.&lt;br /&gt;
* A session variable stores only a single value or NULL (no MVCC, no VACUUM).&lt;br /&gt;
* Temporary tables follow MVCC rules; repeated updates within a transaction generate many dead tuples, making updates more expensive.&lt;br /&gt;
* Temporary tables are not cleaned up by autovacuum, and it is not possible to run VACUUM inside a function.&lt;br /&gt;
* PostgreSQL does not support global temporary tables, so using a temporary table just to store a single value is inefficient and can lead to catalog bloat.&lt;br /&gt;
&lt;br /&gt;
= SQL/PSM =&lt;br /&gt;
&lt;br /&gt;
The SQL standard introduces the concept of modules, which can be associated with schemas (partially). Variables in modules behave like PL/pgSQL variables but are only local. The only objects allowed at the module level are temporary tables, which can be accessed only from routines assigned to that module. Modules can encapsulate private objects (functions, procedure, temp tables), that are not visible outside the module. Inside the module, a  private objects shadows other objects. What I know the modules (and generally SQL/PSM) doesn&#039;t cover a deployment of code, so there is not similarity with PostgreSQL extensions. SQL/PSM Modules are modules like modules from modular languages like Modula2 or Oberon. There is some similarity with Postgres schemas (both are containers) with significant differences (there is not a concept of SEARCH_PATH (on PSM side) or there is not a concept of private or public objects (on Postgres schema side)).&lt;br /&gt;
&lt;br /&gt;
SQL/PSM variables are not transactional (they are used exactly like in PL/pgSQL). The precedence between variables and columns is not specified.&lt;br /&gt;
&lt;br /&gt;
= Implementation Challenges =&lt;br /&gt;
&lt;br /&gt;
* Possible collisions between session variables and other database objects.&lt;br /&gt;
* Memory cleanup after dropping a session variable, especially when DDL operations can be reverted (Postgres has not support for global temp objects).&lt;br /&gt;
* Memory invalidation: if a variable is dropped by one session, other sessions should not continue using it (Postgres has not support for global temp objects).&lt;br /&gt;
&lt;br /&gt;
= Proposed Implementations =&lt;br /&gt;
&lt;br /&gt;
It is clear that no single design is perfect for all use cases. MySQL’s approach is convenient and useful for interactive work, but it cannot be used for storing sensitive data and is considered unsafe. PostgreSQL GUCs are excellent for configuration, but they are not well suited for interactive use and are very limited when used as global variables in PL code.&lt;br /&gt;
&lt;br /&gt;
Because of these trade-offs, no single design is sufficient. In practice, having multiple designs within a single system could support a broader range of use cases more effectively.&lt;br /&gt;
&lt;br /&gt;
== Design session variables as database global temporary typed objects with ANSI SQL syntax for identifiers ==&lt;br /&gt;
&lt;br /&gt;
Author: Pavel Stehule &amp;lt;pavel.stehule@gmail.com&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I searched for a solution that could work well with the specific PostgreSQL environment:&lt;br /&gt;
&lt;br /&gt;
* PostgreSQL has more than one widely used stored procedure language: PL/pgSQL, PL/Python, or PL/Perl.&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL is a reduced version of PL/SQL, and PL/SQL is a modified ADA language. I am pretty happy with the current complexity of PL/pgSQL --- it is a domain-specific language that works well. It is very simple and easy to learn. PL/pgSQL has no packages or modules; instead PostgreSQL schemas are used. But schemas are database objects in their own right, independent of PL.&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL has not own data types, own operators library (language library), own dependency tracking. This is main difference from PL/SQL. PL/pgSQL is just very simple interpret that fully use types, functions, operators, dependency tracking implemented by Postgres for SQL support. Is not possible to implement variables with larger scope than transactions without Postgres dependency tracking. &lt;br /&gt;
&lt;br /&gt;
* PostgreSQL already has a code container --- the schema. I don&#039;t want to introduce another container object (modules), as this would overlap heavily with schemas. Implementing modules for PL/pgSQL could be useful, but doing it properly would be quite complex. The main problem is the concept of public and private objects --- PostgreSQL does not have this. Don&#039;t forget: every PL/pgSQL expression is a SQL expression, so public/private visibility would first need to be implemented internally in PostgreSQL. That would be redundant (and possibly in conflict) with SEARCH_PATH.&lt;br /&gt;
&lt;br /&gt;
So I wanted a design that supports multiple PL languages, does not add complexity to the relatively simple PL/pgSQL, and does not duplicate functionality already available.  &lt;br /&gt;
&lt;br /&gt;
I wrote a larger application in PL/pgSQL. To maintain it, I created plpgsql_lint (later renamed plpgsql_check). So I am always looking for solutions that do not block static code analysis. Static analysis requires persistent objects, which naturally leads to designing session variables as database objects (with their own system catalog).  &lt;br /&gt;
When session variables are database objects, ACLs can be applied naturally. Oracle package variables are well protected by package scope. PostgreSQL, however, has no schema scope --- schema locality is controlled by the SEARCH_PATH GUC, and introducing another mechanism would be messy. But with ACLs, variable contents can also be secured:&lt;br /&gt;
&lt;br /&gt;
    CREATE TEMP VARIABLE x AS int;&lt;br /&gt;
     &lt;br /&gt;
    LET x = 10;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(x);&lt;br /&gt;
    END;&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    SELECT VARIABLE(x);&lt;br /&gt;
&lt;br /&gt;
==== Collisions between column and variable identifiers ====&lt;br /&gt;
&lt;br /&gt;
There is a risk of identifier collisions between variables and columns. With ANSI SQL syntax for session variable identifiers, the problem is how to distinguish variables from columns. This is a known issue in PL/pgSQL and PL/SQL and can be solved either by prioritization or by prohibiting collisions.  &lt;br /&gt;
&lt;br /&gt;
Prohibiting collisions has largely solved this problem in PL/pgSQL. Unfortunately, session variables are global objects, so collisions can occur in any query. A poorly named variable could break queries unexpectedly. Prioritization also has problems --- a variable or a column could be used silently when the other was intended.&lt;br /&gt;
&lt;br /&gt;
    CREATE TABLE tab(a int);&lt;br /&gt;
    CREATE VARIABLE a AS int;&lt;br /&gt;
    &lt;br /&gt;
    SELECT a FROM tab; -- with disallowed collisions, this query fails when a variable named &amp;quot;a&amp;quot; exists&lt;br /&gt;
    &lt;br /&gt;
    LET a = 1;&lt;br /&gt;
    DELETE FROM tab WHERE a = 1; -- with higher priority for variables, all rows could be deleted&lt;br /&gt;
    SELECT a FROM tab; -- with higher priority for columns, the variable is ignored&lt;br /&gt;
&lt;br /&gt;
Even with collision prohibition, mistakes are still possible:&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE _id AS int;&lt;br /&gt;
    LET _id = 10;&lt;br /&gt;
    CREATE TABLE foo(id int);&lt;br /&gt;
    DELETE FROM foo WHERE _id = 10; -- just a typo, but all rows are deleted&lt;br /&gt;
&lt;br /&gt;
These problems are not new. Developers in PL/pgSQL or PL/SQL know them, but the scope of risk is normally limited to stored procedures. After many discussions and designs, I introduced &amp;quot;variable fences&amp;quot; (the syntax VARIABLE(varname)). Inside any query, variables can be used only inside a variable fence. This completely solves collisions and reduces the chance of human error. Currently, fences are required everywhere in queries and expressions. In the future, they might be made optional inside PL/pgSQL, to reduce overhead when porting Oracle applications. This could be controlled by a new plpgsql.extra_checks setting.&lt;br /&gt;
&lt;br /&gt;
A variable fence can collide with a user-defined function named `variable`. This is similar to keyword conflicts such as CUBE. It can be resolved by schema-qualifying or quoting:&lt;br /&gt;
&lt;br /&gt;
    SELECT public.variable(...)&lt;br /&gt;
    SELECT &amp;quot;variable&amp;quot;(...)&lt;br /&gt;
&lt;br /&gt;
Why not use T-SQL (MSSQL, MySQL) syntax for session variables? There are several reasons – some objective, one subjective:&lt;br /&gt;
&lt;br /&gt;
* There is a collision with custom operators. Operators `@` and `@@` are already used, and `@@` is common:&lt;br /&gt;
&lt;br /&gt;
    (2025-09-28 06:57:30) postgres=# create table tab(a int);&lt;br /&gt;
    CREATE TABLE&lt;br /&gt;
    (2025-09-28 06:57:42) postgres=# insert into tab values(10);&lt;br /&gt;
    INSERT 0 1&lt;br /&gt;
    (2025-09-28 06:57:50) postgres=# select @a from tab;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │       10 │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
We could safely use only `:` as a prefix, but that would conflict with psql variables. Similarly, `$` would conflict with query parameters.&lt;br /&gt;
&lt;br /&gt;
* T-SQL variable names look strange in PostgreSQL (in queries and in PL/pgSQL). PostgreSQL uses only ANSI/SQL identifiers, and PL/pgSQL follows the same rules. PostgreSQL is consistent here, and I see little motivation to introduce a new, non-standard syntax (especially since it could cause compatibility issues).&lt;br /&gt;
&lt;br /&gt;
Note: There was also a proposal to use table syntax for variables (similar to T-SQL in-memory table variables, but restricted to a single row). This is very effective for avoiding collisions, but I dislike it. It complicates simple expressions, adds inconsistency, and looks odd for scalar variables (though it could be useful for composite variables). This design does not solve every problem --- for example, variables can still be shadowed by tables because of SEARCH_PATH (a known issue).&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(var); -- variable is not a table&lt;br /&gt;
    END&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      -- variable as a table (I don&#039;t like it)&lt;br /&gt;
      -- can we use DML? How many rows can it hold?&lt;br /&gt;
      -- is the value a row or not?&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, (SELECT var FROM var);&lt;br /&gt;
    END&lt;br /&gt;
    $$&lt;br /&gt;
&lt;br /&gt;
I am not against introducing in-memory tables --- or more correctly, global temporary tables. A well-designed implementation could be very useful. But this is a different feature and should be designed separately.  &lt;br /&gt;
&lt;br /&gt;
There are also performance considerations: PL/pgSQL can use simple expression evaluation when no tables are referenced. This optimization would need to change, which touches sensitive code. It would also be inconsistent.  &lt;br /&gt;
&lt;br /&gt;
I would like global temporary tables (which PostgreSQL currently lacks). Tables should behave like tables, and variables should behave like variables. The natural mental model for session variables comes from variables in PL languages. PostgreSQL internally supports *transition tables*, and I can imagine allowing these outside of triggers. That would be valid, but again, it is a different feature.  &lt;br /&gt;
&lt;br /&gt;
Implementing declared tables inside PL/pgSQL would be simpler from a high-level design perspective (interaction between SQL tables and PL/pgSQL is already well defined), but difficult from a low-level perspective (statistics, optimizer data, etc. --- the same issues as with global temporary tables, unless we add fake proxy local temp tables).&lt;br /&gt;
&lt;br /&gt;
   CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
   AS $$&lt;br /&gt;
   -- not implemented yet, not part of this proposal&lt;br /&gt;
   DECLARE t TABLE(a int, b int); -- this table is invisible outside fx&lt;br /&gt;
   BEGIN&lt;br /&gt;
     INSERT INTO t VALUES(10,20);&lt;br /&gt;
     INSERT INTO t VALUES(30,40);&lt;br /&gt;
     RAISE NOTICE &#039;%&#039;, (SELECT count(*) FROM t);&lt;br /&gt;
   END;&lt;br /&gt;
   $$ LANGUAGE plpgsql;&lt;br /&gt;
&lt;br /&gt;
Some data languages define relational variables, but this concept has no overlap with the common understanding of session variables.&lt;br /&gt;
&lt;br /&gt;
Inside PL/pgSQL it would be possible to configure the parser to allow the use of session variables without a variable fence (similar to ordinary PL/pgSQL variables) in expressions. This is not supported yet, but implementation should not be problematic. Outside PL, variable fences could in principle be optional when there is no risk of collisions (for example, in expressions without subselects). This has not been implemented yet.&lt;br /&gt;
&lt;br /&gt;
==== Assign Command LET ====&lt;br /&gt;
&lt;br /&gt;
The value of a session variable can be assigned either by a dedicated LET command or by the SELECT INTO construct. In most systems where a special command is needed, the keyword SET is used (MySQL, MSSQL, SQL/PSM, DB2). In PostgreSQL, SET could be extended to support a_expr at the parser level, but this would require partially rewriting the current implementation of the SET command. Technically, this should not be a problem.&lt;br /&gt;
&lt;br /&gt;
The main issue is the potential for collisions between GUCs and session variables. GUCs are not declared in advance, are not associated with a schema (the prefix for a custom GUC can be any non-empty string), and access is not controlled by SEARCH_PATH. A variable fence could also solve this issue, but in this case the risk is always present, so fences could not be optional. Some languages use the keyword LET for assignments. Introducing LET in PostgreSQL provides a clean way to eliminate collisions between GUCs and session variables.&lt;br /&gt;
&lt;br /&gt;
It would also be possible to allow assignment to session variables in PL code using the usual PL assignment syntax (not yet implemented). This would be beneficial when porting from PL/SQL (Oracle) and would provide a natural syntax when using session variables as PL global variables. There do not appear to be technical obstacles to implementing this. However, one important issue arises: in PL/pgSQL, only declared variables can be targets of assignment statements. If session variables were allowed here, either (a) this check could not be performed, or (b) PL functions would gain a strong dependency on session variables, similar to the dependency on types today.&lt;br /&gt;
&lt;br /&gt;
    (2025-10-02 07:42:01) postgres=# CREATE OR REPLACE FUNCTION fx() &lt;br /&gt;
    returns void as $$&lt;br /&gt;
    begin&lt;br /&gt;
      declare var mydomain;&lt;br /&gt;
    begin&lt;br /&gt;
      var := 10;&lt;br /&gt;
    end&lt;br /&gt;
    $$ language plpgsql;&lt;br /&gt;
    ERROR:  type &amp;quot;mydomain&amp;quot; does not exist&lt;br /&gt;
    LINE 4:   declare var mydomain;&lt;br /&gt;
                          ^&lt;br /&gt;
&lt;br /&gt;
While such dependencies may not be a problem (the plan cache can be invalidated correctly), they raise new questions about temporary variables. To avoid these complications, the LET command was introduced, meaning the PL/pgSQL assignment mechanism does not need to be modified.&lt;br /&gt;
&lt;br /&gt;
A major consideration is performance, both in general and when LET is used inside PL/pgSQL.&lt;br /&gt;
&lt;br /&gt;
The LET command is a PostgreSQL utility command. Like other utility commands (e.g., CREATE TABLE AS SELECT), it can be prepared. Patches exist to implement PREPARE and EXECUTE for LET (these are not included in the reduced patch set). Even though the plan cache can be used, LET may still perform worse than a PL/pgSQL assignment for two reasons:&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL supports simple expression evaluation (also supported by LET in the enhanced patch set)&lt;br /&gt;
* PL/pgSQL variables are stored in arrays and accessed via an integer offset, while session variables are stored in a hash table and accessed via a hash lookup, which is slower. This slowdown is visible only in worst-case benchmarks:&lt;br /&gt;
&lt;br /&gt;
   DO $$ declare locvar int DEFAULT 0; begin for i in 1..1000000 loop locvar := locvar + 1; end loop; end $$ &lt;br /&gt;
   DO $$ begin for i in 1..100000 loop LET sesvar := sesvar + 1; end loop; end $$&lt;br /&gt;
&lt;br /&gt;
In the reduced patch set, the LET implementation is significantly slower because simple expression evaluation is not used (although it is implemented in the extended patch set). Nevertheless, even with the slowdown, execution remains much faster than queries that touch real tables, so performance should not be an issue in practice.&lt;br /&gt;
&lt;br /&gt;
==== Notes on Implementing Session Variables as Global Temporary Objects ====&lt;br /&gt;
&lt;br /&gt;
The core of this proposal is to design session variables as global temporary objects. Unfortunately, PostgreSQL currently has no support for this type of object. Surprisingly non-trivial is the implementation of non-system objects that have a catalogue entry, but unlike other objects, keep the data purely in memory. The problem is in memory cleaning. There is no hook that could be used to catch the event when the object was 100% deleted. We can (and must - there is nothing else) catch the sinval message, however, it is often delivered as a false alarm.&lt;br /&gt;
&lt;br /&gt;
Two main issues arise with this design:&lt;br /&gt;
&lt;br /&gt;
* the possibility of using invalid values (values stored in valid memory but no longer valid in the catalog),&lt;br /&gt;
* and memory cleanup happening too early or too late.&lt;br /&gt;
&lt;br /&gt;
Each session variable is identified by name (and possibly by an OID from pg_variable). A variable may have its own data type. Values stored in memory are kept in native binary format and remain unchanged until reassigned. Important note: an OID can be reused over time --- OIDs are unique only at a single moment, and there is no guarantee that an OID will not later be assigned to a different object. This creates a real possibility of the following issue:&lt;br /&gt;
&lt;br /&gt;
* session A: CREATE VARIABLE v AS t;&lt;br /&gt;
* session B: LET v = t &#039;value&#039;;&lt;br /&gt;
* session A: DROP VARIABLE v;&lt;br /&gt;
* session B: no activity -- it got lot of sinval messages (signals), but no command is executed, and then is not possibility to handle sinval&lt;br /&gt;
* session A: CREATE VARIABLE v AS nt; -- theoretically I can get same varid and typid like in first step, although t != nt&lt;br /&gt;
* session B: SELECT v; -- crash&lt;br /&gt;
&lt;br /&gt;
To prevent crashes, each stored value must be checked carefully before use. Checking only varid and typid is not sufficient. To ensure correctness, each variable can store its creation LSN (createlsn), which is unique throughout the lifetime of the database. Before using a value, the system checks that value.varid = catalog.varid and value.createlsn = catalog.createlsn. Type changes (ALTER VARIABLE ... ALTER TYPE) are not allowed. Dependencies ensure that the value type always matches the catalog type.&lt;br /&gt;
&lt;br /&gt;
A second problem is deciding when to clean memory. Memory cannot be freed immediately after DROP VARIABLE because the command can be rolled back:&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    BEGIN;&lt;br /&gt;
    LET var := 1;&lt;br /&gt;
    DROP VARIABLE var;&lt;br /&gt;
    ROLLBACK;&lt;br /&gt;
    SELECT VARIABLE(var); -- expected 1&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    BEGIN;&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    LET var := 1;&lt;br /&gt;
    ROLLBACK;&lt;br /&gt;
    -- var should be removed from memory&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    BEGIN;&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    SAVEPOINT s1;&lt;br /&gt;
    LET var := 1;&lt;br /&gt;
    DROP VARIABLE var;&lt;br /&gt;
    ROLLBACK TO s1;&lt;br /&gt;
    COMMIT;&lt;br /&gt;
    SELECT VARIABLE(var); -- expected 1&lt;br /&gt;
&lt;br /&gt;
The simplest solution is to mark the variable as dropped and delay memory cleanup until the next transaction where variables are validated. This approach is simple and robust, although it means memory may only be freed after some delay. While this might look messy when monitoring memory usage, the catalog is transactional, and validation against the catalog ensures correctness.&lt;br /&gt;
&lt;br /&gt;
= Variabes in PostgreSQL (as of v18) =&lt;br /&gt;
&lt;br /&gt;
PostgreSQL provides relatively advanced client-side session variables in psql --- these variables use the `:` prefix. The implementation is pretty much straightforward: `psql`&#039;s parser reads tokens from input and, when it encounters a token related to a variable, replaces it with the variable&#039;s value. The syntax supports literal and identifier escaping, and variables can be used as arguments of the `\bind` command.&lt;br /&gt;
&lt;br /&gt;
`psql` variables are typeless client-side variables, available only inside `psql` or `pgbench`.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:40:18) postgres=# \set myvar ahoj&lt;br /&gt;
    (2025-09-25 09:40:33) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:14) postgres=# select :&#039;myvar&#039;;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:24) postgres=# select $1 \bind :myvar \g&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
The `\set` command allows only simple string concatenation, but it also supports execution of shell commands. When a query is executed with the `\gset` command, its result can be stored in a `psql` variable.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:41:40) postgres=# \set ctime `date`&lt;br /&gt;
    (2025-09-25 09:43:55) postgres=# \echo :ctime&lt;br /&gt;
    Thu Sep 25 09:43:55 CEST 2025&lt;br /&gt;
&lt;br /&gt;
The `\set` command is fairly limited, making complex operations unreadable or non-portable.&lt;br /&gt;
&lt;br /&gt;
This mechanism is good enough for writing tests, but it is not generally available (most users do not need it), and it is not accessible from the server side (e.g., from stored procedures). There is no simple way to access `psql` variables from an anonymous block --- a proxy custom GUC variable must be used:&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:58:38) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    (2025-09-25 09:58:43) postgres=# select set_config(&#039;myvars.myvar&#039;, :&#039;myvar&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ ahoj       │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 10:00:27) postgres=# do $$ begin raise notice &#039;%&#039;, current_setting(&#039;myvars.myvar&#039;); end $$;&lt;br /&gt;
    NOTICE:  ahoj&lt;br /&gt;
    DO&lt;br /&gt;
&lt;br /&gt;
PostgreSQL also has configuration variables (GUC - Grand Unified Configuration). These are primarily designed for PostgreSQL and extension configuration, but they can be used as session variables. GUC variables can be set with the `SET` command or the `set_config` function, and read with `SHOW` or `current_setting`.&lt;br /&gt;
&lt;br /&gt;
GUC variables are meant for holding configuration parameters. When created through the internal API, they can be restricted to superusers, hidden from regular users, and assigned specific types (boolean, memory, number, interval, string, enum) with validation. This type system is separate from the SQL type system and is initialized before PostgreSQL’s SQL types.&lt;br /&gt;
&lt;br /&gt;
Variables created from SQL must be &amp;quot;custom&amp;quot; variables, using a prefix in their name --- these are always strings. Such custom GUCs are often used as a workaround for missing server-side session variables in PostgreSQL, but they are typeless, unprotected, and unsafe.&lt;br /&gt;
&lt;br /&gt;
A notable feature of PostgreSQL GUCs (built-in or custom) is that they can have different scopes: transaction, session, or function execution. They can also be assigned default values at specific events --- configuration loading, role login, database login, or function start. These features are useful for configuration, but problematic when GUCs are repurposed as session variables.&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
    RETURNS void AS $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, current_setting(&#039;my.x&#039;);&lt;br /&gt;
    END $$ LANGUAGE plpgsql SET my.x = 20;&lt;br /&gt;
    CREATE FUNCTION&lt;br /&gt;
    (2025-09-27 06:10:37) postgres=# SET my.x = 0; SELECT fx();&lt;br /&gt;
    SET&lt;br /&gt;
    NOTICE:  20&lt;br /&gt;
    ┌────┐&lt;br /&gt;
    │ fx │&lt;br /&gt;
    ╞════╡&lt;br /&gt;
    │    │&lt;br /&gt;
    └────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    (2025-09-27 06:10:39) postgres=# SHOW my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 0    │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
GUC variables are transactional, and this behavior cannot be disabled.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-27 06:17:31) postgres=# set my.x = 10;&lt;br /&gt;
    SET&lt;br /&gt;
    (2025-09-27 06:19:42) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:19:46) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, true);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:19:55) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:01) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:20:11) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:13) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:20:38) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:45) postgres=# rollback;&lt;br /&gt;
    ROLLBACK&lt;br /&gt;
    (2025-09-27 06:20:52) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:55) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:22:36) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:22:39) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:22:42) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
As a result, there is no reliable way to store details such as error information in custom GUCs.&lt;br /&gt;
&lt;br /&gt;
= Session Variables in Other Systems =&lt;br /&gt;
&lt;br /&gt;
This section surveys how session variables are implemented in several widely used database systems. Each subsection includes code examples to illustrate syntax, behaviour, and scope.&lt;br /&gt;
&lt;br /&gt;
== MySQL ==&lt;br /&gt;
&lt;br /&gt;
Session variables in MySQL have syntax similar to the better-known T-SQL variables (though other details are MySQL-specific). MySQL distinguishes two kinds of variables:&lt;br /&gt;
&lt;br /&gt;
* system variables - referenced using the `@@` prefix (or `@@scope.name`)&lt;br /&gt;
* user-defined variables - referenced using the `@` prefix&lt;br /&gt;
&lt;br /&gt;
System variables are modified with the `SET` statement. The `SET` statement accepts the modifiers `GLOBAL`, `SESSION`, or `PERSIST`. Some variables can only be set using the `GLOBAL` or `SESSION` modifier; when the variable name is specified with the `@@` prefix, it is not necessary to explicitly indicate the scope. System variables are defined by MySQL or by MySQL plugins.&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL some_config = 100;&lt;br /&gt;
    SET @@same_config = 100;&lt;br /&gt;
    &lt;br /&gt;
    SET SESSION sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@SESSION.sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
&lt;br /&gt;
Persistent values are stored in `mysqld-auto.cnf` (in the server data directory).&lt;br /&gt;
&lt;br /&gt;
Resetting system variables can be done by assigning DEFAULT or by copying from the GLOBAL namespace:&lt;br /&gt;
&lt;br /&gt;
    SET @@sql_mode = DEFAULT;&lt;br /&gt;
    SET @@sql_mode = @@GLOBAL.sql_mode&lt;br /&gt;
&lt;br /&gt;
PostgreSQL equivalents:&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL ; -- there is not similar command in Postgres&lt;br /&gt;
    SET SESSION ; -- SET&lt;br /&gt;
    SET PERSIST ; -- ALTER SYSTEM; SELECT pg_reload_conf();&lt;br /&gt;
    SELECT @@var; -- SELECT current_setting(&#039;var);&lt;br /&gt;
&lt;br /&gt;
User-defined variables are created by assignment:&lt;br /&gt;
&lt;br /&gt;
    SET @var = expr [, @var2 = expr [ ... ]];&lt;br /&gt;
&lt;br /&gt;
User-defined variables can only be of type integer, decimal, float, binary, non-binary string, or NULL. Casting between these types is implicit and hidden. User-defined variables cannot be used as table or column names.&lt;br /&gt;
&lt;br /&gt;
An advantage of the T-SQL-style syntax (with special prefixes) is that it provides a simple solution for avoiding identifier collisions. MySQL variables are convenient, and the user experience can be good for people already familiar with T-SQL (MSSQL or Sybase). On the other hand, prefix-based syntax can be confusing for users coming from systems other than MSSQL. The type system is perhaps too simple and can be unsafe. In general, the performance of MySQL (or MariaDB) stored procedures is not good, and stored procedures are therefore not frequently used. There is no protection against typographical errors. Unassigned user variables can be used without error (they default to NULL), which makes static checking of stored procedures impossible in this area.&lt;br /&gt;
&lt;br /&gt;
There is no way to restrict access to user-defined variables in MySQL. They cannot be protected against unintended usage. The only possible safeguard is a naming convention, since all user-defined variables share a single namespace.&lt;br /&gt;
&lt;br /&gt;
== Oracle ==&lt;br /&gt;
&lt;br /&gt;
Oracle PL/SQL allows the use of package variables. PL/SQL is based on the Ada language, and package variables act as &amp;quot;global&amp;quot; variables. They are not directly visible from SQL, but Oracle provides a reduced syntax for functions without arguments, so you typically write a wrapper:&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE PACKAGE my_package&lt;br /&gt;
    AS&lt;br /&gt;
      FUNCTION get_a RETURN NUMBER;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    /&lt;br /&gt;
    &lt;br /&gt;
    CREATE OR REPLACE PACKAGE BODY my_package&lt;br /&gt;
    AS&lt;br /&gt;
      a  NUMBER(20);&lt;br /&gt;
      &lt;br /&gt;
      FUNCTION get_a&lt;br /&gt;
      RETURN NUMBER&lt;br /&gt;
      IS&lt;br /&gt;
      BEGIN&lt;br /&gt;
        RETURN a;&lt;br /&gt;
      END get_a;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    &lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
Inside SQL, SQL identifiers take precedence. Inside non-SQL commands (such as CALL or PL/SQL blocks), package identifiers take precedence.&lt;br /&gt;
&lt;br /&gt;
Oracle allows both syntaxes for calling a function with zero arguments:&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a() FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
This reduces the risk of naming collisions. Package variables persist for the duration of a session.&lt;br /&gt;
&lt;br /&gt;
Another possibility is to use variables in SQL*Plus (similar to `psql` variables, but with the ability to define the type on the server side).&lt;br /&gt;
&lt;br /&gt;
A variable is declared with the `VARIABLE` command and can be accessed in the session using the `:varname` syntax (a prior declaration step may be optional):&lt;br /&gt;
&lt;br /&gt;
    VARIABLE bv_variable_name VARCHAR2(30)&lt;br /&gt;
    BEGIN&lt;br /&gt;
      :bv_variable_name := &#039;Some Value&#039;;&lt;br /&gt;
    END;&lt;br /&gt;
    &lt;br /&gt;
    SELECT column_name&lt;br /&gt;
      FROM   table_name&lt;br /&gt;
     WHERE  column_name = :bv_variable_name;&lt;br /&gt;
&lt;br /&gt;
== DB2 ==&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;user-defined global variables&amp;quot; in DB2 are similar to the proposed PostgreSQL feature. The main difference is in the access rights: DB2 uses `READ` and `WRITE`, while PostgreSQL uses `SELECT` and `UPDATE`. Because PostgreSQL already uses the `SET` command for GUCs, the `LET` command was introduced in the proposal (DB2 uses `SET`).&lt;br /&gt;
&lt;br /&gt;
Variables are visible in all sessions, but their values are private to each session. Variables are not transactional. Their usage is broader than in the proposal: they can be changed by `SET`, `SELECT INTO`, or used as OUT parameters of procedures. The search path (or a similar mechanism) applies to variables as well, but variables have lower priority than tables or columns.&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE myCounter INT DEFAULT 01;&lt;br /&gt;
    SELECT EMPNO, LASTNAME, CASE WHEN myCounter = 1 THEN SALARY ELSE NULL END&lt;br /&gt;
    FROM EMPLOYEE WHERE WORKDEPT = &#039;A00&#039;;&lt;br /&gt;
    SET myCounter = 29;&lt;br /&gt;
&lt;br /&gt;
There also appear to be other kinds of variables, accessed using the function `GETVARIABLE(&#039;name&#039;, &#039;default&#039;)`. These are very similar to PostgreSQL GUCs and the `current_setting` function. Such variables can be set via the connection string, are of type `VARCHAR`, and up to 10 values are allowed. Built-in session variables (configuration) can also be accessed using `GETVARIABLE`.&lt;br /&gt;
&lt;br /&gt;
== MSSQL (T-SQL) ==&lt;br /&gt;
&lt;br /&gt;
MSSQL provides two types of variables:&lt;br /&gt;
&lt;br /&gt;
* Global variables — accessed with the `@@varname` syntax. These are similar to GUCs and provide state information such as `@@ERROR`, `@@ROWCOUNT`, or `@@IDENTITY`. &lt;br /&gt;
* Local variables — accessed with the `@varname` syntax. These must be declared with the `DECLARE` command before use. Their scope is limited to the batch, procedure, or function where they are executed.&lt;br /&gt;
&lt;br /&gt;
    DECLARE @TestVariable AS VARCHAR(100)&lt;br /&gt;
    SET @TestVariable = &#039;Think Green&#039;&lt;br /&gt;
    GO&lt;br /&gt;
    PRINT @TestVariable&lt;br /&gt;
&lt;br /&gt;
This script fails because `PRINT` is executed in a different batch. Therefore, it seems MSSQL does not truly support session variables.&lt;br /&gt;
&lt;br /&gt;
There are also mechanisms similar to PostgreSQL&#039;s custom GUCs and the use of `current_setting` and `set_config` functions. In general, MSSQL is fairly primitive in this area.&lt;br /&gt;
&lt;br /&gt;
    EXEC sp_set_session_context &#039;user_id&#039;, 4;&lt;br /&gt;
    SELECT SESSION_CONTEXT(N&#039;user_id&#039;);&lt;br /&gt;
&lt;br /&gt;
= ToDo =&lt;br /&gt;
&lt;br /&gt;
* SECURITY LABEL support — enhance the `dummy_seclabel` module and the `sepgsql` extension. Update PostgreSQL documentation and ensure `SecLabelSupportsObjectType` includes session variables.&lt;/div&gt;</summary>
		<author><name>Okbobcz</name></author>
	</entry>
	<entry>
		<id>https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41901</id>
		<title>Implementation of declarative catalog session variables</title>
		<link rel="alternate" type="text/html" href="https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41901"/>
		<updated>2025-10-06T05:51:13Z</updated>

		<summary type="html">&lt;p&gt;Okbobcz: /* Introduction */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Session variables are database objects that can hold a value across multiple queries inside a session. The design of session variables varies widely, and many databases implement more than one type of session variable.&lt;br /&gt;
&lt;br /&gt;
The SQL/PSM standard introduces modules and module-level variables. However, this part of the standard has not been implemented in any widely used product. As a result, each database system has developed its own proprietary design, syntax, and feature set.&lt;br /&gt;
&lt;br /&gt;
Session variables are often part of the stored procedure environment, but there are exceptions --- for example, MySQL session variables or client-side session variables in tools like psql. In most systems, session variables do not participate in transactions: once set, their value persists until explicitly changed, even if the surrounding transaction is rolled back. Conceptually, they behave much like variables in general-purpose programming languages.&lt;br /&gt;
&lt;br /&gt;
Because there is no common design, comparisons across systems are difficult. Each implementation has its own advantages and disadvantages, and in some cases a single system supports more than one kind of session variable.&lt;br /&gt;
&lt;br /&gt;
Kinds of session variables:&lt;br /&gt;
* client side (psql, pgadmin, sql plus, ...) - mostly similar to shell variables (based on string operations)&lt;br /&gt;
* server side&lt;br /&gt;
** declarative&lt;br /&gt;
*** created only for batch (T-SQL)&lt;br /&gt;
*** created as an database object (or part of database object) (Oracle, DB2)&lt;br /&gt;
** created by usage (MySQL)&lt;br /&gt;
** with priprietary syntax for identifiers (MySQL, T-SQL)&lt;br /&gt;
** with ANSI SQL syntax for identifiers (Oracle, DB2)&lt;br /&gt;
** Inside only PL (Oracle)&lt;br /&gt;
** Inside db engine (MySQL, T-SQL, DB2)&lt;br /&gt;
&lt;br /&gt;
Possible uses of session variables include:&lt;br /&gt;
&lt;br /&gt;
* Acting as global variables in PL code (for tracing, debugging, or quick hacks),&lt;br /&gt;
* Storing configuration values for PL routines,&lt;br /&gt;
* Keeping frequently used data handy in an interactive session,&lt;br /&gt;
* Storing credentials for Row-Level Security (RLS) and other data-protection mechanisms,&lt;br /&gt;
* Assisting with migrations from other systems (especially Oracle),&lt;br /&gt;
* Enabling parameterization of anonymous code blocks (specific to PostgreSQL).&lt;br /&gt;
* Session variables are writable even on read-only hot standby servers in PostgreSQL.&lt;br /&gt;
&lt;br /&gt;
They behave somewhat like temporary tables, but there are important differences:&lt;br /&gt;
&lt;br /&gt;
* Variables hold single values, while tables hold sets of rows.&lt;br /&gt;
* The syntax for using variables is shorter and more convenient for interactive work.&lt;br /&gt;
* Access (read/write) to variables is generally faster.&lt;br /&gt;
* The contents of session variables are typically non-transactional, whereas temporary tables are fully transactional.&lt;br /&gt;
* A session variable stores only a single value or NULL (no MVCC, no VACUUM).&lt;br /&gt;
* Temporary tables follow MVCC rules; repeated updates within a transaction generate many dead tuples, making updates more expensive.&lt;br /&gt;
* Temporary tables are not cleaned up by autovacuum, and it is not possible to run VACUUM inside a function.&lt;br /&gt;
* PostgreSQL does not support global temporary tables, so using a temporary table just to store a single value is inefficient and can lead to catalog bloat.&lt;br /&gt;
&lt;br /&gt;
= SQL/PSM =&lt;br /&gt;
&lt;br /&gt;
The SQL standard introduces the concept of modules, which can be associated with schemas (partially). Variables in modules behave like PL/pgSQL variables but are only local. The only objects allowed at the module level are temporary tables, which can be accessed only from routines assigned to that module. Modules can encapsulate private objects (functions, procedure, temp tables), that are not visible outside the module. Inside the module, a  private objects shadows other objects. What I know the modules (and generally SQL/PSM) doesn&#039;t cover a deployment of code, so there is not similarity with PostgreSQL extensions. SQL/PSM Modules are modules like modules from modular languages like Modula2 or Oberon. There is some similarity with Postgres schemas (both are containers) with significant differences (there is not a concept of SEARCH_PATH (on PSM side) or there is not a concept of private or public objects (on Postgres schema side)).&lt;br /&gt;
&lt;br /&gt;
SQL/PSM variables are not transactional (they are used exactly like in PL/pgSQL). The precedence between variables and columns is not specified.&lt;br /&gt;
&lt;br /&gt;
= Implementation Challenges =&lt;br /&gt;
&lt;br /&gt;
* Possible collisions between session variables and other database objects.&lt;br /&gt;
* Memory cleanup after dropping a session variable, especially when DDL operations can be reverted (Postgres has not support for global temp objects).&lt;br /&gt;
* Memory invalidation: if a variable is dropped by one session, other sessions should not continue using it (Postgres has not support for global temp objects).&lt;br /&gt;
&lt;br /&gt;
= Proposed Implementations =&lt;br /&gt;
&lt;br /&gt;
It is clear that no single design is perfect for all use cases. MySQL’s approach is convenient and useful for interactive work, but it cannot be used for storing sensitive data and is considered unsafe. PostgreSQL GUCs are excellent for configuration, but they are not well suited for interactive use and are very limited when used as global variables in PL code.&lt;br /&gt;
&lt;br /&gt;
Because of these trade-offs, no single design is sufficient. In practice, having multiple designs within a single system could support a broader range of use cases more effectively.&lt;br /&gt;
&lt;br /&gt;
== Design session variables as database global temporary typed objects with ANSI SQL syntax for identifiers ==&lt;br /&gt;
&lt;br /&gt;
Author: Pavel Stehule &amp;lt;pavel.stehule@gmail.com&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I searched for a solution that could work well with the specific PostgreSQL environment:&lt;br /&gt;
&lt;br /&gt;
* PostgreSQL has more than one widely used stored procedure language: PL/pgSQL, PL/Python, or PL/Perl.&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL is a reduced version of PL/SQL, and PL/SQL is a modified ADA language. I am pretty happy with the current complexity of PL/pgSQL --- it is a domain-specific language that works well. It is very simple and easy to learn. PL/pgSQL has no packages or modules; instead PostgreSQL schemas are used. But schemas are database objects in their own right, independent of PL.&lt;br /&gt;
&lt;br /&gt;
* PostgreSQL already has a code container --- the schema. I don&#039;t want to introduce another container object (modules), as this would overlap heavily with schemas. Implementing modules for PL/pgSQL could be useful, but doing it properly would be quite complex. The main problem is the concept of public and private objects --- PostgreSQL does not have this. Don&#039;t forget: every PL/pgSQL expression is a SQL expression, so public/private visibility would first need to be implemented internally in PostgreSQL. That would be redundant (and possibly in conflict) with SEARCH_PATH.&lt;br /&gt;
&lt;br /&gt;
So I wanted a design that supports multiple PL languages, does not add complexity to the relatively simple PL/pgSQL, and does not duplicate functionality already available.  &lt;br /&gt;
&lt;br /&gt;
I wrote a larger application in PL/pgSQL. To maintain it, I created plpgsql_lint (later renamed plpgsql_check). So I am always looking for solutions that do not block static code analysis. Static analysis requires persistent objects, which naturally leads to designing session variables as database objects (with their own system catalog).  &lt;br /&gt;
When session variables are database objects, ACLs can be applied naturally. Oracle package variables are well protected by package scope. PostgreSQL, however, has no schema scope --- schema locality is controlled by the SEARCH_PATH GUC, and introducing another mechanism would be messy. But with ACLs, variable contents can also be secured:&lt;br /&gt;
&lt;br /&gt;
    CREATE TEMP VARIABLE x AS int;&lt;br /&gt;
     &lt;br /&gt;
    LET x = 10;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(x);&lt;br /&gt;
    END;&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    SELECT VARIABLE(x);&lt;br /&gt;
&lt;br /&gt;
==== Collisions between column and variable identifiers ====&lt;br /&gt;
&lt;br /&gt;
There is a risk of identifier collisions between variables and columns. With ANSI SQL syntax for session variable identifiers, the problem is how to distinguish variables from columns. This is a known issue in PL/pgSQL and PL/SQL and can be solved either by prioritization or by prohibiting collisions.  &lt;br /&gt;
&lt;br /&gt;
Prohibiting collisions has largely solved this problem in PL/pgSQL. Unfortunately, session variables are global objects, so collisions can occur in any query. A poorly named variable could break queries unexpectedly. Prioritization also has problems --- a variable or a column could be used silently when the other was intended.&lt;br /&gt;
&lt;br /&gt;
    CREATE TABLE tab(a int);&lt;br /&gt;
    CREATE VARIABLE a AS int;&lt;br /&gt;
    &lt;br /&gt;
    SELECT a FROM tab; -- with disallowed collisions, this query fails when a variable named &amp;quot;a&amp;quot; exists&lt;br /&gt;
    &lt;br /&gt;
    LET a = 1;&lt;br /&gt;
    DELETE FROM tab WHERE a = 1; -- with higher priority for variables, all rows could be deleted&lt;br /&gt;
    SELECT a FROM tab; -- with higher priority for columns, the variable is ignored&lt;br /&gt;
&lt;br /&gt;
Even with collision prohibition, mistakes are still possible:&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE _id AS int;&lt;br /&gt;
    LET _id = 10;&lt;br /&gt;
    CREATE TABLE foo(id int);&lt;br /&gt;
    DELETE FROM foo WHERE _id = 10; -- just a typo, but all rows are deleted&lt;br /&gt;
&lt;br /&gt;
These problems are not new. Developers in PL/pgSQL or PL/SQL know them, but the scope of risk is normally limited to stored procedures. After many discussions and designs, I introduced &amp;quot;variable fences&amp;quot; (the syntax VARIABLE(varname)). Inside any query, variables can be used only inside a variable fence. This completely solves collisions and reduces the chance of human error. Currently, fences are required everywhere in queries and expressions. In the future, they might be made optional inside PL/pgSQL, to reduce overhead when porting Oracle applications. This could be controlled by a new plpgsql.extra_checks setting.&lt;br /&gt;
&lt;br /&gt;
A variable fence can collide with a user-defined function named `variable`. This is similar to keyword conflicts such as CUBE. It can be resolved by schema-qualifying or quoting:&lt;br /&gt;
&lt;br /&gt;
    SELECT public.variable(...)&lt;br /&gt;
    SELECT &amp;quot;variable&amp;quot;(...)&lt;br /&gt;
&lt;br /&gt;
Why not use T-SQL (MSSQL, MySQL) syntax for session variables? There are several reasons – some objective, one subjective:&lt;br /&gt;
&lt;br /&gt;
* There is a collision with custom operators. Operators `@` and `@@` are already used, and `@@` is common:&lt;br /&gt;
&lt;br /&gt;
    (2025-09-28 06:57:30) postgres=# create table tab(a int);&lt;br /&gt;
    CREATE TABLE&lt;br /&gt;
    (2025-09-28 06:57:42) postgres=# insert into tab values(10);&lt;br /&gt;
    INSERT 0 1&lt;br /&gt;
    (2025-09-28 06:57:50) postgres=# select @a from tab;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │       10 │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
We could safely use only `:` as a prefix, but that would conflict with psql variables. Similarly, `$` would conflict with query parameters.&lt;br /&gt;
&lt;br /&gt;
* T-SQL variable names look strange in PostgreSQL (in queries and in PL/pgSQL). PostgreSQL uses only ANSI/SQL identifiers, and PL/pgSQL follows the same rules. PostgreSQL is consistent here, and I see little motivation to introduce a new, non-standard syntax (especially since it could cause compatibility issues).&lt;br /&gt;
&lt;br /&gt;
Note: There was also a proposal to use table syntax for variables (similar to T-SQL in-memory table variables, but restricted to a single row). This is very effective for avoiding collisions, but I dislike it. It complicates simple expressions, adds inconsistency, and looks odd for scalar variables (though it could be useful for composite variables). This design does not solve every problem --- for example, variables can still be shadowed by tables because of SEARCH_PATH (a known issue).&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(var); -- variable is not a table&lt;br /&gt;
    END&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      -- variable as a table (I don&#039;t like it)&lt;br /&gt;
      -- can we use DML? How many rows can it hold?&lt;br /&gt;
      -- is the value a row or not?&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, (SELECT var FROM var);&lt;br /&gt;
    END&lt;br /&gt;
    $$&lt;br /&gt;
&lt;br /&gt;
I am not against introducing in-memory tables --- or more correctly, global temporary tables. A well-designed implementation could be very useful. But this is a different feature and should be designed separately.  &lt;br /&gt;
&lt;br /&gt;
There are also performance considerations: PL/pgSQL can use simple expression evaluation when no tables are referenced. This optimization would need to change, which touches sensitive code. It would also be inconsistent.  &lt;br /&gt;
&lt;br /&gt;
I would like global temporary tables (which PostgreSQL currently lacks). Tables should behave like tables, and variables should behave like variables. The natural mental model for session variables comes from variables in PL languages. PostgreSQL internally supports *transition tables*, and I can imagine allowing these outside of triggers. That would be valid, but again, it is a different feature.  &lt;br /&gt;
&lt;br /&gt;
Implementing declared tables inside PL/pgSQL would be simpler from a high-level design perspective (interaction between SQL tables and PL/pgSQL is already well defined), but difficult from a low-level perspective (statistics, optimizer data, etc. --- the same issues as with global temporary tables, unless we add fake proxy local temp tables).&lt;br /&gt;
&lt;br /&gt;
   CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
   AS $$&lt;br /&gt;
   -- not implemented yet, not part of this proposal&lt;br /&gt;
   DECLARE t TABLE(a int, b int); -- this table is invisible outside fx&lt;br /&gt;
   BEGIN&lt;br /&gt;
     INSERT INTO t VALUES(10,20);&lt;br /&gt;
     INSERT INTO t VALUES(30,40);&lt;br /&gt;
     RAISE NOTICE &#039;%&#039;, (SELECT count(*) FROM t);&lt;br /&gt;
   END;&lt;br /&gt;
   $$ LANGUAGE plpgsql;&lt;br /&gt;
&lt;br /&gt;
Some data languages define relational variables, but this concept has no overlap with the common understanding of session variables.&lt;br /&gt;
&lt;br /&gt;
Inside PL/pgSQL it would be possible to configure the parser to allow the use of session variables without a variable fence (similar to ordinary PL/pgSQL variables) in expressions. This is not supported yet, but implementation should not be problematic. Outside PL, variable fences could in principle be optional when there is no risk of collisions (for example, in expressions without subselects). This has not been implemented yet.&lt;br /&gt;
&lt;br /&gt;
==== Assign Command LET ====&lt;br /&gt;
&lt;br /&gt;
The value of a session variable can be assigned either by a dedicated LET command or by the SELECT INTO construct. In most systems where a special command is needed, the keyword SET is used (MySQL, MSSQL, SQL/PSM, DB2). In PostgreSQL, SET could be extended to support a_expr at the parser level, but this would require partially rewriting the current implementation of the SET command. Technically, this should not be a problem.&lt;br /&gt;
&lt;br /&gt;
The main issue is the potential for collisions between GUCs and session variables. GUCs are not declared in advance, are not associated with a schema (the prefix for a custom GUC can be any non-empty string), and access is not controlled by SEARCH_PATH. A variable fence could also solve this issue, but in this case the risk is always present, so fences could not be optional. Some languages use the keyword LET for assignments. Introducing LET in PostgreSQL provides a clean way to eliminate collisions between GUCs and session variables.&lt;br /&gt;
&lt;br /&gt;
It would also be possible to allow assignment to session variables in PL code using the usual PL assignment syntax (not yet implemented). This would be beneficial when porting from PL/SQL (Oracle) and would provide a natural syntax when using session variables as PL global variables. There do not appear to be technical obstacles to implementing this. However, one important issue arises: in PL/pgSQL, only declared variables can be targets of assignment statements. If session variables were allowed here, either (a) this check could not be performed, or (b) PL functions would gain a strong dependency on session variables, similar to the dependency on types today.&lt;br /&gt;
&lt;br /&gt;
    (2025-10-02 07:42:01) postgres=# CREATE OR REPLACE FUNCTION fx() &lt;br /&gt;
    returns void as $$&lt;br /&gt;
    begin&lt;br /&gt;
      declare var mydomain;&lt;br /&gt;
    begin&lt;br /&gt;
      var := 10;&lt;br /&gt;
    end&lt;br /&gt;
    $$ language plpgsql;&lt;br /&gt;
    ERROR:  type &amp;quot;mydomain&amp;quot; does not exist&lt;br /&gt;
    LINE 4:   declare var mydomain;&lt;br /&gt;
                          ^&lt;br /&gt;
&lt;br /&gt;
While such dependencies may not be a problem (the plan cache can be invalidated correctly), they raise new questions about temporary variables. To avoid these complications, the LET command was introduced, meaning the PL/pgSQL assignment mechanism does not need to be modified.&lt;br /&gt;
&lt;br /&gt;
A major consideration is performance, both in general and when LET is used inside PL/pgSQL.&lt;br /&gt;
&lt;br /&gt;
The LET command is a PostgreSQL utility command. Like other utility commands (e.g., CREATE TABLE AS SELECT), it can be prepared. Patches exist to implement PREPARE and EXECUTE for LET (these are not included in the reduced patch set). Even though the plan cache can be used, LET may still perform worse than a PL/pgSQL assignment for two reasons:&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL supports simple expression evaluation (also supported by LET in the enhanced patch set)&lt;br /&gt;
* PL/pgSQL variables are stored in arrays and accessed via an integer offset, while session variables are stored in a hash table and accessed via a hash lookup, which is slower. This slowdown is visible only in worst-case benchmarks:&lt;br /&gt;
&lt;br /&gt;
   DO $$ declare locvar int DEFAULT 0; begin for i in 1..1000000 loop locvar := locvar + 1; end loop; end $$ &lt;br /&gt;
   DO $$ begin for i in 1..100000 loop LET sesvar := sesvar + 1; end loop; end $$&lt;br /&gt;
&lt;br /&gt;
In the reduced patch set, the LET implementation is significantly slower because simple expression evaluation is not used (although it is implemented in the extended patch set). Nevertheless, even with the slowdown, execution remains much faster than queries that touch real tables, so performance should not be an issue in practice.&lt;br /&gt;
&lt;br /&gt;
==== Notes on Implementing Session Variables as Global Temporary Objects ====&lt;br /&gt;
&lt;br /&gt;
The core of this proposal is to design session variables as global temporary objects. Unfortunately, PostgreSQL currently has no support for this type of object. Surprisingly non-trivial is the implementation of non-system objects that have a catalogue entry, but unlike other objects, keep the data purely in memory. The problem is in memory cleaning. There is no hook that could be used to catch the event when the object was 100% deleted. We can (and must - there is nothing else) catch the sinval message, however, it is often delivered as a false alarm.&lt;br /&gt;
&lt;br /&gt;
Two main issues arise with this design:&lt;br /&gt;
&lt;br /&gt;
* the possibility of using invalid values (values stored in valid memory but no longer valid in the catalog),&lt;br /&gt;
* and memory cleanup happening too early or too late.&lt;br /&gt;
&lt;br /&gt;
Each session variable is identified by name (and possibly by an OID from pg_variable). A variable may have its own data type. Values stored in memory are kept in native binary format and remain unchanged until reassigned. Important note: an OID can be reused over time --- OIDs are unique only at a single moment, and there is no guarantee that an OID will not later be assigned to a different object. This creates a real possibility of the following issue:&lt;br /&gt;
&lt;br /&gt;
* session A: CREATE VARIABLE v AS t;&lt;br /&gt;
* session B: LET v = t &#039;value&#039;;&lt;br /&gt;
* session A: DROP VARIABLE v;&lt;br /&gt;
* session B: no activity -- it got lot of sinval messages (signals), but no command is executed, and then is not possibility to handle sinval&lt;br /&gt;
* session A: CREATE VARIABLE v AS nt; -- theoretically I can get same varid and typid like in first step, although t != nt&lt;br /&gt;
* session B: SELECT v; -- crash&lt;br /&gt;
&lt;br /&gt;
To prevent crashes, each stored value must be checked carefully before use. Checking only varid and typid is not sufficient. To ensure correctness, each variable can store its creation LSN (createlsn), which is unique throughout the lifetime of the database. Before using a value, the system checks that value.varid = catalog.varid and value.createlsn = catalog.createlsn. Type changes (ALTER VARIABLE ... ALTER TYPE) are not allowed. Dependencies ensure that the value type always matches the catalog type.&lt;br /&gt;
&lt;br /&gt;
A second problem is deciding when to clean memory. Memory cannot be freed immediately after DROP VARIABLE because the command can be rolled back:&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    BEGIN;&lt;br /&gt;
    LET var := 1;&lt;br /&gt;
    DROP VARIABLE var;&lt;br /&gt;
    ROLLBACK;&lt;br /&gt;
    SELECT VARIABLE(var); -- expected 1&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    BEGIN;&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    LET var := 1;&lt;br /&gt;
    ROLLBACK;&lt;br /&gt;
    -- var should be removed from memory&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    BEGIN;&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    SAVEPOINT s1;&lt;br /&gt;
    LET var := 1;&lt;br /&gt;
    DROP VARIABLE var;&lt;br /&gt;
    ROLLBACK TO s1;&lt;br /&gt;
    COMMIT;&lt;br /&gt;
    SELECT VARIABLE(var); -- expected 1&lt;br /&gt;
&lt;br /&gt;
The simplest solution is to mark the variable as dropped and delay memory cleanup until the next transaction where variables are validated. This approach is simple and robust, although it means memory may only be freed after some delay. While this might look messy when monitoring memory usage, the catalog is transactional, and validation against the catalog ensures correctness.&lt;br /&gt;
&lt;br /&gt;
= Variabes in PostgreSQL (as of v18) =&lt;br /&gt;
&lt;br /&gt;
PostgreSQL provides relatively advanced client-side session variables in psql --- these variables use the `:` prefix. The implementation is pretty much straightforward: `psql`&#039;s parser reads tokens from input and, when it encounters a token related to a variable, replaces it with the variable&#039;s value. The syntax supports literal and identifier escaping, and variables can be used as arguments of the `\bind` command.&lt;br /&gt;
&lt;br /&gt;
`psql` variables are typeless client-side variables, available only inside `psql` or `pgbench`.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:40:18) postgres=# \set myvar ahoj&lt;br /&gt;
    (2025-09-25 09:40:33) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:14) postgres=# select :&#039;myvar&#039;;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:24) postgres=# select $1 \bind :myvar \g&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
The `\set` command allows only simple string concatenation, but it also supports execution of shell commands. When a query is executed with the `\gset` command, its result can be stored in a `psql` variable.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:41:40) postgres=# \set ctime `date`&lt;br /&gt;
    (2025-09-25 09:43:55) postgres=# \echo :ctime&lt;br /&gt;
    Thu Sep 25 09:43:55 CEST 2025&lt;br /&gt;
&lt;br /&gt;
The `\set` command is fairly limited, making complex operations unreadable or non-portable.&lt;br /&gt;
&lt;br /&gt;
This mechanism is good enough for writing tests, but it is not generally available (most users do not need it), and it is not accessible from the server side (e.g., from stored procedures). There is no simple way to access `psql` variables from an anonymous block --- a proxy custom GUC variable must be used:&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:58:38) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    (2025-09-25 09:58:43) postgres=# select set_config(&#039;myvars.myvar&#039;, :&#039;myvar&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ ahoj       │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 10:00:27) postgres=# do $$ begin raise notice &#039;%&#039;, current_setting(&#039;myvars.myvar&#039;); end $$;&lt;br /&gt;
    NOTICE:  ahoj&lt;br /&gt;
    DO&lt;br /&gt;
&lt;br /&gt;
PostgreSQL also has configuration variables (GUC - Grand Unified Configuration). These are primarily designed for PostgreSQL and extension configuration, but they can be used as session variables. GUC variables can be set with the `SET` command or the `set_config` function, and read with `SHOW` or `current_setting`.&lt;br /&gt;
&lt;br /&gt;
GUC variables are meant for holding configuration parameters. When created through the internal API, they can be restricted to superusers, hidden from regular users, and assigned specific types (boolean, memory, number, interval, string, enum) with validation. This type system is separate from the SQL type system and is initialized before PostgreSQL’s SQL types.&lt;br /&gt;
&lt;br /&gt;
Variables created from SQL must be &amp;quot;custom&amp;quot; variables, using a prefix in their name --- these are always strings. Such custom GUCs are often used as a workaround for missing server-side session variables in PostgreSQL, but they are typeless, unprotected, and unsafe.&lt;br /&gt;
&lt;br /&gt;
A notable feature of PostgreSQL GUCs (built-in or custom) is that they can have different scopes: transaction, session, or function execution. They can also be assigned default values at specific events --- configuration loading, role login, database login, or function start. These features are useful for configuration, but problematic when GUCs are repurposed as session variables.&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
    RETURNS void AS $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, current_setting(&#039;my.x&#039;);&lt;br /&gt;
    END $$ LANGUAGE plpgsql SET my.x = 20;&lt;br /&gt;
    CREATE FUNCTION&lt;br /&gt;
    (2025-09-27 06:10:37) postgres=# SET my.x = 0; SELECT fx();&lt;br /&gt;
    SET&lt;br /&gt;
    NOTICE:  20&lt;br /&gt;
    ┌────┐&lt;br /&gt;
    │ fx │&lt;br /&gt;
    ╞════╡&lt;br /&gt;
    │    │&lt;br /&gt;
    └────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    (2025-09-27 06:10:39) postgres=# SHOW my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 0    │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
GUC variables are transactional, and this behavior cannot be disabled.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-27 06:17:31) postgres=# set my.x = 10;&lt;br /&gt;
    SET&lt;br /&gt;
    (2025-09-27 06:19:42) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:19:46) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, true);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:19:55) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:01) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:20:11) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:13) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:20:38) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:45) postgres=# rollback;&lt;br /&gt;
    ROLLBACK&lt;br /&gt;
    (2025-09-27 06:20:52) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:55) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:22:36) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:22:39) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:22:42) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
As a result, there is no reliable way to store details such as error information in custom GUCs.&lt;br /&gt;
&lt;br /&gt;
= Session Variables in Other Systems =&lt;br /&gt;
&lt;br /&gt;
This section surveys how session variables are implemented in several widely used database systems. Each subsection includes code examples to illustrate syntax, behaviour, and scope.&lt;br /&gt;
&lt;br /&gt;
== MySQL ==&lt;br /&gt;
&lt;br /&gt;
Session variables in MySQL have syntax similar to the better-known T-SQL variables (though other details are MySQL-specific). MySQL distinguishes two kinds of variables:&lt;br /&gt;
&lt;br /&gt;
* system variables - referenced using the `@@` prefix (or `@@scope.name`)&lt;br /&gt;
* user-defined variables - referenced using the `@` prefix&lt;br /&gt;
&lt;br /&gt;
System variables are modified with the `SET` statement. The `SET` statement accepts the modifiers `GLOBAL`, `SESSION`, or `PERSIST`. Some variables can only be set using the `GLOBAL` or `SESSION` modifier; when the variable name is specified with the `@@` prefix, it is not necessary to explicitly indicate the scope. System variables are defined by MySQL or by MySQL plugins.&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL some_config = 100;&lt;br /&gt;
    SET @@same_config = 100;&lt;br /&gt;
    &lt;br /&gt;
    SET SESSION sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@SESSION.sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
&lt;br /&gt;
Persistent values are stored in `mysqld-auto.cnf` (in the server data directory).&lt;br /&gt;
&lt;br /&gt;
Resetting system variables can be done by assigning DEFAULT or by copying from the GLOBAL namespace:&lt;br /&gt;
&lt;br /&gt;
    SET @@sql_mode = DEFAULT;&lt;br /&gt;
    SET @@sql_mode = @@GLOBAL.sql_mode&lt;br /&gt;
&lt;br /&gt;
PostgreSQL equivalents:&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL ; -- there is not similar command in Postgres&lt;br /&gt;
    SET SESSION ; -- SET&lt;br /&gt;
    SET PERSIST ; -- ALTER SYSTEM; SELECT pg_reload_conf();&lt;br /&gt;
    SELECT @@var; -- SELECT current_setting(&#039;var);&lt;br /&gt;
&lt;br /&gt;
User-defined variables are created by assignment:&lt;br /&gt;
&lt;br /&gt;
    SET @var = expr [, @var2 = expr [ ... ]];&lt;br /&gt;
&lt;br /&gt;
User-defined variables can only be of type integer, decimal, float, binary, non-binary string, or NULL. Casting between these types is implicit and hidden. User-defined variables cannot be used as table or column names.&lt;br /&gt;
&lt;br /&gt;
An advantage of the T-SQL-style syntax (with special prefixes) is that it provides a simple solution for avoiding identifier collisions. MySQL variables are convenient, and the user experience can be good for people already familiar with T-SQL (MSSQL or Sybase). On the other hand, prefix-based syntax can be confusing for users coming from systems other than MSSQL. The type system is perhaps too simple and can be unsafe. In general, the performance of MySQL (or MariaDB) stored procedures is not good, and stored procedures are therefore not frequently used. There is no protection against typographical errors. Unassigned user variables can be used without error (they default to NULL), which makes static checking of stored procedures impossible in this area.&lt;br /&gt;
&lt;br /&gt;
There is no way to restrict access to user-defined variables in MySQL. They cannot be protected against unintended usage. The only possible safeguard is a naming convention, since all user-defined variables share a single namespace.&lt;br /&gt;
&lt;br /&gt;
== Oracle ==&lt;br /&gt;
&lt;br /&gt;
Oracle PL/SQL allows the use of package variables. PL/SQL is based on the Ada language, and package variables act as &amp;quot;global&amp;quot; variables. They are not directly visible from SQL, but Oracle provides a reduced syntax for functions without arguments, so you typically write a wrapper:&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE PACKAGE my_package&lt;br /&gt;
    AS&lt;br /&gt;
      FUNCTION get_a RETURN NUMBER;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    /&lt;br /&gt;
    &lt;br /&gt;
    CREATE OR REPLACE PACKAGE BODY my_package&lt;br /&gt;
    AS&lt;br /&gt;
      a  NUMBER(20);&lt;br /&gt;
      &lt;br /&gt;
      FUNCTION get_a&lt;br /&gt;
      RETURN NUMBER&lt;br /&gt;
      IS&lt;br /&gt;
      BEGIN&lt;br /&gt;
        RETURN a;&lt;br /&gt;
      END get_a;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    &lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
Inside SQL, SQL identifiers take precedence. Inside non-SQL commands (such as CALL or PL/SQL blocks), package identifiers take precedence.&lt;br /&gt;
&lt;br /&gt;
Oracle allows both syntaxes for calling a function with zero arguments:&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a() FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
This reduces the risk of naming collisions. Package variables persist for the duration of a session.&lt;br /&gt;
&lt;br /&gt;
Another possibility is to use variables in SQL*Plus (similar to `psql` variables, but with the ability to define the type on the server side).&lt;br /&gt;
&lt;br /&gt;
A variable is declared with the `VARIABLE` command and can be accessed in the session using the `:varname` syntax (a prior declaration step may be optional):&lt;br /&gt;
&lt;br /&gt;
    VARIABLE bv_variable_name VARCHAR2(30)&lt;br /&gt;
    BEGIN&lt;br /&gt;
      :bv_variable_name := &#039;Some Value&#039;;&lt;br /&gt;
    END;&lt;br /&gt;
    &lt;br /&gt;
    SELECT column_name&lt;br /&gt;
      FROM   table_name&lt;br /&gt;
     WHERE  column_name = :bv_variable_name;&lt;br /&gt;
&lt;br /&gt;
== DB2 ==&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;user-defined global variables&amp;quot; in DB2 are similar to the proposed PostgreSQL feature. The main difference is in the access rights: DB2 uses `READ` and `WRITE`, while PostgreSQL uses `SELECT` and `UPDATE`. Because PostgreSQL already uses the `SET` command for GUCs, the `LET` command was introduced in the proposal (DB2 uses `SET`).&lt;br /&gt;
&lt;br /&gt;
Variables are visible in all sessions, but their values are private to each session. Variables are not transactional. Their usage is broader than in the proposal: they can be changed by `SET`, `SELECT INTO`, or used as OUT parameters of procedures. The search path (or a similar mechanism) applies to variables as well, but variables have lower priority than tables or columns.&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE myCounter INT DEFAULT 01;&lt;br /&gt;
    SELECT EMPNO, LASTNAME, CASE WHEN myCounter = 1 THEN SALARY ELSE NULL END&lt;br /&gt;
    FROM EMPLOYEE WHERE WORKDEPT = &#039;A00&#039;;&lt;br /&gt;
    SET myCounter = 29;&lt;br /&gt;
&lt;br /&gt;
There also appear to be other kinds of variables, accessed using the function `GETVARIABLE(&#039;name&#039;, &#039;default&#039;)`. These are very similar to PostgreSQL GUCs and the `current_setting` function. Such variables can be set via the connection string, are of type `VARCHAR`, and up to 10 values are allowed. Built-in session variables (configuration) can also be accessed using `GETVARIABLE`.&lt;br /&gt;
&lt;br /&gt;
== MSSQL (T-SQL) ==&lt;br /&gt;
&lt;br /&gt;
MSSQL provides two types of variables:&lt;br /&gt;
&lt;br /&gt;
* Global variables — accessed with the `@@varname` syntax. These are similar to GUCs and provide state information such as `@@ERROR`, `@@ROWCOUNT`, or `@@IDENTITY`. &lt;br /&gt;
* Local variables — accessed with the `@varname` syntax. These must be declared with the `DECLARE` command before use. Their scope is limited to the batch, procedure, or function where they are executed.&lt;br /&gt;
&lt;br /&gt;
    DECLARE @TestVariable AS VARCHAR(100)&lt;br /&gt;
    SET @TestVariable = &#039;Think Green&#039;&lt;br /&gt;
    GO&lt;br /&gt;
    PRINT @TestVariable&lt;br /&gt;
&lt;br /&gt;
This script fails because `PRINT` is executed in a different batch. Therefore, it seems MSSQL does not truly support session variables.&lt;br /&gt;
&lt;br /&gt;
There are also mechanisms similar to PostgreSQL&#039;s custom GUCs and the use of `current_setting` and `set_config` functions. In general, MSSQL is fairly primitive in this area.&lt;br /&gt;
&lt;br /&gt;
    EXEC sp_set_session_context &#039;user_id&#039;, 4;&lt;br /&gt;
    SELECT SESSION_CONTEXT(N&#039;user_id&#039;);&lt;br /&gt;
&lt;br /&gt;
= ToDo =&lt;br /&gt;
&lt;br /&gt;
* SECURITY LABEL support — enhance the `dummy_seclabel` module and the `sepgsql` extension. Update PostgreSQL documentation and ensure `SecLabelSupportsObjectType` includes session variables.&lt;/div&gt;</summary>
		<author><name>Okbobcz</name></author>
	</entry>
	<entry>
		<id>https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41900</id>
		<title>Implementation of declarative catalog session variables</title>
		<link rel="alternate" type="text/html" href="https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41900"/>
		<updated>2025-10-06T05:50:01Z</updated>

		<summary type="html">&lt;p&gt;Okbobcz: /* Introduction */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Session variables are database objects that can hold a value across multiple queries inside a session. The design of session variables varies widely, and many databases implement more than one type of session variable.&lt;br /&gt;
&lt;br /&gt;
The SQL/PSM standard introduces modules and module-level variables. However, this part of the standard has not been implemented in any widely used product. As a result, each database system has developed its own proprietary design, syntax, and feature set.&lt;br /&gt;
&lt;br /&gt;
Session variables are often part of the stored procedure environment, but there are exceptions --- for example, MySQL session variables or client-side session variables in tools like psql. In most systems, session variables do not participate in transactions: once set, their value persists until explicitly changed, even if the surrounding transaction is rolled back. Conceptually, they behave much like variables in general-purpose programming languages.&lt;br /&gt;
&lt;br /&gt;
Because there is no common design, comparisons across systems are difficult. Each implementation has its own advantages and disadvantages, and in some cases a single system supports more than one kind of session variable.&lt;br /&gt;
&lt;br /&gt;
Kinds of session variables:&lt;br /&gt;
* client side (psql, pgadmin, ...) - mostly similar to shell variables (based on string operations)&lt;br /&gt;
* server side&lt;br /&gt;
** declarative&lt;br /&gt;
*** created only for batch (T-SQL)&lt;br /&gt;
*** created as an database object (or part of database object) (Oracle, DB2)&lt;br /&gt;
** created by usage (MySQL)&lt;br /&gt;
** with priprietary syntax for identifiers (MySQL, T-SQL)&lt;br /&gt;
** with ANSI SQL syntax for identifiers (Oracle, DB2)&lt;br /&gt;
** Inside only PL (Oracle)&lt;br /&gt;
** Inside db engine (MySQL, T-SQL, DB2)&lt;br /&gt;
&lt;br /&gt;
Possible uses of session variables include:&lt;br /&gt;
&lt;br /&gt;
* Acting as global variables in PL code (for tracing, debugging, or quick hacks),&lt;br /&gt;
* Storing configuration values for PL routines,&lt;br /&gt;
* Keeping frequently used data handy in an interactive session,&lt;br /&gt;
* Storing credentials for Row-Level Security (RLS) and other data-protection mechanisms,&lt;br /&gt;
* Assisting with migrations from other systems (especially Oracle),&lt;br /&gt;
* Enabling parameterization of anonymous code blocks (specific to PostgreSQL).&lt;br /&gt;
* Session variables are writable even on read-only hot standby servers in PostgreSQL.&lt;br /&gt;
&lt;br /&gt;
They behave somewhat like temporary tables, but there are important differences:&lt;br /&gt;
&lt;br /&gt;
* Variables hold single values, while tables hold sets of rows.&lt;br /&gt;
* The syntax for using variables is shorter and more convenient for interactive work.&lt;br /&gt;
* Access (read/write) to variables is generally faster.&lt;br /&gt;
* The contents of session variables are typically non-transactional, whereas temporary tables are fully transactional.&lt;br /&gt;
* A session variable stores only a single value or NULL (no MVCC, no VACUUM).&lt;br /&gt;
* Temporary tables follow MVCC rules; repeated updates within a transaction generate many dead tuples, making updates more expensive.&lt;br /&gt;
* Temporary tables are not cleaned up by autovacuum, and it is not possible to run VACUUM inside a function.&lt;br /&gt;
* PostgreSQL does not support global temporary tables, so using a temporary table just to store a single value is inefficient and can lead to catalog bloat.&lt;br /&gt;
&lt;br /&gt;
= SQL/PSM =&lt;br /&gt;
&lt;br /&gt;
The SQL standard introduces the concept of modules, which can be associated with schemas (partially). Variables in modules behave like PL/pgSQL variables but are only local. The only objects allowed at the module level are temporary tables, which can be accessed only from routines assigned to that module. Modules can encapsulate private objects (functions, procedure, temp tables), that are not visible outside the module. Inside the module, a  private objects shadows other objects. What I know the modules (and generally SQL/PSM) doesn&#039;t cover a deployment of code, so there is not similarity with PostgreSQL extensions. SQL/PSM Modules are modules like modules from modular languages like Modula2 or Oberon. There is some similarity with Postgres schemas (both are containers) with significant differences (there is not a concept of SEARCH_PATH (on PSM side) or there is not a concept of private or public objects (on Postgres schema side)).&lt;br /&gt;
&lt;br /&gt;
SQL/PSM variables are not transactional (they are used exactly like in PL/pgSQL). The precedence between variables and columns is not specified.&lt;br /&gt;
&lt;br /&gt;
= Implementation Challenges =&lt;br /&gt;
&lt;br /&gt;
* Possible collisions between session variables and other database objects.&lt;br /&gt;
* Memory cleanup after dropping a session variable, especially when DDL operations can be reverted (Postgres has not support for global temp objects).&lt;br /&gt;
* Memory invalidation: if a variable is dropped by one session, other sessions should not continue using it (Postgres has not support for global temp objects).&lt;br /&gt;
&lt;br /&gt;
= Proposed Implementations =&lt;br /&gt;
&lt;br /&gt;
It is clear that no single design is perfect for all use cases. MySQL’s approach is convenient and useful for interactive work, but it cannot be used for storing sensitive data and is considered unsafe. PostgreSQL GUCs are excellent for configuration, but they are not well suited for interactive use and are very limited when used as global variables in PL code.&lt;br /&gt;
&lt;br /&gt;
Because of these trade-offs, no single design is sufficient. In practice, having multiple designs within a single system could support a broader range of use cases more effectively.&lt;br /&gt;
&lt;br /&gt;
== Design session variables as database global temporary typed objects with ANSI SQL syntax for identifiers ==&lt;br /&gt;
&lt;br /&gt;
Author: Pavel Stehule &amp;lt;pavel.stehule@gmail.com&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I searched for a solution that could work well with the specific PostgreSQL environment:&lt;br /&gt;
&lt;br /&gt;
* PostgreSQL has more than one widely used stored procedure language: PL/pgSQL, PL/Python, or PL/Perl.&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL is a reduced version of PL/SQL, and PL/SQL is a modified ADA language. I am pretty happy with the current complexity of PL/pgSQL --- it is a domain-specific language that works well. It is very simple and easy to learn. PL/pgSQL has no packages or modules; instead PostgreSQL schemas are used. But schemas are database objects in their own right, independent of PL.&lt;br /&gt;
&lt;br /&gt;
* PostgreSQL already has a code container --- the schema. I don&#039;t want to introduce another container object (modules), as this would overlap heavily with schemas. Implementing modules for PL/pgSQL could be useful, but doing it properly would be quite complex. The main problem is the concept of public and private objects --- PostgreSQL does not have this. Don&#039;t forget: every PL/pgSQL expression is a SQL expression, so public/private visibility would first need to be implemented internally in PostgreSQL. That would be redundant (and possibly in conflict) with SEARCH_PATH.&lt;br /&gt;
&lt;br /&gt;
So I wanted a design that supports multiple PL languages, does not add complexity to the relatively simple PL/pgSQL, and does not duplicate functionality already available.  &lt;br /&gt;
&lt;br /&gt;
I wrote a larger application in PL/pgSQL. To maintain it, I created plpgsql_lint (later renamed plpgsql_check). So I am always looking for solutions that do not block static code analysis. Static analysis requires persistent objects, which naturally leads to designing session variables as database objects (with their own system catalog).  &lt;br /&gt;
When session variables are database objects, ACLs can be applied naturally. Oracle package variables are well protected by package scope. PostgreSQL, however, has no schema scope --- schema locality is controlled by the SEARCH_PATH GUC, and introducing another mechanism would be messy. But with ACLs, variable contents can also be secured:&lt;br /&gt;
&lt;br /&gt;
    CREATE TEMP VARIABLE x AS int;&lt;br /&gt;
     &lt;br /&gt;
    LET x = 10;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(x);&lt;br /&gt;
    END;&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    SELECT VARIABLE(x);&lt;br /&gt;
&lt;br /&gt;
==== Collisions between column and variable identifiers ====&lt;br /&gt;
&lt;br /&gt;
There is a risk of identifier collisions between variables and columns. With ANSI SQL syntax for session variable identifiers, the problem is how to distinguish variables from columns. This is a known issue in PL/pgSQL and PL/SQL and can be solved either by prioritization or by prohibiting collisions.  &lt;br /&gt;
&lt;br /&gt;
Prohibiting collisions has largely solved this problem in PL/pgSQL. Unfortunately, session variables are global objects, so collisions can occur in any query. A poorly named variable could break queries unexpectedly. Prioritization also has problems --- a variable or a column could be used silently when the other was intended.&lt;br /&gt;
&lt;br /&gt;
    CREATE TABLE tab(a int);&lt;br /&gt;
    CREATE VARIABLE a AS int;&lt;br /&gt;
    &lt;br /&gt;
    SELECT a FROM tab; -- with disallowed collisions, this query fails when a variable named &amp;quot;a&amp;quot; exists&lt;br /&gt;
    &lt;br /&gt;
    LET a = 1;&lt;br /&gt;
    DELETE FROM tab WHERE a = 1; -- with higher priority for variables, all rows could be deleted&lt;br /&gt;
    SELECT a FROM tab; -- with higher priority for columns, the variable is ignored&lt;br /&gt;
&lt;br /&gt;
Even with collision prohibition, mistakes are still possible:&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE _id AS int;&lt;br /&gt;
    LET _id = 10;&lt;br /&gt;
    CREATE TABLE foo(id int);&lt;br /&gt;
    DELETE FROM foo WHERE _id = 10; -- just a typo, but all rows are deleted&lt;br /&gt;
&lt;br /&gt;
These problems are not new. Developers in PL/pgSQL or PL/SQL know them, but the scope of risk is normally limited to stored procedures. After many discussions and designs, I introduced &amp;quot;variable fences&amp;quot; (the syntax VARIABLE(varname)). Inside any query, variables can be used only inside a variable fence. This completely solves collisions and reduces the chance of human error. Currently, fences are required everywhere in queries and expressions. In the future, they might be made optional inside PL/pgSQL, to reduce overhead when porting Oracle applications. This could be controlled by a new plpgsql.extra_checks setting.&lt;br /&gt;
&lt;br /&gt;
A variable fence can collide with a user-defined function named `variable`. This is similar to keyword conflicts such as CUBE. It can be resolved by schema-qualifying or quoting:&lt;br /&gt;
&lt;br /&gt;
    SELECT public.variable(...)&lt;br /&gt;
    SELECT &amp;quot;variable&amp;quot;(...)&lt;br /&gt;
&lt;br /&gt;
Why not use T-SQL (MSSQL, MySQL) syntax for session variables? There are several reasons – some objective, one subjective:&lt;br /&gt;
&lt;br /&gt;
* There is a collision with custom operators. Operators `@` and `@@` are already used, and `@@` is common:&lt;br /&gt;
&lt;br /&gt;
    (2025-09-28 06:57:30) postgres=# create table tab(a int);&lt;br /&gt;
    CREATE TABLE&lt;br /&gt;
    (2025-09-28 06:57:42) postgres=# insert into tab values(10);&lt;br /&gt;
    INSERT 0 1&lt;br /&gt;
    (2025-09-28 06:57:50) postgres=# select @a from tab;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │       10 │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
We could safely use only `:` as a prefix, but that would conflict with psql variables. Similarly, `$` would conflict with query parameters.&lt;br /&gt;
&lt;br /&gt;
* T-SQL variable names look strange in PostgreSQL (in queries and in PL/pgSQL). PostgreSQL uses only ANSI/SQL identifiers, and PL/pgSQL follows the same rules. PostgreSQL is consistent here, and I see little motivation to introduce a new, non-standard syntax (especially since it could cause compatibility issues).&lt;br /&gt;
&lt;br /&gt;
Note: There was also a proposal to use table syntax for variables (similar to T-SQL in-memory table variables, but restricted to a single row). This is very effective for avoiding collisions, but I dislike it. It complicates simple expressions, adds inconsistency, and looks odd for scalar variables (though it could be useful for composite variables). This design does not solve every problem --- for example, variables can still be shadowed by tables because of SEARCH_PATH (a known issue).&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(var); -- variable is not a table&lt;br /&gt;
    END&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      -- variable as a table (I don&#039;t like it)&lt;br /&gt;
      -- can we use DML? How many rows can it hold?&lt;br /&gt;
      -- is the value a row or not?&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, (SELECT var FROM var);&lt;br /&gt;
    END&lt;br /&gt;
    $$&lt;br /&gt;
&lt;br /&gt;
I am not against introducing in-memory tables --- or more correctly, global temporary tables. A well-designed implementation could be very useful. But this is a different feature and should be designed separately.  &lt;br /&gt;
&lt;br /&gt;
There are also performance considerations: PL/pgSQL can use simple expression evaluation when no tables are referenced. This optimization would need to change, which touches sensitive code. It would also be inconsistent.  &lt;br /&gt;
&lt;br /&gt;
I would like global temporary tables (which PostgreSQL currently lacks). Tables should behave like tables, and variables should behave like variables. The natural mental model for session variables comes from variables in PL languages. PostgreSQL internally supports *transition tables*, and I can imagine allowing these outside of triggers. That would be valid, but again, it is a different feature.  &lt;br /&gt;
&lt;br /&gt;
Implementing declared tables inside PL/pgSQL would be simpler from a high-level design perspective (interaction between SQL tables and PL/pgSQL is already well defined), but difficult from a low-level perspective (statistics, optimizer data, etc. --- the same issues as with global temporary tables, unless we add fake proxy local temp tables).&lt;br /&gt;
&lt;br /&gt;
   CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
   AS $$&lt;br /&gt;
   -- not implemented yet, not part of this proposal&lt;br /&gt;
   DECLARE t TABLE(a int, b int); -- this table is invisible outside fx&lt;br /&gt;
   BEGIN&lt;br /&gt;
     INSERT INTO t VALUES(10,20);&lt;br /&gt;
     INSERT INTO t VALUES(30,40);&lt;br /&gt;
     RAISE NOTICE &#039;%&#039;, (SELECT count(*) FROM t);&lt;br /&gt;
   END;&lt;br /&gt;
   $$ LANGUAGE plpgsql;&lt;br /&gt;
&lt;br /&gt;
Some data languages define relational variables, but this concept has no overlap with the common understanding of session variables.&lt;br /&gt;
&lt;br /&gt;
Inside PL/pgSQL it would be possible to configure the parser to allow the use of session variables without a variable fence (similar to ordinary PL/pgSQL variables) in expressions. This is not supported yet, but implementation should not be problematic. Outside PL, variable fences could in principle be optional when there is no risk of collisions (for example, in expressions without subselects). This has not been implemented yet.&lt;br /&gt;
&lt;br /&gt;
==== Assign Command LET ====&lt;br /&gt;
&lt;br /&gt;
The value of a session variable can be assigned either by a dedicated LET command or by the SELECT INTO construct. In most systems where a special command is needed, the keyword SET is used (MySQL, MSSQL, SQL/PSM, DB2). In PostgreSQL, SET could be extended to support a_expr at the parser level, but this would require partially rewriting the current implementation of the SET command. Technically, this should not be a problem.&lt;br /&gt;
&lt;br /&gt;
The main issue is the potential for collisions between GUCs and session variables. GUCs are not declared in advance, are not associated with a schema (the prefix for a custom GUC can be any non-empty string), and access is not controlled by SEARCH_PATH. A variable fence could also solve this issue, but in this case the risk is always present, so fences could not be optional. Some languages use the keyword LET for assignments. Introducing LET in PostgreSQL provides a clean way to eliminate collisions between GUCs and session variables.&lt;br /&gt;
&lt;br /&gt;
It would also be possible to allow assignment to session variables in PL code using the usual PL assignment syntax (not yet implemented). This would be beneficial when porting from PL/SQL (Oracle) and would provide a natural syntax when using session variables as PL global variables. There do not appear to be technical obstacles to implementing this. However, one important issue arises: in PL/pgSQL, only declared variables can be targets of assignment statements. If session variables were allowed here, either (a) this check could not be performed, or (b) PL functions would gain a strong dependency on session variables, similar to the dependency on types today.&lt;br /&gt;
&lt;br /&gt;
    (2025-10-02 07:42:01) postgres=# CREATE OR REPLACE FUNCTION fx() &lt;br /&gt;
    returns void as $$&lt;br /&gt;
    begin&lt;br /&gt;
      declare var mydomain;&lt;br /&gt;
    begin&lt;br /&gt;
      var := 10;&lt;br /&gt;
    end&lt;br /&gt;
    $$ language plpgsql;&lt;br /&gt;
    ERROR:  type &amp;quot;mydomain&amp;quot; does not exist&lt;br /&gt;
    LINE 4:   declare var mydomain;&lt;br /&gt;
                          ^&lt;br /&gt;
&lt;br /&gt;
While such dependencies may not be a problem (the plan cache can be invalidated correctly), they raise new questions about temporary variables. To avoid these complications, the LET command was introduced, meaning the PL/pgSQL assignment mechanism does not need to be modified.&lt;br /&gt;
&lt;br /&gt;
A major consideration is performance, both in general and when LET is used inside PL/pgSQL.&lt;br /&gt;
&lt;br /&gt;
The LET command is a PostgreSQL utility command. Like other utility commands (e.g., CREATE TABLE AS SELECT), it can be prepared. Patches exist to implement PREPARE and EXECUTE for LET (these are not included in the reduced patch set). Even though the plan cache can be used, LET may still perform worse than a PL/pgSQL assignment for two reasons:&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL supports simple expression evaluation (also supported by LET in the enhanced patch set)&lt;br /&gt;
* PL/pgSQL variables are stored in arrays and accessed via an integer offset, while session variables are stored in a hash table and accessed via a hash lookup, which is slower. This slowdown is visible only in worst-case benchmarks:&lt;br /&gt;
&lt;br /&gt;
   DO $$ declare locvar int DEFAULT 0; begin for i in 1..1000000 loop locvar := locvar + 1; end loop; end $$ &lt;br /&gt;
   DO $$ begin for i in 1..100000 loop LET sesvar := sesvar + 1; end loop; end $$&lt;br /&gt;
&lt;br /&gt;
In the reduced patch set, the LET implementation is significantly slower because simple expression evaluation is not used (although it is implemented in the extended patch set). Nevertheless, even with the slowdown, execution remains much faster than queries that touch real tables, so performance should not be an issue in practice.&lt;br /&gt;
&lt;br /&gt;
==== Notes on Implementing Session Variables as Global Temporary Objects ====&lt;br /&gt;
&lt;br /&gt;
The core of this proposal is to design session variables as global temporary objects. Unfortunately, PostgreSQL currently has no support for this type of object. Surprisingly non-trivial is the implementation of non-system objects that have a catalogue entry, but unlike other objects, keep the data purely in memory. The problem is in memory cleaning. There is no hook that could be used to catch the event when the object was 100% deleted. We can (and must - there is nothing else) catch the sinval message, however, it is often delivered as a false alarm.&lt;br /&gt;
&lt;br /&gt;
Two main issues arise with this design:&lt;br /&gt;
&lt;br /&gt;
* the possibility of using invalid values (values stored in valid memory but no longer valid in the catalog),&lt;br /&gt;
* and memory cleanup happening too early or too late.&lt;br /&gt;
&lt;br /&gt;
Each session variable is identified by name (and possibly by an OID from pg_variable). A variable may have its own data type. Values stored in memory are kept in native binary format and remain unchanged until reassigned. Important note: an OID can be reused over time --- OIDs are unique only at a single moment, and there is no guarantee that an OID will not later be assigned to a different object. This creates a real possibility of the following issue:&lt;br /&gt;
&lt;br /&gt;
* session A: CREATE VARIABLE v AS t;&lt;br /&gt;
* session B: LET v = t &#039;value&#039;;&lt;br /&gt;
* session A: DROP VARIABLE v;&lt;br /&gt;
* session B: no activity -- it got lot of sinval messages (signals), but no command is executed, and then is not possibility to handle sinval&lt;br /&gt;
* session A: CREATE VARIABLE v AS nt; -- theoretically I can get same varid and typid like in first step, although t != nt&lt;br /&gt;
* session B: SELECT v; -- crash&lt;br /&gt;
&lt;br /&gt;
To prevent crashes, each stored value must be checked carefully before use. Checking only varid and typid is not sufficient. To ensure correctness, each variable can store its creation LSN (createlsn), which is unique throughout the lifetime of the database. Before using a value, the system checks that value.varid = catalog.varid and value.createlsn = catalog.createlsn. Type changes (ALTER VARIABLE ... ALTER TYPE) are not allowed. Dependencies ensure that the value type always matches the catalog type.&lt;br /&gt;
&lt;br /&gt;
A second problem is deciding when to clean memory. Memory cannot be freed immediately after DROP VARIABLE because the command can be rolled back:&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    BEGIN;&lt;br /&gt;
    LET var := 1;&lt;br /&gt;
    DROP VARIABLE var;&lt;br /&gt;
    ROLLBACK;&lt;br /&gt;
    SELECT VARIABLE(var); -- expected 1&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    BEGIN;&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    LET var := 1;&lt;br /&gt;
    ROLLBACK;&lt;br /&gt;
    -- var should be removed from memory&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    BEGIN;&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    SAVEPOINT s1;&lt;br /&gt;
    LET var := 1;&lt;br /&gt;
    DROP VARIABLE var;&lt;br /&gt;
    ROLLBACK TO s1;&lt;br /&gt;
    COMMIT;&lt;br /&gt;
    SELECT VARIABLE(var); -- expected 1&lt;br /&gt;
&lt;br /&gt;
The simplest solution is to mark the variable as dropped and delay memory cleanup until the next transaction where variables are validated. This approach is simple and robust, although it means memory may only be freed after some delay. While this might look messy when monitoring memory usage, the catalog is transactional, and validation against the catalog ensures correctness.&lt;br /&gt;
&lt;br /&gt;
= Variabes in PostgreSQL (as of v18) =&lt;br /&gt;
&lt;br /&gt;
PostgreSQL provides relatively advanced client-side session variables in psql --- these variables use the `:` prefix. The implementation is pretty much straightforward: `psql`&#039;s parser reads tokens from input and, when it encounters a token related to a variable, replaces it with the variable&#039;s value. The syntax supports literal and identifier escaping, and variables can be used as arguments of the `\bind` command.&lt;br /&gt;
&lt;br /&gt;
`psql` variables are typeless client-side variables, available only inside `psql` or `pgbench`.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:40:18) postgres=# \set myvar ahoj&lt;br /&gt;
    (2025-09-25 09:40:33) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:14) postgres=# select :&#039;myvar&#039;;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:24) postgres=# select $1 \bind :myvar \g&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
The `\set` command allows only simple string concatenation, but it also supports execution of shell commands. When a query is executed with the `\gset` command, its result can be stored in a `psql` variable.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:41:40) postgres=# \set ctime `date`&lt;br /&gt;
    (2025-09-25 09:43:55) postgres=# \echo :ctime&lt;br /&gt;
    Thu Sep 25 09:43:55 CEST 2025&lt;br /&gt;
&lt;br /&gt;
The `\set` command is fairly limited, making complex operations unreadable or non-portable.&lt;br /&gt;
&lt;br /&gt;
This mechanism is good enough for writing tests, but it is not generally available (most users do not need it), and it is not accessible from the server side (e.g., from stored procedures). There is no simple way to access `psql` variables from an anonymous block --- a proxy custom GUC variable must be used:&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:58:38) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    (2025-09-25 09:58:43) postgres=# select set_config(&#039;myvars.myvar&#039;, :&#039;myvar&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ ahoj       │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 10:00:27) postgres=# do $$ begin raise notice &#039;%&#039;, current_setting(&#039;myvars.myvar&#039;); end $$;&lt;br /&gt;
    NOTICE:  ahoj&lt;br /&gt;
    DO&lt;br /&gt;
&lt;br /&gt;
PostgreSQL also has configuration variables (GUC - Grand Unified Configuration). These are primarily designed for PostgreSQL and extension configuration, but they can be used as session variables. GUC variables can be set with the `SET` command or the `set_config` function, and read with `SHOW` or `current_setting`.&lt;br /&gt;
&lt;br /&gt;
GUC variables are meant for holding configuration parameters. When created through the internal API, they can be restricted to superusers, hidden from regular users, and assigned specific types (boolean, memory, number, interval, string, enum) with validation. This type system is separate from the SQL type system and is initialized before PostgreSQL’s SQL types.&lt;br /&gt;
&lt;br /&gt;
Variables created from SQL must be &amp;quot;custom&amp;quot; variables, using a prefix in their name --- these are always strings. Such custom GUCs are often used as a workaround for missing server-side session variables in PostgreSQL, but they are typeless, unprotected, and unsafe.&lt;br /&gt;
&lt;br /&gt;
A notable feature of PostgreSQL GUCs (built-in or custom) is that they can have different scopes: transaction, session, or function execution. They can also be assigned default values at specific events --- configuration loading, role login, database login, or function start. These features are useful for configuration, but problematic when GUCs are repurposed as session variables.&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
    RETURNS void AS $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, current_setting(&#039;my.x&#039;);&lt;br /&gt;
    END $$ LANGUAGE plpgsql SET my.x = 20;&lt;br /&gt;
    CREATE FUNCTION&lt;br /&gt;
    (2025-09-27 06:10:37) postgres=# SET my.x = 0; SELECT fx();&lt;br /&gt;
    SET&lt;br /&gt;
    NOTICE:  20&lt;br /&gt;
    ┌────┐&lt;br /&gt;
    │ fx │&lt;br /&gt;
    ╞════╡&lt;br /&gt;
    │    │&lt;br /&gt;
    └────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    (2025-09-27 06:10:39) postgres=# SHOW my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 0    │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
GUC variables are transactional, and this behavior cannot be disabled.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-27 06:17:31) postgres=# set my.x = 10;&lt;br /&gt;
    SET&lt;br /&gt;
    (2025-09-27 06:19:42) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:19:46) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, true);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:19:55) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:01) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:20:11) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:13) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:20:38) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:45) postgres=# rollback;&lt;br /&gt;
    ROLLBACK&lt;br /&gt;
    (2025-09-27 06:20:52) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:55) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:22:36) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:22:39) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:22:42) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
As a result, there is no reliable way to store details such as error information in custom GUCs.&lt;br /&gt;
&lt;br /&gt;
= Session Variables in Other Systems =&lt;br /&gt;
&lt;br /&gt;
This section surveys how session variables are implemented in several widely used database systems. Each subsection includes code examples to illustrate syntax, behaviour, and scope.&lt;br /&gt;
&lt;br /&gt;
== MySQL ==&lt;br /&gt;
&lt;br /&gt;
Session variables in MySQL have syntax similar to the better-known T-SQL variables (though other details are MySQL-specific). MySQL distinguishes two kinds of variables:&lt;br /&gt;
&lt;br /&gt;
* system variables - referenced using the `@@` prefix (or `@@scope.name`)&lt;br /&gt;
* user-defined variables - referenced using the `@` prefix&lt;br /&gt;
&lt;br /&gt;
System variables are modified with the `SET` statement. The `SET` statement accepts the modifiers `GLOBAL`, `SESSION`, or `PERSIST`. Some variables can only be set using the `GLOBAL` or `SESSION` modifier; when the variable name is specified with the `@@` prefix, it is not necessary to explicitly indicate the scope. System variables are defined by MySQL or by MySQL plugins.&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL some_config = 100;&lt;br /&gt;
    SET @@same_config = 100;&lt;br /&gt;
    &lt;br /&gt;
    SET SESSION sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@SESSION.sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
&lt;br /&gt;
Persistent values are stored in `mysqld-auto.cnf` (in the server data directory).&lt;br /&gt;
&lt;br /&gt;
Resetting system variables can be done by assigning DEFAULT or by copying from the GLOBAL namespace:&lt;br /&gt;
&lt;br /&gt;
    SET @@sql_mode = DEFAULT;&lt;br /&gt;
    SET @@sql_mode = @@GLOBAL.sql_mode&lt;br /&gt;
&lt;br /&gt;
PostgreSQL equivalents:&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL ; -- there is not similar command in Postgres&lt;br /&gt;
    SET SESSION ; -- SET&lt;br /&gt;
    SET PERSIST ; -- ALTER SYSTEM; SELECT pg_reload_conf();&lt;br /&gt;
    SELECT @@var; -- SELECT current_setting(&#039;var);&lt;br /&gt;
&lt;br /&gt;
User-defined variables are created by assignment:&lt;br /&gt;
&lt;br /&gt;
    SET @var = expr [, @var2 = expr [ ... ]];&lt;br /&gt;
&lt;br /&gt;
User-defined variables can only be of type integer, decimal, float, binary, non-binary string, or NULL. Casting between these types is implicit and hidden. User-defined variables cannot be used as table or column names.&lt;br /&gt;
&lt;br /&gt;
An advantage of the T-SQL-style syntax (with special prefixes) is that it provides a simple solution for avoiding identifier collisions. MySQL variables are convenient, and the user experience can be good for people already familiar with T-SQL (MSSQL or Sybase). On the other hand, prefix-based syntax can be confusing for users coming from systems other than MSSQL. The type system is perhaps too simple and can be unsafe. In general, the performance of MySQL (or MariaDB) stored procedures is not good, and stored procedures are therefore not frequently used. There is no protection against typographical errors. Unassigned user variables can be used without error (they default to NULL), which makes static checking of stored procedures impossible in this area.&lt;br /&gt;
&lt;br /&gt;
There is no way to restrict access to user-defined variables in MySQL. They cannot be protected against unintended usage. The only possible safeguard is a naming convention, since all user-defined variables share a single namespace.&lt;br /&gt;
&lt;br /&gt;
== Oracle ==&lt;br /&gt;
&lt;br /&gt;
Oracle PL/SQL allows the use of package variables. PL/SQL is based on the Ada language, and package variables act as &amp;quot;global&amp;quot; variables. They are not directly visible from SQL, but Oracle provides a reduced syntax for functions without arguments, so you typically write a wrapper:&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE PACKAGE my_package&lt;br /&gt;
    AS&lt;br /&gt;
      FUNCTION get_a RETURN NUMBER;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    /&lt;br /&gt;
    &lt;br /&gt;
    CREATE OR REPLACE PACKAGE BODY my_package&lt;br /&gt;
    AS&lt;br /&gt;
      a  NUMBER(20);&lt;br /&gt;
      &lt;br /&gt;
      FUNCTION get_a&lt;br /&gt;
      RETURN NUMBER&lt;br /&gt;
      IS&lt;br /&gt;
      BEGIN&lt;br /&gt;
        RETURN a;&lt;br /&gt;
      END get_a;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    &lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
Inside SQL, SQL identifiers take precedence. Inside non-SQL commands (such as CALL or PL/SQL blocks), package identifiers take precedence.&lt;br /&gt;
&lt;br /&gt;
Oracle allows both syntaxes for calling a function with zero arguments:&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a() FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
This reduces the risk of naming collisions. Package variables persist for the duration of a session.&lt;br /&gt;
&lt;br /&gt;
Another possibility is to use variables in SQL*Plus (similar to `psql` variables, but with the ability to define the type on the server side).&lt;br /&gt;
&lt;br /&gt;
A variable is declared with the `VARIABLE` command and can be accessed in the session using the `:varname` syntax (a prior declaration step may be optional):&lt;br /&gt;
&lt;br /&gt;
    VARIABLE bv_variable_name VARCHAR2(30)&lt;br /&gt;
    BEGIN&lt;br /&gt;
      :bv_variable_name := &#039;Some Value&#039;;&lt;br /&gt;
    END;&lt;br /&gt;
    &lt;br /&gt;
    SELECT column_name&lt;br /&gt;
      FROM   table_name&lt;br /&gt;
     WHERE  column_name = :bv_variable_name;&lt;br /&gt;
&lt;br /&gt;
== DB2 ==&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;user-defined global variables&amp;quot; in DB2 are similar to the proposed PostgreSQL feature. The main difference is in the access rights: DB2 uses `READ` and `WRITE`, while PostgreSQL uses `SELECT` and `UPDATE`. Because PostgreSQL already uses the `SET` command for GUCs, the `LET` command was introduced in the proposal (DB2 uses `SET`).&lt;br /&gt;
&lt;br /&gt;
Variables are visible in all sessions, but their values are private to each session. Variables are not transactional. Their usage is broader than in the proposal: they can be changed by `SET`, `SELECT INTO`, or used as OUT parameters of procedures. The search path (or a similar mechanism) applies to variables as well, but variables have lower priority than tables or columns.&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE myCounter INT DEFAULT 01;&lt;br /&gt;
    SELECT EMPNO, LASTNAME, CASE WHEN myCounter = 1 THEN SALARY ELSE NULL END&lt;br /&gt;
    FROM EMPLOYEE WHERE WORKDEPT = &#039;A00&#039;;&lt;br /&gt;
    SET myCounter = 29;&lt;br /&gt;
&lt;br /&gt;
There also appear to be other kinds of variables, accessed using the function `GETVARIABLE(&#039;name&#039;, &#039;default&#039;)`. These are very similar to PostgreSQL GUCs and the `current_setting` function. Such variables can be set via the connection string, are of type `VARCHAR`, and up to 10 values are allowed. Built-in session variables (configuration) can also be accessed using `GETVARIABLE`.&lt;br /&gt;
&lt;br /&gt;
== MSSQL (T-SQL) ==&lt;br /&gt;
&lt;br /&gt;
MSSQL provides two types of variables:&lt;br /&gt;
&lt;br /&gt;
* Global variables — accessed with the `@@varname` syntax. These are similar to GUCs and provide state information such as `@@ERROR`, `@@ROWCOUNT`, or `@@IDENTITY`. &lt;br /&gt;
* Local variables — accessed with the `@varname` syntax. These must be declared with the `DECLARE` command before use. Their scope is limited to the batch, procedure, or function where they are executed.&lt;br /&gt;
&lt;br /&gt;
    DECLARE @TestVariable AS VARCHAR(100)&lt;br /&gt;
    SET @TestVariable = &#039;Think Green&#039;&lt;br /&gt;
    GO&lt;br /&gt;
    PRINT @TestVariable&lt;br /&gt;
&lt;br /&gt;
This script fails because `PRINT` is executed in a different batch. Therefore, it seems MSSQL does not truly support session variables.&lt;br /&gt;
&lt;br /&gt;
There are also mechanisms similar to PostgreSQL&#039;s custom GUCs and the use of `current_setting` and `set_config` functions. In general, MSSQL is fairly primitive in this area.&lt;br /&gt;
&lt;br /&gt;
    EXEC sp_set_session_context &#039;user_id&#039;, 4;&lt;br /&gt;
    SELECT SESSION_CONTEXT(N&#039;user_id&#039;);&lt;br /&gt;
&lt;br /&gt;
= ToDo =&lt;br /&gt;
&lt;br /&gt;
* SECURITY LABEL support — enhance the `dummy_seclabel` module and the `sepgsql` extension. Update PostgreSQL documentation and ensure `SecLabelSupportsObjectType` includes session variables.&lt;/div&gt;</summary>
		<author><name>Okbobcz</name></author>
	</entry>
	<entry>
		<id>https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41880</id>
		<title>Implementation of declarative catalog session variables</title>
		<link rel="alternate" type="text/html" href="https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41880"/>
		<updated>2025-10-03T18:26:11Z</updated>

		<summary type="html">&lt;p&gt;Okbobcz: /* Notes on Implementing Session Variables as Global Temporary Objects */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Session variables are database objects that can hold a value across multiple queries inside a session. The design of session variables varies widely, and many databases implement more than one type of session variable.&lt;br /&gt;
&lt;br /&gt;
The SQL/PSM standard introduces modules and module-level variables. However, this part of the standard has not been implemented in any widely used product. As a result, each database system has developed its own proprietary design, syntax, and feature set.&lt;br /&gt;
&lt;br /&gt;
Session variables are often part of the stored procedure environment, but there are exceptions --- for example, MySQL session variables or client-side session variables in tools like psql. In most systems, session variables do not participate in transactions: once set, their value persists until explicitly changed, even if the surrounding transaction is rolled back. Conceptually, they behave much like variables in general-purpose programming languages.&lt;br /&gt;
&lt;br /&gt;
Because there is no common design, comparisons across systems are difficult. Each implementation has its own advantages and disadvantages, and in some cases a single system supports more than one kind of session variable.&lt;br /&gt;
&lt;br /&gt;
Kinds of session variables:&lt;br /&gt;
* client side (psql, pgadmin, ...) - mostly similar to shell variables (based on string operations)&lt;br /&gt;
* server side&lt;br /&gt;
** declarative&lt;br /&gt;
*** created only for batch (T-SQL)&lt;br /&gt;
*** created as an database object (or part of database object) (Oracle, DB2)&lt;br /&gt;
** created by usage (MySQL)&lt;br /&gt;
** with priprietary syntax for identifiers (MySQL, T-SQL)&lt;br /&gt;
** with ANSI SQL syntax for identifiers (Oracle, DB2)&lt;br /&gt;
&lt;br /&gt;
Possible uses of session variables include:&lt;br /&gt;
&lt;br /&gt;
* Acting as global variables in PL code (for tracing, debugging, or quick hacks),&lt;br /&gt;
* Storing configuration values for PL routines,&lt;br /&gt;
* Keeping frequently used data handy in an interactive session,&lt;br /&gt;
* Storing credentials for Row-Level Security (RLS) and other data-protection mechanisms,&lt;br /&gt;
* Assisting with migrations from other systems (especially Oracle),&lt;br /&gt;
* Enabling parameterization of anonymous code blocks (specific to PostgreSQL).&lt;br /&gt;
* Session variables are writable even on read-only hot standby servers in PostgreSQL.&lt;br /&gt;
&lt;br /&gt;
They behave somewhat like temporary tables, but there are important differences:&lt;br /&gt;
&lt;br /&gt;
* Variables hold single values, while tables hold sets of rows.&lt;br /&gt;
* The syntax for using variables is shorter and more convenient for interactive work.&lt;br /&gt;
* Access (read/write) to variables is generally faster.&lt;br /&gt;
* The contents of session variables are typically non-transactional, whereas temporary tables are fully transactional.&lt;br /&gt;
* A session variable stores only a single value or NULL (no MVCC, no VACUUM).&lt;br /&gt;
* Temporary tables follow MVCC rules; repeated updates within a transaction generate many dead tuples, making updates more expensive.&lt;br /&gt;
* Temporary tables are not cleaned up by autovacuum, and it is not possible to run VACUUM inside a function.&lt;br /&gt;
* PostgreSQL does not support global temporary tables, so using a temporary table just to store a single value is inefficient and can lead to catalog bloat.&lt;br /&gt;
&lt;br /&gt;
= SQL/PSM =&lt;br /&gt;
&lt;br /&gt;
The SQL standard introduces the concept of modules, which can be associated with schemas (partially). Variables in modules behave like PL/pgSQL variables but are only local. The only objects allowed at the module level are temporary tables, which can be accessed only from routines assigned to that module. Modules can encapsulate private objects (functions, procedure, temp tables), that are not visible outside the module. Inside the module, a  private objects shadows other objects. What I know the modules (and generally SQL/PSM) doesn&#039;t cover a deployment of code, so there is not similarity with PostgreSQL extensions. SQL/PSM Modules are modules like modules from modular languages like Modula2 or Oberon. There is some similarity with Postgres schemas (both are containers) with significant differences (there is not a concept of SEARCH_PATH (on PSM side) or there is not a concept of private or public objects (on Postgres schema side)).&lt;br /&gt;
&lt;br /&gt;
SQL/PSM variables are not transactional (they are used exactly like in PL/pgSQL). The precedence between variables and columns is not specified.&lt;br /&gt;
&lt;br /&gt;
= Implementation Challenges =&lt;br /&gt;
&lt;br /&gt;
* Possible collisions between session variables and other database objects.&lt;br /&gt;
* Memory cleanup after dropping a session variable, especially when DDL operations can be reverted (Postgres has not support for global temp objects).&lt;br /&gt;
* Memory invalidation: if a variable is dropped by one session, other sessions should not continue using it (Postgres has not support for global temp objects).&lt;br /&gt;
&lt;br /&gt;
= Proposed Implementations =&lt;br /&gt;
&lt;br /&gt;
It is clear that no single design is perfect for all use cases. MySQL’s approach is convenient and useful for interactive work, but it cannot be used for storing sensitive data and is considered unsafe. PostgreSQL GUCs are excellent for configuration, but they are not well suited for interactive use and are very limited when used as global variables in PL code.&lt;br /&gt;
&lt;br /&gt;
Because of these trade-offs, no single design is sufficient. In practice, having multiple designs within a single system could support a broader range of use cases more effectively.&lt;br /&gt;
&lt;br /&gt;
== Design session variables as database global temporary typed objects with ANSI SQL syntax for identifiers ==&lt;br /&gt;
&lt;br /&gt;
Author: Pavel Stehule &amp;lt;pavel.stehule@gmail.com&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I searched for a solution that could work well with the specific PostgreSQL environment:&lt;br /&gt;
&lt;br /&gt;
* PostgreSQL has more than one widely used stored procedure language: PL/pgSQL, PL/Python, or PL/Perl.&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL is a reduced version of PL/SQL, and PL/SQL is a modified ADA language. I am pretty happy with the current complexity of PL/pgSQL --- it is a domain-specific language that works well. It is very simple and easy to learn. PL/pgSQL has no packages or modules; instead PostgreSQL schemas are used. But schemas are database objects in their own right, independent of PL.&lt;br /&gt;
&lt;br /&gt;
* PostgreSQL already has a code container --- the schema. I don&#039;t want to introduce another container object (modules), as this would overlap heavily with schemas. Implementing modules for PL/pgSQL could be useful, but doing it properly would be quite complex. The main problem is the concept of public and private objects --- PostgreSQL does not have this. Don&#039;t forget: every PL/pgSQL expression is a SQL expression, so public/private visibility would first need to be implemented internally in PostgreSQL. That would be redundant (and possibly in conflict) with SEARCH_PATH.&lt;br /&gt;
&lt;br /&gt;
So I wanted a design that supports multiple PL languages, does not add complexity to the relatively simple PL/pgSQL, and does not duplicate functionality already available.  &lt;br /&gt;
&lt;br /&gt;
I wrote a larger application in PL/pgSQL. To maintain it, I created plpgsql_lint (later renamed plpgsql_check). So I am always looking for solutions that do not block static code analysis. Static analysis requires persistent objects, which naturally leads to designing session variables as database objects (with their own system catalog).  &lt;br /&gt;
When session variables are database objects, ACLs can be applied naturally. Oracle package variables are well protected by package scope. PostgreSQL, however, has no schema scope --- schema locality is controlled by the SEARCH_PATH GUC, and introducing another mechanism would be messy. But with ACLs, variable contents can also be secured:&lt;br /&gt;
&lt;br /&gt;
    CREATE TEMP VARIABLE x AS int;&lt;br /&gt;
     &lt;br /&gt;
    LET x = 10;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(x);&lt;br /&gt;
    END;&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    SELECT VARIABLE(x);&lt;br /&gt;
&lt;br /&gt;
==== Collisions between column and variable identifiers ====&lt;br /&gt;
&lt;br /&gt;
There is a risk of identifier collisions between variables and columns. With ANSI SQL syntax for session variable identifiers, the problem is how to distinguish variables from columns. This is a known issue in PL/pgSQL and PL/SQL and can be solved either by prioritization or by prohibiting collisions.  &lt;br /&gt;
&lt;br /&gt;
Prohibiting collisions has largely solved this problem in PL/pgSQL. Unfortunately, session variables are global objects, so collisions can occur in any query. A poorly named variable could break queries unexpectedly. Prioritization also has problems --- a variable or a column could be used silently when the other was intended.&lt;br /&gt;
&lt;br /&gt;
    CREATE TABLE tab(a int);&lt;br /&gt;
    CREATE VARIABLE a AS int;&lt;br /&gt;
    &lt;br /&gt;
    SELECT a FROM tab; -- with disallowed collisions, this query fails when a variable named &amp;quot;a&amp;quot; exists&lt;br /&gt;
    &lt;br /&gt;
    LET a = 1;&lt;br /&gt;
    DELETE FROM tab WHERE a = 1; -- with higher priority for variables, all rows could be deleted&lt;br /&gt;
    SELECT a FROM tab; -- with higher priority for columns, the variable is ignored&lt;br /&gt;
&lt;br /&gt;
Even with collision prohibition, mistakes are still possible:&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE _id AS int;&lt;br /&gt;
    LET _id = 10;&lt;br /&gt;
    CREATE TABLE foo(id int);&lt;br /&gt;
    DELETE FROM foo WHERE _id = 10; -- just a typo, but all rows are deleted&lt;br /&gt;
&lt;br /&gt;
These problems are not new. Developers in PL/pgSQL or PL/SQL know them, but the scope of risk is normally limited to stored procedures. After many discussions and designs, I introduced &amp;quot;variable fences&amp;quot; (the syntax VARIABLE(varname)). Inside any query, variables can be used only inside a variable fence. This completely solves collisions and reduces the chance of human error. Currently, fences are required everywhere in queries and expressions. In the future, they might be made optional inside PL/pgSQL, to reduce overhead when porting Oracle applications. This could be controlled by a new plpgsql.extra_checks setting.&lt;br /&gt;
&lt;br /&gt;
A variable fence can collide with a user-defined function named `variable`. This is similar to keyword conflicts such as CUBE. It can be resolved by schema-qualifying or quoting:&lt;br /&gt;
&lt;br /&gt;
    SELECT public.variable(...)&lt;br /&gt;
    SELECT &amp;quot;variable&amp;quot;(...)&lt;br /&gt;
&lt;br /&gt;
Why not use T-SQL (MSSQL, MySQL) syntax for session variables? There are several reasons – some objective, one subjective:&lt;br /&gt;
&lt;br /&gt;
* There is a collision with custom operators. Operators `@` and `@@` are already used, and `@@` is common:&lt;br /&gt;
&lt;br /&gt;
    (2025-09-28 06:57:30) postgres=# create table tab(a int);&lt;br /&gt;
    CREATE TABLE&lt;br /&gt;
    (2025-09-28 06:57:42) postgres=# insert into tab values(10);&lt;br /&gt;
    INSERT 0 1&lt;br /&gt;
    (2025-09-28 06:57:50) postgres=# select @a from tab;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │       10 │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
We could safely use only `:` as a prefix, but that would conflict with psql variables. Similarly, `$` would conflict with query parameters.&lt;br /&gt;
&lt;br /&gt;
* T-SQL variable names look strange in PostgreSQL (in queries and in PL/pgSQL). PostgreSQL uses only ANSI/SQL identifiers, and PL/pgSQL follows the same rules. PostgreSQL is consistent here, and I see little motivation to introduce a new, non-standard syntax (especially since it could cause compatibility issues).&lt;br /&gt;
&lt;br /&gt;
Note: There was also a proposal to use table syntax for variables (similar to T-SQL in-memory table variables, but restricted to a single row). This is very effective for avoiding collisions, but I dislike it. It complicates simple expressions, adds inconsistency, and looks odd for scalar variables (though it could be useful for composite variables). This design does not solve every problem --- for example, variables can still be shadowed by tables because of SEARCH_PATH (a known issue).&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(var); -- variable is not a table&lt;br /&gt;
    END&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      -- variable as a table (I don&#039;t like it)&lt;br /&gt;
      -- can we use DML? How many rows can it hold?&lt;br /&gt;
      -- is the value a row or not?&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, (SELECT var FROM var);&lt;br /&gt;
    END&lt;br /&gt;
    $$&lt;br /&gt;
&lt;br /&gt;
I am not against introducing in-memory tables --- or more correctly, global temporary tables. A well-designed implementation could be very useful. But this is a different feature and should be designed separately.  &lt;br /&gt;
&lt;br /&gt;
There are also performance considerations: PL/pgSQL can use simple expression evaluation when no tables are referenced. This optimization would need to change, which touches sensitive code. It would also be inconsistent.  &lt;br /&gt;
&lt;br /&gt;
I would like global temporary tables (which PostgreSQL currently lacks). Tables should behave like tables, and variables should behave like variables. The natural mental model for session variables comes from variables in PL languages. PostgreSQL internally supports *transition tables*, and I can imagine allowing these outside of triggers. That would be valid, but again, it is a different feature.  &lt;br /&gt;
&lt;br /&gt;
Implementing declared tables inside PL/pgSQL would be simpler from a high-level design perspective (interaction between SQL tables and PL/pgSQL is already well defined), but difficult from a low-level perspective (statistics, optimizer data, etc. --- the same issues as with global temporary tables, unless we add fake proxy local temp tables).&lt;br /&gt;
&lt;br /&gt;
   CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
   AS $$&lt;br /&gt;
   -- not implemented yet, not part of this proposal&lt;br /&gt;
   DECLARE t TABLE(a int, b int); -- this table is invisible outside fx&lt;br /&gt;
   BEGIN&lt;br /&gt;
     INSERT INTO t VALUES(10,20);&lt;br /&gt;
     INSERT INTO t VALUES(30,40);&lt;br /&gt;
     RAISE NOTICE &#039;%&#039;, (SELECT count(*) FROM t);&lt;br /&gt;
   END;&lt;br /&gt;
   $$ LANGUAGE plpgsql;&lt;br /&gt;
&lt;br /&gt;
Some data languages define relational variables, but this concept has no overlap with the common understanding of session variables.&lt;br /&gt;
&lt;br /&gt;
Inside PL/pgSQL it would be possible to configure the parser to allow the use of session variables without a variable fence (similar to ordinary PL/pgSQL variables) in expressions. This is not supported yet, but implementation should not be problematic. Outside PL, variable fences could in principle be optional when there is no risk of collisions (for example, in expressions without subselects). This has not been implemented yet.&lt;br /&gt;
&lt;br /&gt;
==== Assign Command LET ====&lt;br /&gt;
&lt;br /&gt;
The value of a session variable can be assigned either by a dedicated LET command or by the SELECT INTO construct. In most systems where a special command is needed, the keyword SET is used (MySQL, MSSQL, SQL/PSM, DB2). In PostgreSQL, SET could be extended to support a_expr at the parser level, but this would require partially rewriting the current implementation of the SET command. Technically, this should not be a problem.&lt;br /&gt;
&lt;br /&gt;
The main issue is the potential for collisions between GUCs and session variables. GUCs are not declared in advance, are not associated with a schema (the prefix for a custom GUC can be any non-empty string), and access is not controlled by SEARCH_PATH. A variable fence could also solve this issue, but in this case the risk is always present, so fences could not be optional. Some languages use the keyword LET for assignments. Introducing LET in PostgreSQL provides a clean way to eliminate collisions between GUCs and session variables.&lt;br /&gt;
&lt;br /&gt;
It would also be possible to allow assignment to session variables in PL code using the usual PL assignment syntax (not yet implemented). This would be beneficial when porting from PL/SQL (Oracle) and would provide a natural syntax when using session variables as PL global variables. There do not appear to be technical obstacles to implementing this. However, one important issue arises: in PL/pgSQL, only declared variables can be targets of assignment statements. If session variables were allowed here, either (a) this check could not be performed, or (b) PL functions would gain a strong dependency on session variables, similar to the dependency on types today.&lt;br /&gt;
&lt;br /&gt;
    (2025-10-02 07:42:01) postgres=# CREATE OR REPLACE FUNCTION fx() &lt;br /&gt;
    returns void as $$&lt;br /&gt;
    begin&lt;br /&gt;
      declare var mydomain;&lt;br /&gt;
    begin&lt;br /&gt;
      var := 10;&lt;br /&gt;
    end&lt;br /&gt;
    $$ language plpgsql;&lt;br /&gt;
    ERROR:  type &amp;quot;mydomain&amp;quot; does not exist&lt;br /&gt;
    LINE 4:   declare var mydomain;&lt;br /&gt;
                          ^&lt;br /&gt;
&lt;br /&gt;
While such dependencies may not be a problem (the plan cache can be invalidated correctly), they raise new questions about temporary variables. To avoid these complications, the LET command was introduced, meaning the PL/pgSQL assignment mechanism does not need to be modified.&lt;br /&gt;
&lt;br /&gt;
A major consideration is performance, both in general and when LET is used inside PL/pgSQL.&lt;br /&gt;
&lt;br /&gt;
The LET command is a PostgreSQL utility command. Like other utility commands (e.g., CREATE TABLE AS SELECT), it can be prepared. Patches exist to implement PREPARE and EXECUTE for LET (these are not included in the reduced patch set). Even though the plan cache can be used, LET may still perform worse than a PL/pgSQL assignment for two reasons:&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL supports simple expression evaluation (also supported by LET in the enhanced patch set)&lt;br /&gt;
* PL/pgSQL variables are stored in arrays and accessed via an integer offset, while session variables are stored in a hash table and accessed via a hash lookup, which is slower. This slowdown is visible only in worst-case benchmarks:&lt;br /&gt;
&lt;br /&gt;
   DO $$ declare locvar int DEFAULT 0; begin for i in 1..1000000 loop locvar := locvar + 1; end loop; end $$ &lt;br /&gt;
   DO $$ begin for i in 1..100000 loop LET sesvar := sesvar + 1; end loop; end $$&lt;br /&gt;
&lt;br /&gt;
In the reduced patch set, the LET implementation is significantly slower because simple expression evaluation is not used (although it is implemented in the extended patch set). Nevertheless, even with the slowdown, execution remains much faster than queries that touch real tables, so performance should not be an issue in practice.&lt;br /&gt;
&lt;br /&gt;
==== Notes on Implementing Session Variables as Global Temporary Objects ====&lt;br /&gt;
&lt;br /&gt;
The core of this proposal is to design session variables as global temporary objects. Unfortunately, PostgreSQL currently has no support for this type of object. Surprisingly non-trivial is the implementation of non-system objects that have a catalogue entry, but unlike other objects, keep the data purely in memory. The problem is in memory cleaning. There is no hook that could be used to catch the event when the object was 100% deleted. We can (and must - there is nothing else) catch the sinval message, however, it is often delivered as a false alarm.&lt;br /&gt;
&lt;br /&gt;
Two main issues arise with this design:&lt;br /&gt;
&lt;br /&gt;
* the possibility of using invalid values (values stored in valid memory but no longer valid in the catalog),&lt;br /&gt;
* and memory cleanup happening too early or too late.&lt;br /&gt;
&lt;br /&gt;
Each session variable is identified by name (and possibly by an OID from pg_variable). A variable may have its own data type. Values stored in memory are kept in native binary format and remain unchanged until reassigned. Important note: an OID can be reused over time --- OIDs are unique only at a single moment, and there is no guarantee that an OID will not later be assigned to a different object. This creates a real possibility of the following issue:&lt;br /&gt;
&lt;br /&gt;
* session A: CREATE VARIABLE v AS t;&lt;br /&gt;
* session B: LET v = t &#039;value&#039;;&lt;br /&gt;
* session A: DROP VARIABLE v;&lt;br /&gt;
* session B: no activity -- it got lot of sinval messages (signals), but no command is executed, and then is not possibility to handle sinval&lt;br /&gt;
* session A: CREATE VARIABLE v AS nt; -- theoretically I can get same varid and typid like in first step, although t != nt&lt;br /&gt;
* session B: SELECT v; -- crash&lt;br /&gt;
&lt;br /&gt;
To prevent crashes, each stored value must be checked carefully before use. Checking only varid and typid is not sufficient. To ensure correctness, each variable can store its creation LSN (createlsn), which is unique throughout the lifetime of the database. Before using a value, the system checks that value.varid = catalog.varid and value.createlsn = catalog.createlsn. Type changes (ALTER VARIABLE ... ALTER TYPE) are not allowed. Dependencies ensure that the value type always matches the catalog type.&lt;br /&gt;
&lt;br /&gt;
A second problem is deciding when to clean memory. Memory cannot be freed immediately after DROP VARIABLE because the command can be rolled back:&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    BEGIN;&lt;br /&gt;
    LET var := 1;&lt;br /&gt;
    DROP VARIABLE var;&lt;br /&gt;
    ROLLBACK;&lt;br /&gt;
    SELECT VARIABLE(var); -- expected 1&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    BEGIN;&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    LET var := 1;&lt;br /&gt;
    ROLLBACK;&lt;br /&gt;
    -- var should be removed from memory&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    BEGIN;&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    SAVEPOINT s1;&lt;br /&gt;
    LET var := 1;&lt;br /&gt;
    DROP VARIABLE var;&lt;br /&gt;
    ROLLBACK TO s1;&lt;br /&gt;
    COMMIT;&lt;br /&gt;
    SELECT VARIABLE(var); -- expected 1&lt;br /&gt;
&lt;br /&gt;
The simplest solution is to mark the variable as dropped and delay memory cleanup until the next transaction where variables are validated. This approach is simple and robust, although it means memory may only be freed after some delay. While this might look messy when monitoring memory usage, the catalog is transactional, and validation against the catalog ensures correctness.&lt;br /&gt;
&lt;br /&gt;
= Variabes in PostgreSQL (as of v18) =&lt;br /&gt;
&lt;br /&gt;
PostgreSQL provides relatively advanced client-side session variables in psql --- these variables use the `:` prefix. The implementation is pretty much straightforward: `psql`&#039;s parser reads tokens from input and, when it encounters a token related to a variable, replaces it with the variable&#039;s value. The syntax supports literal and identifier escaping, and variables can be used as arguments of the `\bind` command.&lt;br /&gt;
&lt;br /&gt;
`psql` variables are typeless client-side variables, available only inside `psql` or `pgbench`.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:40:18) postgres=# \set myvar ahoj&lt;br /&gt;
    (2025-09-25 09:40:33) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:14) postgres=# select :&#039;myvar&#039;;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:24) postgres=# select $1 \bind :myvar \g&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
The `\set` command allows only simple string concatenation, but it also supports execution of shell commands. When a query is executed with the `\gset` command, its result can be stored in a `psql` variable.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:41:40) postgres=# \set ctime `date`&lt;br /&gt;
    (2025-09-25 09:43:55) postgres=# \echo :ctime&lt;br /&gt;
    Thu Sep 25 09:43:55 CEST 2025&lt;br /&gt;
&lt;br /&gt;
The `\set` command is fairly limited, making complex operations unreadable or non-portable.&lt;br /&gt;
&lt;br /&gt;
This mechanism is good enough for writing tests, but it is not generally available (most users do not need it), and it is not accessible from the server side (e.g., from stored procedures). There is no simple way to access `psql` variables from an anonymous block --- a proxy custom GUC variable must be used:&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:58:38) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    (2025-09-25 09:58:43) postgres=# select set_config(&#039;myvars.myvar&#039;, :&#039;myvar&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ ahoj       │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 10:00:27) postgres=# do $$ begin raise notice &#039;%&#039;, current_setting(&#039;myvars.myvar&#039;); end $$;&lt;br /&gt;
    NOTICE:  ahoj&lt;br /&gt;
    DO&lt;br /&gt;
&lt;br /&gt;
PostgreSQL also has configuration variables (GUC - Grand Unified Configuration). These are primarily designed for PostgreSQL and extension configuration, but they can be used as session variables. GUC variables can be set with the `SET` command or the `set_config` function, and read with `SHOW` or `current_setting`.&lt;br /&gt;
&lt;br /&gt;
GUC variables are meant for holding configuration parameters. When created through the internal API, they can be restricted to superusers, hidden from regular users, and assigned specific types (boolean, memory, number, interval, string, enum) with validation. This type system is separate from the SQL type system and is initialized before PostgreSQL’s SQL types.&lt;br /&gt;
&lt;br /&gt;
Variables created from SQL must be &amp;quot;custom&amp;quot; variables, using a prefix in their name --- these are always strings. Such custom GUCs are often used as a workaround for missing server-side session variables in PostgreSQL, but they are typeless, unprotected, and unsafe.&lt;br /&gt;
&lt;br /&gt;
A notable feature of PostgreSQL GUCs (built-in or custom) is that they can have different scopes: transaction, session, or function execution. They can also be assigned default values at specific events --- configuration loading, role login, database login, or function start. These features are useful for configuration, but problematic when GUCs are repurposed as session variables.&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
    RETURNS void AS $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, current_setting(&#039;my.x&#039;);&lt;br /&gt;
    END $$ LANGUAGE plpgsql SET my.x = 20;&lt;br /&gt;
    CREATE FUNCTION&lt;br /&gt;
    (2025-09-27 06:10:37) postgres=# SET my.x = 0; SELECT fx();&lt;br /&gt;
    SET&lt;br /&gt;
    NOTICE:  20&lt;br /&gt;
    ┌────┐&lt;br /&gt;
    │ fx │&lt;br /&gt;
    ╞════╡&lt;br /&gt;
    │    │&lt;br /&gt;
    └────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    (2025-09-27 06:10:39) postgres=# SHOW my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 0    │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
GUC variables are transactional, and this behavior cannot be disabled.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-27 06:17:31) postgres=# set my.x = 10;&lt;br /&gt;
    SET&lt;br /&gt;
    (2025-09-27 06:19:42) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:19:46) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, true);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:19:55) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:01) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:20:11) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:13) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:20:38) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:45) postgres=# rollback;&lt;br /&gt;
    ROLLBACK&lt;br /&gt;
    (2025-09-27 06:20:52) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:55) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:22:36) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:22:39) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:22:42) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
As a result, there is no reliable way to store details such as error information in custom GUCs.&lt;br /&gt;
&lt;br /&gt;
= Session Variables in Other Systems =&lt;br /&gt;
&lt;br /&gt;
This section surveys how session variables are implemented in several widely used database systems. Each subsection includes code examples to illustrate syntax, behaviour, and scope.&lt;br /&gt;
&lt;br /&gt;
== MySQL ==&lt;br /&gt;
&lt;br /&gt;
Session variables in MySQL have syntax similar to the better-known T-SQL variables (though other details are MySQL-specific). MySQL distinguishes two kinds of variables:&lt;br /&gt;
&lt;br /&gt;
* system variables - referenced using the `@@` prefix (or `@@scope.name`)&lt;br /&gt;
* user-defined variables - referenced using the `@` prefix&lt;br /&gt;
&lt;br /&gt;
System variables are modified with the `SET` statement. The `SET` statement accepts the modifiers `GLOBAL`, `SESSION`, or `PERSIST`. Some variables can only be set using the `GLOBAL` or `SESSION` modifier; when the variable name is specified with the `@@` prefix, it is not necessary to explicitly indicate the scope. System variables are defined by MySQL or by MySQL plugins.&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL some_config = 100;&lt;br /&gt;
    SET @@same_config = 100;&lt;br /&gt;
    &lt;br /&gt;
    SET SESSION sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@SESSION.sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
&lt;br /&gt;
Persistent values are stored in `mysqld-auto.cnf` (in the server data directory).&lt;br /&gt;
&lt;br /&gt;
Resetting system variables can be done by assigning DEFAULT or by copying from the GLOBAL namespace:&lt;br /&gt;
&lt;br /&gt;
    SET @@sql_mode = DEFAULT;&lt;br /&gt;
    SET @@sql_mode = @@GLOBAL.sql_mode&lt;br /&gt;
&lt;br /&gt;
PostgreSQL equivalents:&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL ; -- there is not similar command in Postgres&lt;br /&gt;
    SET SESSION ; -- SET&lt;br /&gt;
    SET PERSIST ; -- ALTER SYSTEM; SELECT pg_reload_conf();&lt;br /&gt;
    SELECT @@var; -- SELECT current_setting(&#039;var);&lt;br /&gt;
&lt;br /&gt;
User-defined variables are created by assignment:&lt;br /&gt;
&lt;br /&gt;
    SET @var = expr [, @var2 = expr [ ... ]];&lt;br /&gt;
&lt;br /&gt;
User-defined variables can only be of type integer, decimal, float, binary, non-binary string, or NULL. Casting between these types is implicit and hidden. User-defined variables cannot be used as table or column names.&lt;br /&gt;
&lt;br /&gt;
An advantage of the T-SQL-style syntax (with special prefixes) is that it provides a simple solution for avoiding identifier collisions. MySQL variables are convenient, and the user experience can be good for people already familiar with T-SQL (MSSQL or Sybase). On the other hand, prefix-based syntax can be confusing for users coming from systems other than MSSQL. The type system is perhaps too simple and can be unsafe. In general, the performance of MySQL (or MariaDB) stored procedures is not good, and stored procedures are therefore not frequently used. There is no protection against typographical errors. Unassigned user variables can be used without error (they default to NULL), which makes static checking of stored procedures impossible in this area.&lt;br /&gt;
&lt;br /&gt;
There is no way to restrict access to user-defined variables in MySQL. They cannot be protected against unintended usage. The only possible safeguard is a naming convention, since all user-defined variables share a single namespace.&lt;br /&gt;
&lt;br /&gt;
== Oracle ==&lt;br /&gt;
&lt;br /&gt;
Oracle PL/SQL allows the use of package variables. PL/SQL is based on the Ada language, and package variables act as &amp;quot;global&amp;quot; variables. They are not directly visible from SQL, but Oracle provides a reduced syntax for functions without arguments, so you typically write a wrapper:&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE PACKAGE my_package&lt;br /&gt;
    AS&lt;br /&gt;
      FUNCTION get_a RETURN NUMBER;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    /&lt;br /&gt;
    &lt;br /&gt;
    CREATE OR REPLACE PACKAGE BODY my_package&lt;br /&gt;
    AS&lt;br /&gt;
      a  NUMBER(20);&lt;br /&gt;
      &lt;br /&gt;
      FUNCTION get_a&lt;br /&gt;
      RETURN NUMBER&lt;br /&gt;
      IS&lt;br /&gt;
      BEGIN&lt;br /&gt;
        RETURN a;&lt;br /&gt;
      END get_a;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    &lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
Inside SQL, SQL identifiers take precedence. Inside non-SQL commands (such as CALL or PL/SQL blocks), package identifiers take precedence.&lt;br /&gt;
&lt;br /&gt;
Oracle allows both syntaxes for calling a function with zero arguments:&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a() FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
This reduces the risk of naming collisions. Package variables persist for the duration of a session.&lt;br /&gt;
&lt;br /&gt;
Another possibility is to use variables in SQL*Plus (similar to `psql` variables, but with the ability to define the type on the server side).&lt;br /&gt;
&lt;br /&gt;
A variable is declared with the `VARIABLE` command and can be accessed in the session using the `:varname` syntax (a prior declaration step may be optional):&lt;br /&gt;
&lt;br /&gt;
    VARIABLE bv_variable_name VARCHAR2(30)&lt;br /&gt;
    BEGIN&lt;br /&gt;
      :bv_variable_name := &#039;Some Value&#039;;&lt;br /&gt;
    END;&lt;br /&gt;
    &lt;br /&gt;
    SELECT column_name&lt;br /&gt;
      FROM   table_name&lt;br /&gt;
     WHERE  column_name = :bv_variable_name;&lt;br /&gt;
&lt;br /&gt;
== DB2 ==&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;user-defined global variables&amp;quot; in DB2 are similar to the proposed PostgreSQL feature. The main difference is in the access rights: DB2 uses `READ` and `WRITE`, while PostgreSQL uses `SELECT` and `UPDATE`. Because PostgreSQL already uses the `SET` command for GUCs, the `LET` command was introduced in the proposal (DB2 uses `SET`).&lt;br /&gt;
&lt;br /&gt;
Variables are visible in all sessions, but their values are private to each session. Variables are not transactional. Their usage is broader than in the proposal: they can be changed by `SET`, `SELECT INTO`, or used as OUT parameters of procedures. The search path (or a similar mechanism) applies to variables as well, but variables have lower priority than tables or columns.&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE myCounter INT DEFAULT 01;&lt;br /&gt;
    SELECT EMPNO, LASTNAME, CASE WHEN myCounter = 1 THEN SALARY ELSE NULL END&lt;br /&gt;
    FROM EMPLOYEE WHERE WORKDEPT = &#039;A00&#039;;&lt;br /&gt;
    SET myCounter = 29;&lt;br /&gt;
&lt;br /&gt;
There also appear to be other kinds of variables, accessed using the function `GETVARIABLE(&#039;name&#039;, &#039;default&#039;)`. These are very similar to PostgreSQL GUCs and the `current_setting` function. Such variables can be set via the connection string, are of type `VARCHAR`, and up to 10 values are allowed. Built-in session variables (configuration) can also be accessed using `GETVARIABLE`.&lt;br /&gt;
&lt;br /&gt;
== MSSQL (T-SQL) ==&lt;br /&gt;
&lt;br /&gt;
MSSQL provides two types of variables:&lt;br /&gt;
&lt;br /&gt;
* Global variables — accessed with the `@@varname` syntax. These are similar to GUCs and provide state information such as `@@ERROR`, `@@ROWCOUNT`, or `@@IDENTITY`. &lt;br /&gt;
* Local variables — accessed with the `@varname` syntax. These must be declared with the `DECLARE` command before use. Their scope is limited to the batch, procedure, or function where they are executed.&lt;br /&gt;
&lt;br /&gt;
    DECLARE @TestVariable AS VARCHAR(100)&lt;br /&gt;
    SET @TestVariable = &#039;Think Green&#039;&lt;br /&gt;
    GO&lt;br /&gt;
    PRINT @TestVariable&lt;br /&gt;
&lt;br /&gt;
This script fails because `PRINT` is executed in a different batch. Therefore, it seems MSSQL does not truly support session variables.&lt;br /&gt;
&lt;br /&gt;
There are also mechanisms similar to PostgreSQL&#039;s custom GUCs and the use of `current_setting` and `set_config` functions. In general, MSSQL is fairly primitive in this area.&lt;br /&gt;
&lt;br /&gt;
    EXEC sp_set_session_context &#039;user_id&#039;, 4;&lt;br /&gt;
    SELECT SESSION_CONTEXT(N&#039;user_id&#039;);&lt;br /&gt;
&lt;br /&gt;
= ToDo =&lt;br /&gt;
&lt;br /&gt;
* SECURITY LABEL support — enhance the `dummy_seclabel` module and the `sepgsql` extension. Update PostgreSQL documentation and ensure `SecLabelSupportsObjectType` includes session variables.&lt;/div&gt;</summary>
		<author><name>Okbobcz</name></author>
	</entry>
	<entry>
		<id>https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41870</id>
		<title>Implementation of declarative catalog session variables</title>
		<link rel="alternate" type="text/html" href="https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41870"/>
		<updated>2025-10-02T16:01:18Z</updated>

		<summary type="html">&lt;p&gt;Okbobcz: /* Notes about implementation session variables as global temporary objects */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Session variables are database objects that can hold a value across multiple queries inside a session. The design of session variables varies widely, and many databases implement more than one type of session variable.&lt;br /&gt;
&lt;br /&gt;
The SQL/PSM standard introduces modules and module-level variables. However, this part of the standard has not been implemented in any widely used product. As a result, each database system has developed its own proprietary design, syntax, and feature set.&lt;br /&gt;
&lt;br /&gt;
Session variables are often part of the stored procedure environment, but there are exceptions --- for example, MySQL session variables or client-side session variables in tools like psql. In most systems, session variables do not participate in transactions: once set, their value persists until explicitly changed, even if the surrounding transaction is rolled back. Conceptually, they behave much like variables in general-purpose programming languages.&lt;br /&gt;
&lt;br /&gt;
Because there is no common design, comparisons across systems are difficult. Each implementation has its own advantages and disadvantages, and in some cases a single system supports more than one kind of session variable.&lt;br /&gt;
&lt;br /&gt;
Kinds of session variables:&lt;br /&gt;
* client side (psql, pgadmin, ...) - mostly similar to shell variables (based on string operations)&lt;br /&gt;
* server side&lt;br /&gt;
** declarative&lt;br /&gt;
*** created only for batch (T-SQL)&lt;br /&gt;
*** created as an database object (or part of database object) (Oracle, DB2)&lt;br /&gt;
** created by usage (MySQL)&lt;br /&gt;
** with priprietary syntax for identifiers (MySQL, T-SQL)&lt;br /&gt;
** with ANSI SQL syntax for identifiers (Oracle, DB2)&lt;br /&gt;
&lt;br /&gt;
Possible usage of session variables:&lt;br /&gt;
&lt;br /&gt;
* usage of session variables as global variables in PL (tracing, debugging, hacking),&lt;br /&gt;
* holding configuration (for PL),&lt;br /&gt;
* holding some more used data in interactive session,&lt;br /&gt;
* holding credentials for some RLS and data securing,&lt;br /&gt;
* helping with migration from others systems (mainly from Oracle),&lt;br /&gt;
* allow anonymous block parametrization (only postgres).&lt;br /&gt;
* session variables are writeable on read only hot standby (postgres)&lt;br /&gt;
&lt;br /&gt;
Session variables holds a values like temporary tables. What are differences between temporary tables and variables?:&lt;br /&gt;
&lt;br /&gt;
* variables holds a values, tables holds a set of rows,&lt;br /&gt;
* syntax for usage variables is shorter (more friendly for interactive work), access (read, write) is faster,&lt;br /&gt;
* the content of session variables is generally non transactional, the content of temporary tables is transactional,&lt;br /&gt;
* the content of session variables is just one value or NULL (no MVCC, no VACUUM),&lt;br /&gt;
* temporary tables are tables still (transactional, MVCC), update is expensive, under one transaction repeated updates makes lot of dead tuples,&lt;br /&gt;
* temporary tables are not cleaned by autovacuum (and inside function is not possible to execute VACUUM), &lt;br /&gt;
* Postgres doesn&#039;t support global temporary tables, so usage of temporary table for just few values or value is very expensive (catalogue bloat).&lt;br /&gt;
&lt;br /&gt;
= SQL/PSM =&lt;br /&gt;
&lt;br /&gt;
The SQL standard introduces the concept of modules, which can be associated with schemas (partially). Variables in modules behave like PL/pgSQL variables but are only local. The only objects allowed at the module level are temporary tables, which can be accessed only from routines assigned to that module. Modules can encapsulate private objects (functions, procedure, temp tables), that are not visible outside the module. Inside the module, a  private objects shadows other objects. What I know the modules (and generally SQL/PSM) doesn&#039;t cover a deployment of code, so there is not similarity with PostgreSQL extensions. SQL/PSM Modules are modules like modules from modular languages like Modula2 or Oberon. There is some similarity with Postgres schemas (both are containers) with significant differences (there is not a concept of SEARCH_PATH (on PSM side) or there is not a concept of private or public objects (on Postgres schema side)).&lt;br /&gt;
&lt;br /&gt;
SQL/PSM variables are not transactional (they are used exactly like in PL/pgSQL). The precedence between variables and columns is not specified.&lt;br /&gt;
&lt;br /&gt;
= Implementation Challenges =&lt;br /&gt;
&lt;br /&gt;
* Possible collisions between session variables and other database objects.&lt;br /&gt;
* Memory cleanup after dropping a session variable, especially when DDL operations can be reverted (Postgres has not support for global temp objects).&lt;br /&gt;
* Memory invalidation: if a variable is dropped by one session, other sessions should not continue using it (Postgres has not support for global temp objects).&lt;br /&gt;
&lt;br /&gt;
= Proposed Implementations =&lt;br /&gt;
&lt;br /&gt;
It is clear that no single design is perfect for all use cases. MySQL’s approach is convenient and useful for interactive work, but it cannot be used for storing sensitive data and is considered unsafe. PostgreSQL GUCs are excellent for configuration, but they are not well suited for interactive use and are very limited when used as global variables in PL code.&lt;br /&gt;
&lt;br /&gt;
Because of these trade-offs, no single design is sufficient. In practice, having multiple designs within a single system could support a broader range of use cases more effectively.&lt;br /&gt;
&lt;br /&gt;
== Design session variables as database global temporary typed objects with ANSI SQL syntax for identifiers ==&lt;br /&gt;
&lt;br /&gt;
Author: Pavel Stehule &amp;lt;pavel.stehule@gmail.com&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I searched for a solution that could work well with the specific PostgreSQL environment:&lt;br /&gt;
&lt;br /&gt;
* PostgreSQL has more than one widely used stored procedure language: PL/pgSQL, PL/Python, or PL/Perl.&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL is a reduced version of PL/SQL, and PL/SQL is a modified ADA language. I am pretty happy with the current complexity of PL/pgSQL --- it is a domain-specific language that works well. It is very simple and easy to learn. PL/pgSQL has no packages or modules; instead PostgreSQL schemas are used. But schemas are database objects in their own right, independent of PL.&lt;br /&gt;
&lt;br /&gt;
* PostgreSQL already has a code container --- the schema. I don&#039;t want to introduce another container object (modules), as this would overlap heavily with schemas. Implementing modules for PL/pgSQL could be useful, but doing it properly would be quite complex. The main problem is the concept of public and private objects --- PostgreSQL does not have this. Don&#039;t forget: every PL/pgSQL expression is a SQL expression, so public/private visibility would first need to be implemented internally in PostgreSQL. That would be redundant (and possibly in conflict) with SEARCH_PATH.&lt;br /&gt;
&lt;br /&gt;
So I wanted a design that supports multiple PL languages, does not add complexity to the relatively simple PL/pgSQL, and does not duplicate functionality already available.  &lt;br /&gt;
&lt;br /&gt;
I wrote a larger application in PL/pgSQL. To maintain it, I created plpgsql_lint (later renamed plpgsql_check). So I am always looking for solutions that do not block static code analysis. Static analysis requires persistent objects, which naturally leads to designing session variables as database objects (with their own system catalog).  &lt;br /&gt;
When session variables are database objects, ACLs can be applied naturally. Oracle package variables are well protected by package scope. PostgreSQL, however, has no schema scope --- schema locality is controlled by the SEARCH_PATH GUC, and introducing another mechanism would be messy. But with ACLs, variable contents can also be secured:&lt;br /&gt;
&lt;br /&gt;
    CREATE TEMP VARIABLE x AS int;&lt;br /&gt;
     &lt;br /&gt;
    LET x = 10;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(x);&lt;br /&gt;
    END;&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    SELECT VARIABLE(x);&lt;br /&gt;
&lt;br /&gt;
==== Collisions between column and variable identifiers ====&lt;br /&gt;
&lt;br /&gt;
There is a risk of identifier collisions between variables and columns. With ANSI SQL syntax for session variable identifiers, the problem is how to distinguish variables from columns. This is a known issue in PL/pgSQL and PL/SQL and can be solved either by prioritization or by prohibiting collisions.  &lt;br /&gt;
&lt;br /&gt;
Prohibiting collisions has largely solved this problem in PL/pgSQL. Unfortunately, session variables are global objects, so collisions can occur in any query. A poorly named variable could break queries unexpectedly. Prioritization also has problems --- a variable or a column could be used silently when the other was intended.&lt;br /&gt;
&lt;br /&gt;
    CREATE TABLE tab(a int);&lt;br /&gt;
    CREATE VARIABLE a AS int;&lt;br /&gt;
    &lt;br /&gt;
    SELECT a FROM tab; -- with disallowed collisions, this query fails when a variable named &amp;quot;a&amp;quot; exists&lt;br /&gt;
    &lt;br /&gt;
    LET a = 1;&lt;br /&gt;
    DELETE FROM tab WHERE a = 1; -- with higher priority for variables, all rows could be deleted&lt;br /&gt;
    SELECT a FROM tab; -- with higher priority for columns, the variable is ignored&lt;br /&gt;
&lt;br /&gt;
Even with collision prohibition, mistakes are still possible:&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE _id AS int;&lt;br /&gt;
    LET _id = 10;&lt;br /&gt;
    CREATE TABLE foo(id int);&lt;br /&gt;
    DELETE FROM foo WHERE _id = 10; -- just a typo, but all rows are deleted&lt;br /&gt;
&lt;br /&gt;
These problems are not new. Developers in PL/pgSQL or PL/SQL know them, but the scope of risk is normally limited to stored procedures. After many discussions and designs, I introduced &amp;quot;variable fences&amp;quot; (the syntax VARIABLE(varname)). Inside any query, variables can be used only inside a variable fence. This completely solves collisions and reduces the chance of human error. Currently, fences are required everywhere in queries and expressions. In the future, they might be made optional inside PL/pgSQL, to reduce overhead when porting Oracle applications. This could be controlled by a new plpgsql.extra_checks setting.&lt;br /&gt;
&lt;br /&gt;
A variable fence can collide with a user-defined function named `variable`. This is similar to keyword conflicts such as CUBE. It can be resolved by schema-qualifying or quoting:&lt;br /&gt;
&lt;br /&gt;
    SELECT public.variable(...)&lt;br /&gt;
    SELECT &amp;quot;variable&amp;quot;(...)&lt;br /&gt;
&lt;br /&gt;
Why not use T-SQL (MSSQL, MySQL) syntax for session variables? There are several reasons – some objective, one subjective:&lt;br /&gt;
&lt;br /&gt;
* There is a collision with custom operators. Operators `@` and `@@` are already used, and `@@` is common:&lt;br /&gt;
&lt;br /&gt;
    (2025-09-28 06:57:30) postgres=# create table tab(a int);&lt;br /&gt;
    CREATE TABLE&lt;br /&gt;
    (2025-09-28 06:57:42) postgres=# insert into tab values(10);&lt;br /&gt;
    INSERT 0 1&lt;br /&gt;
    (2025-09-28 06:57:50) postgres=# select @a from tab;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │       10 │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
We could safely use only `:` as a prefix, but that would conflict with psql variables. Similarly, `$` would conflict with query parameters.&lt;br /&gt;
&lt;br /&gt;
* T-SQL variable names look strange in PostgreSQL (in queries and in PL/pgSQL). PostgreSQL uses only ANSI/SQL identifiers, and PL/pgSQL follows the same rules. PostgreSQL is consistent here, and I see little motivation to introduce a new, non-standard syntax (especially since it could cause compatibility issues).&lt;br /&gt;
&lt;br /&gt;
Note: There was also a proposal to use table syntax for variables (similar to T-SQL in-memory table variables, but restricted to a single row). This is very effective for avoiding collisions, but I dislike it. It complicates simple expressions, adds inconsistency, and looks odd for scalar variables (though it could be useful for composite variables). This design doesn&#039;t solve all possible problems - variables can be shadowed by tables still, because we have SEARCH_PATH (but this is known issue):&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(var); -- variable is not a table&lt;br /&gt;
    END&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      -- variable as a table (I don&#039;t like it)&lt;br /&gt;
      -- can we use DML? How many rows can it hold?&lt;br /&gt;
      -- is the value a row or not?&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, (SELECT var FROM var);&lt;br /&gt;
    END&lt;br /&gt;
    $$&lt;br /&gt;
&lt;br /&gt;
I am not against introducing in-memory tables --- or more correctly, global temporary tables. A well-designed implementation could be very useful. But this is a different feature and should be designed separately.  &lt;br /&gt;
&lt;br /&gt;
There are also performance considerations: PL/pgSQL can use simple expression evaluation when no tables are referenced. This optimization would need to change, which touches sensitive code. It would also be inconsistent.  &lt;br /&gt;
&lt;br /&gt;
I would like global temporary tables (which PostgreSQL currently lacks). Tables should behave like tables, and variables should behave like variables. The natural mental model for session variables comes from variables in PL languages. PostgreSQL internally supports *transition tables*, and I can imagine allowing these outside of triggers. That would be valid, but again, it is a different feature.  &lt;br /&gt;
&lt;br /&gt;
Implementing declared tables inside PL/pgSQL would be simpler from a high-level design perspective (interaction between SQL tables and PL/pgSQL is already well defined), but difficult from a low-level perspective (statistics, optimizer data, etc. --- the same issues as with global temporary tables, unless we add fake proxy local temp tables).&lt;br /&gt;
&lt;br /&gt;
   CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
   AS $$&lt;br /&gt;
   -- not implemented yet, not part of this proposal&lt;br /&gt;
   DECLARE t TABLE(a int, b int); -- this table is invisible outside fx&lt;br /&gt;
   BEGIN&lt;br /&gt;
     INSERT INTO t VALUES(10,20);&lt;br /&gt;
     INSERT INTO t VALUES(30,40);&lt;br /&gt;
     RAISE NOTICE &#039;%&#039;, (SELECT count(*) FROM t);&lt;br /&gt;
   END;&lt;br /&gt;
   $$ LANGUAGE plpgsql;&lt;br /&gt;
&lt;br /&gt;
Some data languages define relational variables, but this concept has no overlap with the common understanding of session variables.&lt;br /&gt;
&lt;br /&gt;
Inside PL/pgSQL we can use a possibility to configure a parser, and can be easy to allow a usage of session variables without variable fence (like common plpgsql variable) in expressions. It is not supported yet, but there should not be implementation problems. Outside PL, the variable fences can be optional, when there is not risk of collisions (the expression doesn&#039;t contains subselect) (not implemented yet).&lt;br /&gt;
&lt;br /&gt;
==== Assign command - LET ====&lt;br /&gt;
&lt;br /&gt;
The value of session variable can be assigned by dedicated PL assign command or by `SELECT INTO` construct. When special command is prefixed by some a keyword, then the keyword `SET` is usually used (MySQL, MSSQL, SQL/PSM, DB2). Command `SET` can be enhanced to support `a_expr` on parser level in Postgres too. It requires partial rewriting of current implementation of `SET` command, but there is not technical issues.&lt;br /&gt;
&lt;br /&gt;
Unfortunately, there can be collisions between GUC and session variables. Currently GUC are not declared, and they are not joined to any schema (prefix for custom GUC can be just any not empty string), and access to GUC is not controlled by SEARCH_PATH. We can use same solution like usage session variables in expression - variable fence - but in this case, the risk is every time, and there is not a possibility to usage of variable fences can be optional (for some cases) in future. Some languages uses for a assignment the keyword `LET`. I introduced this keyword as a solution for 100% fix of collisions between GUC and session variables identifiers. &lt;br /&gt;
&lt;br /&gt;
I can imagine the assignment to session variable inside PL by usual PL assignment command (not implemented yet). This can be benefit for translation from PL/SQL (Oracle), and it can be very natural syntax, when session variable is used like PL global variable. Probably (not tested), there are not technical issues for implementation of this. But there is different significant issue. Currently, only PL/pgSQL variables can be target of PL/pgSQL assign statement, and these variables should be declared before usage. If we allow session variables there, then a) we cannot to do this check, or b) there will be new strong dependency from PL functions to session variables - like on types now.&lt;br /&gt;
&lt;br /&gt;
    (2025-10-02 07:42:01) postgres=# CREATE OR REPLACE FUNCTION fx() &lt;br /&gt;
    returns void as $$&lt;br /&gt;
    begin&lt;br /&gt;
      declare var mydomain;&lt;br /&gt;
    begin&lt;br /&gt;
      var := 10;&lt;br /&gt;
    end&lt;br /&gt;
    $$ language plpgsql;&lt;br /&gt;
    ERROR:  type &amp;quot;mydomain&amp;quot; does not exist&lt;br /&gt;
    LINE 4:   declare var mydomain;&lt;br /&gt;
                          ^&lt;br /&gt;
&lt;br /&gt;
The strong dependency today is not problem probably (because plan cache can be correctly invalidated), but there can be some new questions about temporary variables. Again as a solution I proposed a `LET` command, and we don&#039;t need to touch to PL/pgSQL assign statement.&lt;br /&gt;
&lt;br /&gt;
The significant question is performance generally and performance when LET is used inside PL/pgSQL.&lt;br /&gt;
&lt;br /&gt;
The command `LET` is PostgreSQL utility command. Against history, the utility command can be prepared (as `CREATE TABLE AS SELECT`). There are patches that implements `PREPARE` and `EXECUTE` for `LET` command (these patches are not in reduced patch set). Although plan cache can be used, the performance against PL/pgSQL assign can be slower - from two reasons:&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL allows simple expression evaluation (this is supported for `LET` command too in enhanced patch set),&lt;br /&gt;
* PL/pgSQL variables are stored in a array (and indexed by int offset), session variables are stored in hash table, and they are accessed by hash search (and this slower). This slowdown is visible only in worst case benchmarks like:&lt;br /&gt;
&lt;br /&gt;
   DO $$ declare locvar int DEFAULT 0; begin for i in 1..1000000 loop locvar := locvar + 1; end loop; end $$ &lt;br /&gt;
   DO $$ begin for i in 1..100000 loop LET sesvar := sesvar + 1; end loop; end $$&lt;br /&gt;
&lt;br /&gt;
The implementation of `LET` in reduced patch set, is significantly slower, because the simple expression evaluation is not used (it is implemented, but not in reduced patch set). Although there is significant slowdown, the execution is still significantly faster than a execution of any query that touch to real table (it should not be a problem for real usage).&lt;br /&gt;
&lt;br /&gt;
==== Notes about implementation session variables as global temporary objects ====&lt;br /&gt;
The core of this proposal is design of session variables as global temporary objects. Unfortunately, Postgres has zero support for this kind of objects. Session variables has not any session specific data in the catalog. This is much more better (and easier) than for global temporary tables. The data of session variables are in session memory (only). This is big difference from tables. There the data are stored in pages, and pages are stored in some files. The files are important - any cached data can be thrown (and they are frequently thrown). There are two kinds of new issues (related to session variables).&lt;br /&gt;
&lt;br /&gt;
* possibility to use invalid value (stored in valid memory)&lt;br /&gt;
* too early or too delayed memory cleaning&lt;br /&gt;
&lt;br /&gt;
Any session variable is identified by name (and possibly by oid from `pg_variable`). Any variable can use own data type. Values stored in memory are stored in native binary format, and without reassignment, the value is not changed. Important note - the one oid can be assigned more times. oids are unique in just one moment, but there is not a protection so one oid will not be used more times for different objects. There is a real possibility of following issue:&lt;br /&gt;
&lt;br /&gt;
* session A: CREATE VARIABLE v AS t;&lt;br /&gt;
* session B: LET v = t &#039;value&#039;;&lt;br /&gt;
* session A: DROP VARIABLE v;&lt;br /&gt;
* session B: no activity -- it got lot of sinval messages (signals), but no command is executed, and then is not possibility to handle sinval&lt;br /&gt;
* session A: CREATE VARIABLE v AS nt; -- theoretically I can get same varid and typid like in first step, although t != nt&lt;br /&gt;
* session B: SELECT v; -- crash&lt;br /&gt;
&lt;br /&gt;
Before any usage of stored value, we need to do really identity check and varid and typid is not enough. But we can store to variable their create lsn - and this number is unique for all time database (catalog) live. So the necessary check before usage of stored value is just check if value.varid = catalog.varid and value.createlsn = catalog.createlsn. The ALTER VARIABLE ALTER TYPE is not allowed. The dependency mechanism ensure so value type should be identical with catalog type.&lt;br /&gt;
&lt;br /&gt;
Second problem is when we can clean memory. We cannot to do immediately after command `DROP VARIABLE`, because this command can be reverted:&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    BEGIN;&lt;br /&gt;
    LET var := 1;&lt;br /&gt;
    DROP VARIABLE var;&lt;br /&gt;
    ROLLBACK;&lt;br /&gt;
    SELECT VARIABLE(var); -- expected 1&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    BEGIN;&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    LET var := 1;&lt;br /&gt;
    ROLLBACK;&lt;br /&gt;
    -- var should be removed from memory&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    BEGIN;&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    SAVEPOINT s1;&lt;br /&gt;
    LET var := 1;&lt;br /&gt;
    DROP VARIABLE var;&lt;br /&gt;
    ROLLBACK TO s1;&lt;br /&gt;
    COMMIT;&lt;br /&gt;
    SELECT VARIABLE(var); -- expected 1&lt;br /&gt;
&lt;br /&gt;
The most simple solution is marking variable as dropped, and in next transaction when we validate variables (values), we can clean memory after dropped variables. This technique is very simple, and robust, on second hand, the memory is cleaned after any operation with session variable, what can be after long time or maybe messy (if somebody will check used memory). Fortunately, catalogue is transactional, and then we can validate data against catalogue entry.&lt;br /&gt;
&lt;br /&gt;
= Variabes in PostgreSQL (as of v18) =&lt;br /&gt;
&lt;br /&gt;
PostgreSQL provides relatively advanced client-side session variables in psql --- these variables use the `:` prefix. The implementation is pretty much straightforward: `psql`&#039;s parser reads tokens from input and, when it encounters a token related to a variable, replaces it with the variable&#039;s value. The syntax supports literal and identifier escaping, and variables can be used as arguments of the `\bind` command.&lt;br /&gt;
&lt;br /&gt;
`psql` variables are typeless client-side variables, available only inside `psql` or `pgbench`.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:40:18) postgres=# \set myvar ahoj&lt;br /&gt;
    (2025-09-25 09:40:33) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:14) postgres=# select :&#039;myvar&#039;;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:24) postgres=# select $1 \bind :myvar \g&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
The `\set` command allows only simple string concatenation, but it also supports execution of shell commands. When a query is executed with the `\gset` command, its result can be stored in a `psql` variable.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:41:40) postgres=# \set ctime `date`&lt;br /&gt;
    (2025-09-25 09:43:55) postgres=# \echo :ctime&lt;br /&gt;
    Thu Sep 25 09:43:55 CEST 2025&lt;br /&gt;
&lt;br /&gt;
The `\set` command is fairly limited, making complex operations unreadable or non-portable.&lt;br /&gt;
&lt;br /&gt;
This mechanism is good enough for writing tests, but it is not generally available (most users do not need it), and it is not accessible from the server side (e.g., from stored procedures). There is no simple way to access `psql` variables from an anonymous block --- a proxy custom GUC variable must be used:&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:58:38) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    (2025-09-25 09:58:43) postgres=# select set_config(&#039;myvars.myvar&#039;, :&#039;myvar&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ ahoj       │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 10:00:27) postgres=# do $$ begin raise notice &#039;%&#039;, current_setting(&#039;myvars.myvar&#039;); end $$;&lt;br /&gt;
    NOTICE:  ahoj&lt;br /&gt;
    DO&lt;br /&gt;
&lt;br /&gt;
PostgreSQL also has configuration variables (GUC - Grand Unified Configuration). These are primarily designed for PostgreSQL and extension configuration, but they can be used as session variables. GUC variables can be set with the `SET` command or the `set_config` function, and read with `SHOW` or `current_setting`.&lt;br /&gt;
&lt;br /&gt;
GUC variables are meant for holding configuration parameters. When created through the internal API, they can be restricted to superusers, hidden from regular users, and assigned specific types (boolean, memory, number, interval, string, enum) with validation. This type system is separate from the SQL type system and is initialized before PostgreSQL’s SQL types.&lt;br /&gt;
&lt;br /&gt;
Variables created from SQL must be &amp;quot;custom&amp;quot; variables, using a prefix in their name --- these are always strings. Such custom GUCs are often used as a workaround for missing server-side session variables in PostgreSQL, but they are typeless, unprotected, and unsafe.&lt;br /&gt;
&lt;br /&gt;
A notable feature of PostgreSQL GUCs (built-in or custom) is that they can have different scopes: transaction, session, or function execution. They can also be assigned default values at specific events --- configuration loading, role login, database login, or function start. These features are useful for configuration, but problematic when GUCs are repurposed as session variables.&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
    RETURNS void AS $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, current_setting(&#039;my.x&#039;);&lt;br /&gt;
    END $$ LANGUAGE plpgsql SET my.x = 20;&lt;br /&gt;
    CREATE FUNCTION&lt;br /&gt;
    (2025-09-27 06:10:37) postgres=# SET my.x = 0; SELECT fx();&lt;br /&gt;
    SET&lt;br /&gt;
    NOTICE:  20&lt;br /&gt;
    ┌────┐&lt;br /&gt;
    │ fx │&lt;br /&gt;
    ╞════╡&lt;br /&gt;
    │    │&lt;br /&gt;
    └────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    (2025-09-27 06:10:39) postgres=# SHOW my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 0    │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
GUC variables are transactional, and this behavior cannot be disabled.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-27 06:17:31) postgres=# set my.x = 10;&lt;br /&gt;
    SET&lt;br /&gt;
    (2025-09-27 06:19:42) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:19:46) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, true);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:19:55) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:01) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:20:11) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:13) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:20:38) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:45) postgres=# rollback;&lt;br /&gt;
    ROLLBACK&lt;br /&gt;
    (2025-09-27 06:20:52) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:55) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:22:36) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:22:39) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:22:42) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
As a result, there is no reliable way to store details such as error information in custom GUCs.&lt;br /&gt;
&lt;br /&gt;
= Session Variables in Other Systems =&lt;br /&gt;
&lt;br /&gt;
This section surveys how session variables are implemented in several widely used database systems. Each subsection includes code examples to illustrate syntax, behaviour, and scope.&lt;br /&gt;
&lt;br /&gt;
== MySQL ==&lt;br /&gt;
&lt;br /&gt;
Session variables in MySQL have syntax similar to the better-known T-SQL variables (though other details are MySQL-specific). MySQL distinguishes two kinds of variables:&lt;br /&gt;
&lt;br /&gt;
* system variables - referenced using the `@@` prefix (or `@@scope.name`)&lt;br /&gt;
* user-defined variables - referenced using the `@` prefix&lt;br /&gt;
&lt;br /&gt;
System variables are modified with the `SET` statement. The `SET` statement accepts the modifiers `GLOBAL`, `SESSION`, or `PERSIST`. Some variables can only be set using the `GLOBAL` or `SESSION` modifier; when the variable name is specified with the `@@` prefix, it is not necessary to explicitly indicate the scope. System variables are defined by MySQL or by MySQL plugins.&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL some_config = 100;&lt;br /&gt;
    SET @@same_config = 100;&lt;br /&gt;
    &lt;br /&gt;
    SET SESSION sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@SESSION.sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
&lt;br /&gt;
Persistent values are stored in `mysqld-auto.cnf` (in the server data directory).&lt;br /&gt;
&lt;br /&gt;
Resetting system variables can be done by assigning DEFAULT or by copying from the GLOBAL namespace:&lt;br /&gt;
&lt;br /&gt;
    SET @@sql_mode = DEFAULT;&lt;br /&gt;
    SET @@sql_mode = @@GLOBAL.sql_mode&lt;br /&gt;
&lt;br /&gt;
PostgreSQL equivalents:&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL ; -- there is not similar command in Postgres&lt;br /&gt;
    SET SESSION ; -- SET&lt;br /&gt;
    SET PERSIST ; -- ALTER SYSTEM; SELECT pg_reload_conf();&lt;br /&gt;
    SELECT @@var; -- SELECT current_setting(&#039;var);&lt;br /&gt;
&lt;br /&gt;
User-defined variables are created by assignment:&lt;br /&gt;
&lt;br /&gt;
    SET @var = expr [, @var2 = expr [ ... ]];&lt;br /&gt;
&lt;br /&gt;
User-defined variables can only be of type integer, decimal, float, binary, non-binary string, or NULL. Casting between these types is implicit and hidden. User-defined variables cannot be used as table or column names.&lt;br /&gt;
&lt;br /&gt;
An advantage of the T-SQL-style syntax (with special prefixes) is that it provides a simple solution for avoiding identifier collisions. MySQL variables are convenient, and the user experience can be good for people already familiar with T-SQL (MSSQL or Sybase). On the other hand, prefix-based syntax can be confusing for users coming from systems other than MSSQL. The type system is perhaps too simple and can be unsafe. In general, the performance of MySQL (or MariaDB) stored procedures is not good, and stored procedures are therefore not frequently used. There is no protection against typographical errors. Unassigned user variables can be used without error (they default to NULL), which makes static checking of stored procedures impossible in this area.&lt;br /&gt;
&lt;br /&gt;
There is no way to restrict access to user-defined variables in MySQL. They cannot be protected against unintended usage. The only possible safeguard is a naming convention, since all user-defined variables share a single namespace.&lt;br /&gt;
&lt;br /&gt;
== Oracle ==&lt;br /&gt;
&lt;br /&gt;
Oracle PL/SQL allows the use of package variables. PL/SQL is based on the Ada language, and package variables act as &amp;quot;global&amp;quot; variables. They are not directly visible from SQL, but Oracle provides a reduced syntax for functions without arguments, so you typically write a wrapper:&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE PACKAGE my_package&lt;br /&gt;
    AS&lt;br /&gt;
      FUNCTION get_a RETURN NUMBER;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    /&lt;br /&gt;
    &lt;br /&gt;
    CREATE OR REPLACE PACKAGE BODY my_package&lt;br /&gt;
    AS&lt;br /&gt;
      a  NUMBER(20);&lt;br /&gt;
      &lt;br /&gt;
      FUNCTION get_a&lt;br /&gt;
      RETURN NUMBER&lt;br /&gt;
      IS&lt;br /&gt;
      BEGIN&lt;br /&gt;
        RETURN a;&lt;br /&gt;
      END get_a;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    &lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
Inside SQL, SQL identifiers take precedence. Inside non-SQL commands (such as CALL or PL/SQL blocks), package identifiers take precedence.&lt;br /&gt;
&lt;br /&gt;
Oracle allows both syntaxes for calling a function with zero arguments:&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a() FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
This reduces the risk of naming collisions. Package variables persist for the duration of a session.&lt;br /&gt;
&lt;br /&gt;
Another possibility is to use variables in SQL*Plus (similar to `psql` variables, but with the ability to define the type on the server side).&lt;br /&gt;
&lt;br /&gt;
A variable is declared with the `VARIABLE` command and can be accessed in the session using the `:varname` syntax (a prior declaration step may be optional):&lt;br /&gt;
&lt;br /&gt;
    VARIABLE bv_variable_name VARCHAR2(30)&lt;br /&gt;
    BEGIN&lt;br /&gt;
      :bv_variable_name := &#039;Some Value&#039;;&lt;br /&gt;
    END;&lt;br /&gt;
    &lt;br /&gt;
    SELECT column_name&lt;br /&gt;
      FROM   table_name&lt;br /&gt;
     WHERE  column_name = :bv_variable_name;&lt;br /&gt;
&lt;br /&gt;
== DB2 ==&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;user-defined global variables&amp;quot; in DB2 are similar to the proposed PostgreSQL feature. The main difference is in the access rights: DB2 uses `READ` and `WRITE`, while PostgreSQL uses `SELECT` and `UPDATE`. Because PostgreSQL already uses the `SET` command for GUCs, the `LET` command was introduced in the proposal (DB2 uses `SET`).&lt;br /&gt;
&lt;br /&gt;
Variables are visible in all sessions, but their values are private to each session. Variables are not transactional. Their usage is broader than in the proposal: they can be changed by `SET`, `SELECT INTO`, or used as OUT parameters of procedures. The search path (or a similar mechanism) applies to variables as well, but variables have lower priority than tables or columns.&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE myCounter INT DEFAULT 01;&lt;br /&gt;
    SELECT EMPNO, LASTNAME, CASE WHEN myCounter = 1 THEN SALARY ELSE NULL END&lt;br /&gt;
    FROM EMPLOYEE WHERE WORKDEPT = &#039;A00&#039;;&lt;br /&gt;
    SET myCounter = 29;&lt;br /&gt;
&lt;br /&gt;
There also appear to be other kinds of variables, accessed using the function `GETVARIABLE(&#039;name&#039;, &#039;default&#039;)`. These are very similar to PostgreSQL GUCs and the `current_setting` function. Such variables can be set via the connection string, are of type `VARCHAR`, and up to 10 values are allowed. Built-in session variables (configuration) can also be accessed using `GETVARIABLE`.&lt;br /&gt;
&lt;br /&gt;
== MSSQL (T-SQL) ==&lt;br /&gt;
&lt;br /&gt;
MSSQL provides two types of variables:&lt;br /&gt;
&lt;br /&gt;
* Global variables — accessed with the `@@varname` syntax. These are similar to GUCs and provide state information such as `@@ERROR`, `@@ROWCOUNT`, or `@@IDENTITY`. &lt;br /&gt;
* Local variables — accessed with the `@varname` syntax. These must be declared with the `DECLARE` command before use. Their scope is limited to the batch, procedure, or function where they are executed.&lt;br /&gt;
&lt;br /&gt;
    DECLARE @TestVariable AS VARCHAR(100)&lt;br /&gt;
    SET @TestVariable = &#039;Think Green&#039;&lt;br /&gt;
    GO&lt;br /&gt;
    PRINT @TestVariable&lt;br /&gt;
&lt;br /&gt;
This script fails because `PRINT` is executed in a different batch. Therefore, it seems MSSQL does not truly support session variables.&lt;br /&gt;
&lt;br /&gt;
There are also mechanisms similar to PostgreSQL&#039;s custom GUCs and the use of `current_setting` and `set_config` functions. In general, MSSQL is fairly primitive in this area.&lt;br /&gt;
&lt;br /&gt;
    EXEC sp_set_session_context &#039;user_id&#039;, 4;&lt;br /&gt;
    SELECT SESSION_CONTEXT(N&#039;user_id&#039;);&lt;br /&gt;
&lt;br /&gt;
= ToDo =&lt;br /&gt;
&lt;br /&gt;
* SECURITY LABEL support — enhance the `dummy_seclabel` module and the `sepgsql` extension. Update PostgreSQL documentation and ensure `SecLabelSupportsObjectType` includes session variables.&lt;/div&gt;</summary>
		<author><name>Okbobcz</name></author>
	</entry>
	<entry>
		<id>https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41869</id>
		<title>Implementation of declarative catalog session variables</title>
		<link rel="alternate" type="text/html" href="https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41869"/>
		<updated>2025-10-02T11:26:43Z</updated>

		<summary type="html">&lt;p&gt;Okbobcz: /* Notes about implementation session variables as global temporary objects */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Session variables are database objects that can hold a value across multiple queries inside a session. The design of session variables varies widely, and many databases implement more than one type of session variable.&lt;br /&gt;
&lt;br /&gt;
The SQL/PSM standard introduces modules and module-level variables. However, this part of the standard has not been implemented in any widely used product. As a result, each database system has developed its own proprietary design, syntax, and feature set.&lt;br /&gt;
&lt;br /&gt;
Session variables are often part of the stored procedure environment, but there are exceptions --- for example, MySQL session variables or client-side session variables in tools like psql. In most systems, session variables do not participate in transactions: once set, their value persists until explicitly changed, even if the surrounding transaction is rolled back. Conceptually, they behave much like variables in general-purpose programming languages.&lt;br /&gt;
&lt;br /&gt;
Because there is no common design, comparisons across systems are difficult. Each implementation has its own advantages and disadvantages, and in some cases a single system supports more than one kind of session variable.&lt;br /&gt;
&lt;br /&gt;
Kinds of session variables:&lt;br /&gt;
* client side (psql, pgadmin, ...) - mostly similar to shell variables (based on string operations)&lt;br /&gt;
* server side&lt;br /&gt;
** declarative&lt;br /&gt;
*** created only for batch (T-SQL)&lt;br /&gt;
*** created as an database object (or part of database object) (Oracle, DB2)&lt;br /&gt;
** created by usage (MySQL)&lt;br /&gt;
** with priprietary syntax for identifiers (MySQL, T-SQL)&lt;br /&gt;
** with ANSI SQL syntax for identifiers (Oracle, DB2)&lt;br /&gt;
&lt;br /&gt;
Possible usage of session variables:&lt;br /&gt;
&lt;br /&gt;
* usage of session variables as global variables in PL (tracing, debugging, hacking),&lt;br /&gt;
* holding configuration (for PL),&lt;br /&gt;
* holding some more used data in interactive session,&lt;br /&gt;
* holding credentials for some RLS and data securing,&lt;br /&gt;
* helping with migration from others systems (mainly from Oracle),&lt;br /&gt;
* allow anonymous block parametrization (only postgres).&lt;br /&gt;
* session variables are writeable on read only hot standby (postgres)&lt;br /&gt;
&lt;br /&gt;
Session variables holds a values like temporary tables. What are differences between temporary tables and variables?:&lt;br /&gt;
&lt;br /&gt;
* variables holds a values, tables holds a set of rows,&lt;br /&gt;
* syntax for usage variables is shorter (more friendly for interactive work), access (read, write) is faster,&lt;br /&gt;
* the content of session variables is generally non transactional, the content of temporary tables is transactional,&lt;br /&gt;
* the content of session variables is just one value or NULL (no MVCC, no VACUUM),&lt;br /&gt;
* temporary tables are tables still (transactional, MVCC), update is expensive, under one transaction repeated updates makes lot of dead tuples,&lt;br /&gt;
* temporary tables are not cleaned by autovacuum (and inside function is not possible to execute VACUUM), &lt;br /&gt;
* Postgres doesn&#039;t support global temporary tables, so usage of temporary table for just few values or value is very expensive (catalogue bloat).&lt;br /&gt;
&lt;br /&gt;
= SQL/PSM =&lt;br /&gt;
&lt;br /&gt;
The SQL standard introduces the concept of modules, which can be associated with schemas (partially). Variables in modules behave like PL/pgSQL variables but are only local. The only objects allowed at the module level are temporary tables, which can be accessed only from routines assigned to that module. Modules can encapsulate private objects (functions, procedure, temp tables), that are not visible outside the module. Inside the module, a  private objects shadows other objects. What I know the modules (and generally SQL/PSM) doesn&#039;t cover a deployment of code, so there is not similarity with PostgreSQL extensions. SQL/PSM Modules are modules like modules from modular languages like Modula2 or Oberon. There is some similarity with Postgres schemas (both are containers) with significant differences (there is not a concept of SEARCH_PATH (on PSM side) or there is not a concept of private or public objects (on Postgres schema side)).&lt;br /&gt;
&lt;br /&gt;
SQL/PSM variables are not transactional (they are used exactly like in PL/pgSQL). The precedence between variables and columns is not specified.&lt;br /&gt;
&lt;br /&gt;
= Implementation Challenges =&lt;br /&gt;
&lt;br /&gt;
* Possible collisions between session variables and other database objects.&lt;br /&gt;
* Memory cleanup after dropping a session variable, especially when DDL operations can be reverted (Postgres has not support for global temp objects).&lt;br /&gt;
* Memory invalidation: if a variable is dropped by one session, other sessions should not continue using it (Postgres has not support for global temp objects).&lt;br /&gt;
&lt;br /&gt;
= Proposed Implementations =&lt;br /&gt;
&lt;br /&gt;
It is clear that no single design is perfect for all use cases. MySQL’s approach is convenient and useful for interactive work, but it cannot be used for storing sensitive data and is considered unsafe. PostgreSQL GUCs are excellent for configuration, but they are not well suited for interactive use and are very limited when used as global variables in PL code.&lt;br /&gt;
&lt;br /&gt;
Because of these trade-offs, no single design is sufficient. In practice, having multiple designs within a single system could support a broader range of use cases more effectively.&lt;br /&gt;
&lt;br /&gt;
== Design session variables as database global temporary typed objects with ANSI SQL syntax for identifiers ==&lt;br /&gt;
&lt;br /&gt;
Author: Pavel Stehule &amp;lt;pavel.stehule@gmail.com&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I searched for a solution that could work well with the specific PostgreSQL environment:&lt;br /&gt;
&lt;br /&gt;
* PostgreSQL has more than one widely used stored procedure language: PL/pgSQL, PL/Python, or PL/Perl.&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL is a reduced version of PL/SQL, and PL/SQL is a modified ADA language. I am pretty happy with the current complexity of PL/pgSQL --- it is a domain-specific language that works well. It is very simple and easy to learn. PL/pgSQL has no packages or modules; instead PostgreSQL schemas are used. But schemas are database objects in their own right, independent of PL.&lt;br /&gt;
&lt;br /&gt;
* PostgreSQL already has a code container --- the schema. I don&#039;t want to introduce another container object (modules), as this would overlap heavily with schemas. Implementing modules for PL/pgSQL could be useful, but doing it properly would be quite complex. The main problem is the concept of public and private objects --- PostgreSQL does not have this. Don&#039;t forget: every PL/pgSQL expression is a SQL expression, so public/private visibility would first need to be implemented internally in PostgreSQL. That would be redundant (and possibly in conflict) with SEARCH_PATH.&lt;br /&gt;
&lt;br /&gt;
So I wanted a design that supports multiple PL languages, does not add complexity to the relatively simple PL/pgSQL, and does not duplicate functionality already available.  &lt;br /&gt;
&lt;br /&gt;
I wrote a larger application in PL/pgSQL. To maintain it, I created plpgsql_lint (later renamed plpgsql_check). So I am always looking for solutions that do not block static code analysis. Static analysis requires persistent objects, which naturally leads to designing session variables as database objects (with their own system catalog).  &lt;br /&gt;
When session variables are database objects, ACLs can be applied naturally. Oracle package variables are well protected by package scope. PostgreSQL, however, has no schema scope --- schema locality is controlled by the SEARCH_PATH GUC, and introducing another mechanism would be messy. But with ACLs, variable contents can also be secured:&lt;br /&gt;
&lt;br /&gt;
    CREATE TEMP VARIABLE x AS int;&lt;br /&gt;
     &lt;br /&gt;
    LET x = 10;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(x);&lt;br /&gt;
    END;&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    SELECT VARIABLE(x);&lt;br /&gt;
&lt;br /&gt;
==== Collisions between column and variable identifiers ====&lt;br /&gt;
&lt;br /&gt;
There is a risk of identifier collisions between variables and columns. With ANSI SQL syntax for session variable identifiers, the problem is how to distinguish variables from columns. This is a known issue in PL/pgSQL and PL/SQL and can be solved either by prioritization or by prohibiting collisions.  &lt;br /&gt;
&lt;br /&gt;
Prohibiting collisions has largely solved this problem in PL/pgSQL. Unfortunately, session variables are global objects, so collisions can occur in any query. A poorly named variable could break queries unexpectedly. Prioritization also has problems --- a variable or a column could be used silently when the other was intended.&lt;br /&gt;
&lt;br /&gt;
    CREATE TABLE tab(a int);&lt;br /&gt;
    CREATE VARIABLE a AS int;&lt;br /&gt;
    &lt;br /&gt;
    SELECT a FROM tab; -- with disallowed collisions, this query fails when a variable named &amp;quot;a&amp;quot; exists&lt;br /&gt;
    &lt;br /&gt;
    LET a = 1;&lt;br /&gt;
    DELETE FROM tab WHERE a = 1; -- with higher priority for variables, all rows could be deleted&lt;br /&gt;
    SELECT a FROM tab; -- with higher priority for columns, the variable is ignored&lt;br /&gt;
&lt;br /&gt;
Even with collision prohibition, mistakes are still possible:&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE _id AS int;&lt;br /&gt;
    LET _id = 10;&lt;br /&gt;
    CREATE TABLE foo(id int);&lt;br /&gt;
    DELETE FROM foo WHERE _id = 10; -- just a typo, but all rows are deleted&lt;br /&gt;
&lt;br /&gt;
These problems are not new. Developers in PL/pgSQL or PL/SQL know them, but the scope of risk is normally limited to stored procedures. After many discussions and designs, I introduced &amp;quot;variable fences&amp;quot; (the syntax VARIABLE(varname)). Inside any query, variables can be used only inside a variable fence. This completely solves collisions and reduces the chance of human error. Currently, fences are required everywhere in queries and expressions. In the future, they might be made optional inside PL/pgSQL, to reduce overhead when porting Oracle applications. This could be controlled by a new plpgsql.extra_checks setting.&lt;br /&gt;
&lt;br /&gt;
A variable fence can collide with a user-defined function named `variable`. This is similar to keyword conflicts such as CUBE. It can be resolved by schema-qualifying or quoting:&lt;br /&gt;
&lt;br /&gt;
    SELECT public.variable(...)&lt;br /&gt;
    SELECT &amp;quot;variable&amp;quot;(...)&lt;br /&gt;
&lt;br /&gt;
Why not use T-SQL (MSSQL, MySQL) syntax for session variables? There are several reasons – some objective, one subjective:&lt;br /&gt;
&lt;br /&gt;
* There is a collision with custom operators. Operators `@` and `@@` are already used, and `@@` is common:&lt;br /&gt;
&lt;br /&gt;
    (2025-09-28 06:57:30) postgres=# create table tab(a int);&lt;br /&gt;
    CREATE TABLE&lt;br /&gt;
    (2025-09-28 06:57:42) postgres=# insert into tab values(10);&lt;br /&gt;
    INSERT 0 1&lt;br /&gt;
    (2025-09-28 06:57:50) postgres=# select @a from tab;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │       10 │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
We could safely use only `:` as a prefix, but that would conflict with psql variables. Similarly, `$` would conflict with query parameters.&lt;br /&gt;
&lt;br /&gt;
* T-SQL variable names look strange in PostgreSQL (in queries and in PL/pgSQL). PostgreSQL uses only ANSI/SQL identifiers, and PL/pgSQL follows the same rules. PostgreSQL is consistent here, and I see little motivation to introduce a new, non-standard syntax (especially since it could cause compatibility issues).&lt;br /&gt;
&lt;br /&gt;
Note: There was also a proposal to use table syntax for variables (similar to T-SQL in-memory table variables, but restricted to a single row). This is very effective for avoiding collisions, but I dislike it. It complicates simple expressions, adds inconsistency, and looks odd for scalar variables (though it could be useful for composite variables). This design doesn&#039;t solve all possible problems - variables can be shadowed by tables still, because we have SEARCH_PATH (but this is known issue):&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(var); -- variable is not a table&lt;br /&gt;
    END&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      -- variable as a table (I don&#039;t like it)&lt;br /&gt;
      -- can we use DML? How many rows can it hold?&lt;br /&gt;
      -- is the value a row or not?&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, (SELECT var FROM var);&lt;br /&gt;
    END&lt;br /&gt;
    $$&lt;br /&gt;
&lt;br /&gt;
I am not against introducing in-memory tables --- or more correctly, global temporary tables. A well-designed implementation could be very useful. But this is a different feature and should be designed separately.  &lt;br /&gt;
&lt;br /&gt;
There are also performance considerations: PL/pgSQL can use simple expression evaluation when no tables are referenced. This optimization would need to change, which touches sensitive code. It would also be inconsistent.  &lt;br /&gt;
&lt;br /&gt;
I would like global temporary tables (which PostgreSQL currently lacks). Tables should behave like tables, and variables should behave like variables. The natural mental model for session variables comes from variables in PL languages. PostgreSQL internally supports *transition tables*, and I can imagine allowing these outside of triggers. That would be valid, but again, it is a different feature.  &lt;br /&gt;
&lt;br /&gt;
Implementing declared tables inside PL/pgSQL would be simpler from a high-level design perspective (interaction between SQL tables and PL/pgSQL is already well defined), but difficult from a low-level perspective (statistics, optimizer data, etc. --- the same issues as with global temporary tables, unless we add fake proxy local temp tables).&lt;br /&gt;
&lt;br /&gt;
   CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
   AS $$&lt;br /&gt;
   -- not implemented yet, not part of this proposal&lt;br /&gt;
   DECLARE t TABLE(a int, b int); -- this table is invisible outside fx&lt;br /&gt;
   BEGIN&lt;br /&gt;
     INSERT INTO t VALUES(10,20);&lt;br /&gt;
     INSERT INTO t VALUES(30,40);&lt;br /&gt;
     RAISE NOTICE &#039;%&#039;, (SELECT count(*) FROM t);&lt;br /&gt;
   END;&lt;br /&gt;
   $$ LANGUAGE plpgsql;&lt;br /&gt;
&lt;br /&gt;
Some data languages define relational variables, but this concept has no overlap with the common understanding of session variables.&lt;br /&gt;
&lt;br /&gt;
Inside PL/pgSQL we can use a possibility to configure a parser, and can be easy to allow a usage of session variables without variable fence (like common plpgsql variable) in expressions. It is not supported yet, but there should not be implementation problems. Outside PL, the variable fences can be optional, when there is not risk of collisions (the expression doesn&#039;t contains subselect) (not implemented yet).&lt;br /&gt;
&lt;br /&gt;
==== Assign command - LET ====&lt;br /&gt;
&lt;br /&gt;
The value of session variable can be assigned by dedicated PL assign command or by `SELECT INTO` construct. When special command is prefixed by some a keyword, then the keyword `SET` is usually used (MySQL, MSSQL, SQL/PSM, DB2). Command `SET` can be enhanced to support `a_expr` on parser level in Postgres too. It requires partial rewriting of current implementation of `SET` command, but there is not technical issues.&lt;br /&gt;
&lt;br /&gt;
Unfortunately, there can be collisions between GUC and session variables. Currently GUC are not declared, and they are not joined to any schema (prefix for custom GUC can be just any not empty string), and access to GUC is not controlled by SEARCH_PATH. We can use same solution like usage session variables in expression - variable fence - but in this case, the risk is every time, and there is not a possibility to usage of variable fences can be optional (for some cases) in future. Some languages uses for a assignment the keyword `LET`. I introduced this keyword as a solution for 100% fix of collisions between GUC and session variables identifiers. &lt;br /&gt;
&lt;br /&gt;
I can imagine the assignment to session variable inside PL by usual PL assignment command (not implemented yet). This can be benefit for translation from PL/SQL (Oracle), and it can be very natural syntax, when session variable is used like PL global variable. Probably (not tested), there are not technical issues for implementation of this. But there is different significant issue. Currently, only PL/pgSQL variables can be target of PL/pgSQL assign statement, and these variables should be declared before usage. If we allow session variables there, then a) we cannot to do this check, or b) there will be new strong dependency from PL functions to session variables - like on types now.&lt;br /&gt;
&lt;br /&gt;
    (2025-10-02 07:42:01) postgres=# CREATE OR REPLACE FUNCTION fx() &lt;br /&gt;
    returns void as $$&lt;br /&gt;
    begin&lt;br /&gt;
      declare var mydomain;&lt;br /&gt;
    begin&lt;br /&gt;
      var := 10;&lt;br /&gt;
    end&lt;br /&gt;
    $$ language plpgsql;&lt;br /&gt;
    ERROR:  type &amp;quot;mydomain&amp;quot; does not exist&lt;br /&gt;
    LINE 4:   declare var mydomain;&lt;br /&gt;
                          ^&lt;br /&gt;
&lt;br /&gt;
The strong dependency today is not problem probably (because plan cache can be correctly invalidated), but there can be some new questions about temporary variables. Again as a solution I proposed a `LET` command, and we don&#039;t need to touch to PL/pgSQL assign statement.&lt;br /&gt;
&lt;br /&gt;
The significant question is performance generally and performance when LET is used inside PL/pgSQL.&lt;br /&gt;
&lt;br /&gt;
The command `LET` is PostgreSQL utility command. Against history, the utility command can be prepared (as `CREATE TABLE AS SELECT`). There are patches that implements `PREPARE` and `EXECUTE` for `LET` command (these patches are not in reduced patch set). Although plan cache can be used, the performance against PL/pgSQL assign can be slower - from two reasons:&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL allows simple expression evaluation (this is supported for `LET` command too in enhanced patch set),&lt;br /&gt;
* PL/pgSQL variables are stored in a array (and indexed by int offset), session variables are stored in hash table, and they are accessed by hash search (and this slower). This slowdown is visible only in worst case benchmarks like:&lt;br /&gt;
&lt;br /&gt;
   DO $$ declare locvar int DEFAULT 0; begin for i in 1..1000000 loop locvar := locvar + 1; end loop; end $$ &lt;br /&gt;
   DO $$ begin for i in 1..100000 loop LET sesvar := sesvar + 1; end loop; end $$&lt;br /&gt;
&lt;br /&gt;
The implementation of `LET` in reduced patch set, is significantly slower, because the simple expression evaluation is not used (it is implemented, but not in reduced patch set). Although there is significant slowdown, the execution is still significantly faster than a execution of any query that touch to real table (it should not be a problem for real usage).&lt;br /&gt;
&lt;br /&gt;
==== Notes about implementation session variables as global temporary objects ====&lt;br /&gt;
The core of this proposal is design of session variables as global temporary objects. Unfortunately, Postgres has zero support for this kind of objects. Session variables has not any session specific data in the catalog. This is much more better (and easier) than for global temporary tables. The data of session variables are in session memory (only). This is big difference from tables. There the data are stored in pages, and pages are stored in some files. The files are important - any cached data can be thrown (and they are frequently thrown). There are two kinds of new issues (related to session variables).&lt;br /&gt;
&lt;br /&gt;
* possibility to use invalid value (stored in valid memory)&lt;br /&gt;
* too early or too delayed memory cleaning&lt;br /&gt;
&lt;br /&gt;
Any session variable is identified by name (and possibly by oid from `pg_variable`). Any variable can use own data type. Values stored in memory are stored in native binary format, and without reassignment, the value is not changed. Important note - the one oid can be assigned more times. oids are unique in just one moment, but there is not a protection so one oid will not be used more times for different objects. There is a real possibility of following issue:&lt;br /&gt;
&lt;br /&gt;
* session A: CREATE VARIABLE v AS t;&lt;br /&gt;
* session B: LET v = t &#039;value&#039;;&lt;br /&gt;
* session A: DROP VARIABLE v;&lt;br /&gt;
* session B: no activity -- it got lot of sinval messages (signals), but no command is executed, and then is not possibility to handle sinval&lt;br /&gt;
* session A: CREATE VARIABLE v AS nt; -- theoretically I can get same varid and typid like in first step, although t != nt&lt;br /&gt;
* session B: SELECT v; -- crash&lt;br /&gt;
&lt;br /&gt;
Before any usage of stored value, we need to do really identity check and varid and typid is not enough. But we can store to variable their create lsn - and this number is unique for all time database (catalog) live. So the necessary check before usage of stored value is just check if value.varid = catalog.varid and value.createlsn = catalog.createlsn. The ALTER VARIABLE ALTER TYPE is not allowed. The dependency mechanism ensure so value type should be identical with catalog type.&lt;br /&gt;
&lt;br /&gt;
Second problem is when we can clean memory. We cannot to do immediately after command `DROP VARIABLE`, because this command can be reverted&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    BEGIN;&lt;br /&gt;
    &lt;br /&gt;
    LET var := 1;&lt;br /&gt;
    DROP VARIABLE var;&lt;br /&gt;
    &lt;br /&gt;
    ROLLBACK;&lt;br /&gt;
&lt;br /&gt;
The most simple solution is marking variable as dropped, and in next transaction when we validate variables (values), we can clean memory after dropped variables. This technique is very simple, and robust, on second hand, the memory is cleaned after any operation with session variable, what can be after long time or maybe messy (if somebody will check used memory). We can do this check in transaction end or transaction begin - but it can be small overhead any time, although session variables was not used.&lt;br /&gt;
&lt;br /&gt;
= Variabes in PostgreSQL (as of v18) =&lt;br /&gt;
&lt;br /&gt;
PostgreSQL provides relatively advanced client-side session variables in psql --- these variables use the `:` prefix. The implementation is pretty much straightforward: `psql`&#039;s parser reads tokens from input and, when it encounters a token related to a variable, replaces it with the variable&#039;s value. The syntax supports literal and identifier escaping, and variables can be used as arguments of the `\bind` command.&lt;br /&gt;
&lt;br /&gt;
`psql` variables are typeless client-side variables, available only inside `psql` or `pgbench`.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:40:18) postgres=# \set myvar ahoj&lt;br /&gt;
    (2025-09-25 09:40:33) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:14) postgres=# select :&#039;myvar&#039;;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:24) postgres=# select $1 \bind :myvar \g&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
The `\set` command allows only simple string concatenation, but it also supports execution of shell commands. When a query is executed with the `\gset` command, its result can be stored in a `psql` variable.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:41:40) postgres=# \set ctime `date`&lt;br /&gt;
    (2025-09-25 09:43:55) postgres=# \echo :ctime&lt;br /&gt;
    Thu Sep 25 09:43:55 CEST 2025&lt;br /&gt;
&lt;br /&gt;
The `\set` command is fairly limited, making complex operations unreadable or non-portable.&lt;br /&gt;
&lt;br /&gt;
This mechanism is good enough for writing tests, but it is not generally available (most users do not need it), and it is not accessible from the server side (e.g., from stored procedures). There is no simple way to access `psql` variables from an anonymous block --- a proxy custom GUC variable must be used:&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:58:38) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    (2025-09-25 09:58:43) postgres=# select set_config(&#039;myvars.myvar&#039;, :&#039;myvar&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ ahoj       │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 10:00:27) postgres=# do $$ begin raise notice &#039;%&#039;, current_setting(&#039;myvars.myvar&#039;); end $$;&lt;br /&gt;
    NOTICE:  ahoj&lt;br /&gt;
    DO&lt;br /&gt;
&lt;br /&gt;
PostgreSQL also has configuration variables (GUC - Grand Unified Configuration). These are primarily designed for PostgreSQL and extension configuration, but they can be used as session variables. GUC variables can be set with the `SET` command or the `set_config` function, and read with `SHOW` or `current_setting`.&lt;br /&gt;
&lt;br /&gt;
GUC variables are meant for holding configuration parameters. When created through the internal API, they can be restricted to superusers, hidden from regular users, and assigned specific types (boolean, memory, number, interval, string, enum) with validation. This type system is separate from the SQL type system and is initialized before PostgreSQL’s SQL types.&lt;br /&gt;
&lt;br /&gt;
Variables created from SQL must be &amp;quot;custom&amp;quot; variables, using a prefix in their name --- these are always strings. Such custom GUCs are often used as a workaround for missing server-side session variables in PostgreSQL, but they are typeless, unprotected, and unsafe.&lt;br /&gt;
&lt;br /&gt;
A notable feature of PostgreSQL GUCs (built-in or custom) is that they can have different scopes: transaction, session, or function execution. They can also be assigned default values at specific events --- configuration loading, role login, database login, or function start. These features are useful for configuration, but problematic when GUCs are repurposed as session variables.&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
    RETURNS void AS $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, current_setting(&#039;my.x&#039;);&lt;br /&gt;
    END $$ LANGUAGE plpgsql SET my.x = 20;&lt;br /&gt;
    CREATE FUNCTION&lt;br /&gt;
    (2025-09-27 06:10:37) postgres=# SET my.x = 0; SELECT fx();&lt;br /&gt;
    SET&lt;br /&gt;
    NOTICE:  20&lt;br /&gt;
    ┌────┐&lt;br /&gt;
    │ fx │&lt;br /&gt;
    ╞════╡&lt;br /&gt;
    │    │&lt;br /&gt;
    └────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    (2025-09-27 06:10:39) postgres=# SHOW my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 0    │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
GUC variables are transactional, and this behavior cannot be disabled.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-27 06:17:31) postgres=# set my.x = 10;&lt;br /&gt;
    SET&lt;br /&gt;
    (2025-09-27 06:19:42) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:19:46) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, true);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:19:55) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:01) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:20:11) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:13) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:20:38) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:45) postgres=# rollback;&lt;br /&gt;
    ROLLBACK&lt;br /&gt;
    (2025-09-27 06:20:52) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:55) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:22:36) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:22:39) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:22:42) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
As a result, there is no reliable way to store details such as error information in custom GUCs.&lt;br /&gt;
&lt;br /&gt;
= Session Variables in Other Systems =&lt;br /&gt;
&lt;br /&gt;
This section surveys how session variables are implemented in several widely used database systems. Each subsection includes code examples to illustrate syntax, behaviour, and scope.&lt;br /&gt;
&lt;br /&gt;
== MySQL ==&lt;br /&gt;
&lt;br /&gt;
Session variables in MySQL have syntax similar to the better-known T-SQL variables (though other details are MySQL-specific). MySQL distinguishes two kinds of variables:&lt;br /&gt;
&lt;br /&gt;
* system variables - referenced using the `@@` prefix (or `@@scope.name`)&lt;br /&gt;
* user-defined variables - referenced using the `@` prefix&lt;br /&gt;
&lt;br /&gt;
System variables are modified with the `SET` statement. The `SET` statement accepts the modifiers `GLOBAL`, `SESSION`, or `PERSIST`. Some variables can only be set using the `GLOBAL` or `SESSION` modifier; when the variable name is specified with the `@@` prefix, it is not necessary to explicitly indicate the scope. System variables are defined by MySQL or by MySQL plugins.&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL some_config = 100;&lt;br /&gt;
    SET @@same_config = 100;&lt;br /&gt;
    &lt;br /&gt;
    SET SESSION sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@SESSION.sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
&lt;br /&gt;
Persistent values are stored in `mysqld-auto.cnf` (in the server data directory).&lt;br /&gt;
&lt;br /&gt;
Resetting system variables can be done by assigning DEFAULT or by copying from the GLOBAL namespace:&lt;br /&gt;
&lt;br /&gt;
    SET @@sql_mode = DEFAULT;&lt;br /&gt;
    SET @@sql_mode = @@GLOBAL.sql_mode&lt;br /&gt;
&lt;br /&gt;
PostgreSQL equivalents:&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL ; -- there is not similar command in Postgres&lt;br /&gt;
    SET SESSION ; -- SET&lt;br /&gt;
    SET PERSIST ; -- ALTER SYSTEM; SELECT pg_reload_conf();&lt;br /&gt;
    SELECT @@var; -- SELECT current_setting(&#039;var);&lt;br /&gt;
&lt;br /&gt;
User-defined variables are created by assignment:&lt;br /&gt;
&lt;br /&gt;
    SET @var = expr [, @var2 = expr [ ... ]];&lt;br /&gt;
&lt;br /&gt;
User-defined variables can only be of type integer, decimal, float, binary, non-binary string, or NULL. Casting between these types is implicit and hidden. User-defined variables cannot be used as table or column names.&lt;br /&gt;
&lt;br /&gt;
An advantage of the T-SQL-style syntax (with special prefixes) is that it provides a simple solution for avoiding identifier collisions. MySQL variables are convenient, and the user experience can be good for people already familiar with T-SQL (MSSQL or Sybase). On the other hand, prefix-based syntax can be confusing for users coming from systems other than MSSQL. The type system is perhaps too simple and can be unsafe. In general, the performance of MySQL (or MariaDB) stored procedures is not good, and stored procedures are therefore not frequently used. There is no protection against typographical errors. Unassigned user variables can be used without error (they default to NULL), which makes static checking of stored procedures impossible in this area.&lt;br /&gt;
&lt;br /&gt;
There is no way to restrict access to user-defined variables in MySQL. They cannot be protected against unintended usage. The only possible safeguard is a naming convention, since all user-defined variables share a single namespace.&lt;br /&gt;
&lt;br /&gt;
== Oracle ==&lt;br /&gt;
&lt;br /&gt;
Oracle PL/SQL allows the use of package variables. PL/SQL is based on the Ada language, and package variables act as &amp;quot;global&amp;quot; variables. They are not directly visible from SQL, but Oracle provides a reduced syntax for functions without arguments, so you typically write a wrapper:&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE PACKAGE my_package&lt;br /&gt;
    AS&lt;br /&gt;
      FUNCTION get_a RETURN NUMBER;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    /&lt;br /&gt;
    &lt;br /&gt;
    CREATE OR REPLACE PACKAGE BODY my_package&lt;br /&gt;
    AS&lt;br /&gt;
      a  NUMBER(20);&lt;br /&gt;
      &lt;br /&gt;
      FUNCTION get_a&lt;br /&gt;
      RETURN NUMBER&lt;br /&gt;
      IS&lt;br /&gt;
      BEGIN&lt;br /&gt;
        RETURN a;&lt;br /&gt;
      END get_a;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    &lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
Inside SQL, SQL identifiers take precedence. Inside non-SQL commands (such as CALL or PL/SQL blocks), package identifiers take precedence.&lt;br /&gt;
&lt;br /&gt;
Oracle allows both syntaxes for calling a function with zero arguments:&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a() FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
This reduces the risk of naming collisions. Package variables persist for the duration of a session.&lt;br /&gt;
&lt;br /&gt;
Another possibility is to use variables in SQL*Plus (similar to `psql` variables, but with the ability to define the type on the server side).&lt;br /&gt;
&lt;br /&gt;
A variable is declared with the `VARIABLE` command and can be accessed in the session using the `:varname` syntax (a prior declaration step may be optional):&lt;br /&gt;
&lt;br /&gt;
    VARIABLE bv_variable_name VARCHAR2(30)&lt;br /&gt;
    BEGIN&lt;br /&gt;
      :bv_variable_name := &#039;Some Value&#039;;&lt;br /&gt;
    END;&lt;br /&gt;
    &lt;br /&gt;
    SELECT column_name&lt;br /&gt;
      FROM   table_name&lt;br /&gt;
     WHERE  column_name = :bv_variable_name;&lt;br /&gt;
&lt;br /&gt;
== DB2 ==&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;user-defined global variables&amp;quot; in DB2 are similar to the proposed PostgreSQL feature. The main difference is in the access rights: DB2 uses `READ` and `WRITE`, while PostgreSQL uses `SELECT` and `UPDATE`. Because PostgreSQL already uses the `SET` command for GUCs, the `LET` command was introduced in the proposal (DB2 uses `SET`).&lt;br /&gt;
&lt;br /&gt;
Variables are visible in all sessions, but their values are private to each session. Variables are not transactional. Their usage is broader than in the proposal: they can be changed by `SET`, `SELECT INTO`, or used as OUT parameters of procedures. The search path (or a similar mechanism) applies to variables as well, but variables have lower priority than tables or columns.&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE myCounter INT DEFAULT 01;&lt;br /&gt;
    SELECT EMPNO, LASTNAME, CASE WHEN myCounter = 1 THEN SALARY ELSE NULL END&lt;br /&gt;
    FROM EMPLOYEE WHERE WORKDEPT = &#039;A00&#039;;&lt;br /&gt;
    SET myCounter = 29;&lt;br /&gt;
&lt;br /&gt;
There also appear to be other kinds of variables, accessed using the function `GETVARIABLE(&#039;name&#039;, &#039;default&#039;)`. These are very similar to PostgreSQL GUCs and the `current_setting` function. Such variables can be set via the connection string, are of type `VARCHAR`, and up to 10 values are allowed. Built-in session variables (configuration) can also be accessed using `GETVARIABLE`.&lt;br /&gt;
&lt;br /&gt;
== MSSQL (T-SQL) ==&lt;br /&gt;
&lt;br /&gt;
MSSQL provides two types of variables:&lt;br /&gt;
&lt;br /&gt;
* Global variables — accessed with the `@@varname` syntax. These are similar to GUCs and provide state information such as `@@ERROR`, `@@ROWCOUNT`, or `@@IDENTITY`. &lt;br /&gt;
* Local variables — accessed with the `@varname` syntax. These must be declared with the `DECLARE` command before use. Their scope is limited to the batch, procedure, or function where they are executed.&lt;br /&gt;
&lt;br /&gt;
    DECLARE @TestVariable AS VARCHAR(100)&lt;br /&gt;
    SET @TestVariable = &#039;Think Green&#039;&lt;br /&gt;
    GO&lt;br /&gt;
    PRINT @TestVariable&lt;br /&gt;
&lt;br /&gt;
This script fails because `PRINT` is executed in a different batch. Therefore, it seems MSSQL does not truly support session variables.&lt;br /&gt;
&lt;br /&gt;
There are also mechanisms similar to PostgreSQL&#039;s custom GUCs and the use of `current_setting` and `set_config` functions. In general, MSSQL is fairly primitive in this area.&lt;br /&gt;
&lt;br /&gt;
    EXEC sp_set_session_context &#039;user_id&#039;, 4;&lt;br /&gt;
    SELECT SESSION_CONTEXT(N&#039;user_id&#039;);&lt;br /&gt;
&lt;br /&gt;
= ToDo =&lt;br /&gt;
&lt;br /&gt;
* SECURITY LABEL support — enhance the `dummy_seclabel` module and the `sepgsql` extension. Update PostgreSQL documentation and ensure `SecLabelSupportsObjectType` includes session variables.&lt;/div&gt;</summary>
		<author><name>Okbobcz</name></author>
	</entry>
	<entry>
		<id>https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41868</id>
		<title>Implementation of declarative catalog session variables</title>
		<link rel="alternate" type="text/html" href="https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41868"/>
		<updated>2025-10-02T11:19:12Z</updated>

		<summary type="html">&lt;p&gt;Okbobcz: /* Notes about implementation session variables as global temporary objects */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Session variables are database objects that can hold a value across multiple queries inside a session. The design of session variables varies widely, and many databases implement more than one type of session variable.&lt;br /&gt;
&lt;br /&gt;
The SQL/PSM standard introduces modules and module-level variables. However, this part of the standard has not been implemented in any widely used product. As a result, each database system has developed its own proprietary design, syntax, and feature set.&lt;br /&gt;
&lt;br /&gt;
Session variables are often part of the stored procedure environment, but there are exceptions --- for example, MySQL session variables or client-side session variables in tools like psql. In most systems, session variables do not participate in transactions: once set, their value persists until explicitly changed, even if the surrounding transaction is rolled back. Conceptually, they behave much like variables in general-purpose programming languages.&lt;br /&gt;
&lt;br /&gt;
Because there is no common design, comparisons across systems are difficult. Each implementation has its own advantages and disadvantages, and in some cases a single system supports more than one kind of session variable.&lt;br /&gt;
&lt;br /&gt;
Kinds of session variables:&lt;br /&gt;
* client side (psql, pgadmin, ...) - mostly similar to shell variables (based on string operations)&lt;br /&gt;
* server side&lt;br /&gt;
** declarative&lt;br /&gt;
*** created only for batch (T-SQL)&lt;br /&gt;
*** created as an database object (or part of database object) (Oracle, DB2)&lt;br /&gt;
** created by usage (MySQL)&lt;br /&gt;
** with priprietary syntax for identifiers (MySQL, T-SQL)&lt;br /&gt;
** with ANSI SQL syntax for identifiers (Oracle, DB2)&lt;br /&gt;
&lt;br /&gt;
Possible usage of session variables:&lt;br /&gt;
&lt;br /&gt;
* usage of session variables as global variables in PL (tracing, debugging, hacking),&lt;br /&gt;
* holding configuration (for PL),&lt;br /&gt;
* holding some more used data in interactive session,&lt;br /&gt;
* holding credentials for some RLS and data securing,&lt;br /&gt;
* helping with migration from others systems (mainly from Oracle),&lt;br /&gt;
* allow anonymous block parametrization (only postgres).&lt;br /&gt;
* session variables are writeable on read only hot standby (postgres)&lt;br /&gt;
&lt;br /&gt;
Session variables holds a values like temporary tables. What are differences between temporary tables and variables?:&lt;br /&gt;
&lt;br /&gt;
* variables holds a values, tables holds a set of rows,&lt;br /&gt;
* syntax for usage variables is shorter (more friendly for interactive work), access (read, write) is faster,&lt;br /&gt;
* the content of session variables is generally non transactional, the content of temporary tables is transactional,&lt;br /&gt;
* the content of session variables is just one value or NULL (no MVCC, no VACUUM),&lt;br /&gt;
* temporary tables are tables still (transactional, MVCC), update is expensive, under one transaction repeated updates makes lot of dead tuples,&lt;br /&gt;
* temporary tables are not cleaned by autovacuum (and inside function is not possible to execute VACUUM), &lt;br /&gt;
* Postgres doesn&#039;t support global temporary tables, so usage of temporary table for just few values or value is very expensive (catalogue bloat).&lt;br /&gt;
&lt;br /&gt;
= SQL/PSM =&lt;br /&gt;
&lt;br /&gt;
The SQL standard introduces the concept of modules, which can be associated with schemas (partially). Variables in modules behave like PL/pgSQL variables but are only local. The only objects allowed at the module level are temporary tables, which can be accessed only from routines assigned to that module. Modules can encapsulate private objects (functions, procedure, temp tables), that are not visible outside the module. Inside the module, a  private objects shadows other objects. What I know the modules (and generally SQL/PSM) doesn&#039;t cover a deployment of code, so there is not similarity with PostgreSQL extensions. SQL/PSM Modules are modules like modules from modular languages like Modula2 or Oberon. There is some similarity with Postgres schemas (both are containers) with significant differences (there is not a concept of SEARCH_PATH (on PSM side) or there is not a concept of private or public objects (on Postgres schema side)).&lt;br /&gt;
&lt;br /&gt;
SQL/PSM variables are not transactional (they are used exactly like in PL/pgSQL). The precedence between variables and columns is not specified.&lt;br /&gt;
&lt;br /&gt;
= Implementation Challenges =&lt;br /&gt;
&lt;br /&gt;
* Possible collisions between session variables and other database objects.&lt;br /&gt;
* Memory cleanup after dropping a session variable, especially when DDL operations can be reverted (Postgres has not support for global temp objects).&lt;br /&gt;
* Memory invalidation: if a variable is dropped by one session, other sessions should not continue using it (Postgres has not support for global temp objects).&lt;br /&gt;
&lt;br /&gt;
= Proposed Implementations =&lt;br /&gt;
&lt;br /&gt;
It is clear that no single design is perfect for all use cases. MySQL’s approach is convenient and useful for interactive work, but it cannot be used for storing sensitive data and is considered unsafe. PostgreSQL GUCs are excellent for configuration, but they are not well suited for interactive use and are very limited when used as global variables in PL code.&lt;br /&gt;
&lt;br /&gt;
Because of these trade-offs, no single design is sufficient. In practice, having multiple designs within a single system could support a broader range of use cases more effectively.&lt;br /&gt;
&lt;br /&gt;
== Design session variables as database global temporary typed objects with ANSI SQL syntax for identifiers ==&lt;br /&gt;
&lt;br /&gt;
Author: Pavel Stehule &amp;lt;pavel.stehule@gmail.com&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I searched for a solution that could work well with the specific PostgreSQL environment:&lt;br /&gt;
&lt;br /&gt;
* PostgreSQL has more than one widely used stored procedure language: PL/pgSQL, PL/Python, or PL/Perl.&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL is a reduced version of PL/SQL, and PL/SQL is a modified ADA language. I am pretty happy with the current complexity of PL/pgSQL --- it is a domain-specific language that works well. It is very simple and easy to learn. PL/pgSQL has no packages or modules; instead PostgreSQL schemas are used. But schemas are database objects in their own right, independent of PL.&lt;br /&gt;
&lt;br /&gt;
* PostgreSQL already has a code container --- the schema. I don&#039;t want to introduce another container object (modules), as this would overlap heavily with schemas. Implementing modules for PL/pgSQL could be useful, but doing it properly would be quite complex. The main problem is the concept of public and private objects --- PostgreSQL does not have this. Don&#039;t forget: every PL/pgSQL expression is a SQL expression, so public/private visibility would first need to be implemented internally in PostgreSQL. That would be redundant (and possibly in conflict) with SEARCH_PATH.&lt;br /&gt;
&lt;br /&gt;
So I wanted a design that supports multiple PL languages, does not add complexity to the relatively simple PL/pgSQL, and does not duplicate functionality already available.  &lt;br /&gt;
&lt;br /&gt;
I wrote a larger application in PL/pgSQL. To maintain it, I created plpgsql_lint (later renamed plpgsql_check). So I am always looking for solutions that do not block static code analysis. Static analysis requires persistent objects, which naturally leads to designing session variables as database objects (with their own system catalog).  &lt;br /&gt;
When session variables are database objects, ACLs can be applied naturally. Oracle package variables are well protected by package scope. PostgreSQL, however, has no schema scope --- schema locality is controlled by the SEARCH_PATH GUC, and introducing another mechanism would be messy. But with ACLs, variable contents can also be secured:&lt;br /&gt;
&lt;br /&gt;
    CREATE TEMP VARIABLE x AS int;&lt;br /&gt;
     &lt;br /&gt;
    LET x = 10;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(x);&lt;br /&gt;
    END;&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    SELECT VARIABLE(x);&lt;br /&gt;
&lt;br /&gt;
==== Collisions between column and variable identifiers ====&lt;br /&gt;
&lt;br /&gt;
There is a risk of identifier collisions between variables and columns. With ANSI SQL syntax for session variable identifiers, the problem is how to distinguish variables from columns. This is a known issue in PL/pgSQL and PL/SQL and can be solved either by prioritization or by prohibiting collisions.  &lt;br /&gt;
&lt;br /&gt;
Prohibiting collisions has largely solved this problem in PL/pgSQL. Unfortunately, session variables are global objects, so collisions can occur in any query. A poorly named variable could break queries unexpectedly. Prioritization also has problems --- a variable or a column could be used silently when the other was intended.&lt;br /&gt;
&lt;br /&gt;
    CREATE TABLE tab(a int);&lt;br /&gt;
    CREATE VARIABLE a AS int;&lt;br /&gt;
    &lt;br /&gt;
    SELECT a FROM tab; -- with disallowed collisions, this query fails when a variable named &amp;quot;a&amp;quot; exists&lt;br /&gt;
    &lt;br /&gt;
    LET a = 1;&lt;br /&gt;
    DELETE FROM tab WHERE a = 1; -- with higher priority for variables, all rows could be deleted&lt;br /&gt;
    SELECT a FROM tab; -- with higher priority for columns, the variable is ignored&lt;br /&gt;
&lt;br /&gt;
Even with collision prohibition, mistakes are still possible:&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE _id AS int;&lt;br /&gt;
    LET _id = 10;&lt;br /&gt;
    CREATE TABLE foo(id int);&lt;br /&gt;
    DELETE FROM foo WHERE _id = 10; -- just a typo, but all rows are deleted&lt;br /&gt;
&lt;br /&gt;
These problems are not new. Developers in PL/pgSQL or PL/SQL know them, but the scope of risk is normally limited to stored procedures. After many discussions and designs, I introduced &amp;quot;variable fences&amp;quot; (the syntax VARIABLE(varname)). Inside any query, variables can be used only inside a variable fence. This completely solves collisions and reduces the chance of human error. Currently, fences are required everywhere in queries and expressions. In the future, they might be made optional inside PL/pgSQL, to reduce overhead when porting Oracle applications. This could be controlled by a new plpgsql.extra_checks setting.&lt;br /&gt;
&lt;br /&gt;
A variable fence can collide with a user-defined function named `variable`. This is similar to keyword conflicts such as CUBE. It can be resolved by schema-qualifying or quoting:&lt;br /&gt;
&lt;br /&gt;
    SELECT public.variable(...)&lt;br /&gt;
    SELECT &amp;quot;variable&amp;quot;(...)&lt;br /&gt;
&lt;br /&gt;
Why not use T-SQL (MSSQL, MySQL) syntax for session variables? There are several reasons – some objective, one subjective:&lt;br /&gt;
&lt;br /&gt;
* There is a collision with custom operators. Operators `@` and `@@` are already used, and `@@` is common:&lt;br /&gt;
&lt;br /&gt;
    (2025-09-28 06:57:30) postgres=# create table tab(a int);&lt;br /&gt;
    CREATE TABLE&lt;br /&gt;
    (2025-09-28 06:57:42) postgres=# insert into tab values(10);&lt;br /&gt;
    INSERT 0 1&lt;br /&gt;
    (2025-09-28 06:57:50) postgres=# select @a from tab;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │       10 │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
We could safely use only `:` as a prefix, but that would conflict with psql variables. Similarly, `$` would conflict with query parameters.&lt;br /&gt;
&lt;br /&gt;
* T-SQL variable names look strange in PostgreSQL (in queries and in PL/pgSQL). PostgreSQL uses only ANSI/SQL identifiers, and PL/pgSQL follows the same rules. PostgreSQL is consistent here, and I see little motivation to introduce a new, non-standard syntax (especially since it could cause compatibility issues).&lt;br /&gt;
&lt;br /&gt;
Note: There was also a proposal to use table syntax for variables (similar to T-SQL in-memory table variables, but restricted to a single row). This is very effective for avoiding collisions, but I dislike it. It complicates simple expressions, adds inconsistency, and looks odd for scalar variables (though it could be useful for composite variables). This design doesn&#039;t solve all possible problems - variables can be shadowed by tables still, because we have SEARCH_PATH (but this is known issue):&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(var); -- variable is not a table&lt;br /&gt;
    END&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      -- variable as a table (I don&#039;t like it)&lt;br /&gt;
      -- can we use DML? How many rows can it hold?&lt;br /&gt;
      -- is the value a row or not?&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, (SELECT var FROM var);&lt;br /&gt;
    END&lt;br /&gt;
    $$&lt;br /&gt;
&lt;br /&gt;
I am not against introducing in-memory tables --- or more correctly, global temporary tables. A well-designed implementation could be very useful. But this is a different feature and should be designed separately.  &lt;br /&gt;
&lt;br /&gt;
There are also performance considerations: PL/pgSQL can use simple expression evaluation when no tables are referenced. This optimization would need to change, which touches sensitive code. It would also be inconsistent.  &lt;br /&gt;
&lt;br /&gt;
I would like global temporary tables (which PostgreSQL currently lacks). Tables should behave like tables, and variables should behave like variables. The natural mental model for session variables comes from variables in PL languages. PostgreSQL internally supports *transition tables*, and I can imagine allowing these outside of triggers. That would be valid, but again, it is a different feature.  &lt;br /&gt;
&lt;br /&gt;
Implementing declared tables inside PL/pgSQL would be simpler from a high-level design perspective (interaction between SQL tables and PL/pgSQL is already well defined), but difficult from a low-level perspective (statistics, optimizer data, etc. --- the same issues as with global temporary tables, unless we add fake proxy local temp tables).&lt;br /&gt;
&lt;br /&gt;
   CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
   AS $$&lt;br /&gt;
   -- not implemented yet, not part of this proposal&lt;br /&gt;
   DECLARE t TABLE(a int, b int); -- this table is invisible outside fx&lt;br /&gt;
   BEGIN&lt;br /&gt;
     INSERT INTO t VALUES(10,20);&lt;br /&gt;
     INSERT INTO t VALUES(30,40);&lt;br /&gt;
     RAISE NOTICE &#039;%&#039;, (SELECT count(*) FROM t);&lt;br /&gt;
   END;&lt;br /&gt;
   $$ LANGUAGE plpgsql;&lt;br /&gt;
&lt;br /&gt;
Some data languages define relational variables, but this concept has no overlap with the common understanding of session variables.&lt;br /&gt;
&lt;br /&gt;
Inside PL/pgSQL we can use a possibility to configure a parser, and can be easy to allow a usage of session variables without variable fence (like common plpgsql variable) in expressions. It is not supported yet, but there should not be implementation problems. Outside PL, the variable fences can be optional, when there is not risk of collisions (the expression doesn&#039;t contains subselect) (not implemented yet).&lt;br /&gt;
&lt;br /&gt;
==== Assign command - LET ====&lt;br /&gt;
&lt;br /&gt;
The value of session variable can be assigned by dedicated PL assign command or by `SELECT INTO` construct. When special command is prefixed by some a keyword, then the keyword `SET` is usually used (MySQL, MSSQL, SQL/PSM, DB2). Command `SET` can be enhanced to support `a_expr` on parser level in Postgres too. It requires partial rewriting of current implementation of `SET` command, but there is not technical issues.&lt;br /&gt;
&lt;br /&gt;
Unfortunately, there can be collisions between GUC and session variables. Currently GUC are not declared, and they are not joined to any schema (prefix for custom GUC can be just any not empty string), and access to GUC is not controlled by SEARCH_PATH. We can use same solution like usage session variables in expression - variable fence - but in this case, the risk is every time, and there is not a possibility to usage of variable fences can be optional (for some cases) in future. Some languages uses for a assignment the keyword `LET`. I introduced this keyword as a solution for 100% fix of collisions between GUC and session variables identifiers. &lt;br /&gt;
&lt;br /&gt;
I can imagine the assignment to session variable inside PL by usual PL assignment command (not implemented yet). This can be benefit for translation from PL/SQL (Oracle), and it can be very natural syntax, when session variable is used like PL global variable. Probably (not tested), there are not technical issues for implementation of this. But there is different significant issue. Currently, only PL/pgSQL variables can be target of PL/pgSQL assign statement, and these variables should be declared before usage. If we allow session variables there, then a) we cannot to do this check, or b) there will be new strong dependency from PL functions to session variables - like on types now.&lt;br /&gt;
&lt;br /&gt;
    (2025-10-02 07:42:01) postgres=# CREATE OR REPLACE FUNCTION fx() &lt;br /&gt;
    returns void as $$&lt;br /&gt;
    begin&lt;br /&gt;
      declare var mydomain;&lt;br /&gt;
    begin&lt;br /&gt;
      var := 10;&lt;br /&gt;
    end&lt;br /&gt;
    $$ language plpgsql;&lt;br /&gt;
    ERROR:  type &amp;quot;mydomain&amp;quot; does not exist&lt;br /&gt;
    LINE 4:   declare var mydomain;&lt;br /&gt;
                          ^&lt;br /&gt;
&lt;br /&gt;
The strong dependency today is not problem probably (because plan cache can be correctly invalidated), but there can be some new questions about temporary variables. Again as a solution I proposed a `LET` command, and we don&#039;t need to touch to PL/pgSQL assign statement.&lt;br /&gt;
&lt;br /&gt;
The significant question is performance generally and performance when LET is used inside PL/pgSQL.&lt;br /&gt;
&lt;br /&gt;
The command `LET` is PostgreSQL utility command. Against history, the utility command can be prepared (as `CREATE TABLE AS SELECT`). There are patches that implements `PREPARE` and `EXECUTE` for `LET` command (these patches are not in reduced patch set). Although plan cache can be used, the performance against PL/pgSQL assign can be slower - from two reasons:&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL allows simple expression evaluation (this is supported for `LET` command too in enhanced patch set),&lt;br /&gt;
* PL/pgSQL variables are stored in a array (and indexed by int offset), session variables are stored in hash table, and they are accessed by hash search (and this slower). This slowdown is visible only in worst case benchmarks like:&lt;br /&gt;
&lt;br /&gt;
   DO $$ declare locvar int DEFAULT 0; begin for i in 1..1000000 loop locvar := locvar + 1; end loop; end $$ &lt;br /&gt;
   DO $$ begin for i in 1..100000 loop LET sesvar := sesvar + 1; end loop; end $$&lt;br /&gt;
&lt;br /&gt;
The implementation of `LET` in reduced patch set, is significantly slower, because the simple expression evaluation is not used (it is implemented, but not in reduced patch set). Although there is significant slowdown, the execution is still significantly faster than a execution of any query that touch to real table (it should not be a problem for real usage).&lt;br /&gt;
&lt;br /&gt;
==== Notes about implementation session variables as global temporary objects ====&lt;br /&gt;
The core of this proposal is design of session variables as global temporary objects. Unfortunately, Postgres has zero support for this kind of objects. Session variables has not any session specific data in the catalog. This is much more better (and easier) than for global temporary tables. The data of session variables are in session memory (only). This is big difference from tables. There the data are stored in pages, and pages are stored in some files. The files are important - any cached data can be thrown (and they are frequently thrown). There are two kinds of new issues (related to session variables).&lt;br /&gt;
&lt;br /&gt;
* possibility to use invalid value (stored in valid memory)&lt;br /&gt;
* too early or too delayed memory cleaning&lt;br /&gt;
&lt;br /&gt;
Any session variable is identified by name (and possibly by oid from `pg_variable`). Any variable can use own data type. Values stored in memory are stored in native binary format, and without reassignment, the value is not changed. Important note - the one oid can be assigned more times. oids are unique in just one moment, but there is not a protection so one oid will not be used more times for different objects. There is a real possibility of following issue:&lt;br /&gt;
&lt;br /&gt;
* session A: CREATE VARIABLE v AS t;&lt;br /&gt;
* session B: LET v = t &#039;value&#039;;&lt;br /&gt;
* session A: DROP VARIABLE v;&lt;br /&gt;
* session B: no activity -- it got lot of sinval messages (signals), but no command is executed, and then is not possibility to handle sinval&lt;br /&gt;
* session A: CREATE VARIABLE v AS nt; -- theoretically I can get same varid and typid like in first step, although t != nt&lt;br /&gt;
* session B: SELECT v; -- crash&lt;br /&gt;
&lt;br /&gt;
Before any usage of stored value, we need to do really identity check and varid and typid is not enough. But we can store to variable their create lsn - and this number is unique for all time database (catalog) live. So the necessary check before usage of stored value is just check if value.varid = catalog.varid and value.createlsn = catalog.createlsn. The ALTER VARIABLE ALTER TYPE is not allowed. The dependency mechanism ensure so value type should be identical with catalog type.&lt;br /&gt;
&lt;br /&gt;
Second problem is when we can clean memory. We cannot to do immediately after command `DROP VARIABLE`, because this command can be reverted&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    BEGIN;&lt;br /&gt;
&lt;br /&gt;
    LET var := 1;&lt;br /&gt;
    DROP VARIABLE var;&lt;br /&gt;
&lt;br /&gt;
    ROLLBACK;&lt;br /&gt;
&lt;br /&gt;
= Variabes in PostgreSQL (as of v18) =&lt;br /&gt;
&lt;br /&gt;
PostgreSQL provides relatively advanced client-side session variables in psql --- these variables use the `:` prefix. The implementation is pretty much straightforward: `psql`&#039;s parser reads tokens from input and, when it encounters a token related to a variable, replaces it with the variable&#039;s value. The syntax supports literal and identifier escaping, and variables can be used as arguments of the `\bind` command.&lt;br /&gt;
&lt;br /&gt;
`psql` variables are typeless client-side variables, available only inside `psql` or `pgbench`.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:40:18) postgres=# \set myvar ahoj&lt;br /&gt;
    (2025-09-25 09:40:33) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:14) postgres=# select :&#039;myvar&#039;;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:24) postgres=# select $1 \bind :myvar \g&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
The `\set` command allows only simple string concatenation, but it also supports execution of shell commands. When a query is executed with the `\gset` command, its result can be stored in a `psql` variable.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:41:40) postgres=# \set ctime `date`&lt;br /&gt;
    (2025-09-25 09:43:55) postgres=# \echo :ctime&lt;br /&gt;
    Thu Sep 25 09:43:55 CEST 2025&lt;br /&gt;
&lt;br /&gt;
The `\set` command is fairly limited, making complex operations unreadable or non-portable.&lt;br /&gt;
&lt;br /&gt;
This mechanism is good enough for writing tests, but it is not generally available (most users do not need it), and it is not accessible from the server side (e.g., from stored procedures). There is no simple way to access `psql` variables from an anonymous block --- a proxy custom GUC variable must be used:&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:58:38) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    (2025-09-25 09:58:43) postgres=# select set_config(&#039;myvars.myvar&#039;, :&#039;myvar&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ ahoj       │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 10:00:27) postgres=# do $$ begin raise notice &#039;%&#039;, current_setting(&#039;myvars.myvar&#039;); end $$;&lt;br /&gt;
    NOTICE:  ahoj&lt;br /&gt;
    DO&lt;br /&gt;
&lt;br /&gt;
PostgreSQL also has configuration variables (GUC - Grand Unified Configuration). These are primarily designed for PostgreSQL and extension configuration, but they can be used as session variables. GUC variables can be set with the `SET` command or the `set_config` function, and read with `SHOW` or `current_setting`.&lt;br /&gt;
&lt;br /&gt;
GUC variables are meant for holding configuration parameters. When created through the internal API, they can be restricted to superusers, hidden from regular users, and assigned specific types (boolean, memory, number, interval, string, enum) with validation. This type system is separate from the SQL type system and is initialized before PostgreSQL’s SQL types.&lt;br /&gt;
&lt;br /&gt;
Variables created from SQL must be &amp;quot;custom&amp;quot; variables, using a prefix in their name --- these are always strings. Such custom GUCs are often used as a workaround for missing server-side session variables in PostgreSQL, but they are typeless, unprotected, and unsafe.&lt;br /&gt;
&lt;br /&gt;
A notable feature of PostgreSQL GUCs (built-in or custom) is that they can have different scopes: transaction, session, or function execution. They can also be assigned default values at specific events --- configuration loading, role login, database login, or function start. These features are useful for configuration, but problematic when GUCs are repurposed as session variables.&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
    RETURNS void AS $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, current_setting(&#039;my.x&#039;);&lt;br /&gt;
    END $$ LANGUAGE plpgsql SET my.x = 20;&lt;br /&gt;
    CREATE FUNCTION&lt;br /&gt;
    (2025-09-27 06:10:37) postgres=# SET my.x = 0; SELECT fx();&lt;br /&gt;
    SET&lt;br /&gt;
    NOTICE:  20&lt;br /&gt;
    ┌────┐&lt;br /&gt;
    │ fx │&lt;br /&gt;
    ╞════╡&lt;br /&gt;
    │    │&lt;br /&gt;
    └────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    (2025-09-27 06:10:39) postgres=# SHOW my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 0    │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
GUC variables are transactional, and this behavior cannot be disabled.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-27 06:17:31) postgres=# set my.x = 10;&lt;br /&gt;
    SET&lt;br /&gt;
    (2025-09-27 06:19:42) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:19:46) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, true);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:19:55) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:01) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:20:11) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:13) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:20:38) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:45) postgres=# rollback;&lt;br /&gt;
    ROLLBACK&lt;br /&gt;
    (2025-09-27 06:20:52) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:55) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:22:36) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:22:39) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:22:42) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
As a result, there is no reliable way to store details such as error information in custom GUCs.&lt;br /&gt;
&lt;br /&gt;
= Session Variables in Other Systems =&lt;br /&gt;
&lt;br /&gt;
This section surveys how session variables are implemented in several widely used database systems. Each subsection includes code examples to illustrate syntax, behaviour, and scope.&lt;br /&gt;
&lt;br /&gt;
== MySQL ==&lt;br /&gt;
&lt;br /&gt;
Session variables in MySQL have syntax similar to the better-known T-SQL variables (though other details are MySQL-specific). MySQL distinguishes two kinds of variables:&lt;br /&gt;
&lt;br /&gt;
* system variables - referenced using the `@@` prefix (or `@@scope.name`)&lt;br /&gt;
* user-defined variables - referenced using the `@` prefix&lt;br /&gt;
&lt;br /&gt;
System variables are modified with the `SET` statement. The `SET` statement accepts the modifiers `GLOBAL`, `SESSION`, or `PERSIST`. Some variables can only be set using the `GLOBAL` or `SESSION` modifier; when the variable name is specified with the `@@` prefix, it is not necessary to explicitly indicate the scope. System variables are defined by MySQL or by MySQL plugins.&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL some_config = 100;&lt;br /&gt;
    SET @@same_config = 100;&lt;br /&gt;
    &lt;br /&gt;
    SET SESSION sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@SESSION.sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
&lt;br /&gt;
Persistent values are stored in `mysqld-auto.cnf` (in the server data directory).&lt;br /&gt;
&lt;br /&gt;
Resetting system variables can be done by assigning DEFAULT or by copying from the GLOBAL namespace:&lt;br /&gt;
&lt;br /&gt;
    SET @@sql_mode = DEFAULT;&lt;br /&gt;
    SET @@sql_mode = @@GLOBAL.sql_mode&lt;br /&gt;
&lt;br /&gt;
PostgreSQL equivalents:&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL ; -- there is not similar command in Postgres&lt;br /&gt;
    SET SESSION ; -- SET&lt;br /&gt;
    SET PERSIST ; -- ALTER SYSTEM; SELECT pg_reload_conf();&lt;br /&gt;
    SELECT @@var; -- SELECT current_setting(&#039;var);&lt;br /&gt;
&lt;br /&gt;
User-defined variables are created by assignment:&lt;br /&gt;
&lt;br /&gt;
    SET @var = expr [, @var2 = expr [ ... ]];&lt;br /&gt;
&lt;br /&gt;
User-defined variables can only be of type integer, decimal, float, binary, non-binary string, or NULL. Casting between these types is implicit and hidden. User-defined variables cannot be used as table or column names.&lt;br /&gt;
&lt;br /&gt;
An advantage of the T-SQL-style syntax (with special prefixes) is that it provides a simple solution for avoiding identifier collisions. MySQL variables are convenient, and the user experience can be good for people already familiar with T-SQL (MSSQL or Sybase). On the other hand, prefix-based syntax can be confusing for users coming from systems other than MSSQL. The type system is perhaps too simple and can be unsafe. In general, the performance of MySQL (or MariaDB) stored procedures is not good, and stored procedures are therefore not frequently used. There is no protection against typographical errors. Unassigned user variables can be used without error (they default to NULL), which makes static checking of stored procedures impossible in this area.&lt;br /&gt;
&lt;br /&gt;
There is no way to restrict access to user-defined variables in MySQL. They cannot be protected against unintended usage. The only possible safeguard is a naming convention, since all user-defined variables share a single namespace.&lt;br /&gt;
&lt;br /&gt;
== Oracle ==&lt;br /&gt;
&lt;br /&gt;
Oracle PL/SQL allows the use of package variables. PL/SQL is based on the Ada language, and package variables act as &amp;quot;global&amp;quot; variables. They are not directly visible from SQL, but Oracle provides a reduced syntax for functions without arguments, so you typically write a wrapper:&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE PACKAGE my_package&lt;br /&gt;
    AS&lt;br /&gt;
      FUNCTION get_a RETURN NUMBER;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    /&lt;br /&gt;
    &lt;br /&gt;
    CREATE OR REPLACE PACKAGE BODY my_package&lt;br /&gt;
    AS&lt;br /&gt;
      a  NUMBER(20);&lt;br /&gt;
      &lt;br /&gt;
      FUNCTION get_a&lt;br /&gt;
      RETURN NUMBER&lt;br /&gt;
      IS&lt;br /&gt;
      BEGIN&lt;br /&gt;
        RETURN a;&lt;br /&gt;
      END get_a;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    &lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
Inside SQL, SQL identifiers take precedence. Inside non-SQL commands (such as CALL or PL/SQL blocks), package identifiers take precedence.&lt;br /&gt;
&lt;br /&gt;
Oracle allows both syntaxes for calling a function with zero arguments:&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a() FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
This reduces the risk of naming collisions. Package variables persist for the duration of a session.&lt;br /&gt;
&lt;br /&gt;
Another possibility is to use variables in SQL*Plus (similar to `psql` variables, but with the ability to define the type on the server side).&lt;br /&gt;
&lt;br /&gt;
A variable is declared with the `VARIABLE` command and can be accessed in the session using the `:varname` syntax (a prior declaration step may be optional):&lt;br /&gt;
&lt;br /&gt;
    VARIABLE bv_variable_name VARCHAR2(30)&lt;br /&gt;
    BEGIN&lt;br /&gt;
      :bv_variable_name := &#039;Some Value&#039;;&lt;br /&gt;
    END;&lt;br /&gt;
    &lt;br /&gt;
    SELECT column_name&lt;br /&gt;
      FROM   table_name&lt;br /&gt;
     WHERE  column_name = :bv_variable_name;&lt;br /&gt;
&lt;br /&gt;
== DB2 ==&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;user-defined global variables&amp;quot; in DB2 are similar to the proposed PostgreSQL feature. The main difference is in the access rights: DB2 uses `READ` and `WRITE`, while PostgreSQL uses `SELECT` and `UPDATE`. Because PostgreSQL already uses the `SET` command for GUCs, the `LET` command was introduced in the proposal (DB2 uses `SET`).&lt;br /&gt;
&lt;br /&gt;
Variables are visible in all sessions, but their values are private to each session. Variables are not transactional. Their usage is broader than in the proposal: they can be changed by `SET`, `SELECT INTO`, or used as OUT parameters of procedures. The search path (or a similar mechanism) applies to variables as well, but variables have lower priority than tables or columns.&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE myCounter INT DEFAULT 01;&lt;br /&gt;
    SELECT EMPNO, LASTNAME, CASE WHEN myCounter = 1 THEN SALARY ELSE NULL END&lt;br /&gt;
    FROM EMPLOYEE WHERE WORKDEPT = &#039;A00&#039;;&lt;br /&gt;
    SET myCounter = 29;&lt;br /&gt;
&lt;br /&gt;
There also appear to be other kinds of variables, accessed using the function `GETVARIABLE(&#039;name&#039;, &#039;default&#039;)`. These are very similar to PostgreSQL GUCs and the `current_setting` function. Such variables can be set via the connection string, are of type `VARCHAR`, and up to 10 values are allowed. Built-in session variables (configuration) can also be accessed using `GETVARIABLE`.&lt;br /&gt;
&lt;br /&gt;
== MSSQL (T-SQL) ==&lt;br /&gt;
&lt;br /&gt;
MSSQL provides two types of variables:&lt;br /&gt;
&lt;br /&gt;
* Global variables — accessed with the `@@varname` syntax. These are similar to GUCs and provide state information such as `@@ERROR`, `@@ROWCOUNT`, or `@@IDENTITY`. &lt;br /&gt;
* Local variables — accessed with the `@varname` syntax. These must be declared with the `DECLARE` command before use. Their scope is limited to the batch, procedure, or function where they are executed.&lt;br /&gt;
&lt;br /&gt;
    DECLARE @TestVariable AS VARCHAR(100)&lt;br /&gt;
    SET @TestVariable = &#039;Think Green&#039;&lt;br /&gt;
    GO&lt;br /&gt;
    PRINT @TestVariable&lt;br /&gt;
&lt;br /&gt;
This script fails because `PRINT` is executed in a different batch. Therefore, it seems MSSQL does not truly support session variables.&lt;br /&gt;
&lt;br /&gt;
There are also mechanisms similar to PostgreSQL&#039;s custom GUCs and the use of `current_setting` and `set_config` functions. In general, MSSQL is fairly primitive in this area.&lt;br /&gt;
&lt;br /&gt;
    EXEC sp_set_session_context &#039;user_id&#039;, 4;&lt;br /&gt;
    SELECT SESSION_CONTEXT(N&#039;user_id&#039;);&lt;br /&gt;
&lt;br /&gt;
= ToDo =&lt;br /&gt;
&lt;br /&gt;
* SECURITY LABEL support — enhance the `dummy_seclabel` module and the `sepgsql` extension. Update PostgreSQL documentation and ensure `SecLabelSupportsObjectType` includes session variables.&lt;/div&gt;</summary>
		<author><name>Okbobcz</name></author>
	</entry>
	<entry>
		<id>https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41867</id>
		<title>Implementation of declarative catalog session variables</title>
		<link rel="alternate" type="text/html" href="https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41867"/>
		<updated>2025-10-02T07:04:37Z</updated>

		<summary type="html">&lt;p&gt;Okbobcz: /* Notes about implementation session variables as global temporary objects */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Session variables are database objects that can hold a value across multiple queries inside a session. The design of session variables varies widely, and many databases implement more than one type of session variable.&lt;br /&gt;
&lt;br /&gt;
The SQL/PSM standard introduces modules and module-level variables. However, this part of the standard has not been implemented in any widely used product. As a result, each database system has developed its own proprietary design, syntax, and feature set.&lt;br /&gt;
&lt;br /&gt;
Session variables are often part of the stored procedure environment, but there are exceptions --- for example, MySQL session variables or client-side session variables in tools like psql. In most systems, session variables do not participate in transactions: once set, their value persists until explicitly changed, even if the surrounding transaction is rolled back. Conceptually, they behave much like variables in general-purpose programming languages.&lt;br /&gt;
&lt;br /&gt;
Because there is no common design, comparisons across systems are difficult. Each implementation has its own advantages and disadvantages, and in some cases a single system supports more than one kind of session variable.&lt;br /&gt;
&lt;br /&gt;
Kinds of session variables:&lt;br /&gt;
* client side (psql, pgadmin, ...) - mostly similar to shell variables (based on string operations)&lt;br /&gt;
* server side&lt;br /&gt;
** declarative&lt;br /&gt;
*** created only for batch (T-SQL)&lt;br /&gt;
*** created as an database object (or part of database object) (Oracle, DB2)&lt;br /&gt;
** created by usage (MySQL)&lt;br /&gt;
** with priprietary syntax for identifiers (MySQL, T-SQL)&lt;br /&gt;
** with ANSI SQL syntax for identifiers (Oracle, DB2)&lt;br /&gt;
&lt;br /&gt;
Possible usage of session variables:&lt;br /&gt;
&lt;br /&gt;
* usage of session variables as global variables in PL (tracing, debugging, hacking),&lt;br /&gt;
* holding configuration (for PL),&lt;br /&gt;
* holding some more used data in interactive session,&lt;br /&gt;
* holding credentials for some RLS and data securing,&lt;br /&gt;
* helping with migration from others systems (mainly from Oracle),&lt;br /&gt;
* allow anonymous block parametrization (only postgres).&lt;br /&gt;
* session variables are writeable on read only hot standby (postgres)&lt;br /&gt;
&lt;br /&gt;
Session variables holds a values like temporary tables. What are differences between temporary tables and variables?:&lt;br /&gt;
&lt;br /&gt;
* variables holds a values, tables holds a set of rows,&lt;br /&gt;
* syntax for usage variables is shorter (more friendly for interactive work), access (read, write) is faster,&lt;br /&gt;
* the content of session variables is generally non transactional, the content of temporary tables is transactional,&lt;br /&gt;
* the content of session variables is just one value or NULL (no MVCC, no VACUUM),&lt;br /&gt;
* temporary tables are tables still (transactional, MVCC), update is expensive, under one transaction repeated updates makes lot of dead tuples,&lt;br /&gt;
* temporary tables are not cleaned by autovacuum (and inside function is not possible to execute VACUUM), &lt;br /&gt;
* Postgres doesn&#039;t support global temporary tables, so usage of temporary table for just few values or value is very expensive (catalogue bloat).&lt;br /&gt;
&lt;br /&gt;
= SQL/PSM =&lt;br /&gt;
&lt;br /&gt;
The SQL standard introduces the concept of modules, which can be associated with schemas (partially). Variables in modules behave like PL/pgSQL variables but are only local. The only objects allowed at the module level are temporary tables, which can be accessed only from routines assigned to that module. Modules can encapsulate private objects (functions, procedure, temp tables), that are not visible outside the module. Inside the module, a  private objects shadows other objects. What I know the modules (and generally SQL/PSM) doesn&#039;t cover a deployment of code, so there is not similarity with PostgreSQL extensions. SQL/PSM Modules are modules like modules from modular languages like Modula2 or Oberon. There is some similarity with Postgres schemas (both are containers) with significant differences (there is not a concept of SEARCH_PATH (on PSM side) or there is not a concept of private or public objects (on Postgres schema side)).&lt;br /&gt;
&lt;br /&gt;
SQL/PSM variables are not transactional (they are used exactly like in PL/pgSQL). The precedence between variables and columns is not specified.&lt;br /&gt;
&lt;br /&gt;
= Implementation Challenges =&lt;br /&gt;
&lt;br /&gt;
* Possible collisions between session variables and other database objects.&lt;br /&gt;
* Memory cleanup after dropping a session variable, especially when DDL operations can be reverted (Postgres has not support for global temp objects).&lt;br /&gt;
* Memory invalidation: if a variable is dropped by one session, other sessions should not continue using it (Postgres has not support for global temp objects).&lt;br /&gt;
&lt;br /&gt;
= Proposed Implementations =&lt;br /&gt;
&lt;br /&gt;
It is clear that no single design is perfect for all use cases. MySQL’s approach is convenient and useful for interactive work, but it cannot be used for storing sensitive data and is considered unsafe. PostgreSQL GUCs are excellent for configuration, but they are not well suited for interactive use and are very limited when used as global variables in PL code.&lt;br /&gt;
&lt;br /&gt;
Because of these trade-offs, no single design is sufficient. In practice, having multiple designs within a single system could support a broader range of use cases more effectively.&lt;br /&gt;
&lt;br /&gt;
== Design session variables as database global temporary typed objects with ANSI SQL syntax for identifiers ==&lt;br /&gt;
&lt;br /&gt;
Author: Pavel Stehule &amp;lt;pavel.stehule@gmail.com&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I searched for a solution that could work well with the specific PostgreSQL environment:&lt;br /&gt;
&lt;br /&gt;
* PostgreSQL has more than one widely used stored procedure language: PL/pgSQL, PL/Python, or PL/Perl.&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL is a reduced version of PL/SQL, and PL/SQL is a modified ADA language. I am pretty happy with the current complexity of PL/pgSQL --- it is a domain-specific language that works well. It is very simple and easy to learn. PL/pgSQL has no packages or modules; instead PostgreSQL schemas are used. But schemas are database objects in their own right, independent of PL.&lt;br /&gt;
&lt;br /&gt;
* PostgreSQL already has a code container --- the schema. I don&#039;t want to introduce another container object (modules), as this would overlap heavily with schemas. Implementing modules for PL/pgSQL could be useful, but doing it properly would be quite complex. The main problem is the concept of public and private objects --- PostgreSQL does not have this. Don&#039;t forget: every PL/pgSQL expression is a SQL expression, so public/private visibility would first need to be implemented internally in PostgreSQL. That would be redundant (and possibly in conflict) with SEARCH_PATH.&lt;br /&gt;
&lt;br /&gt;
So I wanted a design that supports multiple PL languages, does not add complexity to the relatively simple PL/pgSQL, and does not duplicate functionality already available.  &lt;br /&gt;
&lt;br /&gt;
I wrote a larger application in PL/pgSQL. To maintain it, I created plpgsql_lint (later renamed plpgsql_check). So I am always looking for solutions that do not block static code analysis. Static analysis requires persistent objects, which naturally leads to designing session variables as database objects (with their own system catalog).  &lt;br /&gt;
When session variables are database objects, ACLs can be applied naturally. Oracle package variables are well protected by package scope. PostgreSQL, however, has no schema scope --- schema locality is controlled by the SEARCH_PATH GUC, and introducing another mechanism would be messy. But with ACLs, variable contents can also be secured:&lt;br /&gt;
&lt;br /&gt;
    CREATE TEMP VARIABLE x AS int;&lt;br /&gt;
     &lt;br /&gt;
    LET x = 10;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(x);&lt;br /&gt;
    END;&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    SELECT VARIABLE(x);&lt;br /&gt;
&lt;br /&gt;
==== Collisions between column and variable identifiers ====&lt;br /&gt;
&lt;br /&gt;
There is a risk of identifier collisions between variables and columns. With ANSI SQL syntax for session variable identifiers, the problem is how to distinguish variables from columns. This is a known issue in PL/pgSQL and PL/SQL and can be solved either by prioritization or by prohibiting collisions.  &lt;br /&gt;
&lt;br /&gt;
Prohibiting collisions has largely solved this problem in PL/pgSQL. Unfortunately, session variables are global objects, so collisions can occur in any query. A poorly named variable could break queries unexpectedly. Prioritization also has problems --- a variable or a column could be used silently when the other was intended.&lt;br /&gt;
&lt;br /&gt;
    CREATE TABLE tab(a int);&lt;br /&gt;
    CREATE VARIABLE a AS int;&lt;br /&gt;
    &lt;br /&gt;
    SELECT a FROM tab; -- with disallowed collisions, this query fails when a variable named &amp;quot;a&amp;quot; exists&lt;br /&gt;
    &lt;br /&gt;
    LET a = 1;&lt;br /&gt;
    DELETE FROM tab WHERE a = 1; -- with higher priority for variables, all rows could be deleted&lt;br /&gt;
    SELECT a FROM tab; -- with higher priority for columns, the variable is ignored&lt;br /&gt;
&lt;br /&gt;
Even with collision prohibition, mistakes are still possible:&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE _id AS int;&lt;br /&gt;
    LET _id = 10;&lt;br /&gt;
    CREATE TABLE foo(id int);&lt;br /&gt;
    DELETE FROM foo WHERE _id = 10; -- just a typo, but all rows are deleted&lt;br /&gt;
&lt;br /&gt;
These problems are not new. Developers in PL/pgSQL or PL/SQL know them, but the scope of risk is normally limited to stored procedures. After many discussions and designs, I introduced &amp;quot;variable fences&amp;quot; (the syntax VARIABLE(varname)). Inside any query, variables can be used only inside a variable fence. This completely solves collisions and reduces the chance of human error. Currently, fences are required everywhere in queries and expressions. In the future, they might be made optional inside PL/pgSQL, to reduce overhead when porting Oracle applications. This could be controlled by a new plpgsql.extra_checks setting.&lt;br /&gt;
&lt;br /&gt;
A variable fence can collide with a user-defined function named `variable`. This is similar to keyword conflicts such as CUBE. It can be resolved by schema-qualifying or quoting:&lt;br /&gt;
&lt;br /&gt;
    SELECT public.variable(...)&lt;br /&gt;
    SELECT &amp;quot;variable&amp;quot;(...)&lt;br /&gt;
&lt;br /&gt;
Why not use T-SQL (MSSQL, MySQL) syntax for session variables? There are several reasons – some objective, one subjective:&lt;br /&gt;
&lt;br /&gt;
* There is a collision with custom operators. Operators `@` and `@@` are already used, and `@@` is common:&lt;br /&gt;
&lt;br /&gt;
    (2025-09-28 06:57:30) postgres=# create table tab(a int);&lt;br /&gt;
    CREATE TABLE&lt;br /&gt;
    (2025-09-28 06:57:42) postgres=# insert into tab values(10);&lt;br /&gt;
    INSERT 0 1&lt;br /&gt;
    (2025-09-28 06:57:50) postgres=# select @a from tab;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │       10 │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
We could safely use only `:` as a prefix, but that would conflict with psql variables. Similarly, `$` would conflict with query parameters.&lt;br /&gt;
&lt;br /&gt;
* T-SQL variable names look strange in PostgreSQL (in queries and in PL/pgSQL). PostgreSQL uses only ANSI/SQL identifiers, and PL/pgSQL follows the same rules. PostgreSQL is consistent here, and I see little motivation to introduce a new, non-standard syntax (especially since it could cause compatibility issues).&lt;br /&gt;
&lt;br /&gt;
Note: There was also a proposal to use table syntax for variables (similar to T-SQL in-memory table variables, but restricted to a single row). This is very effective for avoiding collisions, but I dislike it. It complicates simple expressions, adds inconsistency, and looks odd for scalar variables (though it could be useful for composite variables). This design doesn&#039;t solve all possible problems - variables can be shadowed by tables still, because we have SEARCH_PATH (but this is known issue):&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(var); -- variable is not a table&lt;br /&gt;
    END&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      -- variable as a table (I don&#039;t like it)&lt;br /&gt;
      -- can we use DML? How many rows can it hold?&lt;br /&gt;
      -- is the value a row or not?&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, (SELECT var FROM var);&lt;br /&gt;
    END&lt;br /&gt;
    $$&lt;br /&gt;
&lt;br /&gt;
I am not against introducing in-memory tables --- or more correctly, global temporary tables. A well-designed implementation could be very useful. But this is a different feature and should be designed separately.  &lt;br /&gt;
&lt;br /&gt;
There are also performance considerations: PL/pgSQL can use simple expression evaluation when no tables are referenced. This optimization would need to change, which touches sensitive code. It would also be inconsistent.  &lt;br /&gt;
&lt;br /&gt;
I would like global temporary tables (which PostgreSQL currently lacks). Tables should behave like tables, and variables should behave like variables. The natural mental model for session variables comes from variables in PL languages. PostgreSQL internally supports *transition tables*, and I can imagine allowing these outside of triggers. That would be valid, but again, it is a different feature.  &lt;br /&gt;
&lt;br /&gt;
Implementing declared tables inside PL/pgSQL would be simpler from a high-level design perspective (interaction between SQL tables and PL/pgSQL is already well defined), but difficult from a low-level perspective (statistics, optimizer data, etc. --- the same issues as with global temporary tables, unless we add fake proxy local temp tables).&lt;br /&gt;
&lt;br /&gt;
   CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
   AS $$&lt;br /&gt;
   -- not implemented yet, not part of this proposal&lt;br /&gt;
   DECLARE t TABLE(a int, b int); -- this table is invisible outside fx&lt;br /&gt;
   BEGIN&lt;br /&gt;
     INSERT INTO t VALUES(10,20);&lt;br /&gt;
     INSERT INTO t VALUES(30,40);&lt;br /&gt;
     RAISE NOTICE &#039;%&#039;, (SELECT count(*) FROM t);&lt;br /&gt;
   END;&lt;br /&gt;
   $$ LANGUAGE plpgsql;&lt;br /&gt;
&lt;br /&gt;
Some data languages define relational variables, but this concept has no overlap with the common understanding of session variables.&lt;br /&gt;
&lt;br /&gt;
Inside PL/pgSQL we can use a possibility to configure a parser, and can be easy to allow a usage of session variables without variable fence (like common plpgsql variable) in expressions. It is not supported yet, but there should not be implementation problems. Outside PL, the variable fences can be optional, when there is not risk of collisions (the expression doesn&#039;t contains subselect) (not implemented yet).&lt;br /&gt;
&lt;br /&gt;
==== Assign command - LET ====&lt;br /&gt;
&lt;br /&gt;
The value of session variable can be assigned by dedicated PL assign command or by `SELECT INTO` construct. When special command is prefixed by some a keyword, then the keyword `SET` is usually used (MySQL, MSSQL, SQL/PSM, DB2). Command `SET` can be enhanced to support `a_expr` on parser level in Postgres too. It requires partial rewriting of current implementation of `SET` command, but there is not technical issues.&lt;br /&gt;
&lt;br /&gt;
Unfortunately, there can be collisions between GUC and session variables. Currently GUC are not declared, and they are not joined to any schema (prefix for custom GUC can be just any not empty string), and access to GUC is not controlled by SEARCH_PATH. We can use same solution like usage session variables in expression - variable fence - but in this case, the risk is every time, and there is not a possibility to usage of variable fences can be optional (for some cases) in future. Some languages uses for a assignment the keyword `LET`. I introduced this keyword as a solution for 100% fix of collisions between GUC and session variables identifiers. &lt;br /&gt;
&lt;br /&gt;
I can imagine the assignment to session variable inside PL by usual PL assignment command (not implemented yet). This can be benefit for translation from PL/SQL (Oracle), and it can be very natural syntax, when session variable is used like PL global variable. Probably (not tested), there are not technical issues for implementation of this. But there is different significant issue. Currently, only PL/pgSQL variables can be target of PL/pgSQL assign statement, and these variables should be declared before usage. If we allow session variables there, then a) we cannot to do this check, or b) there will be new strong dependency from PL functions to session variables - like on types now.&lt;br /&gt;
&lt;br /&gt;
    (2025-10-02 07:42:01) postgres=# CREATE OR REPLACE FUNCTION fx() &lt;br /&gt;
    returns void as $$&lt;br /&gt;
    begin&lt;br /&gt;
      declare var mydomain;&lt;br /&gt;
    begin&lt;br /&gt;
      var := 10;&lt;br /&gt;
    end&lt;br /&gt;
    $$ language plpgsql;&lt;br /&gt;
    ERROR:  type &amp;quot;mydomain&amp;quot; does not exist&lt;br /&gt;
    LINE 4:   declare var mydomain;&lt;br /&gt;
                          ^&lt;br /&gt;
&lt;br /&gt;
The strong dependency today is not problem probably (because plan cache can be correctly invalidated), but there can be some new questions about temporary variables. Again as a solution I proposed a `LET` command, and we don&#039;t need to touch to PL/pgSQL assign statement.&lt;br /&gt;
&lt;br /&gt;
The significant question is performance generally and performance when LET is used inside PL/pgSQL.&lt;br /&gt;
&lt;br /&gt;
The command `LET` is PostgreSQL utility command. Against history, the utility command can be prepared (as `CREATE TABLE AS SELECT`). There are patches that implements `PREPARE` and `EXECUTE` for `LET` command (these patches are not in reduced patch set). Although plan cache can be used, the performance against PL/pgSQL assign can be slower - from two reasons:&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL allows simple expression evaluation (this is supported for `LET` command too in enhanced patch set),&lt;br /&gt;
* PL/pgSQL variables are stored in a array (and indexed by int offset), session variables are stored in hash table, and they are accessed by hash search (and this slower). This slowdown is visible only in worst case benchmarks like:&lt;br /&gt;
&lt;br /&gt;
   DO $$ declare locvar int DEFAULT 0; begin for i in 1..1000000 loop locvar := locvar + 1; end loop; end $$ &lt;br /&gt;
   DO $$ begin for i in 1..100000 loop LET sesvar := sesvar + 1; end loop; end $$&lt;br /&gt;
&lt;br /&gt;
The implementation of `LET` in reduced patch set, is significantly slower, because the simple expression evaluation is not used (it is implemented, but not in reduced patch set). Although there is significant slowdown, the execution is still significantly faster than a execution of any query that touch to real table (it should not be a problem for real usage).&lt;br /&gt;
&lt;br /&gt;
==== Notes about implementation session variables as global temporary objects ====&lt;br /&gt;
The core of this proposal is design of session variables as global temporary objects. Unfortunately, Postgres has zero support for this kind of objects. Session variables has not any session specific data in the catalog. This is much more better (and easier) than for global temporary tables. The data of session variables are in session memory (only). This is big difference from tables. There the data are stored in pages, and pages are stored in some files. The files are important - any cached data can be thrown (and they are frequently thrown). There are two kinds of new issues (related to session variables).&lt;br /&gt;
&lt;br /&gt;
* possibility to use invalid value (stored in valid memory)&lt;br /&gt;
* too early or too delayed memory cleaning&lt;br /&gt;
&lt;br /&gt;
Any session variable is identified by name (and possibly by oid from `pg_variable`). Any variable can use own data type. Values stored in memory are stored in native binary format, and without reassignment, the value is not changed. Important note - the one oid can be assigned more times. oids are unique in just one moment, but there is not a protection so one oid will not be used more times for different objects. There is a real possibility of following issue:&lt;br /&gt;
&lt;br /&gt;
* session A: CREATE VARIABLE v AS t;&lt;br /&gt;
* session B: LET v = t &#039;value&#039;;&lt;br /&gt;
* session A: DROP VARIABLE v;&lt;br /&gt;
* session B: no activity -- it got lot of sinval messages (signals), but no command is executed, and then is not possibility to handle sinval&lt;br /&gt;
* session A: CREATE VARIABLE v AS nt; -- theoretically I can get same varid and typid like in first step, although t != nt&lt;br /&gt;
* session B: SELECT v; -- crash&lt;br /&gt;
&lt;br /&gt;
Before any usage of stored value, we need to do really identity check and varid and typid is not enough. But we can store to variable their create lsn - and this number is unique for all time database (catalog) live. So the necessary check before usage of stored value is just check if value.varid = catalog.varid and value.createlsn = catalog.createlsn. The ALTER VARIABLE ALTER TYPE is not allowed. The dependency mechanism ensure so value type should be identical with catalog type.&lt;br /&gt;
&lt;br /&gt;
= Variabes in PostgreSQL (as of v18) =&lt;br /&gt;
&lt;br /&gt;
PostgreSQL provides relatively advanced client-side session variables in psql --- these variables use the `:` prefix. The implementation is pretty much straightforward: `psql`&#039;s parser reads tokens from input and, when it encounters a token related to a variable, replaces it with the variable&#039;s value. The syntax supports literal and identifier escaping, and variables can be used as arguments of the `\bind` command.&lt;br /&gt;
&lt;br /&gt;
`psql` variables are typeless client-side variables, available only inside `psql` or `pgbench`.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:40:18) postgres=# \set myvar ahoj&lt;br /&gt;
    (2025-09-25 09:40:33) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:14) postgres=# select :&#039;myvar&#039;;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:24) postgres=# select $1 \bind :myvar \g&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
The `\set` command allows only simple string concatenation, but it also supports execution of shell commands. When a query is executed with the `\gset` command, its result can be stored in a `psql` variable.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:41:40) postgres=# \set ctime `date`&lt;br /&gt;
    (2025-09-25 09:43:55) postgres=# \echo :ctime&lt;br /&gt;
    Thu Sep 25 09:43:55 CEST 2025&lt;br /&gt;
&lt;br /&gt;
The `\set` command is fairly limited, making complex operations unreadable or non-portable.&lt;br /&gt;
&lt;br /&gt;
This mechanism is good enough for writing tests, but it is not generally available (most users do not need it), and it is not accessible from the server side (e.g., from stored procedures). There is no simple way to access `psql` variables from an anonymous block --- a proxy custom GUC variable must be used:&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:58:38) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    (2025-09-25 09:58:43) postgres=# select set_config(&#039;myvars.myvar&#039;, :&#039;myvar&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ ahoj       │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 10:00:27) postgres=# do $$ begin raise notice &#039;%&#039;, current_setting(&#039;myvars.myvar&#039;); end $$;&lt;br /&gt;
    NOTICE:  ahoj&lt;br /&gt;
    DO&lt;br /&gt;
&lt;br /&gt;
PostgreSQL also has configuration variables (GUC - Grand Unified Configuration). These are primarily designed for PostgreSQL and extension configuration, but they can be used as session variables. GUC variables can be set with the `SET` command or the `set_config` function, and read with `SHOW` or `current_setting`.&lt;br /&gt;
&lt;br /&gt;
GUC variables are meant for holding configuration parameters. When created through the internal API, they can be restricted to superusers, hidden from regular users, and assigned specific types (boolean, memory, number, interval, string, enum) with validation. This type system is separate from the SQL type system and is initialized before PostgreSQL’s SQL types.&lt;br /&gt;
&lt;br /&gt;
Variables created from SQL must be &amp;quot;custom&amp;quot; variables, using a prefix in their name --- these are always strings. Such custom GUCs are often used as a workaround for missing server-side session variables in PostgreSQL, but they are typeless, unprotected, and unsafe.&lt;br /&gt;
&lt;br /&gt;
A notable feature of PostgreSQL GUCs (built-in or custom) is that they can have different scopes: transaction, session, or function execution. They can also be assigned default values at specific events --- configuration loading, role login, database login, or function start. These features are useful for configuration, but problematic when GUCs are repurposed as session variables.&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
    RETURNS void AS $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, current_setting(&#039;my.x&#039;);&lt;br /&gt;
    END $$ LANGUAGE plpgsql SET my.x = 20;&lt;br /&gt;
    CREATE FUNCTION&lt;br /&gt;
    (2025-09-27 06:10:37) postgres=# SET my.x = 0; SELECT fx();&lt;br /&gt;
    SET&lt;br /&gt;
    NOTICE:  20&lt;br /&gt;
    ┌────┐&lt;br /&gt;
    │ fx │&lt;br /&gt;
    ╞════╡&lt;br /&gt;
    │    │&lt;br /&gt;
    └────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    (2025-09-27 06:10:39) postgres=# SHOW my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 0    │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
GUC variables are transactional, and this behavior cannot be disabled.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-27 06:17:31) postgres=# set my.x = 10;&lt;br /&gt;
    SET&lt;br /&gt;
    (2025-09-27 06:19:42) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:19:46) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, true);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:19:55) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:01) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:20:11) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:13) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:20:38) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:45) postgres=# rollback;&lt;br /&gt;
    ROLLBACK&lt;br /&gt;
    (2025-09-27 06:20:52) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:55) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:22:36) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:22:39) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:22:42) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
As a result, there is no reliable way to store details such as error information in custom GUCs.&lt;br /&gt;
&lt;br /&gt;
= Session Variables in Other Systems =&lt;br /&gt;
&lt;br /&gt;
This section surveys how session variables are implemented in several widely used database systems. Each subsection includes code examples to illustrate syntax, behaviour, and scope.&lt;br /&gt;
&lt;br /&gt;
== MySQL ==&lt;br /&gt;
&lt;br /&gt;
Session variables in MySQL have syntax similar to the better-known T-SQL variables (though other details are MySQL-specific). MySQL distinguishes two kinds of variables:&lt;br /&gt;
&lt;br /&gt;
* system variables - referenced using the `@@` prefix (or `@@scope.name`)&lt;br /&gt;
* user-defined variables - referenced using the `@` prefix&lt;br /&gt;
&lt;br /&gt;
System variables are modified with the `SET` statement. The `SET` statement accepts the modifiers `GLOBAL`, `SESSION`, or `PERSIST`. Some variables can only be set using the `GLOBAL` or `SESSION` modifier; when the variable name is specified with the `@@` prefix, it is not necessary to explicitly indicate the scope. System variables are defined by MySQL or by MySQL plugins.&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL some_config = 100;&lt;br /&gt;
    SET @@same_config = 100;&lt;br /&gt;
    &lt;br /&gt;
    SET SESSION sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@SESSION.sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
&lt;br /&gt;
Persistent values are stored in `mysqld-auto.cnf` (in the server data directory).&lt;br /&gt;
&lt;br /&gt;
Resetting system variables can be done by assigning DEFAULT or by copying from the GLOBAL namespace:&lt;br /&gt;
&lt;br /&gt;
    SET @@sql_mode = DEFAULT;&lt;br /&gt;
    SET @@sql_mode = @@GLOBAL.sql_mode&lt;br /&gt;
&lt;br /&gt;
PostgreSQL equivalents:&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL ; -- there is not similar command in Postgres&lt;br /&gt;
    SET SESSION ; -- SET&lt;br /&gt;
    SET PERSIST ; -- ALTER SYSTEM; SELECT pg_reload_conf();&lt;br /&gt;
    SELECT @@var; -- SELECT current_setting(&#039;var);&lt;br /&gt;
&lt;br /&gt;
User-defined variables are created by assignment:&lt;br /&gt;
&lt;br /&gt;
    SET @var = expr [, @var2 = expr [ ... ]];&lt;br /&gt;
&lt;br /&gt;
User-defined variables can only be of type integer, decimal, float, binary, non-binary string, or NULL. Casting between these types is implicit and hidden. User-defined variables cannot be used as table or column names.&lt;br /&gt;
&lt;br /&gt;
An advantage of the T-SQL-style syntax (with special prefixes) is that it provides a simple solution for avoiding identifier collisions. MySQL variables are convenient, and the user experience can be good for people already familiar with T-SQL (MSSQL or Sybase). On the other hand, prefix-based syntax can be confusing for users coming from systems other than MSSQL. The type system is perhaps too simple and can be unsafe. In general, the performance of MySQL (or MariaDB) stored procedures is not good, and stored procedures are therefore not frequently used. There is no protection against typographical errors. Unassigned user variables can be used without error (they default to NULL), which makes static checking of stored procedures impossible in this area.&lt;br /&gt;
&lt;br /&gt;
There is no way to restrict access to user-defined variables in MySQL. They cannot be protected against unintended usage. The only possible safeguard is a naming convention, since all user-defined variables share a single namespace.&lt;br /&gt;
&lt;br /&gt;
== Oracle ==&lt;br /&gt;
&lt;br /&gt;
Oracle PL/SQL allows the use of package variables. PL/SQL is based on the Ada language, and package variables act as &amp;quot;global&amp;quot; variables. They are not directly visible from SQL, but Oracle provides a reduced syntax for functions without arguments, so you typically write a wrapper:&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE PACKAGE my_package&lt;br /&gt;
    AS&lt;br /&gt;
      FUNCTION get_a RETURN NUMBER;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    /&lt;br /&gt;
    &lt;br /&gt;
    CREATE OR REPLACE PACKAGE BODY my_package&lt;br /&gt;
    AS&lt;br /&gt;
      a  NUMBER(20);&lt;br /&gt;
      &lt;br /&gt;
      FUNCTION get_a&lt;br /&gt;
      RETURN NUMBER&lt;br /&gt;
      IS&lt;br /&gt;
      BEGIN&lt;br /&gt;
        RETURN a;&lt;br /&gt;
      END get_a;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    &lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
Inside SQL, SQL identifiers take precedence. Inside non-SQL commands (such as CALL or PL/SQL blocks), package identifiers take precedence.&lt;br /&gt;
&lt;br /&gt;
Oracle allows both syntaxes for calling a function with zero arguments:&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a() FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
This reduces the risk of naming collisions. Package variables persist for the duration of a session.&lt;br /&gt;
&lt;br /&gt;
Another possibility is to use variables in SQL*Plus (similar to `psql` variables, but with the ability to define the type on the server side).&lt;br /&gt;
&lt;br /&gt;
A variable is declared with the `VARIABLE` command and can be accessed in the session using the `:varname` syntax (a prior declaration step may be optional):&lt;br /&gt;
&lt;br /&gt;
    VARIABLE bv_variable_name VARCHAR2(30)&lt;br /&gt;
    BEGIN&lt;br /&gt;
      :bv_variable_name := &#039;Some Value&#039;;&lt;br /&gt;
    END;&lt;br /&gt;
    &lt;br /&gt;
    SELECT column_name&lt;br /&gt;
      FROM   table_name&lt;br /&gt;
     WHERE  column_name = :bv_variable_name;&lt;br /&gt;
&lt;br /&gt;
== DB2 ==&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;user-defined global variables&amp;quot; in DB2 are similar to the proposed PostgreSQL feature. The main difference is in the access rights: DB2 uses `READ` and `WRITE`, while PostgreSQL uses `SELECT` and `UPDATE`. Because PostgreSQL already uses the `SET` command for GUCs, the `LET` command was introduced in the proposal (DB2 uses `SET`).&lt;br /&gt;
&lt;br /&gt;
Variables are visible in all sessions, but their values are private to each session. Variables are not transactional. Their usage is broader than in the proposal: they can be changed by `SET`, `SELECT INTO`, or used as OUT parameters of procedures. The search path (or a similar mechanism) applies to variables as well, but variables have lower priority than tables or columns.&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE myCounter INT DEFAULT 01;&lt;br /&gt;
    SELECT EMPNO, LASTNAME, CASE WHEN myCounter = 1 THEN SALARY ELSE NULL END&lt;br /&gt;
    FROM EMPLOYEE WHERE WORKDEPT = &#039;A00&#039;;&lt;br /&gt;
    SET myCounter = 29;&lt;br /&gt;
&lt;br /&gt;
There also appear to be other kinds of variables, accessed using the function `GETVARIABLE(&#039;name&#039;, &#039;default&#039;)`. These are very similar to PostgreSQL GUCs and the `current_setting` function. Such variables can be set via the connection string, are of type `VARCHAR`, and up to 10 values are allowed. Built-in session variables (configuration) can also be accessed using `GETVARIABLE`.&lt;br /&gt;
&lt;br /&gt;
== MSSQL (T-SQL) ==&lt;br /&gt;
&lt;br /&gt;
MSSQL provides two types of variables:&lt;br /&gt;
&lt;br /&gt;
* Global variables — accessed with the `@@varname` syntax. These are similar to GUCs and provide state information such as `@@ERROR`, `@@ROWCOUNT`, or `@@IDENTITY`. &lt;br /&gt;
* Local variables — accessed with the `@varname` syntax. These must be declared with the `DECLARE` command before use. Their scope is limited to the batch, procedure, or function where they are executed.&lt;br /&gt;
&lt;br /&gt;
    DECLARE @TestVariable AS VARCHAR(100)&lt;br /&gt;
    SET @TestVariable = &#039;Think Green&#039;&lt;br /&gt;
    GO&lt;br /&gt;
    PRINT @TestVariable&lt;br /&gt;
&lt;br /&gt;
This script fails because `PRINT` is executed in a different batch. Therefore, it seems MSSQL does not truly support session variables.&lt;br /&gt;
&lt;br /&gt;
There are also mechanisms similar to PostgreSQL&#039;s custom GUCs and the use of `current_setting` and `set_config` functions. In general, MSSQL is fairly primitive in this area.&lt;br /&gt;
&lt;br /&gt;
    EXEC sp_set_session_context &#039;user_id&#039;, 4;&lt;br /&gt;
    SELECT SESSION_CONTEXT(N&#039;user_id&#039;);&lt;br /&gt;
&lt;br /&gt;
= ToDo =&lt;br /&gt;
&lt;br /&gt;
* SECURITY LABEL support — enhance the `dummy_seclabel` module and the `sepgsql` extension. Update PostgreSQL documentation and ensure `SecLabelSupportsObjectType` includes session variables.&lt;/div&gt;</summary>
		<author><name>Okbobcz</name></author>
	</entry>
	<entry>
		<id>https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41866</id>
		<title>Implementation of declarative catalog session variables</title>
		<link rel="alternate" type="text/html" href="https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41866"/>
		<updated>2025-10-02T07:02:15Z</updated>

		<summary type="html">&lt;p&gt;Okbobcz: /* Design session variables as database global temporary typed objects with ANSI SQL syntax for identifiers */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Session variables are database objects that can hold a value across multiple queries inside a session. The design of session variables varies widely, and many databases implement more than one type of session variable.&lt;br /&gt;
&lt;br /&gt;
The SQL/PSM standard introduces modules and module-level variables. However, this part of the standard has not been implemented in any widely used product. As a result, each database system has developed its own proprietary design, syntax, and feature set.&lt;br /&gt;
&lt;br /&gt;
Session variables are often part of the stored procedure environment, but there are exceptions --- for example, MySQL session variables or client-side session variables in tools like psql. In most systems, session variables do not participate in transactions: once set, their value persists until explicitly changed, even if the surrounding transaction is rolled back. Conceptually, they behave much like variables in general-purpose programming languages.&lt;br /&gt;
&lt;br /&gt;
Because there is no common design, comparisons across systems are difficult. Each implementation has its own advantages and disadvantages, and in some cases a single system supports more than one kind of session variable.&lt;br /&gt;
&lt;br /&gt;
Kinds of session variables:&lt;br /&gt;
* client side (psql, pgadmin, ...) - mostly similar to shell variables (based on string operations)&lt;br /&gt;
* server side&lt;br /&gt;
** declarative&lt;br /&gt;
*** created only for batch (T-SQL)&lt;br /&gt;
*** created as an database object (or part of database object) (Oracle, DB2)&lt;br /&gt;
** created by usage (MySQL)&lt;br /&gt;
** with priprietary syntax for identifiers (MySQL, T-SQL)&lt;br /&gt;
** with ANSI SQL syntax for identifiers (Oracle, DB2)&lt;br /&gt;
&lt;br /&gt;
Possible usage of session variables:&lt;br /&gt;
&lt;br /&gt;
* usage of session variables as global variables in PL (tracing, debugging, hacking),&lt;br /&gt;
* holding configuration (for PL),&lt;br /&gt;
* holding some more used data in interactive session,&lt;br /&gt;
* holding credentials for some RLS and data securing,&lt;br /&gt;
* helping with migration from others systems (mainly from Oracle),&lt;br /&gt;
* allow anonymous block parametrization (only postgres).&lt;br /&gt;
* session variables are writeable on read only hot standby (postgres)&lt;br /&gt;
&lt;br /&gt;
Session variables holds a values like temporary tables. What are differences between temporary tables and variables?:&lt;br /&gt;
&lt;br /&gt;
* variables holds a values, tables holds a set of rows,&lt;br /&gt;
* syntax for usage variables is shorter (more friendly for interactive work), access (read, write) is faster,&lt;br /&gt;
* the content of session variables is generally non transactional, the content of temporary tables is transactional,&lt;br /&gt;
* the content of session variables is just one value or NULL (no MVCC, no VACUUM),&lt;br /&gt;
* temporary tables are tables still (transactional, MVCC), update is expensive, under one transaction repeated updates makes lot of dead tuples,&lt;br /&gt;
* temporary tables are not cleaned by autovacuum (and inside function is not possible to execute VACUUM), &lt;br /&gt;
* Postgres doesn&#039;t support global temporary tables, so usage of temporary table for just few values or value is very expensive (catalogue bloat).&lt;br /&gt;
&lt;br /&gt;
= SQL/PSM =&lt;br /&gt;
&lt;br /&gt;
The SQL standard introduces the concept of modules, which can be associated with schemas (partially). Variables in modules behave like PL/pgSQL variables but are only local. The only objects allowed at the module level are temporary tables, which can be accessed only from routines assigned to that module. Modules can encapsulate private objects (functions, procedure, temp tables), that are not visible outside the module. Inside the module, a  private objects shadows other objects. What I know the modules (and generally SQL/PSM) doesn&#039;t cover a deployment of code, so there is not similarity with PostgreSQL extensions. SQL/PSM Modules are modules like modules from modular languages like Modula2 or Oberon. There is some similarity with Postgres schemas (both are containers) with significant differences (there is not a concept of SEARCH_PATH (on PSM side) or there is not a concept of private or public objects (on Postgres schema side)).&lt;br /&gt;
&lt;br /&gt;
SQL/PSM variables are not transactional (they are used exactly like in PL/pgSQL). The precedence between variables and columns is not specified.&lt;br /&gt;
&lt;br /&gt;
= Implementation Challenges =&lt;br /&gt;
&lt;br /&gt;
* Possible collisions between session variables and other database objects.&lt;br /&gt;
* Memory cleanup after dropping a session variable, especially when DDL operations can be reverted (Postgres has not support for global temp objects).&lt;br /&gt;
* Memory invalidation: if a variable is dropped by one session, other sessions should not continue using it (Postgres has not support for global temp objects).&lt;br /&gt;
&lt;br /&gt;
= Proposed Implementations =&lt;br /&gt;
&lt;br /&gt;
It is clear that no single design is perfect for all use cases. MySQL’s approach is convenient and useful for interactive work, but it cannot be used for storing sensitive data and is considered unsafe. PostgreSQL GUCs are excellent for configuration, but they are not well suited for interactive use and are very limited when used as global variables in PL code.&lt;br /&gt;
&lt;br /&gt;
Because of these trade-offs, no single design is sufficient. In practice, having multiple designs within a single system could support a broader range of use cases more effectively.&lt;br /&gt;
&lt;br /&gt;
== Design session variables as database global temporary typed objects with ANSI SQL syntax for identifiers ==&lt;br /&gt;
&lt;br /&gt;
Author: Pavel Stehule &amp;lt;pavel.stehule@gmail.com&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I searched for a solution that could work well with the specific PostgreSQL environment:&lt;br /&gt;
&lt;br /&gt;
* PostgreSQL has more than one widely used stored procedure language: PL/pgSQL, PL/Python, or PL/Perl.&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL is a reduced version of PL/SQL, and PL/SQL is a modified ADA language. I am pretty happy with the current complexity of PL/pgSQL --- it is a domain-specific language that works well. It is very simple and easy to learn. PL/pgSQL has no packages or modules; instead PostgreSQL schemas are used. But schemas are database objects in their own right, independent of PL.&lt;br /&gt;
&lt;br /&gt;
* PostgreSQL already has a code container --- the schema. I don&#039;t want to introduce another container object (modules), as this would overlap heavily with schemas. Implementing modules for PL/pgSQL could be useful, but doing it properly would be quite complex. The main problem is the concept of public and private objects --- PostgreSQL does not have this. Don&#039;t forget: every PL/pgSQL expression is a SQL expression, so public/private visibility would first need to be implemented internally in PostgreSQL. That would be redundant (and possibly in conflict) with SEARCH_PATH.&lt;br /&gt;
&lt;br /&gt;
So I wanted a design that supports multiple PL languages, does not add complexity to the relatively simple PL/pgSQL, and does not duplicate functionality already available.  &lt;br /&gt;
&lt;br /&gt;
I wrote a larger application in PL/pgSQL. To maintain it, I created plpgsql_lint (later renamed plpgsql_check). So I am always looking for solutions that do not block static code analysis. Static analysis requires persistent objects, which naturally leads to designing session variables as database objects (with their own system catalog).  &lt;br /&gt;
When session variables are database objects, ACLs can be applied naturally. Oracle package variables are well protected by package scope. PostgreSQL, however, has no schema scope --- schema locality is controlled by the SEARCH_PATH GUC, and introducing another mechanism would be messy. But with ACLs, variable contents can also be secured:&lt;br /&gt;
&lt;br /&gt;
    CREATE TEMP VARIABLE x AS int;&lt;br /&gt;
     &lt;br /&gt;
    LET x = 10;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(x);&lt;br /&gt;
    END;&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    SELECT VARIABLE(x);&lt;br /&gt;
&lt;br /&gt;
==== Collisions between column and variable identifiers ====&lt;br /&gt;
&lt;br /&gt;
There is a risk of identifier collisions between variables and columns. With ANSI SQL syntax for session variable identifiers, the problem is how to distinguish variables from columns. This is a known issue in PL/pgSQL and PL/SQL and can be solved either by prioritization or by prohibiting collisions.  &lt;br /&gt;
&lt;br /&gt;
Prohibiting collisions has largely solved this problem in PL/pgSQL. Unfortunately, session variables are global objects, so collisions can occur in any query. A poorly named variable could break queries unexpectedly. Prioritization also has problems --- a variable or a column could be used silently when the other was intended.&lt;br /&gt;
&lt;br /&gt;
    CREATE TABLE tab(a int);&lt;br /&gt;
    CREATE VARIABLE a AS int;&lt;br /&gt;
    &lt;br /&gt;
    SELECT a FROM tab; -- with disallowed collisions, this query fails when a variable named &amp;quot;a&amp;quot; exists&lt;br /&gt;
    &lt;br /&gt;
    LET a = 1;&lt;br /&gt;
    DELETE FROM tab WHERE a = 1; -- with higher priority for variables, all rows could be deleted&lt;br /&gt;
    SELECT a FROM tab; -- with higher priority for columns, the variable is ignored&lt;br /&gt;
&lt;br /&gt;
Even with collision prohibition, mistakes are still possible:&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE _id AS int;&lt;br /&gt;
    LET _id = 10;&lt;br /&gt;
    CREATE TABLE foo(id int);&lt;br /&gt;
    DELETE FROM foo WHERE _id = 10; -- just a typo, but all rows are deleted&lt;br /&gt;
&lt;br /&gt;
These problems are not new. Developers in PL/pgSQL or PL/SQL know them, but the scope of risk is normally limited to stored procedures. After many discussions and designs, I introduced &amp;quot;variable fences&amp;quot; (the syntax VARIABLE(varname)). Inside any query, variables can be used only inside a variable fence. This completely solves collisions and reduces the chance of human error. Currently, fences are required everywhere in queries and expressions. In the future, they might be made optional inside PL/pgSQL, to reduce overhead when porting Oracle applications. This could be controlled by a new plpgsql.extra_checks setting.&lt;br /&gt;
&lt;br /&gt;
A variable fence can collide with a user-defined function named `variable`. This is similar to keyword conflicts such as CUBE. It can be resolved by schema-qualifying or quoting:&lt;br /&gt;
&lt;br /&gt;
    SELECT public.variable(...)&lt;br /&gt;
    SELECT &amp;quot;variable&amp;quot;(...)&lt;br /&gt;
&lt;br /&gt;
Why not use T-SQL (MSSQL, MySQL) syntax for session variables? There are several reasons – some objective, one subjective:&lt;br /&gt;
&lt;br /&gt;
* There is a collision with custom operators. Operators `@` and `@@` are already used, and `@@` is common:&lt;br /&gt;
&lt;br /&gt;
    (2025-09-28 06:57:30) postgres=# create table tab(a int);&lt;br /&gt;
    CREATE TABLE&lt;br /&gt;
    (2025-09-28 06:57:42) postgres=# insert into tab values(10);&lt;br /&gt;
    INSERT 0 1&lt;br /&gt;
    (2025-09-28 06:57:50) postgres=# select @a from tab;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │       10 │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
We could safely use only `:` as a prefix, but that would conflict with psql variables. Similarly, `$` would conflict with query parameters.&lt;br /&gt;
&lt;br /&gt;
* T-SQL variable names look strange in PostgreSQL (in queries and in PL/pgSQL). PostgreSQL uses only ANSI/SQL identifiers, and PL/pgSQL follows the same rules. PostgreSQL is consistent here, and I see little motivation to introduce a new, non-standard syntax (especially since it could cause compatibility issues).&lt;br /&gt;
&lt;br /&gt;
Note: There was also a proposal to use table syntax for variables (similar to T-SQL in-memory table variables, but restricted to a single row). This is very effective for avoiding collisions, but I dislike it. It complicates simple expressions, adds inconsistency, and looks odd for scalar variables (though it could be useful for composite variables). This design doesn&#039;t solve all possible problems - variables can be shadowed by tables still, because we have SEARCH_PATH (but this is known issue):&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(var); -- variable is not a table&lt;br /&gt;
    END&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      -- variable as a table (I don&#039;t like it)&lt;br /&gt;
      -- can we use DML? How many rows can it hold?&lt;br /&gt;
      -- is the value a row or not?&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, (SELECT var FROM var);&lt;br /&gt;
    END&lt;br /&gt;
    $$&lt;br /&gt;
&lt;br /&gt;
I am not against introducing in-memory tables --- or more correctly, global temporary tables. A well-designed implementation could be very useful. But this is a different feature and should be designed separately.  &lt;br /&gt;
&lt;br /&gt;
There are also performance considerations: PL/pgSQL can use simple expression evaluation when no tables are referenced. This optimization would need to change, which touches sensitive code. It would also be inconsistent.  &lt;br /&gt;
&lt;br /&gt;
I would like global temporary tables (which PostgreSQL currently lacks). Tables should behave like tables, and variables should behave like variables. The natural mental model for session variables comes from variables in PL languages. PostgreSQL internally supports *transition tables*, and I can imagine allowing these outside of triggers. That would be valid, but again, it is a different feature.  &lt;br /&gt;
&lt;br /&gt;
Implementing declared tables inside PL/pgSQL would be simpler from a high-level design perspective (interaction between SQL tables and PL/pgSQL is already well defined), but difficult from a low-level perspective (statistics, optimizer data, etc. --- the same issues as with global temporary tables, unless we add fake proxy local temp tables).&lt;br /&gt;
&lt;br /&gt;
   CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
   AS $$&lt;br /&gt;
   -- not implemented yet, not part of this proposal&lt;br /&gt;
   DECLARE t TABLE(a int, b int); -- this table is invisible outside fx&lt;br /&gt;
   BEGIN&lt;br /&gt;
     INSERT INTO t VALUES(10,20);&lt;br /&gt;
     INSERT INTO t VALUES(30,40);&lt;br /&gt;
     RAISE NOTICE &#039;%&#039;, (SELECT count(*) FROM t);&lt;br /&gt;
   END;&lt;br /&gt;
   $$ LANGUAGE plpgsql;&lt;br /&gt;
&lt;br /&gt;
Some data languages define relational variables, but this concept has no overlap with the common understanding of session variables.&lt;br /&gt;
&lt;br /&gt;
Inside PL/pgSQL we can use a possibility to configure a parser, and can be easy to allow a usage of session variables without variable fence (like common plpgsql variable) in expressions. It is not supported yet, but there should not be implementation problems. Outside PL, the variable fences can be optional, when there is not risk of collisions (the expression doesn&#039;t contains subselect) (not implemented yet).&lt;br /&gt;
&lt;br /&gt;
==== Assign command - LET ====&lt;br /&gt;
&lt;br /&gt;
The value of session variable can be assigned by dedicated PL assign command or by `SELECT INTO` construct. When special command is prefixed by some a keyword, then the keyword `SET` is usually used (MySQL, MSSQL, SQL/PSM, DB2). Command `SET` can be enhanced to support `a_expr` on parser level in Postgres too. It requires partial rewriting of current implementation of `SET` command, but there is not technical issues.&lt;br /&gt;
&lt;br /&gt;
Unfortunately, there can be collisions between GUC and session variables. Currently GUC are not declared, and they are not joined to any schema (prefix for custom GUC can be just any not empty string), and access to GUC is not controlled by SEARCH_PATH. We can use same solution like usage session variables in expression - variable fence - but in this case, the risk is every time, and there is not a possibility to usage of variable fences can be optional (for some cases) in future. Some languages uses for a assignment the keyword `LET`. I introduced this keyword as a solution for 100% fix of collisions between GUC and session variables identifiers. &lt;br /&gt;
&lt;br /&gt;
I can imagine the assignment to session variable inside PL by usual PL assignment command (not implemented yet). This can be benefit for translation from PL/SQL (Oracle), and it can be very natural syntax, when session variable is used like PL global variable. Probably (not tested), there are not technical issues for implementation of this. But there is different significant issue. Currently, only PL/pgSQL variables can be target of PL/pgSQL assign statement, and these variables should be declared before usage. If we allow session variables there, then a) we cannot to do this check, or b) there will be new strong dependency from PL functions to session variables - like on types now.&lt;br /&gt;
&lt;br /&gt;
    (2025-10-02 07:42:01) postgres=# CREATE OR REPLACE FUNCTION fx() &lt;br /&gt;
    returns void as $$&lt;br /&gt;
    begin&lt;br /&gt;
      declare var mydomain;&lt;br /&gt;
    begin&lt;br /&gt;
      var := 10;&lt;br /&gt;
    end&lt;br /&gt;
    $$ language plpgsql;&lt;br /&gt;
    ERROR:  type &amp;quot;mydomain&amp;quot; does not exist&lt;br /&gt;
    LINE 4:   declare var mydomain;&lt;br /&gt;
                          ^&lt;br /&gt;
&lt;br /&gt;
The strong dependency today is not problem probably (because plan cache can be correctly invalidated), but there can be some new questions about temporary variables. Again as a solution I proposed a `LET` command, and we don&#039;t need to touch to PL/pgSQL assign statement.&lt;br /&gt;
&lt;br /&gt;
The significant question is performance generally and performance when LET is used inside PL/pgSQL.&lt;br /&gt;
&lt;br /&gt;
The command `LET` is PostgreSQL utility command. Against history, the utility command can be prepared (as `CREATE TABLE AS SELECT`). There are patches that implements `PREPARE` and `EXECUTE` for `LET` command (these patches are not in reduced patch set). Although plan cache can be used, the performance against PL/pgSQL assign can be slower - from two reasons:&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL allows simple expression evaluation (this is supported for `LET` command too in enhanced patch set),&lt;br /&gt;
* PL/pgSQL variables are stored in a array (and indexed by int offset), session variables are stored in hash table, and they are accessed by hash search (and this slower). This slowdown is visible only in worst case benchmarks like:&lt;br /&gt;
&lt;br /&gt;
   DO $$ declare locvar int DEFAULT 0; begin for i in 1..1000000 loop locvar := locvar + 1; end loop; end $$ &lt;br /&gt;
   DO $$ begin for i in 1..100000 loop LET sesvar := sesvar + 1; end loop; end $$&lt;br /&gt;
&lt;br /&gt;
The implementation of `LET` in reduced patch set, is significantly slower, because the simple expression evaluation is not used (it is implemented, but not in reduced patch set). Although there is significant slowdown, the execution is still significantly faster than a execution of any query that touch to real table (it should not be a problem for real usage).&lt;br /&gt;
&lt;br /&gt;
==== Notes about implementation session variables as global temporary objects ====&lt;br /&gt;
The core of this proposal is design of session variables as global temporary objects. Unfortunately, Postgres has zero support for this kind of objects. Session variables has not any session specific data in the catalog. This is much more better (and easier) than for global temporary tables. The data of session variables are in session memory (only). This is big difference from tables. There the data are stored in pages, and pages are stored in some files. The files are important - any cached data can be thrown (and they are frequently thrown). There are two kinds of new issues (related to session variables).&lt;br /&gt;
&lt;br /&gt;
* possibility to use invalid value (stored in valid memory)&lt;br /&gt;
* too early or too delayed memory cleaning&lt;br /&gt;
&lt;br /&gt;
Any session variable is identified by name (and possibly by oid from `pg_variable`). Any variable can use own data type. Values stored in memory are stored in native binary format, and without reassignment, the value is not changed. Important note - the one oid can be assigned more times. oids are unique in just one moment, but there is not a protection so one oid will not be used more times for different objects. There is a real possibility of following issue:&lt;br /&gt;
&lt;br /&gt;
1. session A: CREATE VARIABLE v AS t;&lt;br /&gt;
2. session B: LET v = t &#039;value&#039;;&lt;br /&gt;
3. session A: DROP VARIABLE v;&lt;br /&gt;
4. session B: no activity&lt;br /&gt;
5. session A: CREATE VARIABLE v AS nt; -- theoretically I can get same varid and typid like in first step, although t != nt&lt;br /&gt;
6. session B: SELECT v; -- crash&lt;br /&gt;
&lt;br /&gt;
Before any usage of stored value, we need to do really identity check and varid and typid is not enough. But we can store to variable their create lsn - and this number is unique for all time database (catalog) live. So the necessary check before usage of stored value is just check if value.varid = catalog.varid and value.createlsn = catalog.createlsn. The ALTER VARIABLE ALTER TYPE is not allowed. The dependency mechanism ensure so value type should be identical with catalog type.&lt;br /&gt;
&lt;br /&gt;
= Variabes in PostgreSQL (as of v18) =&lt;br /&gt;
&lt;br /&gt;
PostgreSQL provides relatively advanced client-side session variables in psql --- these variables use the `:` prefix. The implementation is pretty much straightforward: `psql`&#039;s parser reads tokens from input and, when it encounters a token related to a variable, replaces it with the variable&#039;s value. The syntax supports literal and identifier escaping, and variables can be used as arguments of the `\bind` command.&lt;br /&gt;
&lt;br /&gt;
`psql` variables are typeless client-side variables, available only inside `psql` or `pgbench`.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:40:18) postgres=# \set myvar ahoj&lt;br /&gt;
    (2025-09-25 09:40:33) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:14) postgres=# select :&#039;myvar&#039;;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:24) postgres=# select $1 \bind :myvar \g&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
The `\set` command allows only simple string concatenation, but it also supports execution of shell commands. When a query is executed with the `\gset` command, its result can be stored in a `psql` variable.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:41:40) postgres=# \set ctime `date`&lt;br /&gt;
    (2025-09-25 09:43:55) postgres=# \echo :ctime&lt;br /&gt;
    Thu Sep 25 09:43:55 CEST 2025&lt;br /&gt;
&lt;br /&gt;
The `\set` command is fairly limited, making complex operations unreadable or non-portable.&lt;br /&gt;
&lt;br /&gt;
This mechanism is good enough for writing tests, but it is not generally available (most users do not need it), and it is not accessible from the server side (e.g., from stored procedures). There is no simple way to access `psql` variables from an anonymous block --- a proxy custom GUC variable must be used:&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:58:38) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    (2025-09-25 09:58:43) postgres=# select set_config(&#039;myvars.myvar&#039;, :&#039;myvar&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ ahoj       │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 10:00:27) postgres=# do $$ begin raise notice &#039;%&#039;, current_setting(&#039;myvars.myvar&#039;); end $$;&lt;br /&gt;
    NOTICE:  ahoj&lt;br /&gt;
    DO&lt;br /&gt;
&lt;br /&gt;
PostgreSQL also has configuration variables (GUC - Grand Unified Configuration). These are primarily designed for PostgreSQL and extension configuration, but they can be used as session variables. GUC variables can be set with the `SET` command or the `set_config` function, and read with `SHOW` or `current_setting`.&lt;br /&gt;
&lt;br /&gt;
GUC variables are meant for holding configuration parameters. When created through the internal API, they can be restricted to superusers, hidden from regular users, and assigned specific types (boolean, memory, number, interval, string, enum) with validation. This type system is separate from the SQL type system and is initialized before PostgreSQL’s SQL types.&lt;br /&gt;
&lt;br /&gt;
Variables created from SQL must be &amp;quot;custom&amp;quot; variables, using a prefix in their name --- these are always strings. Such custom GUCs are often used as a workaround for missing server-side session variables in PostgreSQL, but they are typeless, unprotected, and unsafe.&lt;br /&gt;
&lt;br /&gt;
A notable feature of PostgreSQL GUCs (built-in or custom) is that they can have different scopes: transaction, session, or function execution. They can also be assigned default values at specific events --- configuration loading, role login, database login, or function start. These features are useful for configuration, but problematic when GUCs are repurposed as session variables.&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
    RETURNS void AS $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, current_setting(&#039;my.x&#039;);&lt;br /&gt;
    END $$ LANGUAGE plpgsql SET my.x = 20;&lt;br /&gt;
    CREATE FUNCTION&lt;br /&gt;
    (2025-09-27 06:10:37) postgres=# SET my.x = 0; SELECT fx();&lt;br /&gt;
    SET&lt;br /&gt;
    NOTICE:  20&lt;br /&gt;
    ┌────┐&lt;br /&gt;
    │ fx │&lt;br /&gt;
    ╞════╡&lt;br /&gt;
    │    │&lt;br /&gt;
    └────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    (2025-09-27 06:10:39) postgres=# SHOW my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 0    │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
GUC variables are transactional, and this behavior cannot be disabled.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-27 06:17:31) postgres=# set my.x = 10;&lt;br /&gt;
    SET&lt;br /&gt;
    (2025-09-27 06:19:42) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:19:46) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, true);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:19:55) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:01) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:20:11) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:13) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:20:38) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:45) postgres=# rollback;&lt;br /&gt;
    ROLLBACK&lt;br /&gt;
    (2025-09-27 06:20:52) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:55) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:22:36) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:22:39) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:22:42) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
As a result, there is no reliable way to store details such as error information in custom GUCs.&lt;br /&gt;
&lt;br /&gt;
= Session Variables in Other Systems =&lt;br /&gt;
&lt;br /&gt;
This section surveys how session variables are implemented in several widely used database systems. Each subsection includes code examples to illustrate syntax, behaviour, and scope.&lt;br /&gt;
&lt;br /&gt;
== MySQL ==&lt;br /&gt;
&lt;br /&gt;
Session variables in MySQL have syntax similar to the better-known T-SQL variables (though other details are MySQL-specific). MySQL distinguishes two kinds of variables:&lt;br /&gt;
&lt;br /&gt;
* system variables - referenced using the `@@` prefix (or `@@scope.name`)&lt;br /&gt;
* user-defined variables - referenced using the `@` prefix&lt;br /&gt;
&lt;br /&gt;
System variables are modified with the `SET` statement. The `SET` statement accepts the modifiers `GLOBAL`, `SESSION`, or `PERSIST`. Some variables can only be set using the `GLOBAL` or `SESSION` modifier; when the variable name is specified with the `@@` prefix, it is not necessary to explicitly indicate the scope. System variables are defined by MySQL or by MySQL plugins.&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL some_config = 100;&lt;br /&gt;
    SET @@same_config = 100;&lt;br /&gt;
    &lt;br /&gt;
    SET SESSION sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@SESSION.sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
&lt;br /&gt;
Persistent values are stored in `mysqld-auto.cnf` (in the server data directory).&lt;br /&gt;
&lt;br /&gt;
Resetting system variables can be done by assigning DEFAULT or by copying from the GLOBAL namespace:&lt;br /&gt;
&lt;br /&gt;
    SET @@sql_mode = DEFAULT;&lt;br /&gt;
    SET @@sql_mode = @@GLOBAL.sql_mode&lt;br /&gt;
&lt;br /&gt;
PostgreSQL equivalents:&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL ; -- there is not similar command in Postgres&lt;br /&gt;
    SET SESSION ; -- SET&lt;br /&gt;
    SET PERSIST ; -- ALTER SYSTEM; SELECT pg_reload_conf();&lt;br /&gt;
    SELECT @@var; -- SELECT current_setting(&#039;var);&lt;br /&gt;
&lt;br /&gt;
User-defined variables are created by assignment:&lt;br /&gt;
&lt;br /&gt;
    SET @var = expr [, @var2 = expr [ ... ]];&lt;br /&gt;
&lt;br /&gt;
User-defined variables can only be of type integer, decimal, float, binary, non-binary string, or NULL. Casting between these types is implicit and hidden. User-defined variables cannot be used as table or column names.&lt;br /&gt;
&lt;br /&gt;
An advantage of the T-SQL-style syntax (with special prefixes) is that it provides a simple solution for avoiding identifier collisions. MySQL variables are convenient, and the user experience can be good for people already familiar with T-SQL (MSSQL or Sybase). On the other hand, prefix-based syntax can be confusing for users coming from systems other than MSSQL. The type system is perhaps too simple and can be unsafe. In general, the performance of MySQL (or MariaDB) stored procedures is not good, and stored procedures are therefore not frequently used. There is no protection against typographical errors. Unassigned user variables can be used without error (they default to NULL), which makes static checking of stored procedures impossible in this area.&lt;br /&gt;
&lt;br /&gt;
There is no way to restrict access to user-defined variables in MySQL. They cannot be protected against unintended usage. The only possible safeguard is a naming convention, since all user-defined variables share a single namespace.&lt;br /&gt;
&lt;br /&gt;
== Oracle ==&lt;br /&gt;
&lt;br /&gt;
Oracle PL/SQL allows the use of package variables. PL/SQL is based on the Ada language, and package variables act as &amp;quot;global&amp;quot; variables. They are not directly visible from SQL, but Oracle provides a reduced syntax for functions without arguments, so you typically write a wrapper:&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE PACKAGE my_package&lt;br /&gt;
    AS&lt;br /&gt;
      FUNCTION get_a RETURN NUMBER;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    /&lt;br /&gt;
    &lt;br /&gt;
    CREATE OR REPLACE PACKAGE BODY my_package&lt;br /&gt;
    AS&lt;br /&gt;
      a  NUMBER(20);&lt;br /&gt;
      &lt;br /&gt;
      FUNCTION get_a&lt;br /&gt;
      RETURN NUMBER&lt;br /&gt;
      IS&lt;br /&gt;
      BEGIN&lt;br /&gt;
        RETURN a;&lt;br /&gt;
      END get_a;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    &lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
Inside SQL, SQL identifiers take precedence. Inside non-SQL commands (such as CALL or PL/SQL blocks), package identifiers take precedence.&lt;br /&gt;
&lt;br /&gt;
Oracle allows both syntaxes for calling a function with zero arguments:&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a() FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
This reduces the risk of naming collisions. Package variables persist for the duration of a session.&lt;br /&gt;
&lt;br /&gt;
Another possibility is to use variables in SQL*Plus (similar to `psql` variables, but with the ability to define the type on the server side).&lt;br /&gt;
&lt;br /&gt;
A variable is declared with the `VARIABLE` command and can be accessed in the session using the `:varname` syntax (a prior declaration step may be optional):&lt;br /&gt;
&lt;br /&gt;
    VARIABLE bv_variable_name VARCHAR2(30)&lt;br /&gt;
    BEGIN&lt;br /&gt;
      :bv_variable_name := &#039;Some Value&#039;;&lt;br /&gt;
    END;&lt;br /&gt;
    &lt;br /&gt;
    SELECT column_name&lt;br /&gt;
      FROM   table_name&lt;br /&gt;
     WHERE  column_name = :bv_variable_name;&lt;br /&gt;
&lt;br /&gt;
== DB2 ==&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;user-defined global variables&amp;quot; in DB2 are similar to the proposed PostgreSQL feature. The main difference is in the access rights: DB2 uses `READ` and `WRITE`, while PostgreSQL uses `SELECT` and `UPDATE`. Because PostgreSQL already uses the `SET` command for GUCs, the `LET` command was introduced in the proposal (DB2 uses `SET`).&lt;br /&gt;
&lt;br /&gt;
Variables are visible in all sessions, but their values are private to each session. Variables are not transactional. Their usage is broader than in the proposal: they can be changed by `SET`, `SELECT INTO`, or used as OUT parameters of procedures. The search path (or a similar mechanism) applies to variables as well, but variables have lower priority than tables or columns.&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE myCounter INT DEFAULT 01;&lt;br /&gt;
    SELECT EMPNO, LASTNAME, CASE WHEN myCounter = 1 THEN SALARY ELSE NULL END&lt;br /&gt;
    FROM EMPLOYEE WHERE WORKDEPT = &#039;A00&#039;;&lt;br /&gt;
    SET myCounter = 29;&lt;br /&gt;
&lt;br /&gt;
There also appear to be other kinds of variables, accessed using the function `GETVARIABLE(&#039;name&#039;, &#039;default&#039;)`. These are very similar to PostgreSQL GUCs and the `current_setting` function. Such variables can be set via the connection string, are of type `VARCHAR`, and up to 10 values are allowed. Built-in session variables (configuration) can also be accessed using `GETVARIABLE`.&lt;br /&gt;
&lt;br /&gt;
== MSSQL (T-SQL) ==&lt;br /&gt;
&lt;br /&gt;
MSSQL provides two types of variables:&lt;br /&gt;
&lt;br /&gt;
* Global variables — accessed with the `@@varname` syntax. These are similar to GUCs and provide state information such as `@@ERROR`, `@@ROWCOUNT`, or `@@IDENTITY`. &lt;br /&gt;
* Local variables — accessed with the `@varname` syntax. These must be declared with the `DECLARE` command before use. Their scope is limited to the batch, procedure, or function where they are executed.&lt;br /&gt;
&lt;br /&gt;
    DECLARE @TestVariable AS VARCHAR(100)&lt;br /&gt;
    SET @TestVariable = &#039;Think Green&#039;&lt;br /&gt;
    GO&lt;br /&gt;
    PRINT @TestVariable&lt;br /&gt;
&lt;br /&gt;
This script fails because `PRINT` is executed in a different batch. Therefore, it seems MSSQL does not truly support session variables.&lt;br /&gt;
&lt;br /&gt;
There are also mechanisms similar to PostgreSQL&#039;s custom GUCs and the use of `current_setting` and `set_config` functions. In general, MSSQL is fairly primitive in this area.&lt;br /&gt;
&lt;br /&gt;
    EXEC sp_set_session_context &#039;user_id&#039;, 4;&lt;br /&gt;
    SELECT SESSION_CONTEXT(N&#039;user_id&#039;);&lt;br /&gt;
&lt;br /&gt;
= ToDo =&lt;br /&gt;
&lt;br /&gt;
* SECURITY LABEL support — enhance the `dummy_seclabel` module and the `sepgsql` extension. Update PostgreSQL documentation and ensure `SecLabelSupportsObjectType` includes session variables.&lt;/div&gt;</summary>
		<author><name>Okbobcz</name></author>
	</entry>
	<entry>
		<id>https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41865</id>
		<title>Implementation of declarative catalog session variables</title>
		<link rel="alternate" type="text/html" href="https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41865"/>
		<updated>2025-10-02T06:21:14Z</updated>

		<summary type="html">&lt;p&gt;Okbobcz: /* Assign command - LET */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Session variables are database objects that can hold a value across multiple queries inside a session. The design of session variables varies widely, and many databases implement more than one type of session variable.&lt;br /&gt;
&lt;br /&gt;
The SQL/PSM standard introduces modules and module-level variables. However, this part of the standard has not been implemented in any widely used product. As a result, each database system has developed its own proprietary design, syntax, and feature set.&lt;br /&gt;
&lt;br /&gt;
Session variables are often part of the stored procedure environment, but there are exceptions --- for example, MySQL session variables or client-side session variables in tools like psql. In most systems, session variables do not participate in transactions: once set, their value persists until explicitly changed, even if the surrounding transaction is rolled back. Conceptually, they behave much like variables in general-purpose programming languages.&lt;br /&gt;
&lt;br /&gt;
Because there is no common design, comparisons across systems are difficult. Each implementation has its own advantages and disadvantages, and in some cases a single system supports more than one kind of session variable.&lt;br /&gt;
&lt;br /&gt;
Kinds of session variables:&lt;br /&gt;
* client side (psql, pgadmin, ...) - mostly similar to shell variables (based on string operations)&lt;br /&gt;
* server side&lt;br /&gt;
** declarative&lt;br /&gt;
*** created only for batch (T-SQL)&lt;br /&gt;
*** created as an database object (or part of database object) (Oracle, DB2)&lt;br /&gt;
** created by usage (MySQL)&lt;br /&gt;
** with priprietary syntax for identifiers (MySQL, T-SQL)&lt;br /&gt;
** with ANSI SQL syntax for identifiers (Oracle, DB2)&lt;br /&gt;
&lt;br /&gt;
Possible usage of session variables:&lt;br /&gt;
&lt;br /&gt;
* usage of session variables as global variables in PL (tracing, debugging, hacking),&lt;br /&gt;
* holding configuration (for PL),&lt;br /&gt;
* holding some more used data in interactive session,&lt;br /&gt;
* holding credentials for some RLS and data securing,&lt;br /&gt;
* helping with migration from others systems (mainly from Oracle),&lt;br /&gt;
* allow anonymous block parametrization (only postgres).&lt;br /&gt;
* session variables are writeable on read only hot standby (postgres)&lt;br /&gt;
&lt;br /&gt;
Session variables holds a values like temporary tables. What are differences between temporary tables and variables?:&lt;br /&gt;
&lt;br /&gt;
* variables holds a values, tables holds a set of rows,&lt;br /&gt;
* syntax for usage variables is shorter (more friendly for interactive work), access (read, write) is faster,&lt;br /&gt;
* the content of session variables is generally non transactional, the content of temporary tables is transactional,&lt;br /&gt;
* the content of session variables is just one value or NULL (no MVCC, no VACUUM),&lt;br /&gt;
* temporary tables are tables still (transactional, MVCC), update is expensive, under one transaction repeated updates makes lot of dead tuples,&lt;br /&gt;
* temporary tables are not cleaned by autovacuum (and inside function is not possible to execute VACUUM), &lt;br /&gt;
* Postgres doesn&#039;t support global temporary tables, so usage of temporary table for just few values or value is very expensive (catalogue bloat).&lt;br /&gt;
&lt;br /&gt;
= SQL/PSM =&lt;br /&gt;
&lt;br /&gt;
The SQL standard introduces the concept of modules, which can be associated with schemas (partially). Variables in modules behave like PL/pgSQL variables but are only local. The only objects allowed at the module level are temporary tables, which can be accessed only from routines assigned to that module. Modules can encapsulate private objects (functions, procedure, temp tables), that are not visible outside the module. Inside the module, a  private objects shadows other objects. What I know the modules (and generally SQL/PSM) doesn&#039;t cover a deployment of code, so there is not similarity with PostgreSQL extensions. SQL/PSM Modules are modules like modules from modular languages like Modula2 or Oberon. There is some similarity with Postgres schemas (both are containers) with significant differences (there is not a concept of SEARCH_PATH (on PSM side) or there is not a concept of private or public objects (on Postgres schema side)).&lt;br /&gt;
&lt;br /&gt;
SQL/PSM variables are not transactional (they are used exactly like in PL/pgSQL). The precedence between variables and columns is not specified.&lt;br /&gt;
&lt;br /&gt;
= Implementation Challenges =&lt;br /&gt;
&lt;br /&gt;
* Possible collisions between session variables and other database objects.&lt;br /&gt;
* Memory cleanup after dropping a session variable, especially when DDL operations can be reverted (Postgres has not support for global temp objects).&lt;br /&gt;
* Memory invalidation: if a variable is dropped by one session, other sessions should not continue using it (Postgres has not support for global temp objects).&lt;br /&gt;
&lt;br /&gt;
= Proposed Implementations =&lt;br /&gt;
&lt;br /&gt;
It is clear that no single design is perfect for all use cases. MySQL’s approach is convenient and useful for interactive work, but it cannot be used for storing sensitive data and is considered unsafe. PostgreSQL GUCs are excellent for configuration, but they are not well suited for interactive use and are very limited when used as global variables in PL code.&lt;br /&gt;
&lt;br /&gt;
Because of these trade-offs, no single design is sufficient. In practice, having multiple designs within a single system could support a broader range of use cases more effectively.&lt;br /&gt;
&lt;br /&gt;
== Design session variables as database global temporary typed objects with ANSI SQL syntax for identifiers ==&lt;br /&gt;
&lt;br /&gt;
Author: Pavel Stehule &amp;lt;pavel.stehule@gmail.com&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I searched for a solution that could work well with the specific PostgreSQL environment:&lt;br /&gt;
&lt;br /&gt;
* PostgreSQL has more than one widely used stored procedure language: PL/pgSQL, PL/Python, or PL/Perl.&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL is a reduced version of PL/SQL, and PL/SQL is a modified ADA language. I am pretty happy with the current complexity of PL/pgSQL --- it is a domain-specific language that works well. It is very simple and easy to learn. PL/pgSQL has no packages or modules; instead PostgreSQL schemas are used. But schemas are database objects in their own right, independent of PL.&lt;br /&gt;
&lt;br /&gt;
* PostgreSQL already has a code container --- the schema. I don&#039;t want to introduce another container object (modules), as this would overlap heavily with schemas. Implementing modules for PL/pgSQL could be useful, but doing it properly would be quite complex. The main problem is the concept of public and private objects --- PostgreSQL does not have this. Don&#039;t forget: every PL/pgSQL expression is a SQL expression, so public/private visibility would first need to be implemented internally in PostgreSQL. That would be redundant (and possibly in conflict) with SEARCH_PATH.&lt;br /&gt;
&lt;br /&gt;
So I wanted a design that supports multiple PL languages, does not add complexity to the relatively simple PL/pgSQL, and does not duplicate functionality already available.  &lt;br /&gt;
&lt;br /&gt;
I wrote a larger application in PL/pgSQL. To maintain it, I created plpgsql_lint (later renamed plpgsql_check). So I am always looking for solutions that do not block static code analysis. Static analysis requires persistent objects, which naturally leads to designing session variables as database objects (with their own system catalog).  &lt;br /&gt;
When session variables are database objects, ACLs can be applied naturally. Oracle package variables are well protected by package scope. PostgreSQL, however, has no schema scope --- schema locality is controlled by the SEARCH_PATH GUC, and introducing another mechanism would be messy. But with ACLs, variable contents can also be secured:&lt;br /&gt;
&lt;br /&gt;
    CREATE TEMP VARIABLE x AS int;&lt;br /&gt;
     &lt;br /&gt;
    LET x = 10;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(x);&lt;br /&gt;
    END;&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    SELECT VARIABLE(x);&lt;br /&gt;
&lt;br /&gt;
==== Collisions between column and variable identifiers ====&lt;br /&gt;
&lt;br /&gt;
There is a risk of identifier collisions between variables and columns. With ANSI SQL syntax for session variable identifiers, the problem is how to distinguish variables from columns. This is a known issue in PL/pgSQL and PL/SQL and can be solved either by prioritization or by prohibiting collisions.  &lt;br /&gt;
&lt;br /&gt;
Prohibiting collisions has largely solved this problem in PL/pgSQL. Unfortunately, session variables are global objects, so collisions can occur in any query. A poorly named variable could break queries unexpectedly. Prioritization also has problems --- a variable or a column could be used silently when the other was intended.&lt;br /&gt;
&lt;br /&gt;
    CREATE TABLE tab(a int);&lt;br /&gt;
    CREATE VARIABLE a AS int;&lt;br /&gt;
    &lt;br /&gt;
    SELECT a FROM tab; -- with disallowed collisions, this query fails when a variable named &amp;quot;a&amp;quot; exists&lt;br /&gt;
    &lt;br /&gt;
    LET a = 1;&lt;br /&gt;
    DELETE FROM tab WHERE a = 1; -- with higher priority for variables, all rows could be deleted&lt;br /&gt;
    SELECT a FROM tab; -- with higher priority for columns, the variable is ignored&lt;br /&gt;
&lt;br /&gt;
Even with collision prohibition, mistakes are still possible:&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE _id AS int;&lt;br /&gt;
    LET _id = 10;&lt;br /&gt;
    CREATE TABLE foo(id int);&lt;br /&gt;
    DELETE FROM foo WHERE _id = 10; -- just a typo, but all rows are deleted&lt;br /&gt;
&lt;br /&gt;
These problems are not new. Developers in PL/pgSQL or PL/SQL know them, but the scope of risk is normally limited to stored procedures. After many discussions and designs, I introduced &amp;quot;variable fences&amp;quot; (the syntax VARIABLE(varname)). Inside any query, variables can be used only inside a variable fence. This completely solves collisions and reduces the chance of human error. Currently, fences are required everywhere in queries and expressions. In the future, they might be made optional inside PL/pgSQL, to reduce overhead when porting Oracle applications. This could be controlled by a new plpgsql.extra_checks setting.&lt;br /&gt;
&lt;br /&gt;
A variable fence can collide with a user-defined function named `variable`. This is similar to keyword conflicts such as CUBE. It can be resolved by schema-qualifying or quoting:&lt;br /&gt;
&lt;br /&gt;
    SELECT public.variable(...)&lt;br /&gt;
    SELECT &amp;quot;variable&amp;quot;(...)&lt;br /&gt;
&lt;br /&gt;
Why not use T-SQL (MSSQL, MySQL) syntax for session variables? There are several reasons – some objective, one subjective:&lt;br /&gt;
&lt;br /&gt;
* There is a collision with custom operators. Operators `@` and `@@` are already used, and `@@` is common:&lt;br /&gt;
&lt;br /&gt;
    (2025-09-28 06:57:30) postgres=# create table tab(a int);&lt;br /&gt;
    CREATE TABLE&lt;br /&gt;
    (2025-09-28 06:57:42) postgres=# insert into tab values(10);&lt;br /&gt;
    INSERT 0 1&lt;br /&gt;
    (2025-09-28 06:57:50) postgres=# select @a from tab;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │       10 │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
We could safely use only `:` as a prefix, but that would conflict with psql variables. Similarly, `$` would conflict with query parameters.&lt;br /&gt;
&lt;br /&gt;
* T-SQL variable names look strange in PostgreSQL (in queries and in PL/pgSQL). PostgreSQL uses only ANSI/SQL identifiers, and PL/pgSQL follows the same rules. PostgreSQL is consistent here, and I see little motivation to introduce a new, non-standard syntax (especially since it could cause compatibility issues).&lt;br /&gt;
&lt;br /&gt;
Note: There was also a proposal to use table syntax for variables (similar to T-SQL in-memory table variables, but restricted to a single row). This is very effective for avoiding collisions, but I dislike it. It complicates simple expressions, adds inconsistency, and looks odd for scalar variables (though it could be useful for composite variables). This design doesn&#039;t solve all possible problems - variables can be shadowed by tables still, because we have SEARCH_PATH (but this is known issue):&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(var); -- variable is not a table&lt;br /&gt;
    END&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      -- variable as a table (I don&#039;t like it)&lt;br /&gt;
      -- can we use DML? How many rows can it hold?&lt;br /&gt;
      -- is the value a row or not?&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, (SELECT var FROM var);&lt;br /&gt;
    END&lt;br /&gt;
    $$&lt;br /&gt;
&lt;br /&gt;
I am not against introducing in-memory tables --- or more correctly, global temporary tables. A well-designed implementation could be very useful. But this is a different feature and should be designed separately.  &lt;br /&gt;
&lt;br /&gt;
There are also performance considerations: PL/pgSQL can use simple expression evaluation when no tables are referenced. This optimization would need to change, which touches sensitive code. It would also be inconsistent.  &lt;br /&gt;
&lt;br /&gt;
I would like global temporary tables (which PostgreSQL currently lacks). Tables should behave like tables, and variables should behave like variables. The natural mental model for session variables comes from variables in PL languages. PostgreSQL internally supports *transition tables*, and I can imagine allowing these outside of triggers. That would be valid, but again, it is a different feature.  &lt;br /&gt;
&lt;br /&gt;
Implementing declared tables inside PL/pgSQL would be simpler from a high-level design perspective (interaction between SQL tables and PL/pgSQL is already well defined), but difficult from a low-level perspective (statistics, optimizer data, etc. --- the same issues as with global temporary tables, unless we add fake proxy local temp tables).&lt;br /&gt;
&lt;br /&gt;
   CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
   AS $$&lt;br /&gt;
   -- not implemented yet, not part of this proposal&lt;br /&gt;
   DECLARE t TABLE(a int, b int); -- this table is invisible outside fx&lt;br /&gt;
   BEGIN&lt;br /&gt;
     INSERT INTO t VALUES(10,20);&lt;br /&gt;
     INSERT INTO t VALUES(30,40);&lt;br /&gt;
     RAISE NOTICE &#039;%&#039;, (SELECT count(*) FROM t);&lt;br /&gt;
   END;&lt;br /&gt;
   $$ LANGUAGE plpgsql;&lt;br /&gt;
&lt;br /&gt;
Some data languages define relational variables, but this concept has no overlap with the common understanding of session variables.&lt;br /&gt;
&lt;br /&gt;
Inside PL/pgSQL we can use a possibility to configure a parser, and can be easy to allow a usage of session variables without variable fence (like common plpgsql variable) in expressions. It is not supported yet, but there should not be implementation problems. Outside PL, the variable fences can be optional, when there is not risk of collisions (the expression doesn&#039;t contains subselect) (not implemented yet).&lt;br /&gt;
&lt;br /&gt;
==== Assign command - LET ====&lt;br /&gt;
&lt;br /&gt;
The value of session variable can be assigned by dedicated PL assign command or by `SELECT INTO` construct. When special command is prefixed by some a keyword, then the keyword `SET` is usually used (MySQL, MSSQL, SQL/PSM, DB2). Command `SET` can be enhanced to support `a_expr` on parser level in Postgres too. It requires partial rewriting of current implementation of `SET` command, but there is not technical issues.&lt;br /&gt;
&lt;br /&gt;
Unfortunately, there can be collisions between GUC and session variables. Currently GUC are not declared, and they are not joined to any schema (prefix for custom GUC can be just any not empty string), and access to GUC is not controlled by SEARCH_PATH. We can use same solution like usage session variables in expression - variable fence - but in this case, the risk is every time, and there is not a possibility to usage of variable fences can be optional (for some cases) in future. Some languages uses for a assignment the keyword `LET`. I introduced this keyword as a solution for 100% fix of collisions between GUC and session variables identifiers. &lt;br /&gt;
&lt;br /&gt;
I can imagine the assignment to session variable inside PL by usual PL assignment command (not implemented yet). This can be benefit for translation from PL/SQL (Oracle), and it can be very natural syntax, when session variable is used like PL global variable. Probably (not tested), there are not technical issues for implementation of this. But there is different significant issue. Currently, only PL/pgSQL variables can be target of PL/pgSQL assign statement, and these variables should be declared before usage. If we allow session variables there, then a) we cannot to do this check, or b) there will be new strong dependency from PL functions to session variables - like on types now.&lt;br /&gt;
&lt;br /&gt;
    (2025-10-02 07:42:01) postgres=# CREATE OR REPLACE FUNCTION fx() &lt;br /&gt;
    returns void as $$&lt;br /&gt;
    begin&lt;br /&gt;
      declare var mydomain;&lt;br /&gt;
    begin&lt;br /&gt;
      var := 10;&lt;br /&gt;
    end&lt;br /&gt;
    $$ language plpgsql;&lt;br /&gt;
    ERROR:  type &amp;quot;mydomain&amp;quot; does not exist&lt;br /&gt;
    LINE 4:   declare var mydomain;&lt;br /&gt;
                          ^&lt;br /&gt;
&lt;br /&gt;
The strong dependency today is not problem probably (because plan cache can be correctly invalidated), but there can be some new questions about temporary variables. Again as a solution I proposed a `LET` command, and we don&#039;t need to touch to PL/pgSQL assign statement.&lt;br /&gt;
&lt;br /&gt;
The significant question is performance generally and performance when LET is used inside PL/pgSQL.&lt;br /&gt;
&lt;br /&gt;
The command `LET` is PostgreSQL utility command. Against history, the utility command can be prepared (as `CREATE TABLE AS SELECT`). There are patches that implements `PREPARE` and `EXECUTE` for `LET` command (these patches are not in reduced patch set). Although plan cache can be used, the performance against PL/pgSQL assign can be slower - from two reasons:&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL allows simple expression evaluation (this is supported for `LET` command too in enhanced patch set),&lt;br /&gt;
* PL/pgSQL variables are stored in a array (and indexed by int offset), session variables are stored in hash table, and they are accessed by hash search (and this slower). This slowdown is visible only in worst case benchmarks like:&lt;br /&gt;
&lt;br /&gt;
   DO $$ declare locvar int DEFAULT 0; begin for i in 1..1000000 loop locvar := locvar + 1; end loop; end $$ &lt;br /&gt;
   DO $$ begin for i in 1..100000 loop LET sesvar := sesvar + 1; end loop; end $$&lt;br /&gt;
&lt;br /&gt;
The implementation of `LET` in reduced patch set, is significantly slower, because the simple expression evaluation is not used (it is implemented, but not in reduced patch set). Although there is significant slowdown, the execution is still significantly faster than a execution of any query that touch to real table (it should not be a problem for real usage).&lt;br /&gt;
&lt;br /&gt;
= Variabes in PostgreSQL (as of v18) =&lt;br /&gt;
&lt;br /&gt;
PostgreSQL provides relatively advanced client-side session variables in psql --- these variables use the `:` prefix. The implementation is pretty much straightforward: `psql`&#039;s parser reads tokens from input and, when it encounters a token related to a variable, replaces it with the variable&#039;s value. The syntax supports literal and identifier escaping, and variables can be used as arguments of the `\bind` command.&lt;br /&gt;
&lt;br /&gt;
`psql` variables are typeless client-side variables, available only inside `psql` or `pgbench`.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:40:18) postgres=# \set myvar ahoj&lt;br /&gt;
    (2025-09-25 09:40:33) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:14) postgres=# select :&#039;myvar&#039;;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:24) postgres=# select $1 \bind :myvar \g&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
The `\set` command allows only simple string concatenation, but it also supports execution of shell commands. When a query is executed with the `\gset` command, its result can be stored in a `psql` variable.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:41:40) postgres=# \set ctime `date`&lt;br /&gt;
    (2025-09-25 09:43:55) postgres=# \echo :ctime&lt;br /&gt;
    Thu Sep 25 09:43:55 CEST 2025&lt;br /&gt;
&lt;br /&gt;
The `\set` command is fairly limited, making complex operations unreadable or non-portable.&lt;br /&gt;
&lt;br /&gt;
This mechanism is good enough for writing tests, but it is not generally available (most users do not need it), and it is not accessible from the server side (e.g., from stored procedures). There is no simple way to access `psql` variables from an anonymous block --- a proxy custom GUC variable must be used:&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:58:38) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    (2025-09-25 09:58:43) postgres=# select set_config(&#039;myvars.myvar&#039;, :&#039;myvar&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ ahoj       │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 10:00:27) postgres=# do $$ begin raise notice &#039;%&#039;, current_setting(&#039;myvars.myvar&#039;); end $$;&lt;br /&gt;
    NOTICE:  ahoj&lt;br /&gt;
    DO&lt;br /&gt;
&lt;br /&gt;
PostgreSQL also has configuration variables (GUC - Grand Unified Configuration). These are primarily designed for PostgreSQL and extension configuration, but they can be used as session variables. GUC variables can be set with the `SET` command or the `set_config` function, and read with `SHOW` or `current_setting`.&lt;br /&gt;
&lt;br /&gt;
GUC variables are meant for holding configuration parameters. When created through the internal API, they can be restricted to superusers, hidden from regular users, and assigned specific types (boolean, memory, number, interval, string, enum) with validation. This type system is separate from the SQL type system and is initialized before PostgreSQL’s SQL types.&lt;br /&gt;
&lt;br /&gt;
Variables created from SQL must be &amp;quot;custom&amp;quot; variables, using a prefix in their name --- these are always strings. Such custom GUCs are often used as a workaround for missing server-side session variables in PostgreSQL, but they are typeless, unprotected, and unsafe.&lt;br /&gt;
&lt;br /&gt;
A notable feature of PostgreSQL GUCs (built-in or custom) is that they can have different scopes: transaction, session, or function execution. They can also be assigned default values at specific events --- configuration loading, role login, database login, or function start. These features are useful for configuration, but problematic when GUCs are repurposed as session variables.&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
    RETURNS void AS $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, current_setting(&#039;my.x&#039;);&lt;br /&gt;
    END $$ LANGUAGE plpgsql SET my.x = 20;&lt;br /&gt;
    CREATE FUNCTION&lt;br /&gt;
    (2025-09-27 06:10:37) postgres=# SET my.x = 0; SELECT fx();&lt;br /&gt;
    SET&lt;br /&gt;
    NOTICE:  20&lt;br /&gt;
    ┌────┐&lt;br /&gt;
    │ fx │&lt;br /&gt;
    ╞════╡&lt;br /&gt;
    │    │&lt;br /&gt;
    └────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    (2025-09-27 06:10:39) postgres=# SHOW my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 0    │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
GUC variables are transactional, and this behavior cannot be disabled.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-27 06:17:31) postgres=# set my.x = 10;&lt;br /&gt;
    SET&lt;br /&gt;
    (2025-09-27 06:19:42) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:19:46) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, true);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:19:55) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:01) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:20:11) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:13) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:20:38) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:45) postgres=# rollback;&lt;br /&gt;
    ROLLBACK&lt;br /&gt;
    (2025-09-27 06:20:52) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:55) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:22:36) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:22:39) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:22:42) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
As a result, there is no reliable way to store details such as error information in custom GUCs.&lt;br /&gt;
&lt;br /&gt;
= Session Variables in Other Systems =&lt;br /&gt;
&lt;br /&gt;
This section surveys how session variables are implemented in several widely used database systems. Each subsection includes code examples to illustrate syntax, behaviour, and scope.&lt;br /&gt;
&lt;br /&gt;
== MySQL ==&lt;br /&gt;
&lt;br /&gt;
Session variables in MySQL have syntax similar to the better-known T-SQL variables (though other details are MySQL-specific). MySQL distinguishes two kinds of variables:&lt;br /&gt;
&lt;br /&gt;
* system variables - referenced using the `@@` prefix (or `@@scope.name`)&lt;br /&gt;
* user-defined variables - referenced using the `@` prefix&lt;br /&gt;
&lt;br /&gt;
System variables are modified with the `SET` statement. The `SET` statement accepts the modifiers `GLOBAL`, `SESSION`, or `PERSIST`. Some variables can only be set using the `GLOBAL` or `SESSION` modifier; when the variable name is specified with the `@@` prefix, it is not necessary to explicitly indicate the scope. System variables are defined by MySQL or by MySQL plugins.&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL some_config = 100;&lt;br /&gt;
    SET @@same_config = 100;&lt;br /&gt;
    &lt;br /&gt;
    SET SESSION sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@SESSION.sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
&lt;br /&gt;
Persistent values are stored in `mysqld-auto.cnf` (in the server data directory).&lt;br /&gt;
&lt;br /&gt;
Resetting system variables can be done by assigning DEFAULT or by copying from the GLOBAL namespace:&lt;br /&gt;
&lt;br /&gt;
    SET @@sql_mode = DEFAULT;&lt;br /&gt;
    SET @@sql_mode = @@GLOBAL.sql_mode&lt;br /&gt;
&lt;br /&gt;
PostgreSQL equivalents:&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL ; -- there is not similar command in Postgres&lt;br /&gt;
    SET SESSION ; -- SET&lt;br /&gt;
    SET PERSIST ; -- ALTER SYSTEM; SELECT pg_reload_conf();&lt;br /&gt;
    SELECT @@var; -- SELECT current_setting(&#039;var);&lt;br /&gt;
&lt;br /&gt;
User-defined variables are created by assignment:&lt;br /&gt;
&lt;br /&gt;
    SET @var = expr [, @var2 = expr [ ... ]];&lt;br /&gt;
&lt;br /&gt;
User-defined variables can only be of type integer, decimal, float, binary, non-binary string, or NULL. Casting between these types is implicit and hidden. User-defined variables cannot be used as table or column names.&lt;br /&gt;
&lt;br /&gt;
An advantage of the T-SQL-style syntax (with special prefixes) is that it provides a simple solution for avoiding identifier collisions. MySQL variables are convenient, and the user experience can be good for people already familiar with T-SQL (MSSQL or Sybase). On the other hand, prefix-based syntax can be confusing for users coming from systems other than MSSQL. The type system is perhaps too simple and can be unsafe. In general, the performance of MySQL (or MariaDB) stored procedures is not good, and stored procedures are therefore not frequently used. There is no protection against typographical errors. Unassigned user variables can be used without error (they default to NULL), which makes static checking of stored procedures impossible in this area.&lt;br /&gt;
&lt;br /&gt;
There is no way to restrict access to user-defined variables in MySQL. They cannot be protected against unintended usage. The only possible safeguard is a naming convention, since all user-defined variables share a single namespace.&lt;br /&gt;
&lt;br /&gt;
== Oracle ==&lt;br /&gt;
&lt;br /&gt;
Oracle PL/SQL allows the use of package variables. PL/SQL is based on the Ada language, and package variables act as &amp;quot;global&amp;quot; variables. They are not directly visible from SQL, but Oracle provides a reduced syntax for functions without arguments, so you typically write a wrapper:&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE PACKAGE my_package&lt;br /&gt;
    AS&lt;br /&gt;
      FUNCTION get_a RETURN NUMBER;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    /&lt;br /&gt;
    &lt;br /&gt;
    CREATE OR REPLACE PACKAGE BODY my_package&lt;br /&gt;
    AS&lt;br /&gt;
      a  NUMBER(20);&lt;br /&gt;
      &lt;br /&gt;
      FUNCTION get_a&lt;br /&gt;
      RETURN NUMBER&lt;br /&gt;
      IS&lt;br /&gt;
      BEGIN&lt;br /&gt;
        RETURN a;&lt;br /&gt;
      END get_a;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    &lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
Inside SQL, SQL identifiers take precedence. Inside non-SQL commands (such as CALL or PL/SQL blocks), package identifiers take precedence.&lt;br /&gt;
&lt;br /&gt;
Oracle allows both syntaxes for calling a function with zero arguments:&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a() FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
This reduces the risk of naming collisions. Package variables persist for the duration of a session.&lt;br /&gt;
&lt;br /&gt;
Another possibility is to use variables in SQL*Plus (similar to `psql` variables, but with the ability to define the type on the server side).&lt;br /&gt;
&lt;br /&gt;
A variable is declared with the `VARIABLE` command and can be accessed in the session using the `:varname` syntax (a prior declaration step may be optional):&lt;br /&gt;
&lt;br /&gt;
    VARIABLE bv_variable_name VARCHAR2(30)&lt;br /&gt;
    BEGIN&lt;br /&gt;
      :bv_variable_name := &#039;Some Value&#039;;&lt;br /&gt;
    END;&lt;br /&gt;
    &lt;br /&gt;
    SELECT column_name&lt;br /&gt;
      FROM   table_name&lt;br /&gt;
     WHERE  column_name = :bv_variable_name;&lt;br /&gt;
&lt;br /&gt;
== DB2 ==&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;user-defined global variables&amp;quot; in DB2 are similar to the proposed PostgreSQL feature. The main difference is in the access rights: DB2 uses `READ` and `WRITE`, while PostgreSQL uses `SELECT` and `UPDATE`. Because PostgreSQL already uses the `SET` command for GUCs, the `LET` command was introduced in the proposal (DB2 uses `SET`).&lt;br /&gt;
&lt;br /&gt;
Variables are visible in all sessions, but their values are private to each session. Variables are not transactional. Their usage is broader than in the proposal: they can be changed by `SET`, `SELECT INTO`, or used as OUT parameters of procedures. The search path (or a similar mechanism) applies to variables as well, but variables have lower priority than tables or columns.&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE myCounter INT DEFAULT 01;&lt;br /&gt;
    SELECT EMPNO, LASTNAME, CASE WHEN myCounter = 1 THEN SALARY ELSE NULL END&lt;br /&gt;
    FROM EMPLOYEE WHERE WORKDEPT = &#039;A00&#039;;&lt;br /&gt;
    SET myCounter = 29;&lt;br /&gt;
&lt;br /&gt;
There also appear to be other kinds of variables, accessed using the function `GETVARIABLE(&#039;name&#039;, &#039;default&#039;)`. These are very similar to PostgreSQL GUCs and the `current_setting` function. Such variables can be set via the connection string, are of type `VARCHAR`, and up to 10 values are allowed. Built-in session variables (configuration) can also be accessed using `GETVARIABLE`.&lt;br /&gt;
&lt;br /&gt;
== MSSQL (T-SQL) ==&lt;br /&gt;
&lt;br /&gt;
MSSQL provides two types of variables:&lt;br /&gt;
&lt;br /&gt;
* Global variables — accessed with the `@@varname` syntax. These are similar to GUCs and provide state information such as `@@ERROR`, `@@ROWCOUNT`, or `@@IDENTITY`. &lt;br /&gt;
* Local variables — accessed with the `@varname` syntax. These must be declared with the `DECLARE` command before use. Their scope is limited to the batch, procedure, or function where they are executed.&lt;br /&gt;
&lt;br /&gt;
    DECLARE @TestVariable AS VARCHAR(100)&lt;br /&gt;
    SET @TestVariable = &#039;Think Green&#039;&lt;br /&gt;
    GO&lt;br /&gt;
    PRINT @TestVariable&lt;br /&gt;
&lt;br /&gt;
This script fails because `PRINT` is executed in a different batch. Therefore, it seems MSSQL does not truly support session variables.&lt;br /&gt;
&lt;br /&gt;
There are also mechanisms similar to PostgreSQL&#039;s custom GUCs and the use of `current_setting` and `set_config` functions. In general, MSSQL is fairly primitive in this area.&lt;br /&gt;
&lt;br /&gt;
    EXEC sp_set_session_context &#039;user_id&#039;, 4;&lt;br /&gt;
    SELECT SESSION_CONTEXT(N&#039;user_id&#039;);&lt;br /&gt;
&lt;br /&gt;
= ToDo =&lt;br /&gt;
&lt;br /&gt;
* SECURITY LABEL support — enhance the `dummy_seclabel` module and the `sepgsql` extension. Update PostgreSQL documentation and ensure `SecLabelSupportsObjectType` includes session variables.&lt;/div&gt;</summary>
		<author><name>Okbobcz</name></author>
	</entry>
	<entry>
		<id>https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41864</id>
		<title>Implementation of declarative catalog session variables</title>
		<link rel="alternate" type="text/html" href="https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41864"/>
		<updated>2025-10-02T05:56:31Z</updated>

		<summary type="html">&lt;p&gt;Okbobcz: /* Design session variables as database global temporary typed objects with ANSI SQL syntax for identifiers */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Session variables are database objects that can hold a value across multiple queries inside a session. The design of session variables varies widely, and many databases implement more than one type of session variable.&lt;br /&gt;
&lt;br /&gt;
The SQL/PSM standard introduces modules and module-level variables. However, this part of the standard has not been implemented in any widely used product. As a result, each database system has developed its own proprietary design, syntax, and feature set.&lt;br /&gt;
&lt;br /&gt;
Session variables are often part of the stored procedure environment, but there are exceptions --- for example, MySQL session variables or client-side session variables in tools like psql. In most systems, session variables do not participate in transactions: once set, their value persists until explicitly changed, even if the surrounding transaction is rolled back. Conceptually, they behave much like variables in general-purpose programming languages.&lt;br /&gt;
&lt;br /&gt;
Because there is no common design, comparisons across systems are difficult. Each implementation has its own advantages and disadvantages, and in some cases a single system supports more than one kind of session variable.&lt;br /&gt;
&lt;br /&gt;
Kinds of session variables:&lt;br /&gt;
* client side (psql, pgadmin, ...) - mostly similar to shell variables (based on string operations)&lt;br /&gt;
* server side&lt;br /&gt;
** declarative&lt;br /&gt;
*** created only for batch (T-SQL)&lt;br /&gt;
*** created as an database object (or part of database object) (Oracle, DB2)&lt;br /&gt;
** created by usage (MySQL)&lt;br /&gt;
** with priprietary syntax for identifiers (MySQL, T-SQL)&lt;br /&gt;
** with ANSI SQL syntax for identifiers (Oracle, DB2)&lt;br /&gt;
&lt;br /&gt;
Possible usage of session variables:&lt;br /&gt;
&lt;br /&gt;
* usage of session variables as global variables in PL (tracing, debugging, hacking),&lt;br /&gt;
* holding configuration (for PL),&lt;br /&gt;
* holding some more used data in interactive session,&lt;br /&gt;
* holding credentials for some RLS and data securing,&lt;br /&gt;
* helping with migration from others systems (mainly from Oracle),&lt;br /&gt;
* allow anonymous block parametrization (only postgres).&lt;br /&gt;
* session variables are writeable on read only hot standby (postgres)&lt;br /&gt;
&lt;br /&gt;
Session variables holds a values like temporary tables. What are differences between temporary tables and variables?:&lt;br /&gt;
&lt;br /&gt;
* variables holds a values, tables holds a set of rows,&lt;br /&gt;
* syntax for usage variables is shorter (more friendly for interactive work), access (read, write) is faster,&lt;br /&gt;
* the content of session variables is generally non transactional, the content of temporary tables is transactional,&lt;br /&gt;
* the content of session variables is just one value or NULL (no MVCC, no VACUUM),&lt;br /&gt;
* temporary tables are tables still (transactional, MVCC), update is expensive, under one transaction repeated updates makes lot of dead tuples,&lt;br /&gt;
* temporary tables are not cleaned by autovacuum (and inside function is not possible to execute VACUUM), &lt;br /&gt;
* Postgres doesn&#039;t support global temporary tables, so usage of temporary table for just few values or value is very expensive (catalogue bloat).&lt;br /&gt;
&lt;br /&gt;
= SQL/PSM =&lt;br /&gt;
&lt;br /&gt;
The SQL standard introduces the concept of modules, which can be associated with schemas (partially). Variables in modules behave like PL/pgSQL variables but are only local. The only objects allowed at the module level are temporary tables, which can be accessed only from routines assigned to that module. Modules can encapsulate private objects (functions, procedure, temp tables), that are not visible outside the module. Inside the module, a  private objects shadows other objects. What I know the modules (and generally SQL/PSM) doesn&#039;t cover a deployment of code, so there is not similarity with PostgreSQL extensions. SQL/PSM Modules are modules like modules from modular languages like Modula2 or Oberon. There is some similarity with Postgres schemas (both are containers) with significant differences (there is not a concept of SEARCH_PATH (on PSM side) or there is not a concept of private or public objects (on Postgres schema side)).&lt;br /&gt;
&lt;br /&gt;
SQL/PSM variables are not transactional (they are used exactly like in PL/pgSQL). The precedence between variables and columns is not specified.&lt;br /&gt;
&lt;br /&gt;
= Implementation Challenges =&lt;br /&gt;
&lt;br /&gt;
* Possible collisions between session variables and other database objects.&lt;br /&gt;
* Memory cleanup after dropping a session variable, especially when DDL operations can be reverted (Postgres has not support for global temp objects).&lt;br /&gt;
* Memory invalidation: if a variable is dropped by one session, other sessions should not continue using it (Postgres has not support for global temp objects).&lt;br /&gt;
&lt;br /&gt;
= Proposed Implementations =&lt;br /&gt;
&lt;br /&gt;
It is clear that no single design is perfect for all use cases. MySQL’s approach is convenient and useful for interactive work, but it cannot be used for storing sensitive data and is considered unsafe. PostgreSQL GUCs are excellent for configuration, but they are not well suited for interactive use and are very limited when used as global variables in PL code.&lt;br /&gt;
&lt;br /&gt;
Because of these trade-offs, no single design is sufficient. In practice, having multiple designs within a single system could support a broader range of use cases more effectively.&lt;br /&gt;
&lt;br /&gt;
== Design session variables as database global temporary typed objects with ANSI SQL syntax for identifiers ==&lt;br /&gt;
&lt;br /&gt;
Author: Pavel Stehule &amp;lt;pavel.stehule@gmail.com&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I searched for a solution that could work well with the specific PostgreSQL environment:&lt;br /&gt;
&lt;br /&gt;
* PostgreSQL has more than one widely used stored procedure language: PL/pgSQL, PL/Python, or PL/Perl.&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL is a reduced version of PL/SQL, and PL/SQL is a modified ADA language. I am pretty happy with the current complexity of PL/pgSQL --- it is a domain-specific language that works well. It is very simple and easy to learn. PL/pgSQL has no packages or modules; instead PostgreSQL schemas are used. But schemas are database objects in their own right, independent of PL.&lt;br /&gt;
&lt;br /&gt;
* PostgreSQL already has a code container --- the schema. I don&#039;t want to introduce another container object (modules), as this would overlap heavily with schemas. Implementing modules for PL/pgSQL could be useful, but doing it properly would be quite complex. The main problem is the concept of public and private objects --- PostgreSQL does not have this. Don&#039;t forget: every PL/pgSQL expression is a SQL expression, so public/private visibility would first need to be implemented internally in PostgreSQL. That would be redundant (and possibly in conflict) with SEARCH_PATH.&lt;br /&gt;
&lt;br /&gt;
So I wanted a design that supports multiple PL languages, does not add complexity to the relatively simple PL/pgSQL, and does not duplicate functionality already available.  &lt;br /&gt;
&lt;br /&gt;
I wrote a larger application in PL/pgSQL. To maintain it, I created plpgsql_lint (later renamed plpgsql_check). So I am always looking for solutions that do not block static code analysis. Static analysis requires persistent objects, which naturally leads to designing session variables as database objects (with their own system catalog).  &lt;br /&gt;
When session variables are database objects, ACLs can be applied naturally. Oracle package variables are well protected by package scope. PostgreSQL, however, has no schema scope --- schema locality is controlled by the SEARCH_PATH GUC, and introducing another mechanism would be messy. But with ACLs, variable contents can also be secured:&lt;br /&gt;
&lt;br /&gt;
    CREATE TEMP VARIABLE x AS int;&lt;br /&gt;
     &lt;br /&gt;
    LET x = 10;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(x);&lt;br /&gt;
    END;&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    SELECT VARIABLE(x);&lt;br /&gt;
&lt;br /&gt;
==== Collisions between column and variable identifiers ====&lt;br /&gt;
&lt;br /&gt;
There is a risk of identifier collisions between variables and columns. With ANSI SQL syntax for session variable identifiers, the problem is how to distinguish variables from columns. This is a known issue in PL/pgSQL and PL/SQL and can be solved either by prioritization or by prohibiting collisions.  &lt;br /&gt;
&lt;br /&gt;
Prohibiting collisions has largely solved this problem in PL/pgSQL. Unfortunately, session variables are global objects, so collisions can occur in any query. A poorly named variable could break queries unexpectedly. Prioritization also has problems --- a variable or a column could be used silently when the other was intended.&lt;br /&gt;
&lt;br /&gt;
    CREATE TABLE tab(a int);&lt;br /&gt;
    CREATE VARIABLE a AS int;&lt;br /&gt;
    &lt;br /&gt;
    SELECT a FROM tab; -- with disallowed collisions, this query fails when a variable named &amp;quot;a&amp;quot; exists&lt;br /&gt;
    &lt;br /&gt;
    LET a = 1;&lt;br /&gt;
    DELETE FROM tab WHERE a = 1; -- with higher priority for variables, all rows could be deleted&lt;br /&gt;
    SELECT a FROM tab; -- with higher priority for columns, the variable is ignored&lt;br /&gt;
&lt;br /&gt;
Even with collision prohibition, mistakes are still possible:&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE _id AS int;&lt;br /&gt;
    LET _id = 10;&lt;br /&gt;
    CREATE TABLE foo(id int);&lt;br /&gt;
    DELETE FROM foo WHERE _id = 10; -- just a typo, but all rows are deleted&lt;br /&gt;
&lt;br /&gt;
These problems are not new. Developers in PL/pgSQL or PL/SQL know them, but the scope of risk is normally limited to stored procedures. After many discussions and designs, I introduced &amp;quot;variable fences&amp;quot; (the syntax VARIABLE(varname)). Inside any query, variables can be used only inside a variable fence. This completely solves collisions and reduces the chance of human error. Currently, fences are required everywhere in queries and expressions. In the future, they might be made optional inside PL/pgSQL, to reduce overhead when porting Oracle applications. This could be controlled by a new plpgsql.extra_checks setting.&lt;br /&gt;
&lt;br /&gt;
A variable fence can collide with a user-defined function named `variable`. This is similar to keyword conflicts such as CUBE. It can be resolved by schema-qualifying or quoting:&lt;br /&gt;
&lt;br /&gt;
    SELECT public.variable(...)&lt;br /&gt;
    SELECT &amp;quot;variable&amp;quot;(...)&lt;br /&gt;
&lt;br /&gt;
Why not use T-SQL (MSSQL, MySQL) syntax for session variables? There are several reasons – some objective, one subjective:&lt;br /&gt;
&lt;br /&gt;
* There is a collision with custom operators. Operators `@` and `@@` are already used, and `@@` is common:&lt;br /&gt;
&lt;br /&gt;
    (2025-09-28 06:57:30) postgres=# create table tab(a int);&lt;br /&gt;
    CREATE TABLE&lt;br /&gt;
    (2025-09-28 06:57:42) postgres=# insert into tab values(10);&lt;br /&gt;
    INSERT 0 1&lt;br /&gt;
    (2025-09-28 06:57:50) postgres=# select @a from tab;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │       10 │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
We could safely use only `:` as a prefix, but that would conflict with psql variables. Similarly, `$` would conflict with query parameters.&lt;br /&gt;
&lt;br /&gt;
* T-SQL variable names look strange in PostgreSQL (in queries and in PL/pgSQL). PostgreSQL uses only ANSI/SQL identifiers, and PL/pgSQL follows the same rules. PostgreSQL is consistent here, and I see little motivation to introduce a new, non-standard syntax (especially since it could cause compatibility issues).&lt;br /&gt;
&lt;br /&gt;
Note: There was also a proposal to use table syntax for variables (similar to T-SQL in-memory table variables, but restricted to a single row). This is very effective for avoiding collisions, but I dislike it. It complicates simple expressions, adds inconsistency, and looks odd for scalar variables (though it could be useful for composite variables). This design doesn&#039;t solve all possible problems - variables can be shadowed by tables still, because we have SEARCH_PATH (but this is known issue):&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(var); -- variable is not a table&lt;br /&gt;
    END&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      -- variable as a table (I don&#039;t like it)&lt;br /&gt;
      -- can we use DML? How many rows can it hold?&lt;br /&gt;
      -- is the value a row or not?&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, (SELECT var FROM var);&lt;br /&gt;
    END&lt;br /&gt;
    $$&lt;br /&gt;
&lt;br /&gt;
I am not against introducing in-memory tables --- or more correctly, global temporary tables. A well-designed implementation could be very useful. But this is a different feature and should be designed separately.  &lt;br /&gt;
&lt;br /&gt;
There are also performance considerations: PL/pgSQL can use simple expression evaluation when no tables are referenced. This optimization would need to change, which touches sensitive code. It would also be inconsistent.  &lt;br /&gt;
&lt;br /&gt;
I would like global temporary tables (which PostgreSQL currently lacks). Tables should behave like tables, and variables should behave like variables. The natural mental model for session variables comes from variables in PL languages. PostgreSQL internally supports *transition tables*, and I can imagine allowing these outside of triggers. That would be valid, but again, it is a different feature.  &lt;br /&gt;
&lt;br /&gt;
Implementing declared tables inside PL/pgSQL would be simpler from a high-level design perspective (interaction between SQL tables and PL/pgSQL is already well defined), but difficult from a low-level perspective (statistics, optimizer data, etc. --- the same issues as with global temporary tables, unless we add fake proxy local temp tables).&lt;br /&gt;
&lt;br /&gt;
   CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
   AS $$&lt;br /&gt;
   -- not implemented yet, not part of this proposal&lt;br /&gt;
   DECLARE t TABLE(a int, b int); -- this table is invisible outside fx&lt;br /&gt;
   BEGIN&lt;br /&gt;
     INSERT INTO t VALUES(10,20);&lt;br /&gt;
     INSERT INTO t VALUES(30,40);&lt;br /&gt;
     RAISE NOTICE &#039;%&#039;, (SELECT count(*) FROM t);&lt;br /&gt;
   END;&lt;br /&gt;
   $$ LANGUAGE plpgsql;&lt;br /&gt;
&lt;br /&gt;
Some data languages define relational variables, but this concept has no overlap with the common understanding of session variables.&lt;br /&gt;
&lt;br /&gt;
Inside PL/pgSQL we can use a possibility to configure a parser, and can be easy to allow a usage of session variables without variable fence (like common plpgsql variable) in expressions. It is not supported yet, but there should not be implementation problems. Outside PL, the variable fences can be optional, when there is not risk of collisions (the expression doesn&#039;t contains subselect) (not implemented yet).&lt;br /&gt;
&lt;br /&gt;
==== Assign command - LET ====&lt;br /&gt;
&lt;br /&gt;
The value of session variable can be assigned by dedicated PL assign command or by `SELECT INTO` construct. When special command is prefixed by some a keyword, then the keyword `SET` is usually used (MySQL, MSSQL, SQL/PSM, DB2). Command `SET` can be enhanced to support `a_expr` on parser level in Postgres too. It requires partial rewriting of current implementation of `SET` command, but there is not technical issues.&lt;br /&gt;
&lt;br /&gt;
Unfortunately, there can be collisions between GUC and session variables. Currently GUC are not declared, and they are not joined to any schema (prefix for custom GUC can be just any not empty string), and access to GUC is not controlled by SEARCH_PATH. We can use same solution like usage session variables in expression - variable fence - but in this case, the risk is every time, and there is not a possibility to usage of variable fences can be optional (for some cases) in future. Some languages uses for a assignment the keyword `LET`. I introduced this keyword as a solution for 100% fix of collisions between GUC and session variables identifiers. &lt;br /&gt;
&lt;br /&gt;
I can imagine the assignment to session variable inside PL by usual PL assignment command (not implemented yet). This can be benefit for translation from PL/SQL (Oracle), and it can be very natural syntax, when session variable is used like PL global variable. Probably (not tested), there are not technical issues for implementation of this. But there is different significant issue. Currently, only PL/pgSQL variables can be target of PL/pgSQL assign statement, and these variables should be declared before usage. If we allow session variables there, then a) we cannot to do this check, or b) there will be new strong dependency from PL functions to session variables - like on types now.&lt;br /&gt;
&lt;br /&gt;
    (2025-10-02 07:42:01) postgres=# CREATE OR REPLACE FUNCTION fx() &lt;br /&gt;
    returns void as $$&lt;br /&gt;
    begin&lt;br /&gt;
      declare var mydomain;&lt;br /&gt;
    begin&lt;br /&gt;
      var := 10;&lt;br /&gt;
    end&lt;br /&gt;
    $$ language plpgsql;&lt;br /&gt;
    ERROR:  type &amp;quot;mydomain&amp;quot; does not exist&lt;br /&gt;
    LINE 4:   declare var mydomain;&lt;br /&gt;
                          ^&lt;br /&gt;
&lt;br /&gt;
The strong dependency today is not problem probably (because plan cache can be correctly invalidated), but there can be some new questions about temporary variables. Again as a solution I proposed a `LET` command, and we don&#039;t need to touch to PL/pgSQL assign statement.&lt;br /&gt;
&lt;br /&gt;
The significant question is performance generally and performance when LET is used inside PL/pgSQL.&lt;br /&gt;
&lt;br /&gt;
The command `LET` is PostgreSQL utility command. Against history, the utility command can be prepared (as `CREATE TABLE AS SELECT`). There are patches that implements `PREPARE` and `EXECUTE` for `LET` command (these patches are not in reduced patch set). Although plan cache can be used, the performance against PL/pgSQL assign can be slower - from two reasons:&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL allows simple expression evaluation (this is supported for `LET` command too in enhanced patch set),&lt;br /&gt;
* PL/pgSQL variables are stored in a array (and indexed by int offset), session variables are stored in hash table, and they are accessed by hash search (and this slower).&lt;br /&gt;
&lt;br /&gt;
= Variabes in PostgreSQL (as of v18) =&lt;br /&gt;
&lt;br /&gt;
PostgreSQL provides relatively advanced client-side session variables in psql --- these variables use the `:` prefix. The implementation is pretty much straightforward: `psql`&#039;s parser reads tokens from input and, when it encounters a token related to a variable, replaces it with the variable&#039;s value. The syntax supports literal and identifier escaping, and variables can be used as arguments of the `\bind` command.&lt;br /&gt;
&lt;br /&gt;
`psql` variables are typeless client-side variables, available only inside `psql` or `pgbench`.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:40:18) postgres=# \set myvar ahoj&lt;br /&gt;
    (2025-09-25 09:40:33) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:14) postgres=# select :&#039;myvar&#039;;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:24) postgres=# select $1 \bind :myvar \g&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
The `\set` command allows only simple string concatenation, but it also supports execution of shell commands. When a query is executed with the `\gset` command, its result can be stored in a `psql` variable.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:41:40) postgres=# \set ctime `date`&lt;br /&gt;
    (2025-09-25 09:43:55) postgres=# \echo :ctime&lt;br /&gt;
    Thu Sep 25 09:43:55 CEST 2025&lt;br /&gt;
&lt;br /&gt;
The `\set` command is fairly limited, making complex operations unreadable or non-portable.&lt;br /&gt;
&lt;br /&gt;
This mechanism is good enough for writing tests, but it is not generally available (most users do not need it), and it is not accessible from the server side (e.g., from stored procedures). There is no simple way to access `psql` variables from an anonymous block --- a proxy custom GUC variable must be used:&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:58:38) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    (2025-09-25 09:58:43) postgres=# select set_config(&#039;myvars.myvar&#039;, :&#039;myvar&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ ahoj       │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 10:00:27) postgres=# do $$ begin raise notice &#039;%&#039;, current_setting(&#039;myvars.myvar&#039;); end $$;&lt;br /&gt;
    NOTICE:  ahoj&lt;br /&gt;
    DO&lt;br /&gt;
&lt;br /&gt;
PostgreSQL also has configuration variables (GUC - Grand Unified Configuration). These are primarily designed for PostgreSQL and extension configuration, but they can be used as session variables. GUC variables can be set with the `SET` command or the `set_config` function, and read with `SHOW` or `current_setting`.&lt;br /&gt;
&lt;br /&gt;
GUC variables are meant for holding configuration parameters. When created through the internal API, they can be restricted to superusers, hidden from regular users, and assigned specific types (boolean, memory, number, interval, string, enum) with validation. This type system is separate from the SQL type system and is initialized before PostgreSQL’s SQL types.&lt;br /&gt;
&lt;br /&gt;
Variables created from SQL must be &amp;quot;custom&amp;quot; variables, using a prefix in their name --- these are always strings. Such custom GUCs are often used as a workaround for missing server-side session variables in PostgreSQL, but they are typeless, unprotected, and unsafe.&lt;br /&gt;
&lt;br /&gt;
A notable feature of PostgreSQL GUCs (built-in or custom) is that they can have different scopes: transaction, session, or function execution. They can also be assigned default values at specific events --- configuration loading, role login, database login, or function start. These features are useful for configuration, but problematic when GUCs are repurposed as session variables.&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
    RETURNS void AS $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, current_setting(&#039;my.x&#039;);&lt;br /&gt;
    END $$ LANGUAGE plpgsql SET my.x = 20;&lt;br /&gt;
    CREATE FUNCTION&lt;br /&gt;
    (2025-09-27 06:10:37) postgres=# SET my.x = 0; SELECT fx();&lt;br /&gt;
    SET&lt;br /&gt;
    NOTICE:  20&lt;br /&gt;
    ┌────┐&lt;br /&gt;
    │ fx │&lt;br /&gt;
    ╞════╡&lt;br /&gt;
    │    │&lt;br /&gt;
    └────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    (2025-09-27 06:10:39) postgres=# SHOW my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 0    │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
GUC variables are transactional, and this behavior cannot be disabled.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-27 06:17:31) postgres=# set my.x = 10;&lt;br /&gt;
    SET&lt;br /&gt;
    (2025-09-27 06:19:42) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:19:46) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, true);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:19:55) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:01) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:20:11) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:13) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:20:38) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:45) postgres=# rollback;&lt;br /&gt;
    ROLLBACK&lt;br /&gt;
    (2025-09-27 06:20:52) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:55) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:22:36) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:22:39) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:22:42) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
As a result, there is no reliable way to store details such as error information in custom GUCs.&lt;br /&gt;
&lt;br /&gt;
= Session Variables in Other Systems =&lt;br /&gt;
&lt;br /&gt;
This section surveys how session variables are implemented in several widely used database systems. Each subsection includes code examples to illustrate syntax, behaviour, and scope.&lt;br /&gt;
&lt;br /&gt;
== MySQL ==&lt;br /&gt;
&lt;br /&gt;
Session variables in MySQL have syntax similar to the better-known T-SQL variables (though other details are MySQL-specific). MySQL distinguishes two kinds of variables:&lt;br /&gt;
&lt;br /&gt;
* system variables - referenced using the `@@` prefix (or `@@scope.name`)&lt;br /&gt;
* user-defined variables - referenced using the `@` prefix&lt;br /&gt;
&lt;br /&gt;
System variables are modified with the `SET` statement. The `SET` statement accepts the modifiers `GLOBAL`, `SESSION`, or `PERSIST`. Some variables can only be set using the `GLOBAL` or `SESSION` modifier; when the variable name is specified with the `@@` prefix, it is not necessary to explicitly indicate the scope. System variables are defined by MySQL or by MySQL plugins.&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL some_config = 100;&lt;br /&gt;
    SET @@same_config = 100;&lt;br /&gt;
    &lt;br /&gt;
    SET SESSION sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@SESSION.sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
&lt;br /&gt;
Persistent values are stored in `mysqld-auto.cnf` (in the server data directory).&lt;br /&gt;
&lt;br /&gt;
Resetting system variables can be done by assigning DEFAULT or by copying from the GLOBAL namespace:&lt;br /&gt;
&lt;br /&gt;
    SET @@sql_mode = DEFAULT;&lt;br /&gt;
    SET @@sql_mode = @@GLOBAL.sql_mode&lt;br /&gt;
&lt;br /&gt;
PostgreSQL equivalents:&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL ; -- there is not similar command in Postgres&lt;br /&gt;
    SET SESSION ; -- SET&lt;br /&gt;
    SET PERSIST ; -- ALTER SYSTEM; SELECT pg_reload_conf();&lt;br /&gt;
    SELECT @@var; -- SELECT current_setting(&#039;var);&lt;br /&gt;
&lt;br /&gt;
User-defined variables are created by assignment:&lt;br /&gt;
&lt;br /&gt;
    SET @var = expr [, @var2 = expr [ ... ]];&lt;br /&gt;
&lt;br /&gt;
User-defined variables can only be of type integer, decimal, float, binary, non-binary string, or NULL. Casting between these types is implicit and hidden. User-defined variables cannot be used as table or column names.&lt;br /&gt;
&lt;br /&gt;
An advantage of the T-SQL-style syntax (with special prefixes) is that it provides a simple solution for avoiding identifier collisions. MySQL variables are convenient, and the user experience can be good for people already familiar with T-SQL (MSSQL or Sybase). On the other hand, prefix-based syntax can be confusing for users coming from systems other than MSSQL. The type system is perhaps too simple and can be unsafe. In general, the performance of MySQL (or MariaDB) stored procedures is not good, and stored procedures are therefore not frequently used. There is no protection against typographical errors. Unassigned user variables can be used without error (they default to NULL), which makes static checking of stored procedures impossible in this area.&lt;br /&gt;
&lt;br /&gt;
There is no way to restrict access to user-defined variables in MySQL. They cannot be protected against unintended usage. The only possible safeguard is a naming convention, since all user-defined variables share a single namespace.&lt;br /&gt;
&lt;br /&gt;
== Oracle ==&lt;br /&gt;
&lt;br /&gt;
Oracle PL/SQL allows the use of package variables. PL/SQL is based on the Ada language, and package variables act as &amp;quot;global&amp;quot; variables. They are not directly visible from SQL, but Oracle provides a reduced syntax for functions without arguments, so you typically write a wrapper:&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE PACKAGE my_package&lt;br /&gt;
    AS&lt;br /&gt;
      FUNCTION get_a RETURN NUMBER;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    /&lt;br /&gt;
    &lt;br /&gt;
    CREATE OR REPLACE PACKAGE BODY my_package&lt;br /&gt;
    AS&lt;br /&gt;
      a  NUMBER(20);&lt;br /&gt;
      &lt;br /&gt;
      FUNCTION get_a&lt;br /&gt;
      RETURN NUMBER&lt;br /&gt;
      IS&lt;br /&gt;
      BEGIN&lt;br /&gt;
        RETURN a;&lt;br /&gt;
      END get_a;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    &lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
Inside SQL, SQL identifiers take precedence. Inside non-SQL commands (such as CALL or PL/SQL blocks), package identifiers take precedence.&lt;br /&gt;
&lt;br /&gt;
Oracle allows both syntaxes for calling a function with zero arguments:&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a() FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
This reduces the risk of naming collisions. Package variables persist for the duration of a session.&lt;br /&gt;
&lt;br /&gt;
Another possibility is to use variables in SQL*Plus (similar to `psql` variables, but with the ability to define the type on the server side).&lt;br /&gt;
&lt;br /&gt;
A variable is declared with the `VARIABLE` command and can be accessed in the session using the `:varname` syntax (a prior declaration step may be optional):&lt;br /&gt;
&lt;br /&gt;
    VARIABLE bv_variable_name VARCHAR2(30)&lt;br /&gt;
    BEGIN&lt;br /&gt;
      :bv_variable_name := &#039;Some Value&#039;;&lt;br /&gt;
    END;&lt;br /&gt;
    &lt;br /&gt;
    SELECT column_name&lt;br /&gt;
      FROM   table_name&lt;br /&gt;
     WHERE  column_name = :bv_variable_name;&lt;br /&gt;
&lt;br /&gt;
== DB2 ==&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;user-defined global variables&amp;quot; in DB2 are similar to the proposed PostgreSQL feature. The main difference is in the access rights: DB2 uses `READ` and `WRITE`, while PostgreSQL uses `SELECT` and `UPDATE`. Because PostgreSQL already uses the `SET` command for GUCs, the `LET` command was introduced in the proposal (DB2 uses `SET`).&lt;br /&gt;
&lt;br /&gt;
Variables are visible in all sessions, but their values are private to each session. Variables are not transactional. Their usage is broader than in the proposal: they can be changed by `SET`, `SELECT INTO`, or used as OUT parameters of procedures. The search path (or a similar mechanism) applies to variables as well, but variables have lower priority than tables or columns.&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE myCounter INT DEFAULT 01;&lt;br /&gt;
    SELECT EMPNO, LASTNAME, CASE WHEN myCounter = 1 THEN SALARY ELSE NULL END&lt;br /&gt;
    FROM EMPLOYEE WHERE WORKDEPT = &#039;A00&#039;;&lt;br /&gt;
    SET myCounter = 29;&lt;br /&gt;
&lt;br /&gt;
There also appear to be other kinds of variables, accessed using the function `GETVARIABLE(&#039;name&#039;, &#039;default&#039;)`. These are very similar to PostgreSQL GUCs and the `current_setting` function. Such variables can be set via the connection string, are of type `VARCHAR`, and up to 10 values are allowed. Built-in session variables (configuration) can also be accessed using `GETVARIABLE`.&lt;br /&gt;
&lt;br /&gt;
== MSSQL (T-SQL) ==&lt;br /&gt;
&lt;br /&gt;
MSSQL provides two types of variables:&lt;br /&gt;
&lt;br /&gt;
* Global variables — accessed with the `@@varname` syntax. These are similar to GUCs and provide state information such as `@@ERROR`, `@@ROWCOUNT`, or `@@IDENTITY`. &lt;br /&gt;
* Local variables — accessed with the `@varname` syntax. These must be declared with the `DECLARE` command before use. Their scope is limited to the batch, procedure, or function where they are executed.&lt;br /&gt;
&lt;br /&gt;
    DECLARE @TestVariable AS VARCHAR(100)&lt;br /&gt;
    SET @TestVariable = &#039;Think Green&#039;&lt;br /&gt;
    GO&lt;br /&gt;
    PRINT @TestVariable&lt;br /&gt;
&lt;br /&gt;
This script fails because `PRINT` is executed in a different batch. Therefore, it seems MSSQL does not truly support session variables.&lt;br /&gt;
&lt;br /&gt;
There are also mechanisms similar to PostgreSQL&#039;s custom GUCs and the use of `current_setting` and `set_config` functions. In general, MSSQL is fairly primitive in this area.&lt;br /&gt;
&lt;br /&gt;
    EXEC sp_set_session_context &#039;user_id&#039;, 4;&lt;br /&gt;
    SELECT SESSION_CONTEXT(N&#039;user_id&#039;);&lt;br /&gt;
&lt;br /&gt;
= ToDo =&lt;br /&gt;
&lt;br /&gt;
* SECURITY LABEL support — enhance the `dummy_seclabel` module and the `sepgsql` extension. Update PostgreSQL documentation and ensure `SecLabelSupportsObjectType` includes session variables.&lt;/div&gt;</summary>
		<author><name>Okbobcz</name></author>
	</entry>
	<entry>
		<id>https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41854</id>
		<title>Implementation of declarative catalog session variables</title>
		<link rel="alternate" type="text/html" href="https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41854"/>
		<updated>2025-09-30T05:42:57Z</updated>

		<summary type="html">&lt;p&gt;Okbobcz: /* Collisions between column and variable identifiers */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Session variables are database objects that can hold a value across multiple queries inside a session. The design of session variables varies widely, and many databases implement more than one type of session variable.&lt;br /&gt;
&lt;br /&gt;
The SQL/PSM standard introduces modules and module-level variables. However, this part of the standard has not been implemented in any widely used product. As a result, each database system has developed its own proprietary design, syntax, and feature set.&lt;br /&gt;
&lt;br /&gt;
Session variables are often part of the stored procedure environment, but there are exceptions --- for example, MySQL session variables or client-side session variables in tools like psql. In most systems, session variables do not participate in transactions: once set, their value persists until explicitly changed, even if the surrounding transaction is rolled back. Conceptually, they behave much like variables in general-purpose programming languages.&lt;br /&gt;
&lt;br /&gt;
Because there is no common design, comparisons across systems are difficult. Each implementation has its own advantages and disadvantages, and in some cases a single system supports more than one kind of session variable.&lt;br /&gt;
&lt;br /&gt;
Kinds of session variables:&lt;br /&gt;
* client side (psql, pgadmin, ...) - mostly similar to shell variables (based on string operations)&lt;br /&gt;
* server side&lt;br /&gt;
** declarative&lt;br /&gt;
*** created only for batch (T-SQL)&lt;br /&gt;
*** created as an database object (or part of database object) (Oracle, DB2)&lt;br /&gt;
** created by usage (MySQL)&lt;br /&gt;
** with priprietary syntax for identifiers (MySQL, T-SQL)&lt;br /&gt;
** with ANSI SQL syntax for identifiers (Oracle, DB2)&lt;br /&gt;
&lt;br /&gt;
Possible usage of session variables:&lt;br /&gt;
&lt;br /&gt;
* usage of session variables as global variables in PL (tracing, debugging, hacking),&lt;br /&gt;
* holding configuration (for PL),&lt;br /&gt;
* holding some more used data in interactive session,&lt;br /&gt;
* holding credentials for some RLS and data securing,&lt;br /&gt;
* helping with migration from others systems (mainly from Oracle),&lt;br /&gt;
* allow anonymous block parametrization (only postgres).&lt;br /&gt;
* session variables are writeable on read only hot standby (postgres)&lt;br /&gt;
&lt;br /&gt;
Session variables holds a values like temporary tables. What are differences between temporary tables and variables?:&lt;br /&gt;
&lt;br /&gt;
* variables holds a values, tables holds a set of rows,&lt;br /&gt;
* syntax for usage variables is shorter (more friendly for interactive work), access (read, write) is faster,&lt;br /&gt;
* the content of session variables is generally non transactional, the content of temporary tables is transactional,&lt;br /&gt;
* the content of session variables is just one value or NULL (no MVCC, no VACUUM),&lt;br /&gt;
* temporary tables are tables still (transactional, MVCC), update is expensive, under one transaction repeated updates makes lot of dead tuples,&lt;br /&gt;
* temporary tables are not cleaned by autovacuum (and inside function is not possible to execute VACUUM), &lt;br /&gt;
* Postgres doesn&#039;t support global temporary tables, so usage of temporary table for just few values or value is very expensive (catalogue bloat).&lt;br /&gt;
&lt;br /&gt;
= SQL/PSM =&lt;br /&gt;
&lt;br /&gt;
The SQL standard introduces the concept of modules, which can be associated with schemas (partially). Variables in modules behave like PL/pgSQL variables but are only local. The only objects allowed at the module level are temporary tables, which can be accessed only from routines assigned to that module. Modules can encapsulate private objects (functions, procedure, temp tables), that are not visible outside the module. Inside the module, a  private objects shadows other objects. What I know the modules (and generally SQL/PSM) doesn&#039;t cover a deployment of code, so there is not similarity with PostgreSQL extensions. SQL/PSM Modules are modules like modules from modular languages like Modula2 or Oberon. There is some similarity with Postgres schemas (both are containers) with significant differences (there is not a concept of SEARCH_PATH (on PSM side) or there is not a concept of private or public objects (on Postgres schema side)).&lt;br /&gt;
&lt;br /&gt;
SQL/PSM variables are not transactional (they are used exactly like in PL/pgSQL). The precedence between variables and columns is not specified.&lt;br /&gt;
&lt;br /&gt;
= Implementation Challenges =&lt;br /&gt;
&lt;br /&gt;
* Possible collisions between session variables and other database objects.&lt;br /&gt;
* Memory cleanup after dropping a session variable, especially when DDL operations can be reverted (Postgres has not support for global temp objects).&lt;br /&gt;
* Memory invalidation: if a variable is dropped by one session, other sessions should not continue using it (Postgres has not support for global temp objects).&lt;br /&gt;
&lt;br /&gt;
= Proposed Implementations =&lt;br /&gt;
&lt;br /&gt;
It is clear that no single design is perfect for all use cases. MySQL’s approach is convenient and useful for interactive work, but it cannot be used for storing sensitive data and is considered unsafe. PostgreSQL GUCs are excellent for configuration, but they are not well suited for interactive use and are very limited when used as global variables in PL code.&lt;br /&gt;
&lt;br /&gt;
Because of these trade-offs, no single design is sufficient. In practice, having multiple designs within a single system could support a broader range of use cases more effectively.&lt;br /&gt;
&lt;br /&gt;
== Design session variables as database global temporary typed objects with ANSI SQL syntax for identifiers ==&lt;br /&gt;
&lt;br /&gt;
Author: Pavel Stehule &amp;lt;pavel.stehule@gmail.com&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I searched for a solution that could work well with the specific PostgreSQL environment:&lt;br /&gt;
&lt;br /&gt;
* PostgreSQL has more than one widely used stored procedure language: PL/pgSQL, PL/Python, or PL/Perl.&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL is a reduced version of PL/SQL, and PL/SQL is a modified ADA language. I am pretty happy with the current complexity of PL/pgSQL --- it is a domain-specific language that works well. It is very simple and easy to learn. PL/pgSQL has no packages or modules; instead PostgreSQL schemas are used. But schemas are database objects in their own right, independent of PL.&lt;br /&gt;
&lt;br /&gt;
* PostgreSQL already has a code container --- the schema. I don&#039;t want to introduce another container object (modules), as this would overlap heavily with schemas. Implementing modules for PL/pgSQL could be useful, but doing it properly would be quite complex. The main problem is the concept of public and private objects --- PostgreSQL does not have this. Don&#039;t forget: every PL/pgSQL expression is a SQL expression, so public/private visibility would first need to be implemented internally in PostgreSQL. That would be redundant (and possibly in conflict) with SEARCH_PATH.&lt;br /&gt;
&lt;br /&gt;
So I wanted a design that supports multiple PL languages, does not add complexity to the relatively simple PL/pgSQL, and does not duplicate functionality already available.  &lt;br /&gt;
&lt;br /&gt;
I wrote a larger application in PL/pgSQL. To maintain it, I created plpgsql_lint (later renamed plpgsql_check). So I am always looking for solutions that do not block static code analysis. Static analysis requires persistent objects, which naturally leads to designing session variables as database objects (with their own system catalog).  &lt;br /&gt;
When session variables are database objects, ACLs can be applied naturally. Oracle package variables are well protected by package scope. PostgreSQL, however, has no schema scope --- schema locality is controlled by the SEARCH_PATH GUC, and introducing another mechanism would be messy. But with ACLs, variable contents can also be secured:&lt;br /&gt;
&lt;br /&gt;
    CREATE TEMP VARIABLE x AS int;&lt;br /&gt;
     &lt;br /&gt;
    LET x = 10;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(x);&lt;br /&gt;
    END;&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    SELECT VARIABLE(x);&lt;br /&gt;
&lt;br /&gt;
==== Collisions between column and variable identifiers ====&lt;br /&gt;
&lt;br /&gt;
There is a risk of identifier collisions between variables and columns. With ANSI SQL syntax for session variable identifiers, the problem is how to distinguish variables from columns. This is a known issue in PL/pgSQL and PL/SQL and can be solved either by prioritization or by prohibiting collisions.  &lt;br /&gt;
&lt;br /&gt;
Prohibiting collisions has largely solved this problem in PL/pgSQL. Unfortunately, session variables are global objects, so collisions can occur in any query. A poorly named variable could break queries unexpectedly. Prioritization also has problems --- a variable or a column could be used silently when the other was intended.&lt;br /&gt;
&lt;br /&gt;
    CREATE TABLE tab(a int);&lt;br /&gt;
    CREATE VARIABLE a AS int;&lt;br /&gt;
    &lt;br /&gt;
    SELECT a FROM tab; -- with disallowed collisions, this query fails when a variable named &amp;quot;a&amp;quot; exists&lt;br /&gt;
    &lt;br /&gt;
    LET a = 1;&lt;br /&gt;
    DELETE FROM tab WHERE a = 1; -- with higher priority for variables, all rows could be deleted&lt;br /&gt;
    SELECT a FROM tab; -- with higher priority for columns, the variable is ignored&lt;br /&gt;
&lt;br /&gt;
Even with collision prohibition, mistakes are still possible:&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE _id AS int;&lt;br /&gt;
    LET _id = 10;&lt;br /&gt;
    CREATE TABLE foo(id int);&lt;br /&gt;
    DELETE FROM foo WHERE _id = 10; -- just a typo, but all rows are deleted&lt;br /&gt;
&lt;br /&gt;
These problems are not new. Developers in PL/pgSQL or PL/SQL know them, but the scope of risk is normally limited to stored procedures. After many discussions and designs, I introduced &amp;quot;variable fences&amp;quot; (the syntax VARIABLE(varname)). Inside any query, variables can be used only inside a variable fence. This completely solves collisions and reduces the chance of human error. Currently, fences are required everywhere in queries and expressions. In the future, they might be made optional inside PL/pgSQL, to reduce overhead when porting Oracle applications. This could be controlled by a new plpgsql.extra_checks setting.&lt;br /&gt;
&lt;br /&gt;
A variable fence can collide with a user-defined function named `variable`. This is similar to keyword conflicts such as CUBE. It can be resolved by schema-qualifying or quoting:&lt;br /&gt;
&lt;br /&gt;
    SELECT public.variable(...)&lt;br /&gt;
    SELECT &amp;quot;variable&amp;quot;(...)&lt;br /&gt;
&lt;br /&gt;
Why not use T-SQL (MSSQL, MySQL) syntax for session variables? There are several reasons – some objective, one subjective:&lt;br /&gt;
&lt;br /&gt;
* There is a collision with custom operators. Operators `@` and `@@` are already used, and `@@` is common:&lt;br /&gt;
&lt;br /&gt;
    (2025-09-28 06:57:30) postgres=# create table tab(a int);&lt;br /&gt;
    CREATE TABLE&lt;br /&gt;
    (2025-09-28 06:57:42) postgres=# insert into tab values(10);&lt;br /&gt;
    INSERT 0 1&lt;br /&gt;
    (2025-09-28 06:57:50) postgres=# select @a from tab;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │       10 │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
We could safely use only `:` as a prefix, but that would conflict with psql variables. Similarly, `$` would conflict with query parameters.&lt;br /&gt;
&lt;br /&gt;
* T-SQL variable names look strange in PostgreSQL (in queries and in PL/pgSQL). PostgreSQL uses only ANSI/SQL identifiers, and PL/pgSQL follows the same rules. PostgreSQL is consistent here, and I see little motivation to introduce a new, non-standard syntax (especially since it could cause compatibility issues).&lt;br /&gt;
&lt;br /&gt;
Note: There was also a proposal to use table syntax for variables (similar to T-SQL in-memory table variables, but restricted to a single row). This is very effective for avoiding collisions, but I dislike it. It complicates simple expressions, adds inconsistency, and looks odd for scalar variables (though it could be useful for composite variables). This design doesn&#039;t solve all possible problems - variables can be shadowed by tables still, because we have SEARCH_PATH (but this is known issue):&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(var); -- variable is not a table&lt;br /&gt;
    END&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      -- variable as a table (I don&#039;t like it)&lt;br /&gt;
      -- can we use DML? How many rows can it hold?&lt;br /&gt;
      -- is the value a row or not?&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, (SELECT var FROM var);&lt;br /&gt;
    END&lt;br /&gt;
    $$&lt;br /&gt;
&lt;br /&gt;
I am not against introducing in-memory tables --- or more correctly, global temporary tables. A well-designed implementation could be very useful. But this is a different feature and should be designed separately.  &lt;br /&gt;
&lt;br /&gt;
There are also performance considerations: PL/pgSQL can use simple expression evaluation when no tables are referenced. This optimization would need to change, which touches sensitive code. It would also be inconsistent.  &lt;br /&gt;
&lt;br /&gt;
I would like global temporary tables (which PostgreSQL currently lacks). Tables should behave like tables, and variables should behave like variables. The natural mental model for session variables comes from variables in PL languages. PostgreSQL internally supports *transition tables*, and I can imagine allowing these outside of triggers. That would be valid, but again, it is a different feature.  &lt;br /&gt;
&lt;br /&gt;
Implementing declared tables inside PL/pgSQL would be simpler from a high-level design perspective (interaction between SQL tables and PL/pgSQL is already well defined), but difficult from a low-level perspective (statistics, optimizer data, etc. --- the same issues as with global temporary tables, unless we add fake proxy local temp tables).&lt;br /&gt;
&lt;br /&gt;
   CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
   AS $$&lt;br /&gt;
   -- not implemented yet, not part of this proposal&lt;br /&gt;
   DECLARE t TABLE(a int, b int); -- this table is invisible outside fx&lt;br /&gt;
   BEGIN&lt;br /&gt;
     INSERT INTO t VALUES(10,20);&lt;br /&gt;
     INSERT INTO t VALUES(30,40);&lt;br /&gt;
     RAISE NOTICE &#039;%&#039;, (SELECT count(*) FROM t);&lt;br /&gt;
   END;&lt;br /&gt;
   $$ LANGUAGE plpgsql;&lt;br /&gt;
&lt;br /&gt;
Some data languages define relational variables, but this concept has no overlap with the common understanding of session variables.&lt;br /&gt;
&lt;br /&gt;
Inside PL/pgSQL we can use a possibility to configure a parser, and can be easy to allow a usage of session variables without variable fence (like common plpgsql variable) in expressions. It is not supported yet, but there should not be implementation problems.&lt;br /&gt;
&lt;br /&gt;
= Variabes in PostgreSQL (as of v18) =&lt;br /&gt;
&lt;br /&gt;
PostgreSQL provides relatively advanced client-side session variables in psql --- these variables use the `:` prefix. The implementation is pretty much straightforward: `psql`&#039;s parser reads tokens from input and, when it encounters a token related to a variable, replaces it with the variable&#039;s value. The syntax supports literal and identifier escaping, and variables can be used as arguments of the `\bind` command.&lt;br /&gt;
&lt;br /&gt;
`psql` variables are typeless client-side variables, available only inside `psql` or `pgbench`.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:40:18) postgres=# \set myvar ahoj&lt;br /&gt;
    (2025-09-25 09:40:33) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:14) postgres=# select :&#039;myvar&#039;;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:24) postgres=# select $1 \bind :myvar \g&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
The `\set` command allows only simple string concatenation, but it also supports execution of shell commands. When a query is executed with the `\gset` command, its result can be stored in a `psql` variable.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:41:40) postgres=# \set ctime `date`&lt;br /&gt;
    (2025-09-25 09:43:55) postgres=# \echo :ctime&lt;br /&gt;
    Thu Sep 25 09:43:55 CEST 2025&lt;br /&gt;
&lt;br /&gt;
The `\set` command is fairly limited, making complex operations unreadable or non-portable.&lt;br /&gt;
&lt;br /&gt;
This mechanism is good enough for writing tests, but it is not generally available (most users do not need it), and it is not accessible from the server side (e.g., from stored procedures). There is no simple way to access `psql` variables from an anonymous block --- a proxy custom GUC variable must be used:&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:58:38) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    (2025-09-25 09:58:43) postgres=# select set_config(&#039;myvars.myvar&#039;, :&#039;myvar&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ ahoj       │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 10:00:27) postgres=# do $$ begin raise notice &#039;%&#039;, current_setting(&#039;myvars.myvar&#039;); end $$;&lt;br /&gt;
    NOTICE:  ahoj&lt;br /&gt;
    DO&lt;br /&gt;
&lt;br /&gt;
PostgreSQL also has configuration variables (GUC - Grand Unified Configuration). These are primarily designed for PostgreSQL and extension configuration, but they can be used as session variables. GUC variables can be set with the `SET` command or the `set_config` function, and read with `SHOW` or `current_setting`.&lt;br /&gt;
&lt;br /&gt;
GUC variables are meant for holding configuration parameters. When created through the internal API, they can be restricted to superusers, hidden from regular users, and assigned specific types (boolean, memory, number, interval, string, enum) with validation. This type system is separate from the SQL type system and is initialized before PostgreSQL’s SQL types.&lt;br /&gt;
&lt;br /&gt;
Variables created from SQL must be &amp;quot;custom&amp;quot; variables, using a prefix in their name --- these are always strings. Such custom GUCs are often used as a workaround for missing server-side session variables in PostgreSQL, but they are typeless, unprotected, and unsafe.&lt;br /&gt;
&lt;br /&gt;
A notable feature of PostgreSQL GUCs (built-in or custom) is that they can have different scopes: transaction, session, or function execution. They can also be assigned default values at specific events --- configuration loading, role login, database login, or function start. These features are useful for configuration, but problematic when GUCs are repurposed as session variables.&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
    RETURNS void AS $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, current_setting(&#039;my.x&#039;);&lt;br /&gt;
    END $$ LANGUAGE plpgsql SET my.x = 20;&lt;br /&gt;
    CREATE FUNCTION&lt;br /&gt;
    (2025-09-27 06:10:37) postgres=# SET my.x = 0; SELECT fx();&lt;br /&gt;
    SET&lt;br /&gt;
    NOTICE:  20&lt;br /&gt;
    ┌────┐&lt;br /&gt;
    │ fx │&lt;br /&gt;
    ╞════╡&lt;br /&gt;
    │    │&lt;br /&gt;
    └────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    (2025-09-27 06:10:39) postgres=# SHOW my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 0    │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
GUC variables are transactional, and this behavior cannot be disabled.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-27 06:17:31) postgres=# set my.x = 10;&lt;br /&gt;
    SET&lt;br /&gt;
    (2025-09-27 06:19:42) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:19:46) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, true);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:19:55) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:01) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:20:11) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:13) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:20:38) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:45) postgres=# rollback;&lt;br /&gt;
    ROLLBACK&lt;br /&gt;
    (2025-09-27 06:20:52) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:55) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:22:36) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:22:39) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:22:42) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
As a result, there is no reliable way to store details such as error information in custom GUCs.&lt;br /&gt;
&lt;br /&gt;
= Session Variables in Other Systems =&lt;br /&gt;
&lt;br /&gt;
This section surveys how session variables are implemented in several widely used database systems. Each subsection includes code examples to illustrate syntax, behaviour, and scope.&lt;br /&gt;
&lt;br /&gt;
== MySQL ==&lt;br /&gt;
&lt;br /&gt;
Session variables in MySQL have syntax similar to the better-known T-SQL variables (though other details are MySQL-specific). MySQL distinguishes two kinds of variables:&lt;br /&gt;
&lt;br /&gt;
* system variables - referenced using the `@@` prefix (or `@@scope.name`)&lt;br /&gt;
* user-defined variables - referenced using the `@` prefix&lt;br /&gt;
&lt;br /&gt;
System variables are modified with the `SET` statement. The `SET` statement accepts the modifiers `GLOBAL`, `SESSION`, or `PERSIST`. Some variables can only be set using the `GLOBAL` or `SESSION` modifier; when the variable name is specified with the `@@` prefix, it is not necessary to explicitly indicate the scope. System variables are defined by MySQL or by MySQL plugins.&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL some_config = 100;&lt;br /&gt;
    SET @@same_config = 100;&lt;br /&gt;
    &lt;br /&gt;
    SET SESSION sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@SESSION.sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
&lt;br /&gt;
Persistent values are stored in `mysqld-auto.cnf` (in the server data directory).&lt;br /&gt;
&lt;br /&gt;
Resetting system variables can be done by assigning DEFAULT or by copying from the GLOBAL namespace:&lt;br /&gt;
&lt;br /&gt;
    SET @@sql_mode = DEFAULT;&lt;br /&gt;
    SET @@sql_mode = @@GLOBAL.sql_mode&lt;br /&gt;
&lt;br /&gt;
PostgreSQL equivalents:&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL ; -- there is not similar command in Postgres&lt;br /&gt;
    SET SESSION ; -- SET&lt;br /&gt;
    SET PERSIST ; -- ALTER SYSTEM; SELECT pg_reload_conf();&lt;br /&gt;
    SELECT @@var; -- SELECT current_setting(&#039;var);&lt;br /&gt;
&lt;br /&gt;
User-defined variables are created by assignment:&lt;br /&gt;
&lt;br /&gt;
    SET @var = expr [, @var2 = expr [ ... ]];&lt;br /&gt;
&lt;br /&gt;
User-defined variables can only be of type integer, decimal, float, binary, non-binary string, or NULL. Casting between these types is implicit and hidden. User-defined variables cannot be used as table or column names.&lt;br /&gt;
&lt;br /&gt;
An advantage of the T-SQL-style syntax (with special prefixes) is that it provides a simple solution for avoiding identifier collisions. MySQL variables are convenient, and the user experience can be good for people already familiar with T-SQL (MSSQL or Sybase). On the other hand, prefix-based syntax can be confusing for users coming from systems other than MSSQL. The type system is perhaps too simple and can be unsafe. In general, the performance of MySQL (or MariaDB) stored procedures is not good, and stored procedures are therefore not frequently used. There is no protection against typographical errors. Unassigned user variables can be used without error (they default to NULL), which makes static checking of stored procedures impossible in this area.&lt;br /&gt;
&lt;br /&gt;
There is no way to restrict access to user-defined variables in MySQL. They cannot be protected against unintended usage. The only possible safeguard is a naming convention, since all user-defined variables share a single namespace.&lt;br /&gt;
&lt;br /&gt;
== Oracle ==&lt;br /&gt;
&lt;br /&gt;
Oracle PL/SQL allows the use of package variables. PL/SQL is based on the Ada language, and package variables act as &amp;quot;global&amp;quot; variables. They are not directly visible from SQL, but Oracle provides a reduced syntax for functions without arguments, so you typically write a wrapper:&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE PACKAGE my_package&lt;br /&gt;
    AS&lt;br /&gt;
      FUNCTION get_a RETURN NUMBER;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    /&lt;br /&gt;
    &lt;br /&gt;
    CREATE OR REPLACE PACKAGE BODY my_package&lt;br /&gt;
    AS&lt;br /&gt;
      a  NUMBER(20);&lt;br /&gt;
      &lt;br /&gt;
      FUNCTION get_a&lt;br /&gt;
      RETURN NUMBER&lt;br /&gt;
      IS&lt;br /&gt;
      BEGIN&lt;br /&gt;
        RETURN a;&lt;br /&gt;
      END get_a;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    &lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
Inside SQL, SQL identifiers take precedence. Inside non-SQL commands (such as CALL or PL/SQL blocks), package identifiers take precedence.&lt;br /&gt;
&lt;br /&gt;
Oracle allows both syntaxes for calling a function with zero arguments:&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a() FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
This reduces the risk of naming collisions. Package variables persist for the duration of a session.&lt;br /&gt;
&lt;br /&gt;
Another possibility is to use variables in SQL*Plus (similar to `psql` variables, but with the ability to define the type on the server side).&lt;br /&gt;
&lt;br /&gt;
A variable is declared with the `VARIABLE` command and can be accessed in the session using the `:varname` syntax (a prior declaration step may be optional):&lt;br /&gt;
&lt;br /&gt;
    VARIABLE bv_variable_name VARCHAR2(30)&lt;br /&gt;
    BEGIN&lt;br /&gt;
      :bv_variable_name := &#039;Some Value&#039;;&lt;br /&gt;
    END;&lt;br /&gt;
    &lt;br /&gt;
    SELECT column_name&lt;br /&gt;
      FROM   table_name&lt;br /&gt;
     WHERE  column_name = :bv_variable_name;&lt;br /&gt;
&lt;br /&gt;
== DB2 ==&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;user-defined global variables&amp;quot; in DB2 are similar to the proposed PostgreSQL feature. The main difference is in the access rights: DB2 uses `READ` and `WRITE`, while PostgreSQL uses `SELECT` and `UPDATE`. Because PostgreSQL already uses the `SET` command for GUCs, the `LET` command was introduced in the proposal (DB2 uses `SET`).&lt;br /&gt;
&lt;br /&gt;
Variables are visible in all sessions, but their values are private to each session. Variables are not transactional. Their usage is broader than in the proposal: they can be changed by `SET`, `SELECT INTO`, or used as OUT parameters of procedures. The search path (or a similar mechanism) applies to variables as well, but variables have lower priority than tables or columns.&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE myCounter INT DEFAULT 01;&lt;br /&gt;
    SELECT EMPNO, LASTNAME, CASE WHEN myCounter = 1 THEN SALARY ELSE NULL END&lt;br /&gt;
    FROM EMPLOYEE WHERE WORKDEPT = &#039;A00&#039;;&lt;br /&gt;
    SET myCounter = 29;&lt;br /&gt;
&lt;br /&gt;
There also appear to be other kinds of variables, accessed using the function `GETVARIABLE(&#039;name&#039;, &#039;default&#039;)`. These are very similar to PostgreSQL GUCs and the `current_setting` function. Such variables can be set via the connection string, are of type `VARCHAR`, and up to 10 values are allowed. Built-in session variables (configuration) can also be accessed using `GETVARIABLE`.&lt;br /&gt;
&lt;br /&gt;
== MSSQL (T-SQL) ==&lt;br /&gt;
&lt;br /&gt;
MSSQL provides two types of variables:&lt;br /&gt;
&lt;br /&gt;
* Global variables — accessed with the `@@varname` syntax. These are similar to GUCs and provide state information such as `@@ERROR`, `@@ROWCOUNT`, or `@@IDENTITY`. &lt;br /&gt;
* Local variables — accessed with the `@varname` syntax. These must be declared with the `DECLARE` command before use. Their scope is limited to the batch, procedure, or function where they are executed.&lt;br /&gt;
&lt;br /&gt;
    DECLARE @TestVariable AS VARCHAR(100)&lt;br /&gt;
    SET @TestVariable = &#039;Think Green&#039;&lt;br /&gt;
    GO&lt;br /&gt;
    PRINT @TestVariable&lt;br /&gt;
&lt;br /&gt;
This script fails because `PRINT` is executed in a different batch. Therefore, it seems MSSQL does not truly support session variables.&lt;br /&gt;
&lt;br /&gt;
There are also mechanisms similar to PostgreSQL&#039;s custom GUCs and the use of `current_setting` and `set_config` functions. In general, MSSQL is fairly primitive in this area.&lt;br /&gt;
&lt;br /&gt;
    EXEC sp_set_session_context &#039;user_id&#039;, 4;&lt;br /&gt;
    SELECT SESSION_CONTEXT(N&#039;user_id&#039;);&lt;br /&gt;
&lt;br /&gt;
= ToDo =&lt;br /&gt;
&lt;br /&gt;
* SECURITY LABEL support — enhance the `dummy_seclabel` module and the `sepgsql` extension. Update PostgreSQL documentation and ensure `SecLabelSupportsObjectType` includes session variables.&lt;/div&gt;</summary>
		<author><name>Okbobcz</name></author>
	</entry>
	<entry>
		<id>https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41853</id>
		<title>Implementation of declarative catalog session variables</title>
		<link rel="alternate" type="text/html" href="https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41853"/>
		<updated>2025-09-30T05:29:43Z</updated>

		<summary type="html">&lt;p&gt;Okbobcz: /* Introduction */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Session variables are database objects that can hold a value across multiple queries inside a session. The design of session variables varies widely, and many databases implement more than one type of session variable.&lt;br /&gt;
&lt;br /&gt;
The SQL/PSM standard introduces modules and module-level variables. However, this part of the standard has not been implemented in any widely used product. As a result, each database system has developed its own proprietary design, syntax, and feature set.&lt;br /&gt;
&lt;br /&gt;
Session variables are often part of the stored procedure environment, but there are exceptions --- for example, MySQL session variables or client-side session variables in tools like psql. In most systems, session variables do not participate in transactions: once set, their value persists until explicitly changed, even if the surrounding transaction is rolled back. Conceptually, they behave much like variables in general-purpose programming languages.&lt;br /&gt;
&lt;br /&gt;
Because there is no common design, comparisons across systems are difficult. Each implementation has its own advantages and disadvantages, and in some cases a single system supports more than one kind of session variable.&lt;br /&gt;
&lt;br /&gt;
Kinds of session variables:&lt;br /&gt;
* client side (psql, pgadmin, ...) - mostly similar to shell variables (based on string operations)&lt;br /&gt;
* server side&lt;br /&gt;
** declarative&lt;br /&gt;
*** created only for batch (T-SQL)&lt;br /&gt;
*** created as an database object (or part of database object) (Oracle, DB2)&lt;br /&gt;
** created by usage (MySQL)&lt;br /&gt;
** with priprietary syntax for identifiers (MySQL, T-SQL)&lt;br /&gt;
** with ANSI SQL syntax for identifiers (Oracle, DB2)&lt;br /&gt;
&lt;br /&gt;
Possible usage of session variables:&lt;br /&gt;
&lt;br /&gt;
* usage of session variables as global variables in PL (tracing, debugging, hacking),&lt;br /&gt;
* holding configuration (for PL),&lt;br /&gt;
* holding some more used data in interactive session,&lt;br /&gt;
* holding credentials for some RLS and data securing,&lt;br /&gt;
* helping with migration from others systems (mainly from Oracle),&lt;br /&gt;
* allow anonymous block parametrization (only postgres).&lt;br /&gt;
* session variables are writeable on read only hot standby (postgres)&lt;br /&gt;
&lt;br /&gt;
Session variables holds a values like temporary tables. What are differences between temporary tables and variables?:&lt;br /&gt;
&lt;br /&gt;
* variables holds a values, tables holds a set of rows,&lt;br /&gt;
* syntax for usage variables is shorter (more friendly for interactive work), access (read, write) is faster,&lt;br /&gt;
* the content of session variables is generally non transactional, the content of temporary tables is transactional,&lt;br /&gt;
* the content of session variables is just one value or NULL (no MVCC, no VACUUM),&lt;br /&gt;
* temporary tables are tables still (transactional, MVCC), update is expensive, under one transaction repeated updates makes lot of dead tuples,&lt;br /&gt;
* temporary tables are not cleaned by autovacuum (and inside function is not possible to execute VACUUM), &lt;br /&gt;
* Postgres doesn&#039;t support global temporary tables, so usage of temporary table for just few values or value is very expensive (catalogue bloat).&lt;br /&gt;
&lt;br /&gt;
= SQL/PSM =&lt;br /&gt;
&lt;br /&gt;
The SQL standard introduces the concept of modules, which can be associated with schemas (partially). Variables in modules behave like PL/pgSQL variables but are only local. The only objects allowed at the module level are temporary tables, which can be accessed only from routines assigned to that module. Modules can encapsulate private objects (functions, procedure, temp tables), that are not visible outside the module. Inside the module, a  private objects shadows other objects. What I know the modules (and generally SQL/PSM) doesn&#039;t cover a deployment of code, so there is not similarity with PostgreSQL extensions. SQL/PSM Modules are modules like modules from modular languages like Modula2 or Oberon. There is some similarity with Postgres schemas (both are containers) with significant differences (there is not a concept of SEARCH_PATH (on PSM side) or there is not a concept of private or public objects (on Postgres schema side)).&lt;br /&gt;
&lt;br /&gt;
SQL/PSM variables are not transactional (they are used exactly like in PL/pgSQL). The precedence between variables and columns is not specified.&lt;br /&gt;
&lt;br /&gt;
= Implementation Challenges =&lt;br /&gt;
&lt;br /&gt;
* Possible collisions between session variables and other database objects.&lt;br /&gt;
* Memory cleanup after dropping a session variable, especially when DDL operations can be reverted (Postgres has not support for global temp objects).&lt;br /&gt;
* Memory invalidation: if a variable is dropped by one session, other sessions should not continue using it (Postgres has not support for global temp objects).&lt;br /&gt;
&lt;br /&gt;
= Proposed Implementations =&lt;br /&gt;
&lt;br /&gt;
It is clear that no single design is perfect for all use cases. MySQL’s approach is convenient and useful for interactive work, but it cannot be used for storing sensitive data and is considered unsafe. PostgreSQL GUCs are excellent for configuration, but they are not well suited for interactive use and are very limited when used as global variables in PL code.&lt;br /&gt;
&lt;br /&gt;
Because of these trade-offs, no single design is sufficient. In practice, having multiple designs within a single system could support a broader range of use cases more effectively.&lt;br /&gt;
&lt;br /&gt;
== Design session variables as database global temporary typed objects with ANSI SQL syntax for identifiers ==&lt;br /&gt;
&lt;br /&gt;
Author: Pavel Stehule &amp;lt;pavel.stehule@gmail.com&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I searched for a solution that could work well with the specific PostgreSQL environment:&lt;br /&gt;
&lt;br /&gt;
* PostgreSQL has more than one widely used stored procedure language: PL/pgSQL, PL/Python, or PL/Perl.&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL is a reduced version of PL/SQL, and PL/SQL is a modified ADA language. I am pretty happy with the current complexity of PL/pgSQL --- it is a domain-specific language that works well. It is very simple and easy to learn. PL/pgSQL has no packages or modules; instead PostgreSQL schemas are used. But schemas are database objects in their own right, independent of PL.&lt;br /&gt;
&lt;br /&gt;
* PostgreSQL already has a code container --- the schema. I don&#039;t want to introduce another container object (modules), as this would overlap heavily with schemas. Implementing modules for PL/pgSQL could be useful, but doing it properly would be quite complex. The main problem is the concept of public and private objects --- PostgreSQL does not have this. Don&#039;t forget: every PL/pgSQL expression is a SQL expression, so public/private visibility would first need to be implemented internally in PostgreSQL. That would be redundant (and possibly in conflict) with SEARCH_PATH.&lt;br /&gt;
&lt;br /&gt;
So I wanted a design that supports multiple PL languages, does not add complexity to the relatively simple PL/pgSQL, and does not duplicate functionality already available.  &lt;br /&gt;
&lt;br /&gt;
I wrote a larger application in PL/pgSQL. To maintain it, I created plpgsql_lint (later renamed plpgsql_check). So I am always looking for solutions that do not block static code analysis. Static analysis requires persistent objects, which naturally leads to designing session variables as database objects (with their own system catalog).  &lt;br /&gt;
When session variables are database objects, ACLs can be applied naturally. Oracle package variables are well protected by package scope. PostgreSQL, however, has no schema scope --- schema locality is controlled by the SEARCH_PATH GUC, and introducing another mechanism would be messy. But with ACLs, variable contents can also be secured:&lt;br /&gt;
&lt;br /&gt;
    CREATE TEMP VARIABLE x AS int;&lt;br /&gt;
     &lt;br /&gt;
    LET x = 10;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(x);&lt;br /&gt;
    END;&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    SELECT VARIABLE(x);&lt;br /&gt;
&lt;br /&gt;
==== Collisions between column and variable identifiers ====&lt;br /&gt;
&lt;br /&gt;
There is a risk of identifier collisions between variables and columns. With ANSI SQL syntax for session variable identifiers, the problem is how to distinguish variables from columns. This is a known issue in PL/pgSQL and PL/SQL and can be solved either by prioritization or by prohibiting collisions.  &lt;br /&gt;
&lt;br /&gt;
Prohibiting collisions has largely solved this problem in PL/pgSQL. Unfortunately, session variables are global objects, so collisions can occur in any query. A poorly named variable could break queries unexpectedly. Prioritization also has problems --- a variable or a column could be used silently when the other was intended.&lt;br /&gt;
&lt;br /&gt;
    CREATE TABLE tab(a int);&lt;br /&gt;
    CREATE VARIABLE a AS int;&lt;br /&gt;
    &lt;br /&gt;
    SELECT a FROM tab; -- with disallowed collisions, this query fails when a variable named &amp;quot;a&amp;quot; exists&lt;br /&gt;
    &lt;br /&gt;
    LET a = 1;&lt;br /&gt;
    DELETE FROM tab WHERE a = 1; -- with higher priority for variables, all rows could be deleted&lt;br /&gt;
    SELECT a FROM tab; -- with higher priority for columns, the variable is ignored&lt;br /&gt;
&lt;br /&gt;
Even with collision prohibition, mistakes are still possible:&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE _id AS int;&lt;br /&gt;
    LET _id = 10;&lt;br /&gt;
    CREATE TABLE foo(id int);&lt;br /&gt;
    DELETE FROM foo WHERE _id = 10; -- just a typo, but all rows are deleted&lt;br /&gt;
&lt;br /&gt;
These problems are not new. Developers in PL/pgSQL or PL/SQL know them, but the scope of risk is normally limited to stored procedures. After many discussions and designs, I introduced &amp;quot;variable fences&amp;quot; (the syntax VARIABLE(varname)). Inside any query, variables can be used only inside a variable fence. This completely solves collisions and reduces the chance of human error. Currently, fences are required everywhere in queries and expressions. In the future, they might be made optional inside PL/pgSQL, to reduce overhead when porting Oracle applications. This could be controlled by a new plpgsql.extra_checks setting.&lt;br /&gt;
&lt;br /&gt;
A variable fence can collide with a user-defined function named `variable`. This is similar to keyword conflicts such as CUBE. It can be resolved by schema-qualifying or quoting:&lt;br /&gt;
&lt;br /&gt;
    SELECT public.variable(...)&lt;br /&gt;
    SELECT &amp;quot;variable&amp;quot;(...)&lt;br /&gt;
&lt;br /&gt;
Why not use T-SQL (MSSQL, MySQL) syntax for session variables? There are several reasons – some objective, one subjective:&lt;br /&gt;
&lt;br /&gt;
* There is a collision with custom operators. Operators `@` and `@@` are already used, and `@@` is common:&lt;br /&gt;
&lt;br /&gt;
    (2025-09-28 06:57:30) postgres=# create table tab(a int);&lt;br /&gt;
    CREATE TABLE&lt;br /&gt;
    (2025-09-28 06:57:42) postgres=# insert into tab values(10);&lt;br /&gt;
    INSERT 0 1&lt;br /&gt;
    (2025-09-28 06:57:50) postgres=# select @a from tab;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │       10 │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
We could safely use only `:` as a prefix, but that would conflict with psql variables. Similarly, `$` would conflict with query parameters.&lt;br /&gt;
&lt;br /&gt;
* T-SQL variable names look strange in PostgreSQL (in queries and in PL/pgSQL). PostgreSQL uses only ANSI/SQL identifiers, and PL/pgSQL follows the same rules. PostgreSQL is consistent here, and I see little motivation to introduce a new, non-standard syntax (especially since it could cause compatibility issues).&lt;br /&gt;
&lt;br /&gt;
Note: There was also a proposal to use table syntax for variables (similar to T-SQL in-memory table variables, but restricted to a single row). This is very effective for avoiding collisions, but I dislike it. It complicates simple expressions, adds inconsistency, and looks odd for scalar variables (though it could be useful for composite variables). This design doesn&#039;t solve all possible problems - variables can be shadowed by tables still, because we have SEARCH_PATH (but this is known issue):&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(var); -- variable is not a table&lt;br /&gt;
    END&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      -- variable as a table (I don&#039;t like it)&lt;br /&gt;
      -- can we use DML? How many rows can it hold?&lt;br /&gt;
      -- is the value a row or not?&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, (SELECT var FROM var);&lt;br /&gt;
    END&lt;br /&gt;
    $$&lt;br /&gt;
&lt;br /&gt;
I am not against introducing in-memory tables --- or more correctly, global temporary tables. A well-designed implementation could be very useful. But this is a different feature and should be designed separately.  &lt;br /&gt;
&lt;br /&gt;
There are also performance considerations: PL/pgSQL can use simple expression evaluation when no tables are referenced. This optimization would need to change, which touches sensitive code. It would also be inconsistent.  &lt;br /&gt;
&lt;br /&gt;
I would like global temporary tables (which PostgreSQL currently lacks). Tables should behave like tables, and variables should behave like variables. The natural mental model for session variables comes from variables in PL languages. PostgreSQL internally supports *transition tables*, and I can imagine allowing these outside of triggers. That would be valid, but again, it is a different feature.  &lt;br /&gt;
&lt;br /&gt;
Implementing declared tables inside PL/pgSQL would be simpler from a high-level design perspective (interaction between SQL tables and PL/pgSQL is already well defined), but difficult from a low-level perspective (statistics, optimizer data, etc. --- the same issues as with global temporary tables, unless we add fake proxy local temp tables).&lt;br /&gt;
&lt;br /&gt;
   CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
   AS $$&lt;br /&gt;
   -- not implemented yet, not part of this proposal&lt;br /&gt;
   DECLARE t TABLE(a int, b int); -- this table is invisible outside fx&lt;br /&gt;
   BEGIN&lt;br /&gt;
     INSERT INTO t VALUES(10,20);&lt;br /&gt;
     INSERT INTO t VALUES(30,40);&lt;br /&gt;
     RAISE NOTICE &#039;%&#039;, (SELECT count(*) FROM t);&lt;br /&gt;
   END;&lt;br /&gt;
   $$ LANGUAGE plpgsql;&lt;br /&gt;
&lt;br /&gt;
Some data languages define relational variables, but this concept has no overlap with the common understanding of session variables.&lt;br /&gt;
&lt;br /&gt;
= Variabes in PostgreSQL (as of v18) =&lt;br /&gt;
&lt;br /&gt;
PostgreSQL provides relatively advanced client-side session variables in psql --- these variables use the `:` prefix. The implementation is pretty much straightforward: `psql`&#039;s parser reads tokens from input and, when it encounters a token related to a variable, replaces it with the variable&#039;s value. The syntax supports literal and identifier escaping, and variables can be used as arguments of the `\bind` command.&lt;br /&gt;
&lt;br /&gt;
`psql` variables are typeless client-side variables, available only inside `psql` or `pgbench`.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:40:18) postgres=# \set myvar ahoj&lt;br /&gt;
    (2025-09-25 09:40:33) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:14) postgres=# select :&#039;myvar&#039;;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:24) postgres=# select $1 \bind :myvar \g&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
The `\set` command allows only simple string concatenation, but it also supports execution of shell commands. When a query is executed with the `\gset` command, its result can be stored in a `psql` variable.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:41:40) postgres=# \set ctime `date`&lt;br /&gt;
    (2025-09-25 09:43:55) postgres=# \echo :ctime&lt;br /&gt;
    Thu Sep 25 09:43:55 CEST 2025&lt;br /&gt;
&lt;br /&gt;
The `\set` command is fairly limited, making complex operations unreadable or non-portable.&lt;br /&gt;
&lt;br /&gt;
This mechanism is good enough for writing tests, but it is not generally available (most users do not need it), and it is not accessible from the server side (e.g., from stored procedures). There is no simple way to access `psql` variables from an anonymous block --- a proxy custom GUC variable must be used:&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:58:38) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    (2025-09-25 09:58:43) postgres=# select set_config(&#039;myvars.myvar&#039;, :&#039;myvar&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ ahoj       │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 10:00:27) postgres=# do $$ begin raise notice &#039;%&#039;, current_setting(&#039;myvars.myvar&#039;); end $$;&lt;br /&gt;
    NOTICE:  ahoj&lt;br /&gt;
    DO&lt;br /&gt;
&lt;br /&gt;
PostgreSQL also has configuration variables (GUC - Grand Unified Configuration). These are primarily designed for PostgreSQL and extension configuration, but they can be used as session variables. GUC variables can be set with the `SET` command or the `set_config` function, and read with `SHOW` or `current_setting`.&lt;br /&gt;
&lt;br /&gt;
GUC variables are meant for holding configuration parameters. When created through the internal API, they can be restricted to superusers, hidden from regular users, and assigned specific types (boolean, memory, number, interval, string, enum) with validation. This type system is separate from the SQL type system and is initialized before PostgreSQL’s SQL types.&lt;br /&gt;
&lt;br /&gt;
Variables created from SQL must be &amp;quot;custom&amp;quot; variables, using a prefix in their name --- these are always strings. Such custom GUCs are often used as a workaround for missing server-side session variables in PostgreSQL, but they are typeless, unprotected, and unsafe.&lt;br /&gt;
&lt;br /&gt;
A notable feature of PostgreSQL GUCs (built-in or custom) is that they can have different scopes: transaction, session, or function execution. They can also be assigned default values at specific events --- configuration loading, role login, database login, or function start. These features are useful for configuration, but problematic when GUCs are repurposed as session variables.&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
    RETURNS void AS $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, current_setting(&#039;my.x&#039;);&lt;br /&gt;
    END $$ LANGUAGE plpgsql SET my.x = 20;&lt;br /&gt;
    CREATE FUNCTION&lt;br /&gt;
    (2025-09-27 06:10:37) postgres=# SET my.x = 0; SELECT fx();&lt;br /&gt;
    SET&lt;br /&gt;
    NOTICE:  20&lt;br /&gt;
    ┌────┐&lt;br /&gt;
    │ fx │&lt;br /&gt;
    ╞════╡&lt;br /&gt;
    │    │&lt;br /&gt;
    └────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    (2025-09-27 06:10:39) postgres=# SHOW my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 0    │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
GUC variables are transactional, and this behavior cannot be disabled.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-27 06:17:31) postgres=# set my.x = 10;&lt;br /&gt;
    SET&lt;br /&gt;
    (2025-09-27 06:19:42) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:19:46) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, true);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:19:55) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:01) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:20:11) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:13) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:20:38) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:45) postgres=# rollback;&lt;br /&gt;
    ROLLBACK&lt;br /&gt;
    (2025-09-27 06:20:52) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:55) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:22:36) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:22:39) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:22:42) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
As a result, there is no reliable way to store details such as error information in custom GUCs.&lt;br /&gt;
&lt;br /&gt;
= Session Variables in Other Systems =&lt;br /&gt;
&lt;br /&gt;
This section surveys how session variables are implemented in several widely used database systems. Each subsection includes code examples to illustrate syntax, behaviour, and scope.&lt;br /&gt;
&lt;br /&gt;
== MySQL ==&lt;br /&gt;
&lt;br /&gt;
Session variables in MySQL have syntax similar to the better-known T-SQL variables (though other details are MySQL-specific). MySQL distinguishes two kinds of variables:&lt;br /&gt;
&lt;br /&gt;
* system variables - referenced using the `@@` prefix (or `@@scope.name`)&lt;br /&gt;
* user-defined variables - referenced using the `@` prefix&lt;br /&gt;
&lt;br /&gt;
System variables are modified with the `SET` statement. The `SET` statement accepts the modifiers `GLOBAL`, `SESSION`, or `PERSIST`. Some variables can only be set using the `GLOBAL` or `SESSION` modifier; when the variable name is specified with the `@@` prefix, it is not necessary to explicitly indicate the scope. System variables are defined by MySQL or by MySQL plugins.&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL some_config = 100;&lt;br /&gt;
    SET @@same_config = 100;&lt;br /&gt;
    &lt;br /&gt;
    SET SESSION sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@SESSION.sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
&lt;br /&gt;
Persistent values are stored in `mysqld-auto.cnf` (in the server data directory).&lt;br /&gt;
&lt;br /&gt;
Resetting system variables can be done by assigning DEFAULT or by copying from the GLOBAL namespace:&lt;br /&gt;
&lt;br /&gt;
    SET @@sql_mode = DEFAULT;&lt;br /&gt;
    SET @@sql_mode = @@GLOBAL.sql_mode&lt;br /&gt;
&lt;br /&gt;
PostgreSQL equivalents:&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL ; -- there is not similar command in Postgres&lt;br /&gt;
    SET SESSION ; -- SET&lt;br /&gt;
    SET PERSIST ; -- ALTER SYSTEM; SELECT pg_reload_conf();&lt;br /&gt;
    SELECT @@var; -- SELECT current_setting(&#039;var);&lt;br /&gt;
&lt;br /&gt;
User-defined variables are created by assignment:&lt;br /&gt;
&lt;br /&gt;
    SET @var = expr [, @var2 = expr [ ... ]];&lt;br /&gt;
&lt;br /&gt;
User-defined variables can only be of type integer, decimal, float, binary, non-binary string, or NULL. Casting between these types is implicit and hidden. User-defined variables cannot be used as table or column names.&lt;br /&gt;
&lt;br /&gt;
An advantage of the T-SQL-style syntax (with special prefixes) is that it provides a simple solution for avoiding identifier collisions. MySQL variables are convenient, and the user experience can be good for people already familiar with T-SQL (MSSQL or Sybase). On the other hand, prefix-based syntax can be confusing for users coming from systems other than MSSQL. The type system is perhaps too simple and can be unsafe. In general, the performance of MySQL (or MariaDB) stored procedures is not good, and stored procedures are therefore not frequently used. There is no protection against typographical errors. Unassigned user variables can be used without error (they default to NULL), which makes static checking of stored procedures impossible in this area.&lt;br /&gt;
&lt;br /&gt;
There is no way to restrict access to user-defined variables in MySQL. They cannot be protected against unintended usage. The only possible safeguard is a naming convention, since all user-defined variables share a single namespace.&lt;br /&gt;
&lt;br /&gt;
== Oracle ==&lt;br /&gt;
&lt;br /&gt;
Oracle PL/SQL allows the use of package variables. PL/SQL is based on the Ada language, and package variables act as &amp;quot;global&amp;quot; variables. They are not directly visible from SQL, but Oracle provides a reduced syntax for functions without arguments, so you typically write a wrapper:&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE PACKAGE my_package&lt;br /&gt;
    AS&lt;br /&gt;
      FUNCTION get_a RETURN NUMBER;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    /&lt;br /&gt;
    &lt;br /&gt;
    CREATE OR REPLACE PACKAGE BODY my_package&lt;br /&gt;
    AS&lt;br /&gt;
      a  NUMBER(20);&lt;br /&gt;
      &lt;br /&gt;
      FUNCTION get_a&lt;br /&gt;
      RETURN NUMBER&lt;br /&gt;
      IS&lt;br /&gt;
      BEGIN&lt;br /&gt;
        RETURN a;&lt;br /&gt;
      END get_a;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    &lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
Inside SQL, SQL identifiers take precedence. Inside non-SQL commands (such as CALL or PL/SQL blocks), package identifiers take precedence.&lt;br /&gt;
&lt;br /&gt;
Oracle allows both syntaxes for calling a function with zero arguments:&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a() FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
This reduces the risk of naming collisions. Package variables persist for the duration of a session.&lt;br /&gt;
&lt;br /&gt;
Another possibility is to use variables in SQL*Plus (similar to `psql` variables, but with the ability to define the type on the server side).&lt;br /&gt;
&lt;br /&gt;
A variable is declared with the `VARIABLE` command and can be accessed in the session using the `:varname` syntax (a prior declaration step may be optional):&lt;br /&gt;
&lt;br /&gt;
    VARIABLE bv_variable_name VARCHAR2(30)&lt;br /&gt;
    BEGIN&lt;br /&gt;
      :bv_variable_name := &#039;Some Value&#039;;&lt;br /&gt;
    END;&lt;br /&gt;
    &lt;br /&gt;
    SELECT column_name&lt;br /&gt;
      FROM   table_name&lt;br /&gt;
     WHERE  column_name = :bv_variable_name;&lt;br /&gt;
&lt;br /&gt;
== DB2 ==&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;user-defined global variables&amp;quot; in DB2 are similar to the proposed PostgreSQL feature. The main difference is in the access rights: DB2 uses `READ` and `WRITE`, while PostgreSQL uses `SELECT` and `UPDATE`. Because PostgreSQL already uses the `SET` command for GUCs, the `LET` command was introduced in the proposal (DB2 uses `SET`).&lt;br /&gt;
&lt;br /&gt;
Variables are visible in all sessions, but their values are private to each session. Variables are not transactional. Their usage is broader than in the proposal: they can be changed by `SET`, `SELECT INTO`, or used as OUT parameters of procedures. The search path (or a similar mechanism) applies to variables as well, but variables have lower priority than tables or columns.&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE myCounter INT DEFAULT 01;&lt;br /&gt;
    SELECT EMPNO, LASTNAME, CASE WHEN myCounter = 1 THEN SALARY ELSE NULL END&lt;br /&gt;
    FROM EMPLOYEE WHERE WORKDEPT = &#039;A00&#039;;&lt;br /&gt;
    SET myCounter = 29;&lt;br /&gt;
&lt;br /&gt;
There also appear to be other kinds of variables, accessed using the function `GETVARIABLE(&#039;name&#039;, &#039;default&#039;)`. These are very similar to PostgreSQL GUCs and the `current_setting` function. Such variables can be set via the connection string, are of type `VARCHAR`, and up to 10 values are allowed. Built-in session variables (configuration) can also be accessed using `GETVARIABLE`.&lt;br /&gt;
&lt;br /&gt;
== MSSQL (T-SQL) ==&lt;br /&gt;
&lt;br /&gt;
MSSQL provides two types of variables:&lt;br /&gt;
&lt;br /&gt;
* Global variables — accessed with the `@@varname` syntax. These are similar to GUCs and provide state information such as `@@ERROR`, `@@ROWCOUNT`, or `@@IDENTITY`. &lt;br /&gt;
* Local variables — accessed with the `@varname` syntax. These must be declared with the `DECLARE` command before use. Their scope is limited to the batch, procedure, or function where they are executed.&lt;br /&gt;
&lt;br /&gt;
    DECLARE @TestVariable AS VARCHAR(100)&lt;br /&gt;
    SET @TestVariable = &#039;Think Green&#039;&lt;br /&gt;
    GO&lt;br /&gt;
    PRINT @TestVariable&lt;br /&gt;
&lt;br /&gt;
This script fails because `PRINT` is executed in a different batch. Therefore, it seems MSSQL does not truly support session variables.&lt;br /&gt;
&lt;br /&gt;
There are also mechanisms similar to PostgreSQL&#039;s custom GUCs and the use of `current_setting` and `set_config` functions. In general, MSSQL is fairly primitive in this area.&lt;br /&gt;
&lt;br /&gt;
    EXEC sp_set_session_context &#039;user_id&#039;, 4;&lt;br /&gt;
    SELECT SESSION_CONTEXT(N&#039;user_id&#039;);&lt;br /&gt;
&lt;br /&gt;
= ToDo =&lt;br /&gt;
&lt;br /&gt;
* SECURITY LABEL support — enhance the `dummy_seclabel` module and the `sepgsql` extension. Update PostgreSQL documentation and ensure `SecLabelSupportsObjectType` includes session variables.&lt;/div&gt;</summary>
		<author><name>Okbobcz</name></author>
	</entry>
	<entry>
		<id>https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41852</id>
		<title>Implementation of declarative catalog session variables</title>
		<link rel="alternate" type="text/html" href="https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41852"/>
		<updated>2025-09-30T04:19:49Z</updated>

		<summary type="html">&lt;p&gt;Okbobcz: /* Introduction */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Session variables are database objects that can hold a value across multiple queries inside a session. The design of session variables varies widely, and many databases implement more than one type of session variable.&lt;br /&gt;
&lt;br /&gt;
The SQL/PSM standard introduces modules and module-level variables. However, this part of the standard has not been implemented in any widely used product. As a result, each database system has developed its own proprietary design, syntax, and feature set.&lt;br /&gt;
&lt;br /&gt;
Session variables are often part of the stored procedure environment, but there are exceptions --- for example, MySQL session variables or client-side session variables in tools like psql. In most systems, session variables do not participate in transactions: once set, their value persists until explicitly changed, even if the surrounding transaction is rolled back. Conceptually, they behave much like variables in general-purpose programming languages.&lt;br /&gt;
&lt;br /&gt;
Because there is no common design, comparisons across systems are difficult. Each implementation has its own advantages and disadvantages, and in some cases a single system supports more than one kind of session variable.&lt;br /&gt;
&lt;br /&gt;
Kinds of session variables:&lt;br /&gt;
* client side (psql, pgadmin, ...) - mostly similar to shell variables (based on string operations)&lt;br /&gt;
* server side&lt;br /&gt;
** declarative&lt;br /&gt;
*** created only for batch (T-SQL)&lt;br /&gt;
*** created as an database object (or part of database object) (Oracle, DB2)&lt;br /&gt;
** created by usage (MySQL)&lt;br /&gt;
** with priprietary syntax for identifiers (MySQL, T-SQL)&lt;br /&gt;
** with ANSI SQL syntax for identifiers (Oracle, DB2)&lt;br /&gt;
&lt;br /&gt;
Possible usage of session variables:&lt;br /&gt;
&lt;br /&gt;
* usage of session variables as global variables in PL (tracing, debugging, hacking),&lt;br /&gt;
* holding configuration (for PL),&lt;br /&gt;
* holding some more used data in interactive session,&lt;br /&gt;
* holding credentials for some RLS and data securing,&lt;br /&gt;
* helping with migration from others systems (mainly from Oracle),&lt;br /&gt;
* allow anonymous block parametrization (only postgres).&lt;br /&gt;
* session variables are writeable on read only hot standby (postgres)&lt;br /&gt;
&lt;br /&gt;
Session variables holds a values like temporary tables. What are differences between temporary tables and variables?:&lt;br /&gt;
&lt;br /&gt;
* variables holds a values, tables holds a set of rows,&lt;br /&gt;
* syntax for usage variables is shorter (more friendly for interactive work), access (read, write) is faster,&lt;br /&gt;
* the content of session variables is generally non transactional, the content of temporary tables is transactional,&lt;br /&gt;
* the content of session variables is just one value or NULL (no MVCC, no VACUUM),&lt;br /&gt;
* Postgres doesn&#039;t support global temporary tables, so usage of temporary table for just few values or value is very expensive (catalogue bloat).&lt;br /&gt;
&lt;br /&gt;
= SQL/PSM =&lt;br /&gt;
&lt;br /&gt;
The SQL standard introduces the concept of modules, which can be associated with schemas (partially). Variables in modules behave like PL/pgSQL variables but are only local. The only objects allowed at the module level are temporary tables, which can be accessed only from routines assigned to that module. Modules can encapsulate private objects (functions, procedure, temp tables), that are not visible outside the module. Inside the module, a  private objects shadows other objects. What I know the modules (and generally SQL/PSM) doesn&#039;t cover a deployment of code, so there is not similarity with PostgreSQL extensions. SQL/PSM Modules are modules like modules from modular languages like Modula2 or Oberon. There is some similarity with Postgres schemas (both are containers) with significant differences (there is not a concept of SEARCH_PATH (on PSM side) or there is not a concept of private or public objects (on Postgres schema side)).&lt;br /&gt;
&lt;br /&gt;
SQL/PSM variables are not transactional (they are used exactly like in PL/pgSQL). The precedence between variables and columns is not specified.&lt;br /&gt;
&lt;br /&gt;
= Implementation Challenges =&lt;br /&gt;
&lt;br /&gt;
* Possible collisions between session variables and other database objects.&lt;br /&gt;
* Memory cleanup after dropping a session variable, especially when DDL operations can be reverted (Postgres has not support for global temp objects).&lt;br /&gt;
* Memory invalidation: if a variable is dropped by one session, other sessions should not continue using it (Postgres has not support for global temp objects).&lt;br /&gt;
&lt;br /&gt;
= Proposed Implementations =&lt;br /&gt;
&lt;br /&gt;
It is clear that no single design is perfect for all use cases. MySQL’s approach is convenient and useful for interactive work, but it cannot be used for storing sensitive data and is considered unsafe. PostgreSQL GUCs are excellent for configuration, but they are not well suited for interactive use and are very limited when used as global variables in PL code.&lt;br /&gt;
&lt;br /&gt;
Because of these trade-offs, no single design is sufficient. In practice, having multiple designs within a single system could support a broader range of use cases more effectively.&lt;br /&gt;
&lt;br /&gt;
== Design session variables as database global temporary typed objects with ANSI SQL syntax for identifiers ==&lt;br /&gt;
&lt;br /&gt;
Author: Pavel Stehule &amp;lt;pavel.stehule@gmail.com&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I searched for a solution that could work well with the specific PostgreSQL environment:&lt;br /&gt;
&lt;br /&gt;
* PostgreSQL has more than one widely used stored procedure language: PL/pgSQL, PL/Python, or PL/Perl.&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL is a reduced version of PL/SQL, and PL/SQL is a modified ADA language. I am pretty happy with the current complexity of PL/pgSQL --- it is a domain-specific language that works well. It is very simple and easy to learn. PL/pgSQL has no packages or modules; instead PostgreSQL schemas are used. But schemas are database objects in their own right, independent of PL.&lt;br /&gt;
&lt;br /&gt;
* PostgreSQL already has a code container --- the schema. I don&#039;t want to introduce another container object (modules), as this would overlap heavily with schemas. Implementing modules for PL/pgSQL could be useful, but doing it properly would be quite complex. The main problem is the concept of public and private objects --- PostgreSQL does not have this. Don&#039;t forget: every PL/pgSQL expression is a SQL expression, so public/private visibility would first need to be implemented internally in PostgreSQL. That would be redundant (and possibly in conflict) with SEARCH_PATH.&lt;br /&gt;
&lt;br /&gt;
So I wanted a design that supports multiple PL languages, does not add complexity to the relatively simple PL/pgSQL, and does not duplicate functionality already available.  &lt;br /&gt;
&lt;br /&gt;
I wrote a larger application in PL/pgSQL. To maintain it, I created plpgsql_lint (later renamed plpgsql_check). So I am always looking for solutions that do not block static code analysis. Static analysis requires persistent objects, which naturally leads to designing session variables as database objects (with their own system catalog).  &lt;br /&gt;
When session variables are database objects, ACLs can be applied naturally. Oracle package variables are well protected by package scope. PostgreSQL, however, has no schema scope --- schema locality is controlled by the SEARCH_PATH GUC, and introducing another mechanism would be messy. But with ACLs, variable contents can also be secured:&lt;br /&gt;
&lt;br /&gt;
    CREATE TEMP VARIABLE x AS int;&lt;br /&gt;
     &lt;br /&gt;
    LET x = 10;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(x);&lt;br /&gt;
    END;&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    SELECT VARIABLE(x);&lt;br /&gt;
&lt;br /&gt;
==== Collisions between column and variable identifiers ====&lt;br /&gt;
&lt;br /&gt;
There is a risk of identifier collisions between variables and columns. With ANSI SQL syntax for session variable identifiers, the problem is how to distinguish variables from columns. This is a known issue in PL/pgSQL and PL/SQL and can be solved either by prioritization or by prohibiting collisions.  &lt;br /&gt;
&lt;br /&gt;
Prohibiting collisions has largely solved this problem in PL/pgSQL. Unfortunately, session variables are global objects, so collisions can occur in any query. A poorly named variable could break queries unexpectedly. Prioritization also has problems --- a variable or a column could be used silently when the other was intended.&lt;br /&gt;
&lt;br /&gt;
    CREATE TABLE tab(a int);&lt;br /&gt;
    CREATE VARIABLE a AS int;&lt;br /&gt;
    &lt;br /&gt;
    SELECT a FROM tab; -- with disallowed collisions, this query fails when a variable named &amp;quot;a&amp;quot; exists&lt;br /&gt;
    &lt;br /&gt;
    LET a = 1;&lt;br /&gt;
    DELETE FROM tab WHERE a = 1; -- with higher priority for variables, all rows could be deleted&lt;br /&gt;
    SELECT a FROM tab; -- with higher priority for columns, the variable is ignored&lt;br /&gt;
&lt;br /&gt;
Even with collision prohibition, mistakes are still possible:&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE _id AS int;&lt;br /&gt;
    LET _id = 10;&lt;br /&gt;
    CREATE TABLE foo(id int);&lt;br /&gt;
    DELETE FROM foo WHERE _id = 10; -- just a typo, but all rows are deleted&lt;br /&gt;
&lt;br /&gt;
These problems are not new. Developers in PL/pgSQL or PL/SQL know them, but the scope of risk is normally limited to stored procedures. After many discussions and designs, I introduced &amp;quot;variable fences&amp;quot; (the syntax VARIABLE(varname)). Inside any query, variables can be used only inside a variable fence. This completely solves collisions and reduces the chance of human error. Currently, fences are required everywhere in queries and expressions. In the future, they might be made optional inside PL/pgSQL, to reduce overhead when porting Oracle applications. This could be controlled by a new plpgsql.extra_checks setting.&lt;br /&gt;
&lt;br /&gt;
A variable fence can collide with a user-defined function named `variable`. This is similar to keyword conflicts such as CUBE. It can be resolved by schema-qualifying or quoting:&lt;br /&gt;
&lt;br /&gt;
    SELECT public.variable(...)&lt;br /&gt;
    SELECT &amp;quot;variable&amp;quot;(...)&lt;br /&gt;
&lt;br /&gt;
Why not use T-SQL (MSSQL, MySQL) syntax for session variables? There are several reasons – some objective, one subjective:&lt;br /&gt;
&lt;br /&gt;
* There is a collision with custom operators. Operators `@` and `@@` are already used, and `@@` is common:&lt;br /&gt;
&lt;br /&gt;
    (2025-09-28 06:57:30) postgres=# create table tab(a int);&lt;br /&gt;
    CREATE TABLE&lt;br /&gt;
    (2025-09-28 06:57:42) postgres=# insert into tab values(10);&lt;br /&gt;
    INSERT 0 1&lt;br /&gt;
    (2025-09-28 06:57:50) postgres=# select @a from tab;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │       10 │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
We could safely use only `:` as a prefix, but that would conflict with psql variables. Similarly, `$` would conflict with query parameters.&lt;br /&gt;
&lt;br /&gt;
* T-SQL variable names look strange in PostgreSQL (in queries and in PL/pgSQL). PostgreSQL uses only ANSI/SQL identifiers, and PL/pgSQL follows the same rules. PostgreSQL is consistent here, and I see little motivation to introduce a new, non-standard syntax (especially since it could cause compatibility issues).&lt;br /&gt;
&lt;br /&gt;
Note: There was also a proposal to use table syntax for variables (similar to T-SQL in-memory table variables, but restricted to a single row). This is very effective for avoiding collisions, but I dislike it. It complicates simple expressions, adds inconsistency, and looks odd for scalar variables (though it could be useful for composite variables). This design doesn&#039;t solve all possible problems - variables can be shadowed by tables still, because we have SEARCH_PATH (but this is known issue):&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(var); -- variable is not a table&lt;br /&gt;
    END&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      -- variable as a table (I don&#039;t like it)&lt;br /&gt;
      -- can we use DML? How many rows can it hold?&lt;br /&gt;
      -- is the value a row or not?&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, (SELECT var FROM var);&lt;br /&gt;
    END&lt;br /&gt;
    $$&lt;br /&gt;
&lt;br /&gt;
I am not against introducing in-memory tables --- or more correctly, global temporary tables. A well-designed implementation could be very useful. But this is a different feature and should be designed separately.  &lt;br /&gt;
&lt;br /&gt;
There are also performance considerations: PL/pgSQL can use simple expression evaluation when no tables are referenced. This optimization would need to change, which touches sensitive code. It would also be inconsistent.  &lt;br /&gt;
&lt;br /&gt;
I would like global temporary tables (which PostgreSQL currently lacks). Tables should behave like tables, and variables should behave like variables. The natural mental model for session variables comes from variables in PL languages. PostgreSQL internally supports *transition tables*, and I can imagine allowing these outside of triggers. That would be valid, but again, it is a different feature.  &lt;br /&gt;
&lt;br /&gt;
Implementing declared tables inside PL/pgSQL would be simpler from a high-level design perspective (interaction between SQL tables and PL/pgSQL is already well defined), but difficult from a low-level perspective (statistics, optimizer data, etc. --- the same issues as with global temporary tables, unless we add fake proxy local temp tables).&lt;br /&gt;
&lt;br /&gt;
   CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
   AS $$&lt;br /&gt;
   -- not implemented yet, not part of this proposal&lt;br /&gt;
   DECLARE t TABLE(a int, b int); -- this table is invisible outside fx&lt;br /&gt;
   BEGIN&lt;br /&gt;
     INSERT INTO t VALUES(10,20);&lt;br /&gt;
     INSERT INTO t VALUES(30,40);&lt;br /&gt;
     RAISE NOTICE &#039;%&#039;, (SELECT count(*) FROM t);&lt;br /&gt;
   END;&lt;br /&gt;
   $$ LANGUAGE plpgsql;&lt;br /&gt;
&lt;br /&gt;
Some data languages define relational variables, but this concept has no overlap with the common understanding of session variables.&lt;br /&gt;
&lt;br /&gt;
= Variabes in PostgreSQL (as of v18) =&lt;br /&gt;
&lt;br /&gt;
PostgreSQL provides relatively advanced client-side session variables in psql --- these variables use the `:` prefix. The implementation is pretty much straightforward: `psql`&#039;s parser reads tokens from input and, when it encounters a token related to a variable, replaces it with the variable&#039;s value. The syntax supports literal and identifier escaping, and variables can be used as arguments of the `\bind` command.&lt;br /&gt;
&lt;br /&gt;
`psql` variables are typeless client-side variables, available only inside `psql` or `pgbench`.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:40:18) postgres=# \set myvar ahoj&lt;br /&gt;
    (2025-09-25 09:40:33) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:14) postgres=# select :&#039;myvar&#039;;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:24) postgres=# select $1 \bind :myvar \g&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
The `\set` command allows only simple string concatenation, but it also supports execution of shell commands. When a query is executed with the `\gset` command, its result can be stored in a `psql` variable.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:41:40) postgres=# \set ctime `date`&lt;br /&gt;
    (2025-09-25 09:43:55) postgres=# \echo :ctime&lt;br /&gt;
    Thu Sep 25 09:43:55 CEST 2025&lt;br /&gt;
&lt;br /&gt;
The `\set` command is fairly limited, making complex operations unreadable or non-portable.&lt;br /&gt;
&lt;br /&gt;
This mechanism is good enough for writing tests, but it is not generally available (most users do not need it), and it is not accessible from the server side (e.g., from stored procedures). There is no simple way to access `psql` variables from an anonymous block --- a proxy custom GUC variable must be used:&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:58:38) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    (2025-09-25 09:58:43) postgres=# select set_config(&#039;myvars.myvar&#039;, :&#039;myvar&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ ahoj       │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 10:00:27) postgres=# do $$ begin raise notice &#039;%&#039;, current_setting(&#039;myvars.myvar&#039;); end $$;&lt;br /&gt;
    NOTICE:  ahoj&lt;br /&gt;
    DO&lt;br /&gt;
&lt;br /&gt;
PostgreSQL also has configuration variables (GUC - Grand Unified Configuration). These are primarily designed for PostgreSQL and extension configuration, but they can be used as session variables. GUC variables can be set with the `SET` command or the `set_config` function, and read with `SHOW` or `current_setting`.&lt;br /&gt;
&lt;br /&gt;
GUC variables are meant for holding configuration parameters. When created through the internal API, they can be restricted to superusers, hidden from regular users, and assigned specific types (boolean, memory, number, interval, string, enum) with validation. This type system is separate from the SQL type system and is initialized before PostgreSQL’s SQL types.&lt;br /&gt;
&lt;br /&gt;
Variables created from SQL must be &amp;quot;custom&amp;quot; variables, using a prefix in their name --- these are always strings. Such custom GUCs are often used as a workaround for missing server-side session variables in PostgreSQL, but they are typeless, unprotected, and unsafe.&lt;br /&gt;
&lt;br /&gt;
A notable feature of PostgreSQL GUCs (built-in or custom) is that they can have different scopes: transaction, session, or function execution. They can also be assigned default values at specific events --- configuration loading, role login, database login, or function start. These features are useful for configuration, but problematic when GUCs are repurposed as session variables.&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
    RETURNS void AS $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, current_setting(&#039;my.x&#039;);&lt;br /&gt;
    END $$ LANGUAGE plpgsql SET my.x = 20;&lt;br /&gt;
    CREATE FUNCTION&lt;br /&gt;
    (2025-09-27 06:10:37) postgres=# SET my.x = 0; SELECT fx();&lt;br /&gt;
    SET&lt;br /&gt;
    NOTICE:  20&lt;br /&gt;
    ┌────┐&lt;br /&gt;
    │ fx │&lt;br /&gt;
    ╞════╡&lt;br /&gt;
    │    │&lt;br /&gt;
    └────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    (2025-09-27 06:10:39) postgres=# SHOW my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 0    │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
GUC variables are transactional, and this behavior cannot be disabled.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-27 06:17:31) postgres=# set my.x = 10;&lt;br /&gt;
    SET&lt;br /&gt;
    (2025-09-27 06:19:42) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:19:46) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, true);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:19:55) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:01) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:20:11) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:13) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:20:38) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:45) postgres=# rollback;&lt;br /&gt;
    ROLLBACK&lt;br /&gt;
    (2025-09-27 06:20:52) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:55) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:22:36) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:22:39) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:22:42) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
As a result, there is no reliable way to store details such as error information in custom GUCs.&lt;br /&gt;
&lt;br /&gt;
= Session Variables in Other Systems =&lt;br /&gt;
&lt;br /&gt;
This section surveys how session variables are implemented in several widely used database systems. Each subsection includes code examples to illustrate syntax, behaviour, and scope.&lt;br /&gt;
&lt;br /&gt;
== MySQL ==&lt;br /&gt;
&lt;br /&gt;
Session variables in MySQL have syntax similar to the better-known T-SQL variables (though other details are MySQL-specific). MySQL distinguishes two kinds of variables:&lt;br /&gt;
&lt;br /&gt;
* system variables - referenced using the `@@` prefix (or `@@scope.name`)&lt;br /&gt;
* user-defined variables - referenced using the `@` prefix&lt;br /&gt;
&lt;br /&gt;
System variables are modified with the `SET` statement. The `SET` statement accepts the modifiers `GLOBAL`, `SESSION`, or `PERSIST`. Some variables can only be set using the `GLOBAL` or `SESSION` modifier; when the variable name is specified with the `@@` prefix, it is not necessary to explicitly indicate the scope. System variables are defined by MySQL or by MySQL plugins.&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL some_config = 100;&lt;br /&gt;
    SET @@same_config = 100;&lt;br /&gt;
    &lt;br /&gt;
    SET SESSION sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@SESSION.sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
&lt;br /&gt;
Persistent values are stored in `mysqld-auto.cnf` (in the server data directory).&lt;br /&gt;
&lt;br /&gt;
Resetting system variables can be done by assigning DEFAULT or by copying from the GLOBAL namespace:&lt;br /&gt;
&lt;br /&gt;
    SET @@sql_mode = DEFAULT;&lt;br /&gt;
    SET @@sql_mode = @@GLOBAL.sql_mode&lt;br /&gt;
&lt;br /&gt;
PostgreSQL equivalents:&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL ; -- there is not similar command in Postgres&lt;br /&gt;
    SET SESSION ; -- SET&lt;br /&gt;
    SET PERSIST ; -- ALTER SYSTEM; SELECT pg_reload_conf();&lt;br /&gt;
    SELECT @@var; -- SELECT current_setting(&#039;var);&lt;br /&gt;
&lt;br /&gt;
User-defined variables are created by assignment:&lt;br /&gt;
&lt;br /&gt;
    SET @var = expr [, @var2 = expr [ ... ]];&lt;br /&gt;
&lt;br /&gt;
User-defined variables can only be of type integer, decimal, float, binary, non-binary string, or NULL. Casting between these types is implicit and hidden. User-defined variables cannot be used as table or column names.&lt;br /&gt;
&lt;br /&gt;
An advantage of the T-SQL-style syntax (with special prefixes) is that it provides a simple solution for avoiding identifier collisions. MySQL variables are convenient, and the user experience can be good for people already familiar with T-SQL (MSSQL or Sybase). On the other hand, prefix-based syntax can be confusing for users coming from systems other than MSSQL. The type system is perhaps too simple and can be unsafe. In general, the performance of MySQL (or MariaDB) stored procedures is not good, and stored procedures are therefore not frequently used. There is no protection against typographical errors. Unassigned user variables can be used without error (they default to NULL), which makes static checking of stored procedures impossible in this area.&lt;br /&gt;
&lt;br /&gt;
There is no way to restrict access to user-defined variables in MySQL. They cannot be protected against unintended usage. The only possible safeguard is a naming convention, since all user-defined variables share a single namespace.&lt;br /&gt;
&lt;br /&gt;
== Oracle ==&lt;br /&gt;
&lt;br /&gt;
Oracle PL/SQL allows the use of package variables. PL/SQL is based on the Ada language, and package variables act as &amp;quot;global&amp;quot; variables. They are not directly visible from SQL, but Oracle provides a reduced syntax for functions without arguments, so you typically write a wrapper:&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE PACKAGE my_package&lt;br /&gt;
    AS&lt;br /&gt;
      FUNCTION get_a RETURN NUMBER;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    /&lt;br /&gt;
    &lt;br /&gt;
    CREATE OR REPLACE PACKAGE BODY my_package&lt;br /&gt;
    AS&lt;br /&gt;
      a  NUMBER(20);&lt;br /&gt;
      &lt;br /&gt;
      FUNCTION get_a&lt;br /&gt;
      RETURN NUMBER&lt;br /&gt;
      IS&lt;br /&gt;
      BEGIN&lt;br /&gt;
        RETURN a;&lt;br /&gt;
      END get_a;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    &lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
Inside SQL, SQL identifiers take precedence. Inside non-SQL commands (such as CALL or PL/SQL blocks), package identifiers take precedence.&lt;br /&gt;
&lt;br /&gt;
Oracle allows both syntaxes for calling a function with zero arguments:&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a() FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
This reduces the risk of naming collisions. Package variables persist for the duration of a session.&lt;br /&gt;
&lt;br /&gt;
Another possibility is to use variables in SQL*Plus (similar to `psql` variables, but with the ability to define the type on the server side).&lt;br /&gt;
&lt;br /&gt;
A variable is declared with the `VARIABLE` command and can be accessed in the session using the `:varname` syntax (a prior declaration step may be optional):&lt;br /&gt;
&lt;br /&gt;
    VARIABLE bv_variable_name VARCHAR2(30)&lt;br /&gt;
    BEGIN&lt;br /&gt;
      :bv_variable_name := &#039;Some Value&#039;;&lt;br /&gt;
    END;&lt;br /&gt;
    &lt;br /&gt;
    SELECT column_name&lt;br /&gt;
      FROM   table_name&lt;br /&gt;
     WHERE  column_name = :bv_variable_name;&lt;br /&gt;
&lt;br /&gt;
== DB2 ==&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;user-defined global variables&amp;quot; in DB2 are similar to the proposed PostgreSQL feature. The main difference is in the access rights: DB2 uses `READ` and `WRITE`, while PostgreSQL uses `SELECT` and `UPDATE`. Because PostgreSQL already uses the `SET` command for GUCs, the `LET` command was introduced in the proposal (DB2 uses `SET`).&lt;br /&gt;
&lt;br /&gt;
Variables are visible in all sessions, but their values are private to each session. Variables are not transactional. Their usage is broader than in the proposal: they can be changed by `SET`, `SELECT INTO`, or used as OUT parameters of procedures. The search path (or a similar mechanism) applies to variables as well, but variables have lower priority than tables or columns.&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE myCounter INT DEFAULT 01;&lt;br /&gt;
    SELECT EMPNO, LASTNAME, CASE WHEN myCounter = 1 THEN SALARY ELSE NULL END&lt;br /&gt;
    FROM EMPLOYEE WHERE WORKDEPT = &#039;A00&#039;;&lt;br /&gt;
    SET myCounter = 29;&lt;br /&gt;
&lt;br /&gt;
There also appear to be other kinds of variables, accessed using the function `GETVARIABLE(&#039;name&#039;, &#039;default&#039;)`. These are very similar to PostgreSQL GUCs and the `current_setting` function. Such variables can be set via the connection string, are of type `VARCHAR`, and up to 10 values are allowed. Built-in session variables (configuration) can also be accessed using `GETVARIABLE`.&lt;br /&gt;
&lt;br /&gt;
== MSSQL (T-SQL) ==&lt;br /&gt;
&lt;br /&gt;
MSSQL provides two types of variables:&lt;br /&gt;
&lt;br /&gt;
* Global variables — accessed with the `@@varname` syntax. These are similar to GUCs and provide state information such as `@@ERROR`, `@@ROWCOUNT`, or `@@IDENTITY`. &lt;br /&gt;
* Local variables — accessed with the `@varname` syntax. These must be declared with the `DECLARE` command before use. Their scope is limited to the batch, procedure, or function where they are executed.&lt;br /&gt;
&lt;br /&gt;
    DECLARE @TestVariable AS VARCHAR(100)&lt;br /&gt;
    SET @TestVariable = &#039;Think Green&#039;&lt;br /&gt;
    GO&lt;br /&gt;
    PRINT @TestVariable&lt;br /&gt;
&lt;br /&gt;
This script fails because `PRINT` is executed in a different batch. Therefore, it seems MSSQL does not truly support session variables.&lt;br /&gt;
&lt;br /&gt;
There are also mechanisms similar to PostgreSQL&#039;s custom GUCs and the use of `current_setting` and `set_config` functions. In general, MSSQL is fairly primitive in this area.&lt;br /&gt;
&lt;br /&gt;
    EXEC sp_set_session_context &#039;user_id&#039;, 4;&lt;br /&gt;
    SELECT SESSION_CONTEXT(N&#039;user_id&#039;);&lt;br /&gt;
&lt;br /&gt;
= ToDo =&lt;br /&gt;
&lt;br /&gt;
* SECURITY LABEL support — enhance the `dummy_seclabel` module and the `sepgsql` extension. Update PostgreSQL documentation and ensure `SecLabelSupportsObjectType` includes session variables.&lt;/div&gt;</summary>
		<author><name>Okbobcz</name></author>
	</entry>
	<entry>
		<id>https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41851</id>
		<title>Implementation of declarative catalog session variables</title>
		<link rel="alternate" type="text/html" href="https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41851"/>
		<updated>2025-09-30T04:08:30Z</updated>

		<summary type="html">&lt;p&gt;Okbobcz: /* Collisions between column and variable identifiers */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Session variables are database objects that can hold a value across multiple queries inside a session. The design of session variables varies widely, and many databases implement more than one type of session variable.&lt;br /&gt;
&lt;br /&gt;
The SQL/PSM standard introduces modules and module-level variables. However, this part of the standard has not been implemented in any widely used product. As a result, each database system has developed its own proprietary design, syntax, and feature set.&lt;br /&gt;
&lt;br /&gt;
Session variables are often part of the stored procedure environment, but there are exceptions --- for example, MySQL session variables or client-side session variables in tools like psql. In most systems, session variables do not participate in transactions: once set, their value persists until explicitly changed, even if the surrounding transaction is rolled back. Conceptually, they behave much like variables in general-purpose programming languages.&lt;br /&gt;
&lt;br /&gt;
Because there is no common design, comparisons across systems are difficult. Each implementation has its own advantages and disadvantages, and in some cases a single system supports more than one kind of session variable.&lt;br /&gt;
&lt;br /&gt;
Kinds of session variables:&lt;br /&gt;
* client side (psql, pgadmin, ...) - mostly similar to shell variables (based on string operations)&lt;br /&gt;
* server side&lt;br /&gt;
** declarative&lt;br /&gt;
*** created only for batch (T-SQL)&lt;br /&gt;
*** created as an database object (or part of database object) (Oracle, DB2)&lt;br /&gt;
** created by usage (MySQL)&lt;br /&gt;
** with priprietary syntax for identifiers (MySQL, T-SQL)&lt;br /&gt;
** with ANSI SQL syntax for identifiers (Oracle, DB2)&lt;br /&gt;
&lt;br /&gt;
Possible usage of session variables:&lt;br /&gt;
&lt;br /&gt;
* usage of session variables as global variables in PL (tracing, debugging, hacking),&lt;br /&gt;
* holding configuration (for PL),&lt;br /&gt;
* holding some more used data in interactive session,&lt;br /&gt;
* holding credentials for some RLS and data securing,&lt;br /&gt;
* helping with migration from others systems (mainly from Oracle),&lt;br /&gt;
* allow anonymous block parametrization (only postgres).&lt;br /&gt;
* session variables are writeable on read only hot standby (postgres)&lt;br /&gt;
&lt;br /&gt;
= SQL/PSM =&lt;br /&gt;
&lt;br /&gt;
The SQL standard introduces the concept of modules, which can be associated with schemas (partially). Variables in modules behave like PL/pgSQL variables but are only local. The only objects allowed at the module level are temporary tables, which can be accessed only from routines assigned to that module. Modules can encapsulate private objects (functions, procedure, temp tables), that are not visible outside the module. Inside the module, a  private objects shadows other objects. What I know the modules (and generally SQL/PSM) doesn&#039;t cover a deployment of code, so there is not similarity with PostgreSQL extensions. SQL/PSM Modules are modules like modules from modular languages like Modula2 or Oberon. There is some similarity with Postgres schemas (both are containers) with significant differences (there is not a concept of SEARCH_PATH (on PSM side) or there is not a concept of private or public objects (on Postgres schema side)).&lt;br /&gt;
&lt;br /&gt;
SQL/PSM variables are not transactional (they are used exactly like in PL/pgSQL). The precedence between variables and columns is not specified.&lt;br /&gt;
&lt;br /&gt;
= Implementation Challenges =&lt;br /&gt;
&lt;br /&gt;
* Possible collisions between session variables and other database objects.&lt;br /&gt;
* Memory cleanup after dropping a session variable, especially when DDL operations can be reverted (Postgres has not support for global temp objects).&lt;br /&gt;
* Memory invalidation: if a variable is dropped by one session, other sessions should not continue using it (Postgres has not support for global temp objects).&lt;br /&gt;
&lt;br /&gt;
= Proposed Implementations =&lt;br /&gt;
&lt;br /&gt;
It is clear that no single design is perfect for all use cases. MySQL’s approach is convenient and useful for interactive work, but it cannot be used for storing sensitive data and is considered unsafe. PostgreSQL GUCs are excellent for configuration, but they are not well suited for interactive use and are very limited when used as global variables in PL code.&lt;br /&gt;
&lt;br /&gt;
Because of these trade-offs, no single design is sufficient. In practice, having multiple designs within a single system could support a broader range of use cases more effectively.&lt;br /&gt;
&lt;br /&gt;
== Design session variables as database global temporary typed objects with ANSI SQL syntax for identifiers ==&lt;br /&gt;
&lt;br /&gt;
Author: Pavel Stehule &amp;lt;pavel.stehule@gmail.com&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I searched for a solution that could work well with the specific PostgreSQL environment:&lt;br /&gt;
&lt;br /&gt;
* PostgreSQL has more than one widely used stored procedure language: PL/pgSQL, PL/Python, or PL/Perl.&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL is a reduced version of PL/SQL, and PL/SQL is a modified ADA language. I am pretty happy with the current complexity of PL/pgSQL --- it is a domain-specific language that works well. It is very simple and easy to learn. PL/pgSQL has no packages or modules; instead PostgreSQL schemas are used. But schemas are database objects in their own right, independent of PL.&lt;br /&gt;
&lt;br /&gt;
* PostgreSQL already has a code container --- the schema. I don&#039;t want to introduce another container object (modules), as this would overlap heavily with schemas. Implementing modules for PL/pgSQL could be useful, but doing it properly would be quite complex. The main problem is the concept of public and private objects --- PostgreSQL does not have this. Don&#039;t forget: every PL/pgSQL expression is a SQL expression, so public/private visibility would first need to be implemented internally in PostgreSQL. That would be redundant (and possibly in conflict) with SEARCH_PATH.&lt;br /&gt;
&lt;br /&gt;
So I wanted a design that supports multiple PL languages, does not add complexity to the relatively simple PL/pgSQL, and does not duplicate functionality already available.  &lt;br /&gt;
&lt;br /&gt;
I wrote a larger application in PL/pgSQL. To maintain it, I created plpgsql_lint (later renamed plpgsql_check). So I am always looking for solutions that do not block static code analysis. Static analysis requires persistent objects, which naturally leads to designing session variables as database objects (with their own system catalog).  &lt;br /&gt;
When session variables are database objects, ACLs can be applied naturally. Oracle package variables are well protected by package scope. PostgreSQL, however, has no schema scope --- schema locality is controlled by the SEARCH_PATH GUC, and introducing another mechanism would be messy. But with ACLs, variable contents can also be secured:&lt;br /&gt;
&lt;br /&gt;
    CREATE TEMP VARIABLE x AS int;&lt;br /&gt;
     &lt;br /&gt;
    LET x = 10;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(x);&lt;br /&gt;
    END;&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    SELECT VARIABLE(x);&lt;br /&gt;
&lt;br /&gt;
==== Collisions between column and variable identifiers ====&lt;br /&gt;
&lt;br /&gt;
There is a risk of identifier collisions between variables and columns. With ANSI SQL syntax for session variable identifiers, the problem is how to distinguish variables from columns. This is a known issue in PL/pgSQL and PL/SQL and can be solved either by prioritization or by prohibiting collisions.  &lt;br /&gt;
&lt;br /&gt;
Prohibiting collisions has largely solved this problem in PL/pgSQL. Unfortunately, session variables are global objects, so collisions can occur in any query. A poorly named variable could break queries unexpectedly. Prioritization also has problems --- a variable or a column could be used silently when the other was intended.&lt;br /&gt;
&lt;br /&gt;
    CREATE TABLE tab(a int);&lt;br /&gt;
    CREATE VARIABLE a AS int;&lt;br /&gt;
    &lt;br /&gt;
    SELECT a FROM tab; -- with disallowed collisions, this query fails when a variable named &amp;quot;a&amp;quot; exists&lt;br /&gt;
    &lt;br /&gt;
    LET a = 1;&lt;br /&gt;
    DELETE FROM tab WHERE a = 1; -- with higher priority for variables, all rows could be deleted&lt;br /&gt;
    SELECT a FROM tab; -- with higher priority for columns, the variable is ignored&lt;br /&gt;
&lt;br /&gt;
Even with collision prohibition, mistakes are still possible:&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE _id AS int;&lt;br /&gt;
    LET _id = 10;&lt;br /&gt;
    CREATE TABLE foo(id int);&lt;br /&gt;
    DELETE FROM foo WHERE _id = 10; -- just a typo, but all rows are deleted&lt;br /&gt;
&lt;br /&gt;
These problems are not new. Developers in PL/pgSQL or PL/SQL know them, but the scope of risk is normally limited to stored procedures. After many discussions and designs, I introduced &amp;quot;variable fences&amp;quot; (the syntax VARIABLE(varname)). Inside any query, variables can be used only inside a variable fence. This completely solves collisions and reduces the chance of human error. Currently, fences are required everywhere in queries and expressions. In the future, they might be made optional inside PL/pgSQL, to reduce overhead when porting Oracle applications. This could be controlled by a new plpgsql.extra_checks setting.&lt;br /&gt;
&lt;br /&gt;
A variable fence can collide with a user-defined function named `variable`. This is similar to keyword conflicts such as CUBE. It can be resolved by schema-qualifying or quoting:&lt;br /&gt;
&lt;br /&gt;
    SELECT public.variable(...)&lt;br /&gt;
    SELECT &amp;quot;variable&amp;quot;(...)&lt;br /&gt;
&lt;br /&gt;
Why not use T-SQL (MSSQL, MySQL) syntax for session variables? There are several reasons – some objective, one subjective:&lt;br /&gt;
&lt;br /&gt;
* There is a collision with custom operators. Operators `@` and `@@` are already used, and `@@` is common:&lt;br /&gt;
&lt;br /&gt;
    (2025-09-28 06:57:30) postgres=# create table tab(a int);&lt;br /&gt;
    CREATE TABLE&lt;br /&gt;
    (2025-09-28 06:57:42) postgres=# insert into tab values(10);&lt;br /&gt;
    INSERT 0 1&lt;br /&gt;
    (2025-09-28 06:57:50) postgres=# select @a from tab;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │       10 │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
We could safely use only `:` as a prefix, but that would conflict with psql variables. Similarly, `$` would conflict with query parameters.&lt;br /&gt;
&lt;br /&gt;
* T-SQL variable names look strange in PostgreSQL (in queries and in PL/pgSQL). PostgreSQL uses only ANSI/SQL identifiers, and PL/pgSQL follows the same rules. PostgreSQL is consistent here, and I see little motivation to introduce a new, non-standard syntax (especially since it could cause compatibility issues).&lt;br /&gt;
&lt;br /&gt;
Note: There was also a proposal to use table syntax for variables (similar to T-SQL in-memory table variables, but restricted to a single row). This is very effective for avoiding collisions, but I dislike it. It complicates simple expressions, adds inconsistency, and looks odd for scalar variables (though it could be useful for composite variables). This design doesn&#039;t solve all possible problems - variables can be shadowed by tables still, because we have SEARCH_PATH (but this is known issue):&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(var); -- variable is not a table&lt;br /&gt;
    END&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      -- variable as a table (I don&#039;t like it)&lt;br /&gt;
      -- can we use DML? How many rows can it hold?&lt;br /&gt;
      -- is the value a row or not?&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, (SELECT var FROM var);&lt;br /&gt;
    END&lt;br /&gt;
    $$&lt;br /&gt;
&lt;br /&gt;
I am not against introducing in-memory tables --- or more correctly, global temporary tables. A well-designed implementation could be very useful. But this is a different feature and should be designed separately.  &lt;br /&gt;
&lt;br /&gt;
There are also performance considerations: PL/pgSQL can use simple expression evaluation when no tables are referenced. This optimization would need to change, which touches sensitive code. It would also be inconsistent.  &lt;br /&gt;
&lt;br /&gt;
I would like global temporary tables (which PostgreSQL currently lacks). Tables should behave like tables, and variables should behave like variables. The natural mental model for session variables comes from variables in PL languages. PostgreSQL internally supports *transition tables*, and I can imagine allowing these outside of triggers. That would be valid, but again, it is a different feature.  &lt;br /&gt;
&lt;br /&gt;
Implementing declared tables inside PL/pgSQL would be simpler from a high-level design perspective (interaction between SQL tables and PL/pgSQL is already well defined), but difficult from a low-level perspective (statistics, optimizer data, etc. --- the same issues as with global temporary tables, unless we add fake proxy local temp tables).&lt;br /&gt;
&lt;br /&gt;
   CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
   AS $$&lt;br /&gt;
   -- not implemented yet, not part of this proposal&lt;br /&gt;
   DECLARE t TABLE(a int, b int); -- this table is invisible outside fx&lt;br /&gt;
   BEGIN&lt;br /&gt;
     INSERT INTO t VALUES(10,20);&lt;br /&gt;
     INSERT INTO t VALUES(30,40);&lt;br /&gt;
     RAISE NOTICE &#039;%&#039;, (SELECT count(*) FROM t);&lt;br /&gt;
   END;&lt;br /&gt;
   $$ LANGUAGE plpgsql;&lt;br /&gt;
&lt;br /&gt;
Some data languages define relational variables, but this concept has no overlap with the common understanding of session variables.&lt;br /&gt;
&lt;br /&gt;
= Variabes in PostgreSQL (as of v18) =&lt;br /&gt;
&lt;br /&gt;
PostgreSQL provides relatively advanced client-side session variables in psql --- these variables use the `:` prefix. The implementation is pretty much straightforward: `psql`&#039;s parser reads tokens from input and, when it encounters a token related to a variable, replaces it with the variable&#039;s value. The syntax supports literal and identifier escaping, and variables can be used as arguments of the `\bind` command.&lt;br /&gt;
&lt;br /&gt;
`psql` variables are typeless client-side variables, available only inside `psql` or `pgbench`.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:40:18) postgres=# \set myvar ahoj&lt;br /&gt;
    (2025-09-25 09:40:33) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:14) postgres=# select :&#039;myvar&#039;;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:24) postgres=# select $1 \bind :myvar \g&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
The `\set` command allows only simple string concatenation, but it also supports execution of shell commands. When a query is executed with the `\gset` command, its result can be stored in a `psql` variable.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:41:40) postgres=# \set ctime `date`&lt;br /&gt;
    (2025-09-25 09:43:55) postgres=# \echo :ctime&lt;br /&gt;
    Thu Sep 25 09:43:55 CEST 2025&lt;br /&gt;
&lt;br /&gt;
The `\set` command is fairly limited, making complex operations unreadable or non-portable.&lt;br /&gt;
&lt;br /&gt;
This mechanism is good enough for writing tests, but it is not generally available (most users do not need it), and it is not accessible from the server side (e.g., from stored procedures). There is no simple way to access `psql` variables from an anonymous block --- a proxy custom GUC variable must be used:&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:58:38) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    (2025-09-25 09:58:43) postgres=# select set_config(&#039;myvars.myvar&#039;, :&#039;myvar&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ ahoj       │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 10:00:27) postgres=# do $$ begin raise notice &#039;%&#039;, current_setting(&#039;myvars.myvar&#039;); end $$;&lt;br /&gt;
    NOTICE:  ahoj&lt;br /&gt;
    DO&lt;br /&gt;
&lt;br /&gt;
PostgreSQL also has configuration variables (GUC - Grand Unified Configuration). These are primarily designed for PostgreSQL and extension configuration, but they can be used as session variables. GUC variables can be set with the `SET` command or the `set_config` function, and read with `SHOW` or `current_setting`.&lt;br /&gt;
&lt;br /&gt;
GUC variables are meant for holding configuration parameters. When created through the internal API, they can be restricted to superusers, hidden from regular users, and assigned specific types (boolean, memory, number, interval, string, enum) with validation. This type system is separate from the SQL type system and is initialized before PostgreSQL’s SQL types.&lt;br /&gt;
&lt;br /&gt;
Variables created from SQL must be &amp;quot;custom&amp;quot; variables, using a prefix in their name --- these are always strings. Such custom GUCs are often used as a workaround for missing server-side session variables in PostgreSQL, but they are typeless, unprotected, and unsafe.&lt;br /&gt;
&lt;br /&gt;
A notable feature of PostgreSQL GUCs (built-in or custom) is that they can have different scopes: transaction, session, or function execution. They can also be assigned default values at specific events --- configuration loading, role login, database login, or function start. These features are useful for configuration, but problematic when GUCs are repurposed as session variables.&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
    RETURNS void AS $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, current_setting(&#039;my.x&#039;);&lt;br /&gt;
    END $$ LANGUAGE plpgsql SET my.x = 20;&lt;br /&gt;
    CREATE FUNCTION&lt;br /&gt;
    (2025-09-27 06:10:37) postgres=# SET my.x = 0; SELECT fx();&lt;br /&gt;
    SET&lt;br /&gt;
    NOTICE:  20&lt;br /&gt;
    ┌────┐&lt;br /&gt;
    │ fx │&lt;br /&gt;
    ╞════╡&lt;br /&gt;
    │    │&lt;br /&gt;
    └────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    (2025-09-27 06:10:39) postgres=# SHOW my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 0    │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
GUC variables are transactional, and this behavior cannot be disabled.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-27 06:17:31) postgres=# set my.x = 10;&lt;br /&gt;
    SET&lt;br /&gt;
    (2025-09-27 06:19:42) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:19:46) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, true);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:19:55) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:01) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:20:11) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:13) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:20:38) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:45) postgres=# rollback;&lt;br /&gt;
    ROLLBACK&lt;br /&gt;
    (2025-09-27 06:20:52) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:55) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:22:36) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:22:39) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:22:42) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
As a result, there is no reliable way to store details such as error information in custom GUCs.&lt;br /&gt;
&lt;br /&gt;
= Session Variables in Other Systems =&lt;br /&gt;
&lt;br /&gt;
This section surveys how session variables are implemented in several widely used database systems. Each subsection includes code examples to illustrate syntax, behaviour, and scope.&lt;br /&gt;
&lt;br /&gt;
== MySQL ==&lt;br /&gt;
&lt;br /&gt;
Session variables in MySQL have syntax similar to the better-known T-SQL variables (though other details are MySQL-specific). MySQL distinguishes two kinds of variables:&lt;br /&gt;
&lt;br /&gt;
* system variables - referenced using the `@@` prefix (or `@@scope.name`)&lt;br /&gt;
* user-defined variables - referenced using the `@` prefix&lt;br /&gt;
&lt;br /&gt;
System variables are modified with the `SET` statement. The `SET` statement accepts the modifiers `GLOBAL`, `SESSION`, or `PERSIST`. Some variables can only be set using the `GLOBAL` or `SESSION` modifier; when the variable name is specified with the `@@` prefix, it is not necessary to explicitly indicate the scope. System variables are defined by MySQL or by MySQL plugins.&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL some_config = 100;&lt;br /&gt;
    SET @@same_config = 100;&lt;br /&gt;
    &lt;br /&gt;
    SET SESSION sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@SESSION.sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
&lt;br /&gt;
Persistent values are stored in `mysqld-auto.cnf` (in the server data directory).&lt;br /&gt;
&lt;br /&gt;
Resetting system variables can be done by assigning DEFAULT or by copying from the GLOBAL namespace:&lt;br /&gt;
&lt;br /&gt;
    SET @@sql_mode = DEFAULT;&lt;br /&gt;
    SET @@sql_mode = @@GLOBAL.sql_mode&lt;br /&gt;
&lt;br /&gt;
PostgreSQL equivalents:&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL ; -- there is not similar command in Postgres&lt;br /&gt;
    SET SESSION ; -- SET&lt;br /&gt;
    SET PERSIST ; -- ALTER SYSTEM; SELECT pg_reload_conf();&lt;br /&gt;
    SELECT @@var; -- SELECT current_setting(&#039;var);&lt;br /&gt;
&lt;br /&gt;
User-defined variables are created by assignment:&lt;br /&gt;
&lt;br /&gt;
    SET @var = expr [, @var2 = expr [ ... ]];&lt;br /&gt;
&lt;br /&gt;
User-defined variables can only be of type integer, decimal, float, binary, non-binary string, or NULL. Casting between these types is implicit and hidden. User-defined variables cannot be used as table or column names.&lt;br /&gt;
&lt;br /&gt;
An advantage of the T-SQL-style syntax (with special prefixes) is that it provides a simple solution for avoiding identifier collisions. MySQL variables are convenient, and the user experience can be good for people already familiar with T-SQL (MSSQL or Sybase). On the other hand, prefix-based syntax can be confusing for users coming from systems other than MSSQL. The type system is perhaps too simple and can be unsafe. In general, the performance of MySQL (or MariaDB) stored procedures is not good, and stored procedures are therefore not frequently used. There is no protection against typographical errors. Unassigned user variables can be used without error (they default to NULL), which makes static checking of stored procedures impossible in this area.&lt;br /&gt;
&lt;br /&gt;
There is no way to restrict access to user-defined variables in MySQL. They cannot be protected against unintended usage. The only possible safeguard is a naming convention, since all user-defined variables share a single namespace.&lt;br /&gt;
&lt;br /&gt;
== Oracle ==&lt;br /&gt;
&lt;br /&gt;
Oracle PL/SQL allows the use of package variables. PL/SQL is based on the Ada language, and package variables act as &amp;quot;global&amp;quot; variables. They are not directly visible from SQL, but Oracle provides a reduced syntax for functions without arguments, so you typically write a wrapper:&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE PACKAGE my_package&lt;br /&gt;
    AS&lt;br /&gt;
      FUNCTION get_a RETURN NUMBER;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    /&lt;br /&gt;
    &lt;br /&gt;
    CREATE OR REPLACE PACKAGE BODY my_package&lt;br /&gt;
    AS&lt;br /&gt;
      a  NUMBER(20);&lt;br /&gt;
      &lt;br /&gt;
      FUNCTION get_a&lt;br /&gt;
      RETURN NUMBER&lt;br /&gt;
      IS&lt;br /&gt;
      BEGIN&lt;br /&gt;
        RETURN a;&lt;br /&gt;
      END get_a;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    &lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
Inside SQL, SQL identifiers take precedence. Inside non-SQL commands (such as CALL or PL/SQL blocks), package identifiers take precedence.&lt;br /&gt;
&lt;br /&gt;
Oracle allows both syntaxes for calling a function with zero arguments:&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a() FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
This reduces the risk of naming collisions. Package variables persist for the duration of a session.&lt;br /&gt;
&lt;br /&gt;
Another possibility is to use variables in SQL*Plus (similar to `psql` variables, but with the ability to define the type on the server side).&lt;br /&gt;
&lt;br /&gt;
A variable is declared with the `VARIABLE` command and can be accessed in the session using the `:varname` syntax (a prior declaration step may be optional):&lt;br /&gt;
&lt;br /&gt;
    VARIABLE bv_variable_name VARCHAR2(30)&lt;br /&gt;
    BEGIN&lt;br /&gt;
      :bv_variable_name := &#039;Some Value&#039;;&lt;br /&gt;
    END;&lt;br /&gt;
    &lt;br /&gt;
    SELECT column_name&lt;br /&gt;
      FROM   table_name&lt;br /&gt;
     WHERE  column_name = :bv_variable_name;&lt;br /&gt;
&lt;br /&gt;
== DB2 ==&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;user-defined global variables&amp;quot; in DB2 are similar to the proposed PostgreSQL feature. The main difference is in the access rights: DB2 uses `READ` and `WRITE`, while PostgreSQL uses `SELECT` and `UPDATE`. Because PostgreSQL already uses the `SET` command for GUCs, the `LET` command was introduced in the proposal (DB2 uses `SET`).&lt;br /&gt;
&lt;br /&gt;
Variables are visible in all sessions, but their values are private to each session. Variables are not transactional. Their usage is broader than in the proposal: they can be changed by `SET`, `SELECT INTO`, or used as OUT parameters of procedures. The search path (or a similar mechanism) applies to variables as well, but variables have lower priority than tables or columns.&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE myCounter INT DEFAULT 01;&lt;br /&gt;
    SELECT EMPNO, LASTNAME, CASE WHEN myCounter = 1 THEN SALARY ELSE NULL END&lt;br /&gt;
    FROM EMPLOYEE WHERE WORKDEPT = &#039;A00&#039;;&lt;br /&gt;
    SET myCounter = 29;&lt;br /&gt;
&lt;br /&gt;
There also appear to be other kinds of variables, accessed using the function `GETVARIABLE(&#039;name&#039;, &#039;default&#039;)`. These are very similar to PostgreSQL GUCs and the `current_setting` function. Such variables can be set via the connection string, are of type `VARCHAR`, and up to 10 values are allowed. Built-in session variables (configuration) can also be accessed using `GETVARIABLE`.&lt;br /&gt;
&lt;br /&gt;
== MSSQL (T-SQL) ==&lt;br /&gt;
&lt;br /&gt;
MSSQL provides two types of variables:&lt;br /&gt;
&lt;br /&gt;
* Global variables — accessed with the `@@varname` syntax. These are similar to GUCs and provide state information such as `@@ERROR`, `@@ROWCOUNT`, or `@@IDENTITY`. &lt;br /&gt;
* Local variables — accessed with the `@varname` syntax. These must be declared with the `DECLARE` command before use. Their scope is limited to the batch, procedure, or function where they are executed.&lt;br /&gt;
&lt;br /&gt;
    DECLARE @TestVariable AS VARCHAR(100)&lt;br /&gt;
    SET @TestVariable = &#039;Think Green&#039;&lt;br /&gt;
    GO&lt;br /&gt;
    PRINT @TestVariable&lt;br /&gt;
&lt;br /&gt;
This script fails because `PRINT` is executed in a different batch. Therefore, it seems MSSQL does not truly support session variables.&lt;br /&gt;
&lt;br /&gt;
There are also mechanisms similar to PostgreSQL&#039;s custom GUCs and the use of `current_setting` and `set_config` functions. In general, MSSQL is fairly primitive in this area.&lt;br /&gt;
&lt;br /&gt;
    EXEC sp_set_session_context &#039;user_id&#039;, 4;&lt;br /&gt;
    SELECT SESSION_CONTEXT(N&#039;user_id&#039;);&lt;br /&gt;
&lt;br /&gt;
= ToDo =&lt;br /&gt;
&lt;br /&gt;
* SECURITY LABEL support — enhance the `dummy_seclabel` module and the `sepgsql` extension. Update PostgreSQL documentation and ensure `SecLabelSupportsObjectType` includes session variables.&lt;/div&gt;</summary>
		<author><name>Okbobcz</name></author>
	</entry>
	<entry>
		<id>https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41850</id>
		<title>Implementation of declarative catalog session variables</title>
		<link rel="alternate" type="text/html" href="https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41850"/>
		<updated>2025-09-30T04:05:44Z</updated>

		<summary type="html">&lt;p&gt;Okbobcz: /* Introduction */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Session variables are database objects that can hold a value across multiple queries inside a session. The design of session variables varies widely, and many databases implement more than one type of session variable.&lt;br /&gt;
&lt;br /&gt;
The SQL/PSM standard introduces modules and module-level variables. However, this part of the standard has not been implemented in any widely used product. As a result, each database system has developed its own proprietary design, syntax, and feature set.&lt;br /&gt;
&lt;br /&gt;
Session variables are often part of the stored procedure environment, but there are exceptions --- for example, MySQL session variables or client-side session variables in tools like psql. In most systems, session variables do not participate in transactions: once set, their value persists until explicitly changed, even if the surrounding transaction is rolled back. Conceptually, they behave much like variables in general-purpose programming languages.&lt;br /&gt;
&lt;br /&gt;
Because there is no common design, comparisons across systems are difficult. Each implementation has its own advantages and disadvantages, and in some cases a single system supports more than one kind of session variable.&lt;br /&gt;
&lt;br /&gt;
Kinds of session variables:&lt;br /&gt;
* client side (psql, pgadmin, ...) - mostly similar to shell variables (based on string operations)&lt;br /&gt;
* server side&lt;br /&gt;
** declarative&lt;br /&gt;
*** created only for batch (T-SQL)&lt;br /&gt;
*** created as an database object (or part of database object) (Oracle, DB2)&lt;br /&gt;
** created by usage (MySQL)&lt;br /&gt;
** with priprietary syntax for identifiers (MySQL, T-SQL)&lt;br /&gt;
** with ANSI SQL syntax for identifiers (Oracle, DB2)&lt;br /&gt;
&lt;br /&gt;
Possible usage of session variables:&lt;br /&gt;
&lt;br /&gt;
* usage of session variables as global variables in PL (tracing, debugging, hacking),&lt;br /&gt;
* holding configuration (for PL),&lt;br /&gt;
* holding some more used data in interactive session,&lt;br /&gt;
* holding credentials for some RLS and data securing,&lt;br /&gt;
* helping with migration from others systems (mainly from Oracle),&lt;br /&gt;
* allow anonymous block parametrization (only postgres).&lt;br /&gt;
* session variables are writeable on read only hot standby (postgres)&lt;br /&gt;
&lt;br /&gt;
= SQL/PSM =&lt;br /&gt;
&lt;br /&gt;
The SQL standard introduces the concept of modules, which can be associated with schemas (partially). Variables in modules behave like PL/pgSQL variables but are only local. The only objects allowed at the module level are temporary tables, which can be accessed only from routines assigned to that module. Modules can encapsulate private objects (functions, procedure, temp tables), that are not visible outside the module. Inside the module, a  private objects shadows other objects. What I know the modules (and generally SQL/PSM) doesn&#039;t cover a deployment of code, so there is not similarity with PostgreSQL extensions. SQL/PSM Modules are modules like modules from modular languages like Modula2 or Oberon. There is some similarity with Postgres schemas (both are containers) with significant differences (there is not a concept of SEARCH_PATH (on PSM side) or there is not a concept of private or public objects (on Postgres schema side)).&lt;br /&gt;
&lt;br /&gt;
SQL/PSM variables are not transactional (they are used exactly like in PL/pgSQL). The precedence between variables and columns is not specified.&lt;br /&gt;
&lt;br /&gt;
= Implementation Challenges =&lt;br /&gt;
&lt;br /&gt;
* Possible collisions between session variables and other database objects.&lt;br /&gt;
* Memory cleanup after dropping a session variable, especially when DDL operations can be reverted (Postgres has not support for global temp objects).&lt;br /&gt;
* Memory invalidation: if a variable is dropped by one session, other sessions should not continue using it (Postgres has not support for global temp objects).&lt;br /&gt;
&lt;br /&gt;
= Proposed Implementations =&lt;br /&gt;
&lt;br /&gt;
It is clear that no single design is perfect for all use cases. MySQL’s approach is convenient and useful for interactive work, but it cannot be used for storing sensitive data and is considered unsafe. PostgreSQL GUCs are excellent for configuration, but they are not well suited for interactive use and are very limited when used as global variables in PL code.&lt;br /&gt;
&lt;br /&gt;
Because of these trade-offs, no single design is sufficient. In practice, having multiple designs within a single system could support a broader range of use cases more effectively.&lt;br /&gt;
&lt;br /&gt;
== Design session variables as database global temporary typed objects with ANSI SQL syntax for identifiers ==&lt;br /&gt;
&lt;br /&gt;
Author: Pavel Stehule &amp;lt;pavel.stehule@gmail.com&amp;gt;&lt;br /&gt;
&lt;br /&gt;
I searched for a solution that could work well with the specific PostgreSQL environment:&lt;br /&gt;
&lt;br /&gt;
* PostgreSQL has more than one widely used stored procedure language: PL/pgSQL, PL/Python, or PL/Perl.&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL is a reduced version of PL/SQL, and PL/SQL is a modified ADA language. I am pretty happy with the current complexity of PL/pgSQL --- it is a domain-specific language that works well. It is very simple and easy to learn. PL/pgSQL has no packages or modules; instead PostgreSQL schemas are used. But schemas are database objects in their own right, independent of PL.&lt;br /&gt;
&lt;br /&gt;
* PostgreSQL already has a code container --- the schema. I don&#039;t want to introduce another container object (modules), as this would overlap heavily with schemas. Implementing modules for PL/pgSQL could be useful, but doing it properly would be quite complex. The main problem is the concept of public and private objects --- PostgreSQL does not have this. Don&#039;t forget: every PL/pgSQL expression is a SQL expression, so public/private visibility would first need to be implemented internally in PostgreSQL. That would be redundant (and possibly in conflict) with SEARCH_PATH.&lt;br /&gt;
&lt;br /&gt;
So I wanted a design that supports multiple PL languages, does not add complexity to the relatively simple PL/pgSQL, and does not duplicate functionality already available.  &lt;br /&gt;
&lt;br /&gt;
I wrote a larger application in PL/pgSQL. To maintain it, I created plpgsql_lint (later renamed plpgsql_check). So I am always looking for solutions that do not block static code analysis. Static analysis requires persistent objects, which naturally leads to designing session variables as database objects (with their own system catalog).  &lt;br /&gt;
When session variables are database objects, ACLs can be applied naturally. Oracle package variables are well protected by package scope. PostgreSQL, however, has no schema scope --- schema locality is controlled by the SEARCH_PATH GUC, and introducing another mechanism would be messy. But with ACLs, variable contents can also be secured:&lt;br /&gt;
&lt;br /&gt;
    CREATE TEMP VARIABLE x AS int;&lt;br /&gt;
     &lt;br /&gt;
    LET x = 10;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(x);&lt;br /&gt;
    END;&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    SELECT VARIABLE(x);&lt;br /&gt;
&lt;br /&gt;
==== Collisions between column and variable identifiers ====&lt;br /&gt;
&lt;br /&gt;
There is a risk of identifier collisions between variables and columns. With ANSI SQL syntax for session variable identifiers, the problem is how to distinguish variables from columns. This is a known issue in PL/pgSQL and PL/SQL and can be solved either by prioritization or by prohibiting collisions.  &lt;br /&gt;
&lt;br /&gt;
Prohibiting collisions has largely solved this problem in PL/pgSQL. Unfortunately, session variables are global objects, so collisions can occur in any query. A poorly named variable could break queries unexpectedly. Prioritization also has problems --- a variable or a column could be used silently when the other was intended.&lt;br /&gt;
&lt;br /&gt;
    CREATE TABLE tab(a int);&lt;br /&gt;
    CREATE VARIABLE a AS int;&lt;br /&gt;
    &lt;br /&gt;
    SELECT a FROM tab; -- with disallowed collisions, this query fails when a variable named &amp;quot;a&amp;quot; exists&lt;br /&gt;
    &lt;br /&gt;
    LET a = 1;&lt;br /&gt;
    DELETE FROM tab WHERE a = 1; -- with higher priority for variables, all rows could be deleted&lt;br /&gt;
    SELECT a FROM tab; -- with higher priority for columns, the variable is ignored&lt;br /&gt;
&lt;br /&gt;
Even with collision prohibition, mistakes are still possible:&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE _id AS int;&lt;br /&gt;
    LET _id = 10;&lt;br /&gt;
    CREATE TABLE foo(id int);&lt;br /&gt;
    DELETE FROM foo WHERE _id = 10; -- just a typo, but all rows are deleted&lt;br /&gt;
&lt;br /&gt;
These problems are not new. Developers in PL/pgSQL or PL/SQL know them, but the scope of risk is normally limited to stored procedures. After many discussions and designs, I introduced &amp;quot;variable fences&amp;quot; (the syntax VARIABLE(varname)). Inside any query, variables can be used only inside a variable fence. This completely solves collisions and reduces the chance of human error. Currently, fences are required everywhere in queries and expressions. In the future, they might be made optional inside PL/pgSQL, to reduce overhead when porting Oracle applications. This could be controlled by a new plpgsql.extra_checks setting.&lt;br /&gt;
&lt;br /&gt;
A variable fence can collide with a user-defined function named `variable`. This is similar to keyword conflicts such as CUBE. It can be resolved by schema-qualifying or quoting:&lt;br /&gt;
&lt;br /&gt;
    SELECT public.variable(...)&lt;br /&gt;
    SELECT &amp;quot;variable&amp;quot;(...)&lt;br /&gt;
&lt;br /&gt;
Why not use T-SQL (MSSQL, MySQL) syntax for session variables? There are several reasons – some objective, one subjective:&lt;br /&gt;
&lt;br /&gt;
* There is a collision with custom operators. Operators `@` and `@@` are already used, and `@@` is common:&lt;br /&gt;
&lt;br /&gt;
    (2025-09-28 06:57:30) postgres=# create table tab(a int);&lt;br /&gt;
    CREATE TABLE&lt;br /&gt;
    (2025-09-28 06:57:42) postgres=# insert into tab values(10);&lt;br /&gt;
    INSERT 0 1&lt;br /&gt;
    (2025-09-28 06:57:50) postgres=# select @a from tab;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │       10 │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
We could safely use only `:` as a prefix, but that would conflict with psql variables. Similarly, `$` would conflict with query parameters.&lt;br /&gt;
&lt;br /&gt;
* T-SQL variable names look strange in PostgreSQL (in queries and in PL/pgSQL). PostgreSQL uses only ANSI/SQL identifiers, and PL/pgSQL follows the same rules. PostgreSQL is consistent here, and I see little motivation to introduce a new, non-standard syntax (especially since it could cause compatibility issues).&lt;br /&gt;
&lt;br /&gt;
Note: There was also a proposal to use table syntax for variables (similar to T-SQL in-memory table variables, but restricted to a single row). This is very effective for avoiding collisions, but I dislike it. It complicates simple expressions, adds inconsistency, and looks odd for scalar variables (though it could be useful for composite variables):&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(var); -- variable is not a table&lt;br /&gt;
    END&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      -- variable as a table (I don&#039;t like it)&lt;br /&gt;
      -- can we use DML? How many rows can it hold?&lt;br /&gt;
      -- is the value a row or not?&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, (SELECT var FROM var);&lt;br /&gt;
    END&lt;br /&gt;
    $$&lt;br /&gt;
&lt;br /&gt;
I am not against introducing in-memory tables --- or more correctly, global temporary tables. A well-designed implementation could be very useful. But this is a different feature and should be designed separately.  &lt;br /&gt;
&lt;br /&gt;
There are also performance considerations: PL/pgSQL can use simple expression evaluation when no tables are referenced. This optimization would need to change, which touches sensitive code. It would also be inconsistent.  &lt;br /&gt;
&lt;br /&gt;
I would like global temporary tables (which PostgreSQL currently lacks). Tables should behave like tables, and variables should behave like variables. The natural mental model for session variables comes from variables in PL languages. PostgreSQL internally supports *transition tables*, and I can imagine allowing these outside of triggers. That would be valid, but again, it is a different feature.  &lt;br /&gt;
&lt;br /&gt;
Implementing declared tables inside PL/pgSQL would be simpler from a high-level design perspective (interaction between SQL tables and PL/pgSQL is already well defined), but difficult from a low-level perspective (statistics, optimizer data, etc. --- the same issues as with global temporary tables, unless we add fake proxy local temp tables).&lt;br /&gt;
&lt;br /&gt;
   CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
   AS $$&lt;br /&gt;
   -- not implemented yet, not part of this proposal&lt;br /&gt;
   DECLARE t TABLE(a int, b int); -- this table is invisible outside fx&lt;br /&gt;
   BEGIN&lt;br /&gt;
     INSERT INTO t VALUES(10,20);&lt;br /&gt;
     INSERT INTO t VALUES(30,40);&lt;br /&gt;
     RAISE NOTICE &#039;%&#039;, (SELECT count(*) FROM t);&lt;br /&gt;
   END;&lt;br /&gt;
   $$ LANGUAGE plpgsql;&lt;br /&gt;
&lt;br /&gt;
Some data languages define relational variables, but this concept has no overlap with the common understanding of session variables.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
= Variabes in PostgreSQL (as of v18) =&lt;br /&gt;
&lt;br /&gt;
PostgreSQL provides relatively advanced client-side session variables in psql --- these variables use the `:` prefix. The implementation is pretty much straightforward: `psql`&#039;s parser reads tokens from input and, when it encounters a token related to a variable, replaces it with the variable&#039;s value. The syntax supports literal and identifier escaping, and variables can be used as arguments of the `\bind` command.&lt;br /&gt;
&lt;br /&gt;
`psql` variables are typeless client-side variables, available only inside `psql` or `pgbench`.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:40:18) postgres=# \set myvar ahoj&lt;br /&gt;
    (2025-09-25 09:40:33) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:14) postgres=# select :&#039;myvar&#039;;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:24) postgres=# select $1 \bind :myvar \g&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
The `\set` command allows only simple string concatenation, but it also supports execution of shell commands. When a query is executed with the `\gset` command, its result can be stored in a `psql` variable.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:41:40) postgres=# \set ctime `date`&lt;br /&gt;
    (2025-09-25 09:43:55) postgres=# \echo :ctime&lt;br /&gt;
    Thu Sep 25 09:43:55 CEST 2025&lt;br /&gt;
&lt;br /&gt;
The `\set` command is fairly limited, making complex operations unreadable or non-portable.&lt;br /&gt;
&lt;br /&gt;
This mechanism is good enough for writing tests, but it is not generally available (most users do not need it), and it is not accessible from the server side (e.g., from stored procedures). There is no simple way to access `psql` variables from an anonymous block --- a proxy custom GUC variable must be used:&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:58:38) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    (2025-09-25 09:58:43) postgres=# select set_config(&#039;myvars.myvar&#039;, :&#039;myvar&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ ahoj       │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 10:00:27) postgres=# do $$ begin raise notice &#039;%&#039;, current_setting(&#039;myvars.myvar&#039;); end $$;&lt;br /&gt;
    NOTICE:  ahoj&lt;br /&gt;
    DO&lt;br /&gt;
&lt;br /&gt;
PostgreSQL also has configuration variables (GUC - Grand Unified Configuration). These are primarily designed for PostgreSQL and extension configuration, but they can be used as session variables. GUC variables can be set with the `SET` command or the `set_config` function, and read with `SHOW` or `current_setting`.&lt;br /&gt;
&lt;br /&gt;
GUC variables are meant for holding configuration parameters. When created through the internal API, they can be restricted to superusers, hidden from regular users, and assigned specific types (boolean, memory, number, interval, string, enum) with validation. This type system is separate from the SQL type system and is initialized before PostgreSQL’s SQL types.&lt;br /&gt;
&lt;br /&gt;
Variables created from SQL must be &amp;quot;custom&amp;quot; variables, using a prefix in their name --- these are always strings. Such custom GUCs are often used as a workaround for missing server-side session variables in PostgreSQL, but they are typeless, unprotected, and unsafe.&lt;br /&gt;
&lt;br /&gt;
A notable feature of PostgreSQL GUCs (built-in or custom) is that they can have different scopes: transaction, session, or function execution. They can also be assigned default values at specific events --- configuration loading, role login, database login, or function start. These features are useful for configuration, but problematic when GUCs are repurposed as session variables.&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
    RETURNS void AS $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, current_setting(&#039;my.x&#039;);&lt;br /&gt;
    END $$ LANGUAGE plpgsql SET my.x = 20;&lt;br /&gt;
    CREATE FUNCTION&lt;br /&gt;
    (2025-09-27 06:10:37) postgres=# SET my.x = 0; SELECT fx();&lt;br /&gt;
    SET&lt;br /&gt;
    NOTICE:  20&lt;br /&gt;
    ┌────┐&lt;br /&gt;
    │ fx │&lt;br /&gt;
    ╞════╡&lt;br /&gt;
    │    │&lt;br /&gt;
    └────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    (2025-09-27 06:10:39) postgres=# SHOW my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 0    │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
GUC variables are transactional, and this behavior cannot be disabled.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-27 06:17:31) postgres=# set my.x = 10;&lt;br /&gt;
    SET&lt;br /&gt;
    (2025-09-27 06:19:42) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:19:46) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, true);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:19:55) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:01) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:20:11) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:13) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:20:38) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:45) postgres=# rollback;&lt;br /&gt;
    ROLLBACK&lt;br /&gt;
    (2025-09-27 06:20:52) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:55) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:22:36) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:22:39) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:22:42) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
As a result, there is no reliable way to store details such as error information in custom GUCs.&lt;br /&gt;
&lt;br /&gt;
= Session Variables in Other Systems =&lt;br /&gt;
&lt;br /&gt;
This section surveys how session variables are implemented in several widely used database systems. Each subsection includes code examples to illustrate syntax, behaviour, and scope.&lt;br /&gt;
&lt;br /&gt;
== MySQL ==&lt;br /&gt;
&lt;br /&gt;
Session variables in MySQL have syntax similar to the better-known T-SQL variables (though other details are MySQL-specific). MySQL distinguishes two kinds of variables:&lt;br /&gt;
&lt;br /&gt;
* system variables - referenced using the `@@` prefix (or `@@scope.name`)&lt;br /&gt;
* user-defined variables - referenced using the `@` prefix&lt;br /&gt;
&lt;br /&gt;
System variables are modified with the `SET` statement. The `SET` statement accepts the modifiers `GLOBAL`, `SESSION`, or `PERSIST`. Some variables can only be set using the `GLOBAL` or `SESSION` modifier; when the variable name is specified with the `@@` prefix, it is not necessary to explicitly indicate the scope. System variables are defined by MySQL or by MySQL plugins.&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL some_config = 100;&lt;br /&gt;
    SET @@same_config = 100;&lt;br /&gt;
    &lt;br /&gt;
    SET SESSION sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@SESSION.sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
&lt;br /&gt;
Persistent values are stored in `mysqld-auto.cnf` (in the server data directory).&lt;br /&gt;
&lt;br /&gt;
Resetting system variables can be done by assigning DEFAULT or by copying from the GLOBAL namespace:&lt;br /&gt;
&lt;br /&gt;
    SET @@sql_mode = DEFAULT;&lt;br /&gt;
    SET @@sql_mode = @@GLOBAL.sql_mode&lt;br /&gt;
&lt;br /&gt;
PostgreSQL equivalents:&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL ; -- there is not similar command in Postgres&lt;br /&gt;
    SET SESSION ; -- SET&lt;br /&gt;
    SET PERSIST ; -- ALTER SYSTEM; SELECT pg_reload_conf();&lt;br /&gt;
    SELECT @@var; -- SELECT current_setting(&#039;var);&lt;br /&gt;
&lt;br /&gt;
User-defined variables are created by assignment:&lt;br /&gt;
&lt;br /&gt;
    SET @var = expr [, @var2 = expr [ ... ]];&lt;br /&gt;
&lt;br /&gt;
User-defined variables can only be of type integer, decimal, float, binary, non-binary string, or NULL. Casting between these types is implicit and hidden. User-defined variables cannot be used as table or column names.&lt;br /&gt;
&lt;br /&gt;
An advantage of the T-SQL-style syntax (with special prefixes) is that it provides a simple solution for avoiding identifier collisions. MySQL variables are convenient, and the user experience can be good for people already familiar with T-SQL (MSSQL or Sybase). On the other hand, prefix-based syntax can be confusing for users coming from systems other than MSSQL. The type system is perhaps too simple and can be unsafe. In general, the performance of MySQL (or MariaDB) stored procedures is not good, and stored procedures are therefore not frequently used. There is no protection against typographical errors. Unassigned user variables can be used without error (they default to NULL), which makes static checking of stored procedures impossible in this area.&lt;br /&gt;
&lt;br /&gt;
There is no way to restrict access to user-defined variables in MySQL. They cannot be protected against unintended usage. The only possible safeguard is a naming convention, since all user-defined variables share a single namespace.&lt;br /&gt;
&lt;br /&gt;
== Oracle ==&lt;br /&gt;
&lt;br /&gt;
Oracle PL/SQL allows the use of package variables. PL/SQL is based on the Ada language, and package variables act as &amp;quot;global&amp;quot; variables. They are not directly visible from SQL, but Oracle provides a reduced syntax for functions without arguments, so you typically write a wrapper:&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE PACKAGE my_package&lt;br /&gt;
    AS&lt;br /&gt;
      FUNCTION get_a RETURN NUMBER;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    /&lt;br /&gt;
    &lt;br /&gt;
    CREATE OR REPLACE PACKAGE BODY my_package&lt;br /&gt;
    AS&lt;br /&gt;
      a  NUMBER(20);&lt;br /&gt;
      &lt;br /&gt;
      FUNCTION get_a&lt;br /&gt;
      RETURN NUMBER&lt;br /&gt;
      IS&lt;br /&gt;
      BEGIN&lt;br /&gt;
        RETURN a;&lt;br /&gt;
      END get_a;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    &lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
Inside SQL, SQL identifiers take precedence. Inside non-SQL commands (such as CALL or PL/SQL blocks), package identifiers take precedence.&lt;br /&gt;
&lt;br /&gt;
Oracle allows both syntaxes for calling a function with zero arguments:&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a() FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
This reduces the risk of naming collisions. Package variables persist for the duration of a session.&lt;br /&gt;
&lt;br /&gt;
Another possibility is to use variables in SQL*Plus (similar to `psql` variables, but with the ability to define the type on the server side).&lt;br /&gt;
&lt;br /&gt;
A variable is declared with the `VARIABLE` command and can be accessed in the session using the `:varname` syntax (a prior declaration step may be optional):&lt;br /&gt;
&lt;br /&gt;
    VARIABLE bv_variable_name VARCHAR2(30)&lt;br /&gt;
    BEGIN&lt;br /&gt;
      :bv_variable_name := &#039;Some Value&#039;;&lt;br /&gt;
    END;&lt;br /&gt;
    &lt;br /&gt;
    SELECT column_name&lt;br /&gt;
      FROM   table_name&lt;br /&gt;
     WHERE  column_name = :bv_variable_name;&lt;br /&gt;
&lt;br /&gt;
== DB2 ==&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;user-defined global variables&amp;quot; in DB2 are similar to the proposed PostgreSQL feature. The main difference is in the access rights: DB2 uses `READ` and `WRITE`, while PostgreSQL uses `SELECT` and `UPDATE`. Because PostgreSQL already uses the `SET` command for GUCs, the `LET` command was introduced in the proposal (DB2 uses `SET`).&lt;br /&gt;
&lt;br /&gt;
Variables are visible in all sessions, but their values are private to each session. Variables are not transactional. Their usage is broader than in the proposal: they can be changed by `SET`, `SELECT INTO`, or used as OUT parameters of procedures. The search path (or a similar mechanism) applies to variables as well, but variables have lower priority than tables or columns.&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE myCounter INT DEFAULT 01;&lt;br /&gt;
    SELECT EMPNO, LASTNAME, CASE WHEN myCounter = 1 THEN SALARY ELSE NULL END&lt;br /&gt;
    FROM EMPLOYEE WHERE WORKDEPT = &#039;A00&#039;;&lt;br /&gt;
    SET myCounter = 29;&lt;br /&gt;
&lt;br /&gt;
There also appear to be other kinds of variables, accessed using the function `GETVARIABLE(&#039;name&#039;, &#039;default&#039;)`. These are very similar to PostgreSQL GUCs and the `current_setting` function. Such variables can be set via the connection string, are of type `VARCHAR`, and up to 10 values are allowed. Built-in session variables (configuration) can also be accessed using `GETVARIABLE`.&lt;br /&gt;
&lt;br /&gt;
== MSSQL (T-SQL) ==&lt;br /&gt;
&lt;br /&gt;
MSSQL provides two types of variables:&lt;br /&gt;
&lt;br /&gt;
* Global variables — accessed with the `@@varname` syntax. These are similar to GUCs and provide state information such as `@@ERROR`, `@@ROWCOUNT`, or `@@IDENTITY`. &lt;br /&gt;
* Local variables — accessed with the `@varname` syntax. These must be declared with the `DECLARE` command before use. Their scope is limited to the batch, procedure, or function where they are executed.&lt;br /&gt;
&lt;br /&gt;
    DECLARE @TestVariable AS VARCHAR(100)&lt;br /&gt;
    SET @TestVariable = &#039;Think Green&#039;&lt;br /&gt;
    GO&lt;br /&gt;
    PRINT @TestVariable&lt;br /&gt;
&lt;br /&gt;
This script fails because `PRINT` is executed in a different batch. Therefore, it seems MSSQL does not truly support session variables.&lt;br /&gt;
&lt;br /&gt;
There are also mechanisms similar to PostgreSQL&#039;s custom GUCs and the use of `current_setting` and `set_config` functions. In general, MSSQL is fairly primitive in this area.&lt;br /&gt;
&lt;br /&gt;
    EXEC sp_set_session_context &#039;user_id&#039;, 4;&lt;br /&gt;
    SELECT SESSION_CONTEXT(N&#039;user_id&#039;);&lt;br /&gt;
&lt;br /&gt;
= ToDo =&lt;br /&gt;
&lt;br /&gt;
* SECURITY LABEL support — enhance the `dummy_seclabel` module and the `sepgsql` extension. Update PostgreSQL documentation and ensure `SecLabelSupportsObjectType` includes session variables.&lt;/div&gt;</summary>
		<author><name>Okbobcz</name></author>
	</entry>
	<entry>
		<id>https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41840</id>
		<title>Implementation of declarative catalog session variables</title>
		<link rel="alternate" type="text/html" href="https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41840"/>
		<updated>2025-09-29T07:06:14Z</updated>

		<summary type="html">&lt;p&gt;Okbobcz: /* Proposal for Postgres - design session variables as database global temporary typed objects with ANSI SQL syntax for identifiers */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Session variables are database objects that can hold a value across multiple queries inside a session. The design of session variables varies widely, and many databases implement more than one type of session variable.&lt;br /&gt;
&lt;br /&gt;
The SQL/PSM standard introduces modules and module-level variables. However, this part of the standard has not been implemented in any widely used product. As a result, each database system has developed its own proprietary design, syntax, and feature set.&lt;br /&gt;
&lt;br /&gt;
Session variables are often part of the stored procedure environment, but there are exceptions --- for example, MySQL session variables or client-side session variables in tools like psql. In most systems, session variables do not participate in transactions: once set, their value persists until explicitly changed, even if the surrounding transaction is rolled back. Conceptually, they behave much like variables in general-purpose programming languages.&lt;br /&gt;
&lt;br /&gt;
Because there is no common design, comparisons across systems are difficult. Each implementation has its own advantages and disadvantages, and in some cases a single system supports more than one kind of session variable.&lt;br /&gt;
&lt;br /&gt;
Kinds of session variables:&lt;br /&gt;
* client side (psql, pgadmin, ...) - mostly similar to shell variables (based on string operations)&lt;br /&gt;
* server side&lt;br /&gt;
** declarative&lt;br /&gt;
*** created only for batch (T-SQL)&lt;br /&gt;
*** created as an database object (or part of database object) (Oracle, DB2)&lt;br /&gt;
** created by usage (MySQL)&lt;br /&gt;
** with priprietary syntax for identifiers (MySQL, T-SQL)&lt;br /&gt;
** with ANSI SQL syntax for identifiers (Oracle, DB2)&lt;br /&gt;
&lt;br /&gt;
Possible usage of session variables:&lt;br /&gt;
&lt;br /&gt;
* usage of session variables as global variables in PL (tracing, debugging, hacking),&lt;br /&gt;
* holding configuration (for PL),&lt;br /&gt;
* holding some more used data in interactive session,&lt;br /&gt;
* holding credentials for some RLS and data securing,&lt;br /&gt;
* helping with migration from others systems (mainly from Oracle),&lt;br /&gt;
* allow anonymous block parametrization (only postgres).&lt;br /&gt;
&lt;br /&gt;
== SQL/PSM ==&lt;br /&gt;
&lt;br /&gt;
The SQL standard introduces the concept of modules, which can be associated with schemas (partially). Variables in modules behave like PL/pgSQL variables but are only local. The only objects allowed at the module level are temporary tables, which can be accessed only from routines assigned to that module. Modules can encapsulate private objects (functions, procedure, temp tables), that are not visible outside the module. Inside the module, a  private objects shadows other objects. What I know the modules (and generally SQL/PSM) doesn&#039;t cover a deployment of code, so there is not similarity with PostgreSQL extensions. SQL/PSM Modules are modules like modules from modular languages like Modula2 or Oberon. There is some similarity with Postgres schemas (both are containers) with significant differences (there is not a concept of SEARCH_PATH (on PSM side) or there is not a concept of private or public objects (on Postgres schema side)).&lt;br /&gt;
&lt;br /&gt;
SQL/PSM variables are not transactional (they are used exactly like in PL/pgSQL). The precedence between variables and columns is not specified.&lt;br /&gt;
&lt;br /&gt;
== Implementation Challenges ==&lt;br /&gt;
&lt;br /&gt;
* Possible collisions between session variables and other database objects.&lt;br /&gt;
* Memory cleanup after dropping a session variable, especially when DDL operations can be reverted (Postgres has not support for global temp objects).&lt;br /&gt;
* Memory invalidation: if a variable is dropped by one session, other sessions should not continue using it (Postgres has not support for global temp objects).&lt;br /&gt;
&lt;br /&gt;
== Proposed Implementations ==&lt;br /&gt;
&lt;br /&gt;
It is clean so no one design is perfect for all uses cases. MySQL is good enough and maybe handy for interactive work,&lt;br /&gt;
but cannot be used for storing any sensitive data, and it is unsafe. PostgreSQL GUC are perfect for configuration, but&lt;br /&gt;
are not friendly for interactive work, and are very limited for usage in PL as global variables. So no one design is&lt;br /&gt;
perfect, and I can imagine more different designs in one system, that can support some different use cases better.&lt;br /&gt;
&lt;br /&gt;
== Proposal for Postgres - design session variables as database global temporary typed objects with ANSI SQL syntax for identifiers ==&lt;br /&gt;
&lt;br /&gt;
I searched solution that can work with specific Postgres environment:&lt;br /&gt;
&lt;br /&gt;
* Postgres has more than one widely used language for stored procedures PL/pgSQL + PL/Python or PL/Perl&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL is strongly reduced PL/SQL, and PL/SQL is modified ADA language. I am pretty happy with the current comlexity of PL/pgSQL - it is domain specific language and it works well. It is a very simple language, and it is easy to learn it. PL/pgSQL has no packages or modules. Instead PostgreSQL schemas are used. But Postgres schemas are specific database objects. Schemas are independent on PL.&lt;br /&gt;
&lt;br /&gt;
* Postgres has already code container - schema. I don&#039;t want to introduce additional new container object - modules, with strong overlap with schemas. Implementation of modules for PL/pgSQL can be useful, but can be pretty complex if it should be done well. The main problem is in concept public, private objects - PostgreSQL doesn&#039;t know it. Don&#039;t forget - any PL/pgSQL expression is SQL expression, so public, private objects should be implemented internally in Postgres first - and this mechanism is redundant (and in collision) to SEARCH_PATH.&lt;br /&gt;
&lt;br /&gt;
So I wanted design that can supports more PL languages, didn&#039;t introduce new complexity to relative simple PL/pgSQL and didn&#039;t introduce functionality that can be redundant with already existing objects.&lt;br /&gt;
I wrote bigger application in PL/pgSQL. For maintaining of this application I wrote plpgsql_lint (later plpgsql_check). So every time I am searching solution that doesn&#039;t block code static analyse. Static analyse requires persistent objects, and then it is direct way to design session variables as database objects (with own system catalog). When session variables are designed as database objects, then using ACL is natural. Oracle package variables are well protected just by package scope. Unfortunately, postgres doesn&#039;t know schema scope. The locality of schema objects is defined by setting of SEARCH_PATH guc, and introduction of new mechanism can be messy. But with ACL the content of variables can be safe too:&lt;br /&gt;
&lt;br /&gt;
    CREATE TEMP VARIABLE x AS int;&lt;br /&gt;
     &lt;br /&gt;
    LET x = 10;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(x);&lt;br /&gt;
    END;&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    SELECT VARIABLE(x);&lt;br /&gt;
&lt;br /&gt;
==== Collisions between columns and variables identifiers ====&lt;br /&gt;
There is a new possibility of identifiers collisions between variables and columns identifiers. With ANSI SQL syntax for session variables identifiers there is a problem how to distinct between variable and column. This is known problem in PL/pgSQL or PL/SQL and can be solved by some prioritization or by prohibition of collisions. The prohibition of collisions is solution that almost cleaned this issue from PL/pgSQL. Unfortunately session variables are global objects and then the collision can be in any query, and then the new badly named variable can stop execution of any query. Prioritization variables or columns has own problems - quiet unwanted using variable or column.&lt;br /&gt;
&lt;br /&gt;
    CREATE TABLE tab(a int);&lt;br /&gt;
    CREATE VARIABLE a AS int;&lt;br /&gt;
    &lt;br /&gt;
    SELECT a FROM tab; -- with disallowed collisions, the execution of this query stops when there is a variable named &amp;quot;a&amp;quot;&lt;br /&gt;
    &lt;br /&gt;
    LET a = 1;&lt;br /&gt;
    DELETE FROM tab WHERE a = 1; -- with higher priority for variables all table can be deleted (nobody proposed higher priority for variables).&lt;br /&gt;
    SELECT a FROM tab; -- with higher priority for columns, the variable is not used (quietly)&lt;br /&gt;
&lt;br /&gt;
The prohibition of collision is not absolute protection against possible human errors:&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE _id AS int;&lt;br /&gt;
    LET _id = 10;&lt;br /&gt;
    CREATE TABLE foo(id int);&lt;br /&gt;
    DELETE FROM foo WHERE _id = 10; -- just typo (unwanted _), but can have unwanted impact (all rows removed)&lt;br /&gt;
&lt;br /&gt;
These problems are not new. The developers in PL/pgSQL or PL/SQL knows it, but the scope of these risks are limited only to stored procedures. After very very long discussion and lot of designs I introduced variable fences (syntax `VARIABLE(varname)`). Inside of any query the variable can be used only in variable fence. This is 100% solution of collisions, and I think it can be good solution against human errors and unwanted usage of session variable. Now, variable fences are necessary every where inside a query or expressions, but I thinking about possibility to be optional inside PL/pgSQL (far future). This can reduce overhead with porting applications from Oracle, and generally the developers of stored procedures are experienced with described risks (this can be controlled by new `plpgsql.extra_checks`).&lt;br /&gt;
&lt;br /&gt;
A variable fence can be in collisions with custom function &amp;quot;variable&amp;quot;. There is similarity with usage of keywords `cube`. This can be fixed by usage prefix schema or by usage of parenthesis:&lt;br /&gt;
&lt;br /&gt;
    SELECT public.variable(...)&lt;br /&gt;
    SELECT &amp;quot;variable&amp;quot;(...)&lt;br /&gt;
&lt;br /&gt;
Why I don&#039;t propose usage of T-SQL (MSSQL, MySQL) syntax for session variables? There is more reasons (one is objective, last is subjective):&lt;br /&gt;
&lt;br /&gt;
* There is a collision with custom operators - operators `@` or `@@` are already used - usage of `@@` is common&lt;br /&gt;
&lt;br /&gt;
    (2025-09-28 06:57:30) postgres=# create table tab(a int);&lt;br /&gt;
    CREATE TABLE&lt;br /&gt;
    (2025-09-28 06:57:42) postgres=# insert into tab values(10);&lt;br /&gt;
    INSERT 0 1&lt;br /&gt;
    (2025-09-28 06:57:50) postgres=# select @a from tab;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │       10 │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
We can safely use only `:` prefix, but then we can break a psql variables. Similarly we can use `$` prefix, but then we can break query parameters syntax.&lt;br /&gt;
&lt;br /&gt;
* T-SQL variable names looks weird in Postgres (in queries, in PL/pgSQL). Postgres knows only ANSI/SQL identifiers, PL/pgSQL shares these rules too, and this are the PostgreSQL is consistent, and I missing motivation to introduce something new (with the knowledge so problems with possible compatibility breaks should be solved too). &lt;br /&gt;
&lt;br /&gt;
Note: There was a proposal to use table syntax as variable (like table (in memory) variables from T-SQL but only single row). Although it is very effective solution of collisions, I dislike this idea. It increase a complexity of simple expression (or increase inconsistency of some syntax rules). More looks weird for scalar variables (and can be handy for composite variables):&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(var); -- variable is not a table&lt;br /&gt;
    END&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      -- variable a a table (I don&#039;t like it)&lt;br /&gt;
      -- can we use DML? How much rows this kind of variable can hold&lt;br /&gt;
      -- value is row or not?&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, (SELECT var FROM var);&lt;br /&gt;
    END&lt;br /&gt;
    $$&lt;br /&gt;
&lt;br /&gt;
I am not against for introduction of inmemory tables - or more correctly for global temporary tables. Well implemented global temporary tables can be very nice and handy feature. But it is different feature, and should be designed separately. There can be some performance problems - plpgsql can use simple expression evaluation (when expression has not usage of any table). This optimization can be changed or enhanced, but its is change inside complex sensitive code, and it is not consistent. I very like a global temporary tables (that are not implemented in Postgres). Table (and related operations) should to looks like table, variable (and related operations) should to looks like variable. Common perspective how session variables should to looks is based on variables from PL environment. PostgreSQL internally supports &amp;lt;i&amp;gt;transition tables&amp;lt;/i&amp;gt;, and I can imagine to allow this kind of tables outside of triggers too. This is valid use case (and valid functionality), but it is something different than session variables (although there can be some overlap). The implementation of declared tables inside PL/pgSQL can be more easy from high design perspective (interaction between SQL tables and PL/pgSQL is well defined already), but difficult from low level perspective (where to store column statistics and other important data for optimization - same problems like with global temporary tables (without some fake proxy local temp tables)).&lt;br /&gt;
&lt;br /&gt;
   CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
   AS $$&lt;br /&gt;
   -- not implemented yet, not part of this proposal&lt;br /&gt;
   DECLARE t TABLE(a int, b int); -- this table is invisible outside fx&lt;br /&gt;
   BEGIN&lt;br /&gt;
     INSERT INTO t VALUES(10,20);&lt;br /&gt;
     INSERT INTO t VALUES(30,40);&lt;br /&gt;
     RAISE NOTICE &#039;%&#039;, (SELECT count(*) FROM t);&lt;br /&gt;
   END;&lt;br /&gt;
   $$ LANGUAGE plpgsql;&lt;br /&gt;
&lt;br /&gt;
Some data languages defined relational variables - common concept of session variables has zero intersection with this concept.&lt;br /&gt;
&lt;br /&gt;
== Variabes in PostgreSQL (as of v18) ==&lt;br /&gt;
&lt;br /&gt;
PostgreSQL provides relatively advanced client-side session variables in psql --- these variables use the `:` prefix. The implementation is pretty much straightforward: `psql`&#039;s parser reads tokens from input and, when it encounters a token related to a variable, replaces it with the variable&#039;s value. The syntax supports literal and identifier escaping, and variables can be used as arguments of the `\bind` command.&lt;br /&gt;
&lt;br /&gt;
`psql` variables are typeless client-side variables, available only inside `psql` or `pgbench`.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:40:18) postgres=# \set myvar ahoj&lt;br /&gt;
    (2025-09-25 09:40:33) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:14) postgres=# select :&#039;myvar&#039;;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:24) postgres=# select $1 \bind :myvar \g&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
The `\set` command allows only simple string concatenation, but it also supports execution of shell commands. When a query is executed with the `\gset` command, its result can be stored in a `psql` variable.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:41:40) postgres=# \set ctime `date`&lt;br /&gt;
    (2025-09-25 09:43:55) postgres=# \echo :ctime&lt;br /&gt;
    Thu Sep 25 09:43:55 CEST 2025&lt;br /&gt;
&lt;br /&gt;
The `\set` command is fairly limited, making complex operations unreadable or non-portable.&lt;br /&gt;
&lt;br /&gt;
This mechanism is good enough for writing tests, but it is not generally available (most users do not need it), and it is not accessible from the server side (e.g., from stored procedures). There is no simple way to access `psql` variables from an anonymous block --- a proxy custom GUC variable must be used:&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:58:38) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    (2025-09-25 09:58:43) postgres=# select set_config(&#039;myvars.myvar&#039;, :&#039;myvar&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ ahoj       │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 10:00:27) postgres=# do $$ begin raise notice &#039;%&#039;, current_setting(&#039;myvars.myvar&#039;); end $$;&lt;br /&gt;
    NOTICE:  ahoj&lt;br /&gt;
    DO&lt;br /&gt;
&lt;br /&gt;
PostgreSQL also has configuration variables (GUC - Grand Unified Configuration). These are primarily designed for PostgreSQL and extension configuration, but they can be used as session variables. GUC variables can be set with the `SET` command or the `set_config` function, and read with `SHOW` or `current_setting`.&lt;br /&gt;
&lt;br /&gt;
GUC variables are meant for holding configuration parameters. When created through the internal API, they can be restricted to superusers, hidden from regular users, and assigned specific types (boolean, memory, number, interval, string, enum) with validation. This type system is separate from the SQL type system and is initialized before PostgreSQL’s SQL types.&lt;br /&gt;
&lt;br /&gt;
Variables created from SQL must be &amp;quot;custom&amp;quot; variables, using a prefix in their name --- these are always strings. Such custom GUCs are often used as a workaround for missing server-side session variables in PostgreSQL, but they are typeless, unprotected, and unsafe.&lt;br /&gt;
&lt;br /&gt;
A notable feature of PostgreSQL GUCs (built-in or custom) is that they can have different scopes: transaction, session, or function execution. They can also be assigned default values at specific events --- configuration loading, role login, database login, or function start. These features are useful for configuration, but problematic when GUCs are repurposed as session variables.&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
    RETURNS void AS $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, current_setting(&#039;my.x&#039;);&lt;br /&gt;
    END $$ LANGUAGE plpgsql SET my.x = 20;&lt;br /&gt;
    CREATE FUNCTION&lt;br /&gt;
    (2025-09-27 06:10:37) postgres=# SET my.x = 0; SELECT fx();&lt;br /&gt;
    SET&lt;br /&gt;
    NOTICE:  20&lt;br /&gt;
    ┌────┐&lt;br /&gt;
    │ fx │&lt;br /&gt;
    ╞════╡&lt;br /&gt;
    │    │&lt;br /&gt;
    └────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    (2025-09-27 06:10:39) postgres=# SHOW my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 0    │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
GUC variables are transactional, and this behavior cannot be disabled.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-27 06:17:31) postgres=# set my.x = 10;&lt;br /&gt;
    SET&lt;br /&gt;
    (2025-09-27 06:19:42) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:19:46) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, true);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:19:55) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:01) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:20:11) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:13) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:20:38) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:45) postgres=# rollback;&lt;br /&gt;
    ROLLBACK&lt;br /&gt;
    (2025-09-27 06:20:52) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:55) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:22:36) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:22:39) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:22:42) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
As a result, there is no reliable way to store details such as error information in custom GUCs.&lt;br /&gt;
&lt;br /&gt;
== Session Variables in Other Systems ==&lt;br /&gt;
&lt;br /&gt;
This section surveys how session variables are implemented in several widely used database systems. Each subsection includes code examples to illustrate syntax, behaviour, and scope.&lt;br /&gt;
&lt;br /&gt;
=== MySQL ===&lt;br /&gt;
&lt;br /&gt;
Session variables in MySQL have syntax similar to the better-known T-SQL variables (though other details are MySQL-specific). MySQL distinguishes two kinds of variables:&lt;br /&gt;
&lt;br /&gt;
* system variables - referenced using the `@@` prefix (or `@@scope.name`)&lt;br /&gt;
* user-defined variables - referenced using the `@` prefix&lt;br /&gt;
&lt;br /&gt;
System variables are modified with the `SET` statement. The `SET` statement accepts the modifiers `GLOBAL`, `SESSION`, or `PERSIST`. Some variables can only be set using the `GLOBAL` or `SESSION` modifier; when the variable name is specified with the `@@` prefix, it is not necessary to explicitly indicate the scope. System variables are defined by MySQL or by MySQL plugins.&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL some_config = 100;&lt;br /&gt;
    SET @@same_config = 100;&lt;br /&gt;
    &lt;br /&gt;
    SET SESSION sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@SESSION.sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
&lt;br /&gt;
Persistent values are stored in `mysqld-auto.cnf` (in the server data directory).&lt;br /&gt;
&lt;br /&gt;
Resetting system variables can be done by assigning DEFAULT or by copying from the GLOBAL namespace:&lt;br /&gt;
&lt;br /&gt;
    SET @@sql_mode = DEFAULT;&lt;br /&gt;
    SET @@sql_mode = @@GLOBAL.sql_mode&lt;br /&gt;
&lt;br /&gt;
PostgreSQL equivalents:&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL ; -- there is not similar command in Postgres&lt;br /&gt;
    SET SESSION ; -- SET&lt;br /&gt;
    SET PERSIST ; -- ALTER SYSTEM; SELECT pg_reload_conf();&lt;br /&gt;
    SELECT @@var; -- SELECT current_setting(&#039;var);&lt;br /&gt;
&lt;br /&gt;
User-defined variables are created by assignment:&lt;br /&gt;
&lt;br /&gt;
    SET @var = expr [, @var2 = expr [ ... ]];&lt;br /&gt;
&lt;br /&gt;
User-defined variables can only be of type integer, decimal, float, binary, non-binary string, or NULL. Casting between these types is implicit and hidden. User-defined variables cannot be used as table or column names.&lt;br /&gt;
&lt;br /&gt;
An advantage of the T-SQL-style syntax (with special prefixes) is that it provides a simple solution for avoiding identifier collisions. MySQL variables are convenient, and the user experience can be good for people already familiar with T-SQL (MSSQL or Sybase). On the other hand, prefix-based syntax can be confusing for users coming from systems other than MSSQL. The type system is perhaps too simple and can be unsafe. In general, the performance of MySQL (or MariaDB) stored procedures is not good, and stored procedures are therefore not frequently used. There is no protection against typographical errors. Unassigned user variables can be used without error (they default to NULL), which makes static checking of stored procedures impossible in this area.&lt;br /&gt;
&lt;br /&gt;
There is no way to restrict access to user-defined variables in MySQL. They cannot be protected against unintended usage. The only possible safeguard is a naming convention, since all user-defined variables share a single namespace.&lt;br /&gt;
&lt;br /&gt;
=== Oracle ===&lt;br /&gt;
&lt;br /&gt;
Oracle PL/SQL allows the use of package variables. PL/SQL is based on the Ada language, and package variables act as &amp;quot;global&amp;quot; variables. They are not directly visible from SQL, but Oracle provides a reduced syntax for functions without arguments, so you typically write a wrapper:&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE PACKAGE my_package&lt;br /&gt;
    AS&lt;br /&gt;
      FUNCTION get_a RETURN NUMBER;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    /&lt;br /&gt;
    &lt;br /&gt;
    CREATE OR REPLACE PACKAGE BODY my_package&lt;br /&gt;
    AS&lt;br /&gt;
      a  NUMBER(20);&lt;br /&gt;
      &lt;br /&gt;
      FUNCTION get_a&lt;br /&gt;
      RETURN NUMBER&lt;br /&gt;
      IS&lt;br /&gt;
      BEGIN&lt;br /&gt;
        RETURN a;&lt;br /&gt;
      END get_a;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    &lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
Inside SQL, SQL identifiers take precedence. Inside non-SQL commands (such as CALL or PL/SQL blocks), package identifiers take precedence.&lt;br /&gt;
&lt;br /&gt;
Oracle allows both syntaxes for calling a function with zero arguments:&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a() FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
This reduces the risk of naming collisions. Package variables persist for the duration of a session.&lt;br /&gt;
&lt;br /&gt;
Another possibility is to use variables in SQL*Plus (similar to `psql` variables, but with the ability to define the type on the server side).&lt;br /&gt;
&lt;br /&gt;
A variable is declared with the `VARIABLE` command and can be accessed in the session using the `:varname` syntax (a prior declaration step may be optional):&lt;br /&gt;
&lt;br /&gt;
    VARIABLE bv_variable_name VARCHAR2(30)&lt;br /&gt;
    BEGIN&lt;br /&gt;
      :bv_variable_name := &#039;Some Value&#039;;&lt;br /&gt;
    END;&lt;br /&gt;
    &lt;br /&gt;
    SELECT column_name&lt;br /&gt;
      FROM   table_name&lt;br /&gt;
     WHERE  column_name = :bv_variable_name;&lt;br /&gt;
&lt;br /&gt;
=== DB2 ===&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;user-defined global variables&amp;quot; in DB2 are similar to the proposed PostgreSQL feature. The main difference is in the access rights: DB2 uses `READ` and `WRITE`, while PostgreSQL uses `SELECT` and `UPDATE`. Because PostgreSQL already uses the `SET` command for GUCs, the `LET` command was introduced in the proposal (DB2 uses `SET`).&lt;br /&gt;
&lt;br /&gt;
Variables are visible in all sessions, but their values are private to each session. Variables are not transactional. Their usage is broader than in the proposal: they can be changed by `SET`, `SELECT INTO`, or used as OUT parameters of procedures. The search path (or a similar mechanism) applies to variables as well, but variables have lower priority than tables or columns.&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE myCounter INT DEFAULT 01;&lt;br /&gt;
    SELECT EMPNO, LASTNAME, CASE WHEN myCounter = 1 THEN SALARY ELSE NULL END&lt;br /&gt;
    FROM EMPLOYEE WHERE WORKDEPT = &#039;A00&#039;;&lt;br /&gt;
    SET myCounter = 29;&lt;br /&gt;
&lt;br /&gt;
There also appear to be other kinds of variables, accessed using the function `GETVARIABLE(&#039;name&#039;, &#039;default&#039;)`. These are very similar to PostgreSQL GUCs and the `current_setting` function. Such variables can be set via the connection string, are of type `VARCHAR`, and up to 10 values are allowed. Built-in session variables (configuration) can also be accessed using `GETVARIABLE`.&lt;br /&gt;
&lt;br /&gt;
=== MSSQL (T-SQL) ===&lt;br /&gt;
&lt;br /&gt;
MSSQL provides two types of variables:&lt;br /&gt;
&lt;br /&gt;
* Global variables — accessed with the `@@varname` syntax. These are similar to GUCs and provide state information such as `@@ERROR`, `@@ROWCOUNT`, or `@@IDENTITY`. &lt;br /&gt;
* Local variables — accessed with the `@varname` syntax. These must be declared with the `DECLARE` command before use. Their scope is limited to the batch, procedure, or function where they are executed.&lt;br /&gt;
&lt;br /&gt;
    DECLARE @TestVariable AS VARCHAR(100)&lt;br /&gt;
    SET @TestVariable = &#039;Think Green&#039;&lt;br /&gt;
    GO&lt;br /&gt;
    PRINT @TestVariable&lt;br /&gt;
&lt;br /&gt;
This script fails because `PRINT` is executed in a different batch. Therefore, it seems MSSQL does not truly support session variables.&lt;br /&gt;
&lt;br /&gt;
There are also mechanisms similar to PostgreSQL&#039;s custom GUCs and the use of `current_setting` and `set_config` functions. In general, MSSQL is fairly primitive in this area.&lt;br /&gt;
&lt;br /&gt;
    EXEC sp_set_session_context &#039;user_id&#039;, 4;&lt;br /&gt;
    SELECT SESSION_CONTEXT(N&#039;user_id&#039;);&lt;br /&gt;
&lt;br /&gt;
== ToDo ==&lt;br /&gt;
&lt;br /&gt;
* SECURITY LABEL support — enhance the `dummy_seclabel` module and the `sepgsql` extension. Update PostgreSQL documentation and ensure `SecLabelSupportsObjectType` includes session variables.&lt;/div&gt;</summary>
		<author><name>Okbobcz</name></author>
	</entry>
	<entry>
		<id>https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41839</id>
		<title>Implementation of declarative catalog session variables</title>
		<link rel="alternate" type="text/html" href="https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41839"/>
		<updated>2025-09-29T07:00:08Z</updated>

		<summary type="html">&lt;p&gt;Okbobcz: /* Proposal for Postgres - design session variables as database global temporary typed objects with ANSI SQL syntax for identifiers */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Session variables are database objects that can hold a value across multiple queries inside a session. The design of session variables varies widely, and many databases implement more than one type of session variable.&lt;br /&gt;
&lt;br /&gt;
The SQL/PSM standard introduces modules and module-level variables. However, this part of the standard has not been implemented in any widely used product. As a result, each database system has developed its own proprietary design, syntax, and feature set.&lt;br /&gt;
&lt;br /&gt;
Session variables are often part of the stored procedure environment, but there are exceptions --- for example, MySQL session variables or client-side session variables in tools like psql. In most systems, session variables do not participate in transactions: once set, their value persists until explicitly changed, even if the surrounding transaction is rolled back. Conceptually, they behave much like variables in general-purpose programming languages.&lt;br /&gt;
&lt;br /&gt;
Because there is no common design, comparisons across systems are difficult. Each implementation has its own advantages and disadvantages, and in some cases a single system supports more than one kind of session variable.&lt;br /&gt;
&lt;br /&gt;
Kinds of session variables:&lt;br /&gt;
* client side (psql, pgadmin, ...) - mostly similar to shell variables (based on string operations)&lt;br /&gt;
* server side&lt;br /&gt;
** declarative&lt;br /&gt;
*** created only for batch (T-SQL)&lt;br /&gt;
*** created as an database object (or part of database object) (Oracle, DB2)&lt;br /&gt;
** created by usage (MySQL)&lt;br /&gt;
** with priprietary syntax for identifiers (MySQL, T-SQL)&lt;br /&gt;
** with ANSI SQL syntax for identifiers (Oracle, DB2)&lt;br /&gt;
&lt;br /&gt;
Possible usage of session variables:&lt;br /&gt;
&lt;br /&gt;
* usage of session variables as global variables in PL (tracing, debugging, hacking),&lt;br /&gt;
* holding configuration (for PL),&lt;br /&gt;
* holding some more used data in interactive session,&lt;br /&gt;
* holding credentials for some RLS and data securing,&lt;br /&gt;
* helping with migration from others systems (mainly from Oracle),&lt;br /&gt;
* allow anonymous block parametrization (only postgres).&lt;br /&gt;
&lt;br /&gt;
== SQL/PSM ==&lt;br /&gt;
&lt;br /&gt;
The SQL standard introduces the concept of modules, which can be associated with schemas (partially). Variables in modules behave like PL/pgSQL variables but are only local. The only objects allowed at the module level are temporary tables, which can be accessed only from routines assigned to that module. Modules can encapsulate private objects (functions, procedure, temp tables), that are not visible outside the module. Inside the module, a  private objects shadows other objects. What I know the modules (and generally SQL/PSM) doesn&#039;t cover a deployment of code, so there is not similarity with PostgreSQL extensions. SQL/PSM Modules are modules like modules from modular languages like Modula2 or Oberon. There is some similarity with Postgres schemas (both are containers) with significant differences (there is not a concept of SEARCH_PATH (on PSM side) or there is not a concept of private or public objects (on Postgres schema side)).&lt;br /&gt;
&lt;br /&gt;
SQL/PSM variables are not transactional (they are used exactly like in PL/pgSQL). The precedence between variables and columns is not specified.&lt;br /&gt;
&lt;br /&gt;
== Implementation Challenges ==&lt;br /&gt;
&lt;br /&gt;
* Possible collisions between session variables and other database objects.&lt;br /&gt;
* Memory cleanup after dropping a session variable, especially when DDL operations can be reverted (Postgres has not support for global temp objects).&lt;br /&gt;
* Memory invalidation: if a variable is dropped by one session, other sessions should not continue using it (Postgres has not support for global temp objects).&lt;br /&gt;
&lt;br /&gt;
== Proposed Implementations ==&lt;br /&gt;
&lt;br /&gt;
It is clean so no one design is perfect for all uses cases. MySQL is good enough and maybe handy for interactive work,&lt;br /&gt;
but cannot be used for storing any sensitive data, and it is unsafe. PostgreSQL GUC are perfect for configuration, but&lt;br /&gt;
are not friendly for interactive work, and are very limited for usage in PL as global variables. So no one design is&lt;br /&gt;
perfect, and I can imagine more different designs in one system, that can support some different use cases better.&lt;br /&gt;
&lt;br /&gt;
== Proposal for Postgres - design session variables as database global temporary typed objects with ANSI SQL syntax for identifiers ==&lt;br /&gt;
&lt;br /&gt;
I searched solution that can work with specific Postgres environment:&lt;br /&gt;
&lt;br /&gt;
* Postgres has more than one widely used language for stored procedures PL/pgSQL + PL/Python or PL/Perl&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL is strongly reduced PL/SQL, and PL/SQL is modified ADA language. I am pretty happy with the current comlexity of PL/pgSQL - it is domain specific language and it works well. It is a very simple language, and it is easy to learn it. PL/pgSQL has no packages or modules. Instead PostgreSQL schemas are used. But Postgres schemas are specific database objects. Schemas are independent on PL.&lt;br /&gt;
&lt;br /&gt;
* Postgres has already code container - schema. I don&#039;t want to introduce additional new container object - modules, with strong overlap with schemas.&lt;br /&gt;
&lt;br /&gt;
So I wanted design that can supports more PL languages, didn&#039;t introduce new complexity to relative simple PL/pgSQL and didn&#039;t introduce functionality that can be redundant with already existing objects.&lt;br /&gt;
I wrote bigger application in PL/pgSQL. For maintaining of this application I wrote plpgsql_lint (later plpgsql_check). So every time I am searching solution that doesn&#039;t block code static analyse. Static analyse requires persistent objects, and then it is direct way to design session variables as database objects (with own system catalog). When session variables are designed as database objects, then using ACL is natural. Oracle package variables are well protected just by package scope. Unfortunately, postgres doesn&#039;t know schema scope. The locality of schema objects is defined by setting of SEARCH_PATH guc, and introduction of new mechanism can be messy. But with ACL the content of variables can be safe too:&lt;br /&gt;
&lt;br /&gt;
    CREATE TEMP VARIABLE x AS int;&lt;br /&gt;
     &lt;br /&gt;
    LET x = 10;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(x);&lt;br /&gt;
    END;&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    SELECT VARIABLE(x);&lt;br /&gt;
&lt;br /&gt;
==== Collisions between columns and variables identifiers ====&lt;br /&gt;
There is a new possibility of identifiers collisions between variables and columns identifiers. With ANSI SQL syntax for session variables identifiers there is a problem how to distinct between variable and column. This is known problem in PL/pgSQL or PL/SQL and can be solved by some prioritization or by prohibition of collisions. The prohibition of collisions is solution that almost cleaned this issue from PL/pgSQL. Unfortunately session variables are global objects and then the collision can be in any query, and then the new badly named variable can stop execution of any query. Prioritization variables or columns has own problems - quiet unwanted using variable or column.&lt;br /&gt;
&lt;br /&gt;
    CREATE TABLE tab(a int);&lt;br /&gt;
    CREATE VARIABLE a AS int;&lt;br /&gt;
    &lt;br /&gt;
    SELECT a FROM tab; -- with disallowed collisions, the execution of this query stops when there is a variable named &amp;quot;a&amp;quot;&lt;br /&gt;
    &lt;br /&gt;
    LET a = 1;&lt;br /&gt;
    DELETE FROM tab WHERE a = 1; -- with higher priority for variables all table can be deleted (nobody proposed higher priority for variables).&lt;br /&gt;
    SELECT a FROM tab; -- with higher priority for columns, the variable is not used (quietly)&lt;br /&gt;
&lt;br /&gt;
The prohibition of collision is not absolute protection against possible human errors:&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE _id AS int;&lt;br /&gt;
    LET _id = 10;&lt;br /&gt;
    CREATE TABLE foo(id int);&lt;br /&gt;
    DELETE FROM foo WHERE _id = 10; -- just typo (unwanted _), but can have unwanted impact (all rows removed)&lt;br /&gt;
&lt;br /&gt;
These problems are not new. The developers in PL/pgSQL or PL/SQL knows it, but the scope of these risks are limited only to stored procedures. After very very long discussion and lot of designs I introduced variable fences (syntax `VARIABLE(varname)`). Inside of any query the variable can be used only in variable fence. This is 100% solution of collisions, and I think it can be good solution against human errors and unwanted usage of session variable. Now, variable fences are necessary every where inside a query or expressions, but I thinking about possibility to be optional inside PL/pgSQL (far future). This can reduce overhead with porting applications from Oracle, and generally the developers of stored procedures are experienced with described risks (this can be controlled by new `plpgsql.extra_checks`).&lt;br /&gt;
&lt;br /&gt;
A variable fence can be in collisions with custom function &amp;quot;variable&amp;quot;. There is similarity with usage of keywords `cube`. This can be fixed by usage prefix schema or by usage of parenthesis:&lt;br /&gt;
&lt;br /&gt;
    SELECT public.variable(...)&lt;br /&gt;
    SELECT &amp;quot;variable&amp;quot;(...)&lt;br /&gt;
&lt;br /&gt;
Why I don&#039;t propose usage of T-SQL (MSSQL, MySQL) syntax for session variables? There is more reasons (one is objective, last is subjective):&lt;br /&gt;
&lt;br /&gt;
* There is a collision with custom operators - operators `@` or `@@` are already used - usage of `@@` is common&lt;br /&gt;
&lt;br /&gt;
    (2025-09-28 06:57:30) postgres=# create table tab(a int);&lt;br /&gt;
    CREATE TABLE&lt;br /&gt;
    (2025-09-28 06:57:42) postgres=# insert into tab values(10);&lt;br /&gt;
    INSERT 0 1&lt;br /&gt;
    (2025-09-28 06:57:50) postgres=# select @a from tab;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │       10 │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
We can safely use only `:` prefix, but then we can break a psql variables. Similarly we can use `$` prefix, but then we can break query parameters syntax.&lt;br /&gt;
&lt;br /&gt;
* T-SQL variable names looks weird in Postgres (in queries, in PL/pgSQL). Postgres knows only ANSI/SQL identifiers, PL/pgSQL shares these rules too, and this are the PostgreSQL is consistent, and I missing motivation to introduce something new (with the knowledge so problems with possible compatibility breaks should be solved too). &lt;br /&gt;
&lt;br /&gt;
Note: There was a proposal to use table syntax as variable (like table (in memory) variables from T-SQL but only single row). Although it is very effective solution of collisions, I dislike this idea. It increase a complexity of simple expression (or increase inconsistency of some syntax rules). More looks weird for scalar variables (and can be handy for composite variables):&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(var); -- variable is not a table&lt;br /&gt;
    END&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      -- variable a a table (I don&#039;t like it)&lt;br /&gt;
      -- can we use DML? How much rows this kind of variable can hold&lt;br /&gt;
      -- value is row or not?&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, (SELECT var FROM var);&lt;br /&gt;
    END&lt;br /&gt;
    $$&lt;br /&gt;
&lt;br /&gt;
I am not against for introduction of inmemory tables - or more correctly for global temporary tables. Well implemented global temporary tables can be very nice and handy feature. But it is different feature, and should be designed separately. There can be some performance problems - plpgsql can use simple expression evaluation (when expression has not usage of any table). This optimization can be changed or enhanced, but its is change inside complex sensitive code, and it is not consistent. I very like a global temporary tables (that are not implemented in Postgres). Table (and related operations) should to looks like table, variable (and related operations) should to looks like variable. Common perspective how session variables should to looks is based on variables from PL environment. PostgreSQL internally supports &amp;lt;i&amp;gt;transition tables&amp;lt;/i&amp;gt;, and I can imagine to allow this kind of tables outside of triggers too. This is valid use case (and valid functionality), but it is something different than session variables (although there can be some overlap). The implementation of declared tables inside PL/pgSQL can be more easy from high design perspective (interaction between SQL tables and PL/pgSQL is well defined already), but difficult from low level perspective (where to store column statistics and other important data for optimization - same problems like with global temporary tables (without some fake proxy local temp tables)).&lt;br /&gt;
&lt;br /&gt;
   CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
   AS $$&lt;br /&gt;
   -- not implemented yet, not part of this proposal&lt;br /&gt;
   DECLARE t TABLE(a int, b int); -- this table is invisible outside fx&lt;br /&gt;
   BEGIN&lt;br /&gt;
     INSERT INTO t VALUES(10,20);&lt;br /&gt;
     INSERT INTO t VALUES(30,40);&lt;br /&gt;
     RAISE NOTICE &#039;%&#039;, (SELECT count(*) FROM t);&lt;br /&gt;
   END;&lt;br /&gt;
   $$ LANGUAGE plpgsql;&lt;br /&gt;
&lt;br /&gt;
Some data languages defined relational variables - common concept of session variables has zero intersection with this concept.&lt;br /&gt;
&lt;br /&gt;
== Variabes in PostgreSQL (as of v18) ==&lt;br /&gt;
&lt;br /&gt;
PostgreSQL provides relatively advanced client-side session variables in psql --- these variables use the `:` prefix. The implementation is pretty much straightforward: `psql`&#039;s parser reads tokens from input and, when it encounters a token related to a variable, replaces it with the variable&#039;s value. The syntax supports literal and identifier escaping, and variables can be used as arguments of the `\bind` command.&lt;br /&gt;
&lt;br /&gt;
`psql` variables are typeless client-side variables, available only inside `psql` or `pgbench`.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:40:18) postgres=# \set myvar ahoj&lt;br /&gt;
    (2025-09-25 09:40:33) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:14) postgres=# select :&#039;myvar&#039;;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:24) postgres=# select $1 \bind :myvar \g&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
The `\set` command allows only simple string concatenation, but it also supports execution of shell commands. When a query is executed with the `\gset` command, its result can be stored in a `psql` variable.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:41:40) postgres=# \set ctime `date`&lt;br /&gt;
    (2025-09-25 09:43:55) postgres=# \echo :ctime&lt;br /&gt;
    Thu Sep 25 09:43:55 CEST 2025&lt;br /&gt;
&lt;br /&gt;
The `\set` command is fairly limited, making complex operations unreadable or non-portable.&lt;br /&gt;
&lt;br /&gt;
This mechanism is good enough for writing tests, but it is not generally available (most users do not need it), and it is not accessible from the server side (e.g., from stored procedures). There is no simple way to access `psql` variables from an anonymous block --- a proxy custom GUC variable must be used:&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:58:38) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    (2025-09-25 09:58:43) postgres=# select set_config(&#039;myvars.myvar&#039;, :&#039;myvar&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ ahoj       │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 10:00:27) postgres=# do $$ begin raise notice &#039;%&#039;, current_setting(&#039;myvars.myvar&#039;); end $$;&lt;br /&gt;
    NOTICE:  ahoj&lt;br /&gt;
    DO&lt;br /&gt;
&lt;br /&gt;
PostgreSQL also has configuration variables (GUC - Grand Unified Configuration). These are primarily designed for PostgreSQL and extension configuration, but they can be used as session variables. GUC variables can be set with the `SET` command or the `set_config` function, and read with `SHOW` or `current_setting`.&lt;br /&gt;
&lt;br /&gt;
GUC variables are meant for holding configuration parameters. When created through the internal API, they can be restricted to superusers, hidden from regular users, and assigned specific types (boolean, memory, number, interval, string, enum) with validation. This type system is separate from the SQL type system and is initialized before PostgreSQL’s SQL types.&lt;br /&gt;
&lt;br /&gt;
Variables created from SQL must be &amp;quot;custom&amp;quot; variables, using a prefix in their name --- these are always strings. Such custom GUCs are often used as a workaround for missing server-side session variables in PostgreSQL, but they are typeless, unprotected, and unsafe.&lt;br /&gt;
&lt;br /&gt;
A notable feature of PostgreSQL GUCs (built-in or custom) is that they can have different scopes: transaction, session, or function execution. They can also be assigned default values at specific events --- configuration loading, role login, database login, or function start. These features are useful for configuration, but problematic when GUCs are repurposed as session variables.&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
    RETURNS void AS $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, current_setting(&#039;my.x&#039;);&lt;br /&gt;
    END $$ LANGUAGE plpgsql SET my.x = 20;&lt;br /&gt;
    CREATE FUNCTION&lt;br /&gt;
    (2025-09-27 06:10:37) postgres=# SET my.x = 0; SELECT fx();&lt;br /&gt;
    SET&lt;br /&gt;
    NOTICE:  20&lt;br /&gt;
    ┌────┐&lt;br /&gt;
    │ fx │&lt;br /&gt;
    ╞════╡&lt;br /&gt;
    │    │&lt;br /&gt;
    └────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    (2025-09-27 06:10:39) postgres=# SHOW my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 0    │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
GUC variables are transactional, and this behavior cannot be disabled.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-27 06:17:31) postgres=# set my.x = 10;&lt;br /&gt;
    SET&lt;br /&gt;
    (2025-09-27 06:19:42) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:19:46) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, true);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:19:55) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:01) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:20:11) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:13) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:20:38) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:45) postgres=# rollback;&lt;br /&gt;
    ROLLBACK&lt;br /&gt;
    (2025-09-27 06:20:52) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:55) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:22:36) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:22:39) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:22:42) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
As a result, there is no reliable way to store details such as error information in custom GUCs.&lt;br /&gt;
&lt;br /&gt;
== Session Variables in Other Systems ==&lt;br /&gt;
&lt;br /&gt;
This section surveys how session variables are implemented in several widely used database systems. Each subsection includes code examples to illustrate syntax, behaviour, and scope.&lt;br /&gt;
&lt;br /&gt;
=== MySQL ===&lt;br /&gt;
&lt;br /&gt;
Session variables in MySQL have syntax similar to the better-known T-SQL variables (though other details are MySQL-specific). MySQL distinguishes two kinds of variables:&lt;br /&gt;
&lt;br /&gt;
* system variables - referenced using the `@@` prefix (or `@@scope.name`)&lt;br /&gt;
* user-defined variables - referenced using the `@` prefix&lt;br /&gt;
&lt;br /&gt;
System variables are modified with the `SET` statement. The `SET` statement accepts the modifiers `GLOBAL`, `SESSION`, or `PERSIST`. Some variables can only be set using the `GLOBAL` or `SESSION` modifier; when the variable name is specified with the `@@` prefix, it is not necessary to explicitly indicate the scope. System variables are defined by MySQL or by MySQL plugins.&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL some_config = 100;&lt;br /&gt;
    SET @@same_config = 100;&lt;br /&gt;
    &lt;br /&gt;
    SET SESSION sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@SESSION.sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
&lt;br /&gt;
Persistent values are stored in `mysqld-auto.cnf` (in the server data directory).&lt;br /&gt;
&lt;br /&gt;
Resetting system variables can be done by assigning DEFAULT or by copying from the GLOBAL namespace:&lt;br /&gt;
&lt;br /&gt;
    SET @@sql_mode = DEFAULT;&lt;br /&gt;
    SET @@sql_mode = @@GLOBAL.sql_mode&lt;br /&gt;
&lt;br /&gt;
PostgreSQL equivalents:&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL ; -- there is not similar command in Postgres&lt;br /&gt;
    SET SESSION ; -- SET&lt;br /&gt;
    SET PERSIST ; -- ALTER SYSTEM; SELECT pg_reload_conf();&lt;br /&gt;
    SELECT @@var; -- SELECT current_setting(&#039;var);&lt;br /&gt;
&lt;br /&gt;
User-defined variables are created by assignment:&lt;br /&gt;
&lt;br /&gt;
    SET @var = expr [, @var2 = expr [ ... ]];&lt;br /&gt;
&lt;br /&gt;
User-defined variables can only be of type integer, decimal, float, binary, non-binary string, or NULL. Casting between these types is implicit and hidden. User-defined variables cannot be used as table or column names.&lt;br /&gt;
&lt;br /&gt;
An advantage of the T-SQL-style syntax (with special prefixes) is that it provides a simple solution for avoiding identifier collisions. MySQL variables are convenient, and the user experience can be good for people already familiar with T-SQL (MSSQL or Sybase). On the other hand, prefix-based syntax can be confusing for users coming from systems other than MSSQL. The type system is perhaps too simple and can be unsafe. In general, the performance of MySQL (or MariaDB) stored procedures is not good, and stored procedures are therefore not frequently used. There is no protection against typographical errors. Unassigned user variables can be used without error (they default to NULL), which makes static checking of stored procedures impossible in this area.&lt;br /&gt;
&lt;br /&gt;
There is no way to restrict access to user-defined variables in MySQL. They cannot be protected against unintended usage. The only possible safeguard is a naming convention, since all user-defined variables share a single namespace.&lt;br /&gt;
&lt;br /&gt;
=== Oracle ===&lt;br /&gt;
&lt;br /&gt;
Oracle PL/SQL allows the use of package variables. PL/SQL is based on the Ada language, and package variables act as &amp;quot;global&amp;quot; variables. They are not directly visible from SQL, but Oracle provides a reduced syntax for functions without arguments, so you typically write a wrapper:&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE PACKAGE my_package&lt;br /&gt;
    AS&lt;br /&gt;
      FUNCTION get_a RETURN NUMBER;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    /&lt;br /&gt;
    &lt;br /&gt;
    CREATE OR REPLACE PACKAGE BODY my_package&lt;br /&gt;
    AS&lt;br /&gt;
      a  NUMBER(20);&lt;br /&gt;
      &lt;br /&gt;
      FUNCTION get_a&lt;br /&gt;
      RETURN NUMBER&lt;br /&gt;
      IS&lt;br /&gt;
      BEGIN&lt;br /&gt;
        RETURN a;&lt;br /&gt;
      END get_a;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    &lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
Inside SQL, SQL identifiers take precedence. Inside non-SQL commands (such as CALL or PL/SQL blocks), package identifiers take precedence.&lt;br /&gt;
&lt;br /&gt;
Oracle allows both syntaxes for calling a function with zero arguments:&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a() FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
This reduces the risk of naming collisions. Package variables persist for the duration of a session.&lt;br /&gt;
&lt;br /&gt;
Another possibility is to use variables in SQL*Plus (similar to `psql` variables, but with the ability to define the type on the server side).&lt;br /&gt;
&lt;br /&gt;
A variable is declared with the `VARIABLE` command and can be accessed in the session using the `:varname` syntax (a prior declaration step may be optional):&lt;br /&gt;
&lt;br /&gt;
    VARIABLE bv_variable_name VARCHAR2(30)&lt;br /&gt;
    BEGIN&lt;br /&gt;
      :bv_variable_name := &#039;Some Value&#039;;&lt;br /&gt;
    END;&lt;br /&gt;
    &lt;br /&gt;
    SELECT column_name&lt;br /&gt;
      FROM   table_name&lt;br /&gt;
     WHERE  column_name = :bv_variable_name;&lt;br /&gt;
&lt;br /&gt;
=== DB2 ===&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;user-defined global variables&amp;quot; in DB2 are similar to the proposed PostgreSQL feature. The main difference is in the access rights: DB2 uses `READ` and `WRITE`, while PostgreSQL uses `SELECT` and `UPDATE`. Because PostgreSQL already uses the `SET` command for GUCs, the `LET` command was introduced in the proposal (DB2 uses `SET`).&lt;br /&gt;
&lt;br /&gt;
Variables are visible in all sessions, but their values are private to each session. Variables are not transactional. Their usage is broader than in the proposal: they can be changed by `SET`, `SELECT INTO`, or used as OUT parameters of procedures. The search path (or a similar mechanism) applies to variables as well, but variables have lower priority than tables or columns.&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE myCounter INT DEFAULT 01;&lt;br /&gt;
    SELECT EMPNO, LASTNAME, CASE WHEN myCounter = 1 THEN SALARY ELSE NULL END&lt;br /&gt;
    FROM EMPLOYEE WHERE WORKDEPT = &#039;A00&#039;;&lt;br /&gt;
    SET myCounter = 29;&lt;br /&gt;
&lt;br /&gt;
There also appear to be other kinds of variables, accessed using the function `GETVARIABLE(&#039;name&#039;, &#039;default&#039;)`. These are very similar to PostgreSQL GUCs and the `current_setting` function. Such variables can be set via the connection string, are of type `VARCHAR`, and up to 10 values are allowed. Built-in session variables (configuration) can also be accessed using `GETVARIABLE`.&lt;br /&gt;
&lt;br /&gt;
=== MSSQL (T-SQL) ===&lt;br /&gt;
&lt;br /&gt;
MSSQL provides two types of variables:&lt;br /&gt;
&lt;br /&gt;
* Global variables — accessed with the `@@varname` syntax. These are similar to GUCs and provide state information such as `@@ERROR`, `@@ROWCOUNT`, or `@@IDENTITY`. &lt;br /&gt;
* Local variables — accessed with the `@varname` syntax. These must be declared with the `DECLARE` command before use. Their scope is limited to the batch, procedure, or function where they are executed.&lt;br /&gt;
&lt;br /&gt;
    DECLARE @TestVariable AS VARCHAR(100)&lt;br /&gt;
    SET @TestVariable = &#039;Think Green&#039;&lt;br /&gt;
    GO&lt;br /&gt;
    PRINT @TestVariable&lt;br /&gt;
&lt;br /&gt;
This script fails because `PRINT` is executed in a different batch. Therefore, it seems MSSQL does not truly support session variables.&lt;br /&gt;
&lt;br /&gt;
There are also mechanisms similar to PostgreSQL&#039;s custom GUCs and the use of `current_setting` and `set_config` functions. In general, MSSQL is fairly primitive in this area.&lt;br /&gt;
&lt;br /&gt;
    EXEC sp_set_session_context &#039;user_id&#039;, 4;&lt;br /&gt;
    SELECT SESSION_CONTEXT(N&#039;user_id&#039;);&lt;br /&gt;
&lt;br /&gt;
== ToDo ==&lt;br /&gt;
&lt;br /&gt;
* SECURITY LABEL support — enhance the `dummy_seclabel` module and the `sepgsql` extension. Update PostgreSQL documentation and ensure `SecLabelSupportsObjectType` includes session variables.&lt;/div&gt;</summary>
		<author><name>Okbobcz</name></author>
	</entry>
	<entry>
		<id>https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41838</id>
		<title>Implementation of declarative catalog session variables</title>
		<link rel="alternate" type="text/html" href="https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41838"/>
		<updated>2025-09-29T04:45:28Z</updated>

		<summary type="html">&lt;p&gt;Okbobcz: /* Collisions between columns and variables identifiers */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Session variables are database objects that can hold a value across multiple queries inside a session. The design of session variables varies widely, and many databases implement more than one type of session variable.&lt;br /&gt;
&lt;br /&gt;
The SQL/PSM standard introduces modules and module-level variables. However, this part of the standard has not been implemented in any widely used product. As a result, each database system has developed its own proprietary design, syntax, and feature set.&lt;br /&gt;
&lt;br /&gt;
Session variables are often part of the stored procedure environment, but there are exceptions --- for example, MySQL session variables or client-side session variables in tools like psql. In most systems, session variables do not participate in transactions: once set, their value persists until explicitly changed, even if the surrounding transaction is rolled back. Conceptually, they behave much like variables in general-purpose programming languages.&lt;br /&gt;
&lt;br /&gt;
Because there is no common design, comparisons across systems are difficult. Each implementation has its own advantages and disadvantages, and in some cases a single system supports more than one kind of session variable.&lt;br /&gt;
&lt;br /&gt;
Kinds of session variables:&lt;br /&gt;
* client side (psql, pgadmin, ...) - mostly similar to shell variables (based on string operations)&lt;br /&gt;
* server side&lt;br /&gt;
** declarative&lt;br /&gt;
*** created only for batch (T-SQL)&lt;br /&gt;
*** created as an database object (or part of database object) (Oracle, DB2)&lt;br /&gt;
** created by usage (MySQL)&lt;br /&gt;
** with priprietary syntax for identifiers (MySQL, T-SQL)&lt;br /&gt;
** with ANSI SQL syntax for identifiers (Oracle, DB2)&lt;br /&gt;
&lt;br /&gt;
Possible usage of session variables:&lt;br /&gt;
&lt;br /&gt;
* usage of session variables as global variables in PL (tracing, debugging, hacking),&lt;br /&gt;
* holding configuration (for PL),&lt;br /&gt;
* holding some more used data in interactive session,&lt;br /&gt;
* holding credentials for some RLS and data securing,&lt;br /&gt;
* helping with migration from others systems (mainly from Oracle),&lt;br /&gt;
* allow anonymous block parametrization (only postgres).&lt;br /&gt;
&lt;br /&gt;
== SQL/PSM ==&lt;br /&gt;
&lt;br /&gt;
The SQL standard introduces the concept of modules, which can be associated with schemas (partially). Variables in modules behave like PL/pgSQL variables but are only local. The only objects allowed at the module level are temporary tables, which can be accessed only from routines assigned to that module. Modules can encapsulate private objects (functions, procedure, temp tables), that are not visible outside the module. Inside the module, a  private objects shadows other objects. What I know the modules (and generally SQL/PSM) doesn&#039;t cover a deployment of code, so there is not similarity with PostgreSQL extensions. SQL/PSM Modules are modules like modules from modular languages like Modula2 or Oberon. There is some similarity with Postgres schemas (both are containers) with significant differences (there is not a concept of SEARCH_PATH (on PSM side) or there is not a concept of private or public objects (on Postgres schema side)).&lt;br /&gt;
&lt;br /&gt;
SQL/PSM variables are not transactional (they are used exactly like in PL/pgSQL). The precedence between variables and columns is not specified.&lt;br /&gt;
&lt;br /&gt;
== Implementation Challenges ==&lt;br /&gt;
&lt;br /&gt;
* Possible collisions between session variables and other database objects.&lt;br /&gt;
* Memory cleanup after dropping a session variable, especially when DDL operations can be reverted (Postgres has not support for global temp objects).&lt;br /&gt;
* Memory invalidation: if a variable is dropped by one session, other sessions should not continue using it (Postgres has not support for global temp objects).&lt;br /&gt;
&lt;br /&gt;
== Proposed Implementations ==&lt;br /&gt;
&lt;br /&gt;
It is clean so no one design is perfect for all uses cases. MySQL is good enough and maybe handy for interactive work,&lt;br /&gt;
but cannot be used for storing any sensitive data, and it is unsafe. PostgreSQL GUC are perfect for configuration, but&lt;br /&gt;
are not friendly for interactive work, and are very limited for usage in PL as global variables. So no one design is&lt;br /&gt;
perfect, and I can imagine more different designs in one system, that can support some different use cases better.&lt;br /&gt;
&lt;br /&gt;
== Proposal for Postgres - design session variables as database global temporary typed objects with ANSI SQL syntax for identifiers ==&lt;br /&gt;
&lt;br /&gt;
I searched solution that can work with specific Postgres environment:&lt;br /&gt;
&lt;br /&gt;
* Postgres has more than one widely used language for stored procedures PL/pgSQL + PL/Python or PL/Perl&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL is strongly reduced PL/SQL, and PL/SQL is modified ADA language. I am pretty happy with the current scope of PL/SQL - it is domain specific language and it works well. It is a very simple language, and it is easy to learn it. PL/pgSQL has no packages or modules. Instead PostgreSQL schemas are used. But Postgres schemas are specific database objects. Schemas are independent on PL. &lt;br /&gt;
&lt;br /&gt;
So I wanted design that can supports more PL languages, didn&#039;t introduce new complexity to relative simple PL/pgSQL and didn&#039;t introduce functionality that can be redundant with already existing objects.&lt;br /&gt;
I wrote bigger application in PL/pgSQL. For maintaining of this application I wrote plpgsql_lint (later plpgsql_check). So every time I am searching solution that doesn&#039;t block code static analyse. Static analyse requires persistent objects, and then it is direct way to design session variables as database objects (with own system catalog). When session variables are designed as database objects, then using ACL is natural. Oracle package variables are well protected just by package scope. Unfortunately, postgres doesn&#039;t know schema scope. The locality of schema objects is defined by setting of SEARCH_PATH guc, and introduction of new mechanism can be messy. But with ACL the content of variables can be safe too:&lt;br /&gt;
&lt;br /&gt;
    CREATE TEMP VARIABLE x AS int;&lt;br /&gt;
     &lt;br /&gt;
    LET x = 10;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(x);&lt;br /&gt;
    END;&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    SELECT VARIABLE(x);&lt;br /&gt;
&lt;br /&gt;
==== Collisions between columns and variables identifiers ====&lt;br /&gt;
There is a new possibility of identifiers collisions between variables and columns identifiers. With ANSI SQL syntax for session variables identifiers there is a problem how to distinct between variable and column. This is known problem in PL/pgSQL or PL/SQL and can be solved by some prioritization or by prohibition of collisions. The prohibition of collisions is solution that almost cleaned this issue from PL/pgSQL. Unfortunately session variables are global objects and then the collision can be in any query, and then the new badly named variable can stop execution of any query. Prioritization variables or columns has own problems - quiet unwanted using variable or column.&lt;br /&gt;
&lt;br /&gt;
    CREATE TABLE tab(a int);&lt;br /&gt;
    CREATE VARIABLE a AS int;&lt;br /&gt;
    &lt;br /&gt;
    SELECT a FROM tab; -- with disallowed collisions, the execution of this query stops when there is a variable named &amp;quot;a&amp;quot;&lt;br /&gt;
    &lt;br /&gt;
    LET a = 1;&lt;br /&gt;
    DELETE FROM tab WHERE a = 1; -- with higher priority for variables all table can be deleted (nobody proposed higher priority for variables).&lt;br /&gt;
    SELECT a FROM tab; -- with higher priority for columns, the variable is not used (quietly)&lt;br /&gt;
&lt;br /&gt;
The prohibition of collision is not absolute protection against possible human errors:&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE _id AS int;&lt;br /&gt;
    LET _id = 10;&lt;br /&gt;
    CREATE TABLE foo(id int);&lt;br /&gt;
    DELETE FROM foo WHERE _id = 10; -- just typo (unwanted _), but can have unwanted impact (all rows removed)&lt;br /&gt;
&lt;br /&gt;
These problems are not new. The developers in PL/pgSQL or PL/SQL knows it, but the scope of these risks are limited only to stored procedures. After very very long discussion and lot of designs I introduced variable fences (syntax `VARIABLE(varname)`). Inside of any query the variable can be used only in variable fence. This is 100% solution of collisions, and I think it can be good solution against human errors and unwanted usage of session variable. Now, variable fences are necessary every where inside a query or expressions, but I thinking about possibility to be optional inside PL/pgSQL (far future). This can reduce overhead with porting applications from Oracle, and generally the developers of stored procedures are experienced with described risks (this can be controlled by new `plpgsql.extra_checks`).&lt;br /&gt;
&lt;br /&gt;
A variable fence can be in collisions with custom function &amp;quot;variable&amp;quot;. There is similarity with usage of keywords `cube`. This can be fixed by usage prefix schema or by usage of parenthesis:&lt;br /&gt;
&lt;br /&gt;
    SELECT public.variable(...)&lt;br /&gt;
    SELECT &amp;quot;variable&amp;quot;(...)&lt;br /&gt;
&lt;br /&gt;
Why I don&#039;t propose usage of T-SQL (MSSQL, MySQL) syntax for session variables? There is more reasons (one is objective, last is subjective):&lt;br /&gt;
&lt;br /&gt;
* There is a collision with custom operators - operators `@` or `@@` are already used - usage of `@@` is common&lt;br /&gt;
&lt;br /&gt;
    (2025-09-28 06:57:30) postgres=# create table tab(a int);&lt;br /&gt;
    CREATE TABLE&lt;br /&gt;
    (2025-09-28 06:57:42) postgres=# insert into tab values(10);&lt;br /&gt;
    INSERT 0 1&lt;br /&gt;
    (2025-09-28 06:57:50) postgres=# select @a from tab;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │       10 │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
We can safely use only `:` prefix, but then we can break a psql variables. Similarly we can use `$` prefix, but then we can break query parameters syntax.&lt;br /&gt;
&lt;br /&gt;
* T-SQL variable names looks weird in Postgres (in queries, in PL/pgSQL). Postgres knows only ANSI/SQL identifiers, PL/pgSQL shares these rules too, and this are the PostgreSQL is consistent, and I missing motivation to introduce something new (with the knowledge so problems with possible compatibility breaks should be solved too). &lt;br /&gt;
&lt;br /&gt;
Note: There was a proposal to use table syntax as variable (like table (in memory) variables from T-SQL but only single row). Although it is very effective solution of collisions, I dislike this idea. It increase a complexity of simple expression (or increase inconsistency of some syntax rules). More looks weird for scalar variables (and can be handy for composite variables):&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(var); -- variable is not a table&lt;br /&gt;
    END&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      -- variable a a table (I don&#039;t like it)&lt;br /&gt;
      -- can we use DML? How much rows this kind of variable can hold&lt;br /&gt;
      -- value is row or not?&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, (SELECT var FROM var);&lt;br /&gt;
    END&lt;br /&gt;
    $$&lt;br /&gt;
&lt;br /&gt;
I am not against for introduction of inmemory tables - or more correctly for global temporary tables. Well implemented global temporary tables can be very nice and handy feature. But it is different feature, and should be designed separately. There can be some performance problems - plpgsql can use simple expression evaluation (when expression has not usage of any table). This optimization can be changed or enhanced, but its is change inside complex sensitive code, and it is not consistent. I very like a global temporary tables (that are not implemented in Postgres). Table (and related operations) should to looks like table, variable (and related operations) should to looks like variable. Common perspective how session variables should to looks is based on variables from PL environment. PostgreSQL internally supports &amp;lt;i&amp;gt;transition tables&amp;lt;/i&amp;gt;, and I can imagine to allow this kind of tables outside of triggers too. This is valid use case (and valid functionality), but it is something different than session variables (although there can be some overlap). The implementation of declared tables inside PL/pgSQL can be more easy from high design perspective (interaction between SQL tables and PL/pgSQL is well defined already), but difficult from low level perspective (where to store column statistics and other important data for optimization - same problems like with global temporary tables (without some fake proxy local temp tables)).&lt;br /&gt;
&lt;br /&gt;
   CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
   AS $$&lt;br /&gt;
   -- not implemented yet, not part of this proposal&lt;br /&gt;
   DECLARE t TABLE(a int, b int); -- this table is invisible outside fx&lt;br /&gt;
   BEGIN&lt;br /&gt;
     INSERT INTO t VALUES(10,20);&lt;br /&gt;
     INSERT INTO t VALUES(30,40);&lt;br /&gt;
     RAISE NOTICE &#039;%&#039;, (SELECT count(*) FROM t);&lt;br /&gt;
   END;&lt;br /&gt;
   $$ LANGUAGE plpgsql;&lt;br /&gt;
&lt;br /&gt;
Some data languages defined relational variables - common concept of session variables has zero intersection with this concept.&lt;br /&gt;
&lt;br /&gt;
== Variabes in PostgreSQL (as of v18) ==&lt;br /&gt;
&lt;br /&gt;
PostgreSQL provides relatively advanced client-side session variables in psql --- these variables use the `:` prefix. The implementation is pretty much straightforward: `psql`&#039;s parser reads tokens from input and, when it encounters a token related to a variable, replaces it with the variable&#039;s value. The syntax supports literal and identifier escaping, and variables can be used as arguments of the `\bind` command.&lt;br /&gt;
&lt;br /&gt;
`psql` variables are typeless client-side variables, available only inside `psql` or `pgbench`.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:40:18) postgres=# \set myvar ahoj&lt;br /&gt;
    (2025-09-25 09:40:33) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:14) postgres=# select :&#039;myvar&#039;;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:24) postgres=# select $1 \bind :myvar \g&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
The `\set` command allows only simple string concatenation, but it also supports execution of shell commands. When a query is executed with the `\gset` command, its result can be stored in a `psql` variable.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:41:40) postgres=# \set ctime `date`&lt;br /&gt;
    (2025-09-25 09:43:55) postgres=# \echo :ctime&lt;br /&gt;
    Thu Sep 25 09:43:55 CEST 2025&lt;br /&gt;
&lt;br /&gt;
The `\set` command is fairly limited, making complex operations unreadable or non-portable.&lt;br /&gt;
&lt;br /&gt;
This mechanism is good enough for writing tests, but it is not generally available (most users do not need it), and it is not accessible from the server side (e.g., from stored procedures). There is no simple way to access `psql` variables from an anonymous block --- a proxy custom GUC variable must be used:&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:58:38) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    (2025-09-25 09:58:43) postgres=# select set_config(&#039;myvars.myvar&#039;, :&#039;myvar&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ ahoj       │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 10:00:27) postgres=# do $$ begin raise notice &#039;%&#039;, current_setting(&#039;myvars.myvar&#039;); end $$;&lt;br /&gt;
    NOTICE:  ahoj&lt;br /&gt;
    DO&lt;br /&gt;
&lt;br /&gt;
PostgreSQL also has configuration variables (GUC - Grand Unified Configuration). These are primarily designed for PostgreSQL and extension configuration, but they can be used as session variables. GUC variables can be set with the `SET` command or the `set_config` function, and read with `SHOW` or `current_setting`.&lt;br /&gt;
&lt;br /&gt;
GUC variables are meant for holding configuration parameters. When created through the internal API, they can be restricted to superusers, hidden from regular users, and assigned specific types (boolean, memory, number, interval, string, enum) with validation. This type system is separate from the SQL type system and is initialized before PostgreSQL’s SQL types.&lt;br /&gt;
&lt;br /&gt;
Variables created from SQL must be &amp;quot;custom&amp;quot; variables, using a prefix in their name --- these are always strings. Such custom GUCs are often used as a workaround for missing server-side session variables in PostgreSQL, but they are typeless, unprotected, and unsafe.&lt;br /&gt;
&lt;br /&gt;
A notable feature of PostgreSQL GUCs (built-in or custom) is that they can have different scopes: transaction, session, or function execution. They can also be assigned default values at specific events --- configuration loading, role login, database login, or function start. These features are useful for configuration, but problematic when GUCs are repurposed as session variables.&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
    RETURNS void AS $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, current_setting(&#039;my.x&#039;);&lt;br /&gt;
    END $$ LANGUAGE plpgsql SET my.x = 20;&lt;br /&gt;
    CREATE FUNCTION&lt;br /&gt;
    (2025-09-27 06:10:37) postgres=# SET my.x = 0; SELECT fx();&lt;br /&gt;
    SET&lt;br /&gt;
    NOTICE:  20&lt;br /&gt;
    ┌────┐&lt;br /&gt;
    │ fx │&lt;br /&gt;
    ╞════╡&lt;br /&gt;
    │    │&lt;br /&gt;
    └────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    (2025-09-27 06:10:39) postgres=# SHOW my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 0    │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
GUC variables are transactional, and this behavior cannot be disabled.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-27 06:17:31) postgres=# set my.x = 10;&lt;br /&gt;
    SET&lt;br /&gt;
    (2025-09-27 06:19:42) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:19:46) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, true);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:19:55) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:01) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:20:11) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:13) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:20:38) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:45) postgres=# rollback;&lt;br /&gt;
    ROLLBACK&lt;br /&gt;
    (2025-09-27 06:20:52) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:55) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:22:36) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:22:39) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:22:42) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
As a result, there is no reliable way to store details such as error information in custom GUCs.&lt;br /&gt;
&lt;br /&gt;
== Session Variables in Other Systems ==&lt;br /&gt;
&lt;br /&gt;
This section surveys how session variables are implemented in several widely used database systems. Each subsection includes code examples to illustrate syntax, behaviour, and scope.&lt;br /&gt;
&lt;br /&gt;
=== MySQL ===&lt;br /&gt;
&lt;br /&gt;
Session variables in MySQL have syntax similar to the better-known T-SQL variables (though other details are MySQL-specific). MySQL distinguishes two kinds of variables:&lt;br /&gt;
&lt;br /&gt;
* system variables - referenced using the `@@` prefix (or `@@scope.name`)&lt;br /&gt;
* user-defined variables - referenced using the `@` prefix&lt;br /&gt;
&lt;br /&gt;
System variables are modified with the `SET` statement. The `SET` statement accepts the modifiers `GLOBAL`, `SESSION`, or `PERSIST`. Some variables can only be set using the `GLOBAL` or `SESSION` modifier; when the variable name is specified with the `@@` prefix, it is not necessary to explicitly indicate the scope. System variables are defined by MySQL or by MySQL plugins.&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL some_config = 100;&lt;br /&gt;
    SET @@same_config = 100;&lt;br /&gt;
    &lt;br /&gt;
    SET SESSION sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@SESSION.sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
&lt;br /&gt;
Persistent values are stored in `mysqld-auto.cnf` (in the server data directory).&lt;br /&gt;
&lt;br /&gt;
Resetting system variables can be done by assigning DEFAULT or by copying from the GLOBAL namespace:&lt;br /&gt;
&lt;br /&gt;
    SET @@sql_mode = DEFAULT;&lt;br /&gt;
    SET @@sql_mode = @@GLOBAL.sql_mode&lt;br /&gt;
&lt;br /&gt;
PostgreSQL equivalents:&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL ; -- there is not similar command in Postgres&lt;br /&gt;
    SET SESSION ; -- SET&lt;br /&gt;
    SET PERSIST ; -- ALTER SYSTEM; SELECT pg_reload_conf();&lt;br /&gt;
    SELECT @@var; -- SELECT current_setting(&#039;var);&lt;br /&gt;
&lt;br /&gt;
User-defined variables are created by assignment:&lt;br /&gt;
&lt;br /&gt;
    SET @var = expr [, @var2 = expr [ ... ]];&lt;br /&gt;
&lt;br /&gt;
User-defined variables can only be of type integer, decimal, float, binary, non-binary string, or NULL. Casting between these types is implicit and hidden. User-defined variables cannot be used as table or column names.&lt;br /&gt;
&lt;br /&gt;
An advantage of the T-SQL-style syntax (with special prefixes) is that it provides a simple solution for avoiding identifier collisions. MySQL variables are convenient, and the user experience can be good for people already familiar with T-SQL (MSSQL or Sybase). On the other hand, prefix-based syntax can be confusing for users coming from systems other than MSSQL. The type system is perhaps too simple and can be unsafe. In general, the performance of MySQL (or MariaDB) stored procedures is not good, and stored procedures are therefore not frequently used. There is no protection against typographical errors. Unassigned user variables can be used without error (they default to NULL), which makes static checking of stored procedures impossible in this area.&lt;br /&gt;
&lt;br /&gt;
There is no way to restrict access to user-defined variables in MySQL. They cannot be protected against unintended usage. The only possible safeguard is a naming convention, since all user-defined variables share a single namespace.&lt;br /&gt;
&lt;br /&gt;
=== Oracle ===&lt;br /&gt;
&lt;br /&gt;
Oracle PL/SQL allows the use of package variables. PL/SQL is based on the Ada language, and package variables act as &amp;quot;global&amp;quot; variables. They are not directly visible from SQL, but Oracle provides a reduced syntax for functions without arguments, so you typically write a wrapper:&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE PACKAGE my_package&lt;br /&gt;
    AS&lt;br /&gt;
      FUNCTION get_a RETURN NUMBER;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    /&lt;br /&gt;
    &lt;br /&gt;
    CREATE OR REPLACE PACKAGE BODY my_package&lt;br /&gt;
    AS&lt;br /&gt;
      a  NUMBER(20);&lt;br /&gt;
      &lt;br /&gt;
      FUNCTION get_a&lt;br /&gt;
      RETURN NUMBER&lt;br /&gt;
      IS&lt;br /&gt;
      BEGIN&lt;br /&gt;
        RETURN a;&lt;br /&gt;
      END get_a;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    &lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
Inside SQL, SQL identifiers take precedence. Inside non-SQL commands (such as CALL or PL/SQL blocks), package identifiers take precedence.&lt;br /&gt;
&lt;br /&gt;
Oracle allows both syntaxes for calling a function with zero arguments:&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a() FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
This reduces the risk of naming collisions. Package variables persist for the duration of a session.&lt;br /&gt;
&lt;br /&gt;
Another possibility is to use variables in SQL*Plus (similar to `psql` variables, but with the ability to define the type on the server side).&lt;br /&gt;
&lt;br /&gt;
A variable is declared with the `VARIABLE` command and can be accessed in the session using the `:varname` syntax (a prior declaration step may be optional):&lt;br /&gt;
&lt;br /&gt;
    VARIABLE bv_variable_name VARCHAR2(30)&lt;br /&gt;
    BEGIN&lt;br /&gt;
      :bv_variable_name := &#039;Some Value&#039;;&lt;br /&gt;
    END;&lt;br /&gt;
    &lt;br /&gt;
    SELECT column_name&lt;br /&gt;
      FROM   table_name&lt;br /&gt;
     WHERE  column_name = :bv_variable_name;&lt;br /&gt;
&lt;br /&gt;
=== DB2 ===&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;user-defined global variables&amp;quot; in DB2 are similar to the proposed PostgreSQL feature. The main difference is in the access rights: DB2 uses `READ` and `WRITE`, while PostgreSQL uses `SELECT` and `UPDATE`. Because PostgreSQL already uses the `SET` command for GUCs, the `LET` command was introduced in the proposal (DB2 uses `SET`).&lt;br /&gt;
&lt;br /&gt;
Variables are visible in all sessions, but their values are private to each session. Variables are not transactional. Their usage is broader than in the proposal: they can be changed by `SET`, `SELECT INTO`, or used as OUT parameters of procedures. The search path (or a similar mechanism) applies to variables as well, but variables have lower priority than tables or columns.&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE myCounter INT DEFAULT 01;&lt;br /&gt;
    SELECT EMPNO, LASTNAME, CASE WHEN myCounter = 1 THEN SALARY ELSE NULL END&lt;br /&gt;
    FROM EMPLOYEE WHERE WORKDEPT = &#039;A00&#039;;&lt;br /&gt;
    SET myCounter = 29;&lt;br /&gt;
&lt;br /&gt;
There also appear to be other kinds of variables, accessed using the function `GETVARIABLE(&#039;name&#039;, &#039;default&#039;)`. These are very similar to PostgreSQL GUCs and the `current_setting` function. Such variables can be set via the connection string, are of type `VARCHAR`, and up to 10 values are allowed. Built-in session variables (configuration) can also be accessed using `GETVARIABLE`.&lt;br /&gt;
&lt;br /&gt;
=== MSSQL (T-SQL) ===&lt;br /&gt;
&lt;br /&gt;
MSSQL provides two types of variables:&lt;br /&gt;
&lt;br /&gt;
* Global variables — accessed with the `@@varname` syntax. These are similar to GUCs and provide state information such as `@@ERROR`, `@@ROWCOUNT`, or `@@IDENTITY`. &lt;br /&gt;
* Local variables — accessed with the `@varname` syntax. These must be declared with the `DECLARE` command before use. Their scope is limited to the batch, procedure, or function where they are executed.&lt;br /&gt;
&lt;br /&gt;
    DECLARE @TestVariable AS VARCHAR(100)&lt;br /&gt;
    SET @TestVariable = &#039;Think Green&#039;&lt;br /&gt;
    GO&lt;br /&gt;
    PRINT @TestVariable&lt;br /&gt;
&lt;br /&gt;
This script fails because `PRINT` is executed in a different batch. Therefore, it seems MSSQL does not truly support session variables.&lt;br /&gt;
&lt;br /&gt;
There are also mechanisms similar to PostgreSQL&#039;s custom GUCs and the use of `current_setting` and `set_config` functions. In general, MSSQL is fairly primitive in this area.&lt;br /&gt;
&lt;br /&gt;
    EXEC sp_set_session_context &#039;user_id&#039;, 4;&lt;br /&gt;
    SELECT SESSION_CONTEXT(N&#039;user_id&#039;);&lt;br /&gt;
&lt;br /&gt;
== ToDo ==&lt;br /&gt;
&lt;br /&gt;
* SECURITY LABEL support — enhance the `dummy_seclabel` module and the `sepgsql` extension. Update PostgreSQL documentation and ensure `SecLabelSupportsObjectType` includes session variables.&lt;/div&gt;</summary>
		<author><name>Okbobcz</name></author>
	</entry>
	<entry>
		<id>https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41837</id>
		<title>Implementation of declarative catalog session variables</title>
		<link rel="alternate" type="text/html" href="https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41837"/>
		<updated>2025-09-29T04:37:41Z</updated>

		<summary type="html">&lt;p&gt;Okbobcz: /* Oracle */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Session variables are database objects that can hold a value across multiple queries inside a session. The design of session variables varies widely, and many databases implement more than one type of session variable.&lt;br /&gt;
&lt;br /&gt;
The SQL/PSM standard introduces modules and module-level variables. However, this part of the standard has not been implemented in any widely used product. As a result, each database system has developed its own proprietary design, syntax, and feature set.&lt;br /&gt;
&lt;br /&gt;
Session variables are often part of the stored procedure environment, but there are exceptions --- for example, MySQL session variables or client-side session variables in tools like psql. In most systems, session variables do not participate in transactions: once set, their value persists until explicitly changed, even if the surrounding transaction is rolled back. Conceptually, they behave much like variables in general-purpose programming languages.&lt;br /&gt;
&lt;br /&gt;
Because there is no common design, comparisons across systems are difficult. Each implementation has its own advantages and disadvantages, and in some cases a single system supports more than one kind of session variable.&lt;br /&gt;
&lt;br /&gt;
Kinds of session variables:&lt;br /&gt;
* client side (psql, pgadmin, ...) - mostly similar to shell variables (based on string operations)&lt;br /&gt;
* server side&lt;br /&gt;
** declarative&lt;br /&gt;
*** created only for batch (T-SQL)&lt;br /&gt;
*** created as an database object (or part of database object) (Oracle, DB2)&lt;br /&gt;
** created by usage (MySQL)&lt;br /&gt;
** with priprietary syntax for identifiers (MySQL, T-SQL)&lt;br /&gt;
** with ANSI SQL syntax for identifiers (Oracle, DB2)&lt;br /&gt;
&lt;br /&gt;
Possible usage of session variables:&lt;br /&gt;
&lt;br /&gt;
* usage of session variables as global variables in PL (tracing, debugging, hacking),&lt;br /&gt;
* holding configuration (for PL),&lt;br /&gt;
* holding some more used data in interactive session,&lt;br /&gt;
* holding credentials for some RLS and data securing,&lt;br /&gt;
* helping with migration from others systems (mainly from Oracle),&lt;br /&gt;
* allow anonymous block parametrization (only postgres).&lt;br /&gt;
&lt;br /&gt;
== SQL/PSM ==&lt;br /&gt;
&lt;br /&gt;
The SQL standard introduces the concept of modules, which can be associated with schemas (partially). Variables in modules behave like PL/pgSQL variables but are only local. The only objects allowed at the module level are temporary tables, which can be accessed only from routines assigned to that module. Modules can encapsulate private objects (functions, procedure, temp tables), that are not visible outside the module. Inside the module, a  private objects shadows other objects. What I know the modules (and generally SQL/PSM) doesn&#039;t cover a deployment of code, so there is not similarity with PostgreSQL extensions. SQL/PSM Modules are modules like modules from modular languages like Modula2 or Oberon. There is some similarity with Postgres schemas (both are containers) with significant differences (there is not a concept of SEARCH_PATH (on PSM side) or there is not a concept of private or public objects (on Postgres schema side)).&lt;br /&gt;
&lt;br /&gt;
SQL/PSM variables are not transactional (they are used exactly like in PL/pgSQL). The precedence between variables and columns is not specified.&lt;br /&gt;
&lt;br /&gt;
== Implementation Challenges ==&lt;br /&gt;
&lt;br /&gt;
* Possible collisions between session variables and other database objects.&lt;br /&gt;
* Memory cleanup after dropping a session variable, especially when DDL operations can be reverted (Postgres has not support for global temp objects).&lt;br /&gt;
* Memory invalidation: if a variable is dropped by one session, other sessions should not continue using it (Postgres has not support for global temp objects).&lt;br /&gt;
&lt;br /&gt;
== Proposed Implementations ==&lt;br /&gt;
&lt;br /&gt;
It is clean so no one design is perfect for all uses cases. MySQL is good enough and maybe handy for interactive work,&lt;br /&gt;
but cannot be used for storing any sensitive data, and it is unsafe. PostgreSQL GUC are perfect for configuration, but&lt;br /&gt;
are not friendly for interactive work, and are very limited for usage in PL as global variables. So no one design is&lt;br /&gt;
perfect, and I can imagine more different designs in one system, that can support some different use cases better.&lt;br /&gt;
&lt;br /&gt;
== Proposal for Postgres - design session variables as database global temporary typed objects with ANSI SQL syntax for identifiers ==&lt;br /&gt;
&lt;br /&gt;
I searched solution that can work with specific Postgres environment:&lt;br /&gt;
&lt;br /&gt;
* Postgres has more than one widely used language for stored procedures PL/pgSQL + PL/Python or PL/Perl&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL is strongly reduced PL/SQL, and PL/SQL is modified ADA language. I am pretty happy with the current scope of PL/SQL - it is domain specific language and it works well. It is a very simple language, and it is easy to learn it. PL/pgSQL has no packages or modules. Instead PostgreSQL schemas are used. But Postgres schemas are specific database objects. Schemas are independent on PL. &lt;br /&gt;
&lt;br /&gt;
So I wanted design that can supports more PL languages, didn&#039;t introduce new complexity to relative simple PL/pgSQL and didn&#039;t introduce functionality that can be redundant with already existing objects.&lt;br /&gt;
I wrote bigger application in PL/pgSQL. For maintaining of this application I wrote plpgsql_lint (later plpgsql_check). So every time I am searching solution that doesn&#039;t block code static analyse. Static analyse requires persistent objects, and then it is direct way to design session variables as database objects (with own system catalog). When session variables are designed as database objects, then using ACL is natural. Oracle package variables are well protected just by package scope. Unfortunately, postgres doesn&#039;t know schema scope. The locality of schema objects is defined by setting of SEARCH_PATH guc, and introduction of new mechanism can be messy. But with ACL the content of variables can be safe too:&lt;br /&gt;
&lt;br /&gt;
    CREATE TEMP VARIABLE x AS int;&lt;br /&gt;
     &lt;br /&gt;
    LET x = 10;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(x);&lt;br /&gt;
    END;&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    SELECT VARIABLE(x);&lt;br /&gt;
&lt;br /&gt;
==== Collisions between columns and variables identifiers ====&lt;br /&gt;
There is a new possibility of identifiers collisions between variables and columns identifiers. With ANSI SQL syntax for session variables identifiers there is a problem how to distinct between variable and column. This is known problem in PL/pgSQL or PL/SQL and can be solved by some prioritization or by prohibition of collisions. The prohibition of collisions is solution that almost cleaned this issue from PL/pgSQL. Unfortunately session variables are global objects and then the collision can be in any query, and then the new badly named variable can stop execution of any query. Prioritization variables or columns has own problems - quiet unwanted using variable or column.&lt;br /&gt;
&lt;br /&gt;
    CREATE TABLE tab(a int);&lt;br /&gt;
    CREATE VARIABLE a AS int;&lt;br /&gt;
    &lt;br /&gt;
    SELECT a FROM tab; -- with disallowed collisions, the execution of this query stops when there is a variable named &amp;quot;a&amp;quot;&lt;br /&gt;
    &lt;br /&gt;
    LET a = 1;&lt;br /&gt;
    DELETE FROM tab WHERE a = 1; -- with higher priority for variables all table can be deleted (nobody proposed higher priority for variables).&lt;br /&gt;
    SELECT a FROM tab; -- with higher priority for columns, the variable is not used (quietly)&lt;br /&gt;
&lt;br /&gt;
The prohibition of collision is not absolute protection against possible human errors:&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE _id AS int;&lt;br /&gt;
    LET _id = 10;&lt;br /&gt;
    CREATE TABLE foo(id int);&lt;br /&gt;
    DELETE FROM foo WHERE _id = 10; -- just type, but can have unwanted impact (all rows removed)&lt;br /&gt;
&lt;br /&gt;
These problems are not new. The developers in PL/pgSQL or PL/SQL knows it, but the scope of these risks are limited only to stored procedures. After very very long discussion and lot of designs I introduced variable fences (syntax `VARIABLE(varname)`). Inside of any query the variable can be used only in variable fence. This is 100% solution of collisions, and I think it can be good solution against human errors and unwanted usage of session variable. Now, variable fences are necessary every where inside a query or expressions, but I thinking about possibility to be optional inside PL/pgSQL (far future). This can reduce overhead with porting applications from Oracle, and generally the developers of stored procedures are experienced with described risks (this can be controlled by new `plpgsql.extra_checks`).&lt;br /&gt;
&lt;br /&gt;
A variable fence can be in collisions with custom function &amp;quot;variable&amp;quot;. There is similarity with usage of keywords `cube`. This can be fixed by usage prefix schema or by usage of parenthesis:&lt;br /&gt;
&lt;br /&gt;
    SELECT public.variable(...)&lt;br /&gt;
    SELECT &amp;quot;variable&amp;quot;(...)&lt;br /&gt;
&lt;br /&gt;
Why I don&#039;t propose usage of T-SQL (MSSQL, MySQL) syntax for session variables? There is more reasons (one is objective, last is subjective):&lt;br /&gt;
&lt;br /&gt;
* There is a collision with custom operators - operators `@` or `@@` are already used - usage of `@@` is common&lt;br /&gt;
&lt;br /&gt;
    (2025-09-28 06:57:30) postgres=# create table tab(a int);&lt;br /&gt;
    CREATE TABLE&lt;br /&gt;
    (2025-09-28 06:57:42) postgres=# insert into tab values(10);&lt;br /&gt;
    INSERT 0 1&lt;br /&gt;
    (2025-09-28 06:57:50) postgres=# select @a from tab;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │       10 │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
We can safely use only `:` prefix, but then we can break a psql variables. Similarly we can use `$` prefix, but then we can break query parameters syntax.&lt;br /&gt;
&lt;br /&gt;
* T-SQL variable names looks weird in Postgres (in queries, in PL/pgSQL). Postgres knows only ANSI/SQL identifiers, PL/pgSQL shares these rules too, and this are the PostgreSQL is consistent, and I missing motivation to introduce something new (with the knowledge so problems with possible compatibility breaks should be solved too). &lt;br /&gt;
&lt;br /&gt;
Note: There was a proposal to use table syntax as variable (like table (in memory) variables from T-SQL but only single row). Although it is very effective solution of collisions, I dislike this idea. It increase a complexity of simple expression (or increase inconsistency of some syntax rules). More looks weird for scalar variables (and can be handy for composite variables):&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(var); -- variable is not a table&lt;br /&gt;
    END&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      -- variable a a table (I don&#039;t like it)&lt;br /&gt;
      -- can we use DML? How much rows this kind of variable can hold&lt;br /&gt;
      -- value is row or not?&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, (SELECT var FROM var);&lt;br /&gt;
    END&lt;br /&gt;
    $$&lt;br /&gt;
&lt;br /&gt;
I am not against for introduction of inmemory tables - or more correctly for global temporary tables. Well implemented global temporary tables can be very nice and handy feature. But it is different feature, and should be designed separately. There can be some performance problems - plpgsql can use simple expression evaluation (when expression has not usage of any table). This optimization can be changed or enhanced, but its is change inside complex sensitive code, and it is not consistent. I very like a global temporary tables (that are not implemented in Postgres). Table (and related operations) should to looks like table, variable (and related operations) should to looks like variable. Common perspective how session variables should to looks is based on variables from PL environment. PostgreSQL internally supports &amp;lt;i&amp;gt;transition tables&amp;lt;/i&amp;gt;, and I can imagine to allow this kind of tables outside of triggers too. This is valid use case (and valid functionality), but it is something different than session variables (although there can be some overlap). The implementation of declared tables inside PL/pgSQL can be more easy from high design perspective (interaction between SQL tables and PL/pgSQL is well defined already), but difficult from low level perspective (where to store column statistics and other important data for optimization - same problems like with global temporary tables (without some fake proxy local temp tables)).&lt;br /&gt;
&lt;br /&gt;
   CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
   AS $$&lt;br /&gt;
   -- not implemented yet, not part of this proposal&lt;br /&gt;
   DECLARE t TABLE(a int, b int); -- this table is invisible outside fx&lt;br /&gt;
   BEGIN&lt;br /&gt;
     INSERT INTO t VALUES(10,20);&lt;br /&gt;
     INSERT INTO t VALUES(30,40);&lt;br /&gt;
     RAISE NOTICE &#039;%&#039;, (SELECT count(*) FROM t);&lt;br /&gt;
   END;&lt;br /&gt;
   $$ LANGUAGE plpgsql;&lt;br /&gt;
&lt;br /&gt;
Some data languages defined relational variables - common concept of session variables has zero intersection with this concept.&lt;br /&gt;
&lt;br /&gt;
== Variabes in PostgreSQL (as of v18) ==&lt;br /&gt;
&lt;br /&gt;
PostgreSQL provides relatively advanced client-side session variables in psql --- these variables use the `:` prefix. The implementation is pretty much straightforward: `psql`&#039;s parser reads tokens from input and, when it encounters a token related to a variable, replaces it with the variable&#039;s value. The syntax supports literal and identifier escaping, and variables can be used as arguments of the `\bind` command.&lt;br /&gt;
&lt;br /&gt;
`psql` variables are typeless client-side variables, available only inside `psql` or `pgbench`.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:40:18) postgres=# \set myvar ahoj&lt;br /&gt;
    (2025-09-25 09:40:33) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:14) postgres=# select :&#039;myvar&#039;;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:24) postgres=# select $1 \bind :myvar \g&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
The `\set` command allows only simple string concatenation, but it also supports execution of shell commands. When a query is executed with the `\gset` command, its result can be stored in a `psql` variable.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:41:40) postgres=# \set ctime `date`&lt;br /&gt;
    (2025-09-25 09:43:55) postgres=# \echo :ctime&lt;br /&gt;
    Thu Sep 25 09:43:55 CEST 2025&lt;br /&gt;
&lt;br /&gt;
The `\set` command is fairly limited, making complex operations unreadable or non-portable.&lt;br /&gt;
&lt;br /&gt;
This mechanism is good enough for writing tests, but it is not generally available (most users do not need it), and it is not accessible from the server side (e.g., from stored procedures). There is no simple way to access `psql` variables from an anonymous block --- a proxy custom GUC variable must be used:&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:58:38) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    (2025-09-25 09:58:43) postgres=# select set_config(&#039;myvars.myvar&#039;, :&#039;myvar&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ ahoj       │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 10:00:27) postgres=# do $$ begin raise notice &#039;%&#039;, current_setting(&#039;myvars.myvar&#039;); end $$;&lt;br /&gt;
    NOTICE:  ahoj&lt;br /&gt;
    DO&lt;br /&gt;
&lt;br /&gt;
PostgreSQL also has configuration variables (GUC - Grand Unified Configuration). These are primarily designed for PostgreSQL and extension configuration, but they can be used as session variables. GUC variables can be set with the `SET` command or the `set_config` function, and read with `SHOW` or `current_setting`.&lt;br /&gt;
&lt;br /&gt;
GUC variables are meant for holding configuration parameters. When created through the internal API, they can be restricted to superusers, hidden from regular users, and assigned specific types (boolean, memory, number, interval, string, enum) with validation. This type system is separate from the SQL type system and is initialized before PostgreSQL’s SQL types.&lt;br /&gt;
&lt;br /&gt;
Variables created from SQL must be &amp;quot;custom&amp;quot; variables, using a prefix in their name --- these are always strings. Such custom GUCs are often used as a workaround for missing server-side session variables in PostgreSQL, but they are typeless, unprotected, and unsafe.&lt;br /&gt;
&lt;br /&gt;
A notable feature of PostgreSQL GUCs (built-in or custom) is that they can have different scopes: transaction, session, or function execution. They can also be assigned default values at specific events --- configuration loading, role login, database login, or function start. These features are useful for configuration, but problematic when GUCs are repurposed as session variables.&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
    RETURNS void AS $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, current_setting(&#039;my.x&#039;);&lt;br /&gt;
    END $$ LANGUAGE plpgsql SET my.x = 20;&lt;br /&gt;
    CREATE FUNCTION&lt;br /&gt;
    (2025-09-27 06:10:37) postgres=# SET my.x = 0; SELECT fx();&lt;br /&gt;
    SET&lt;br /&gt;
    NOTICE:  20&lt;br /&gt;
    ┌────┐&lt;br /&gt;
    │ fx │&lt;br /&gt;
    ╞════╡&lt;br /&gt;
    │    │&lt;br /&gt;
    └────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    (2025-09-27 06:10:39) postgres=# SHOW my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 0    │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
GUC variables are transactional, and this behavior cannot be disabled.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-27 06:17:31) postgres=# set my.x = 10;&lt;br /&gt;
    SET&lt;br /&gt;
    (2025-09-27 06:19:42) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:19:46) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, true);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:19:55) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:01) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:20:11) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:13) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:20:38) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:45) postgres=# rollback;&lt;br /&gt;
    ROLLBACK&lt;br /&gt;
    (2025-09-27 06:20:52) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:55) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:22:36) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:22:39) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:22:42) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
As a result, there is no reliable way to store details such as error information in custom GUCs.&lt;br /&gt;
&lt;br /&gt;
== Session Variables in Other Systems ==&lt;br /&gt;
&lt;br /&gt;
This section surveys how session variables are implemented in several widely used database systems. Each subsection includes code examples to illustrate syntax, behaviour, and scope.&lt;br /&gt;
&lt;br /&gt;
=== MySQL ===&lt;br /&gt;
&lt;br /&gt;
Session variables in MySQL have syntax similar to the better-known T-SQL variables (though other details are MySQL-specific). MySQL distinguishes two kinds of variables:&lt;br /&gt;
&lt;br /&gt;
* system variables - referenced using the `@@` prefix (or `@@scope.name`)&lt;br /&gt;
* user-defined variables - referenced using the `@` prefix&lt;br /&gt;
&lt;br /&gt;
System variables are modified with the `SET` statement. The `SET` statement accepts the modifiers `GLOBAL`, `SESSION`, or `PERSIST`. Some variables can only be set using the `GLOBAL` or `SESSION` modifier; when the variable name is specified with the `@@` prefix, it is not necessary to explicitly indicate the scope. System variables are defined by MySQL or by MySQL plugins.&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL some_config = 100;&lt;br /&gt;
    SET @@same_config = 100;&lt;br /&gt;
    &lt;br /&gt;
    SET SESSION sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@SESSION.sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
&lt;br /&gt;
Persistent values are stored in `mysqld-auto.cnf` (in the server data directory).&lt;br /&gt;
&lt;br /&gt;
Resetting system variables can be done by assigning DEFAULT or by copying from the GLOBAL namespace:&lt;br /&gt;
&lt;br /&gt;
    SET @@sql_mode = DEFAULT;&lt;br /&gt;
    SET @@sql_mode = @@GLOBAL.sql_mode&lt;br /&gt;
&lt;br /&gt;
PostgreSQL equivalents:&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL ; -- there is not similar command in Postgres&lt;br /&gt;
    SET SESSION ; -- SET&lt;br /&gt;
    SET PERSIST ; -- ALTER SYSTEM; SELECT pg_reload_conf();&lt;br /&gt;
    SELECT @@var; -- SELECT current_setting(&#039;var);&lt;br /&gt;
&lt;br /&gt;
User-defined variables are created by assignment:&lt;br /&gt;
&lt;br /&gt;
    SET @var = expr [, @var2 = expr [ ... ]];&lt;br /&gt;
&lt;br /&gt;
User-defined variables can only be of type integer, decimal, float, binary, non-binary string, or NULL. Casting between these types is implicit and hidden. User-defined variables cannot be used as table or column names.&lt;br /&gt;
&lt;br /&gt;
An advantage of the T-SQL-style syntax (with special prefixes) is that it provides a simple solution for avoiding identifier collisions. MySQL variables are convenient, and the user experience can be good for people already familiar with T-SQL (MSSQL or Sybase). On the other hand, prefix-based syntax can be confusing for users coming from systems other than MSSQL. The type system is perhaps too simple and can be unsafe. In general, the performance of MySQL (or MariaDB) stored procedures is not good, and stored procedures are therefore not frequently used. There is no protection against typographical errors. Unassigned user variables can be used without error (they default to NULL), which makes static checking of stored procedures impossible in this area.&lt;br /&gt;
&lt;br /&gt;
There is no way to restrict access to user-defined variables in MySQL. They cannot be protected against unintended usage. The only possible safeguard is a naming convention, since all user-defined variables share a single namespace.&lt;br /&gt;
&lt;br /&gt;
=== Oracle ===&lt;br /&gt;
&lt;br /&gt;
Oracle PL/SQL allows the use of package variables. PL/SQL is based on the Ada language, and package variables act as &amp;quot;global&amp;quot; variables. They are not directly visible from SQL, but Oracle provides a reduced syntax for functions without arguments, so you typically write a wrapper:&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE PACKAGE my_package&lt;br /&gt;
    AS&lt;br /&gt;
      FUNCTION get_a RETURN NUMBER;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    /&lt;br /&gt;
    &lt;br /&gt;
    CREATE OR REPLACE PACKAGE BODY my_package&lt;br /&gt;
    AS&lt;br /&gt;
      a  NUMBER(20);&lt;br /&gt;
      &lt;br /&gt;
      FUNCTION get_a&lt;br /&gt;
      RETURN NUMBER&lt;br /&gt;
      IS&lt;br /&gt;
      BEGIN&lt;br /&gt;
        RETURN a;&lt;br /&gt;
      END get_a;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    &lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
Inside SQL, SQL identifiers take precedence. Inside non-SQL commands (such as CALL or PL/SQL blocks), package identifiers take precedence.&lt;br /&gt;
&lt;br /&gt;
Oracle allows both syntaxes for calling a function with zero arguments:&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a() FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
This reduces the risk of naming collisions. Package variables persist for the duration of a session.&lt;br /&gt;
&lt;br /&gt;
Another possibility is to use variables in SQL*Plus (similar to `psql` variables, but with the ability to define the type on the server side).&lt;br /&gt;
&lt;br /&gt;
A variable is declared with the `VARIABLE` command and can be accessed in the session using the `:varname` syntax (a prior declaration step may be optional):&lt;br /&gt;
&lt;br /&gt;
    VARIABLE bv_variable_name VARCHAR2(30)&lt;br /&gt;
    BEGIN&lt;br /&gt;
      :bv_variable_name := &#039;Some Value&#039;;&lt;br /&gt;
    END;&lt;br /&gt;
    &lt;br /&gt;
    SELECT column_name&lt;br /&gt;
      FROM   table_name&lt;br /&gt;
     WHERE  column_name = :bv_variable_name;&lt;br /&gt;
&lt;br /&gt;
=== DB2 ===&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;user-defined global variables&amp;quot; in DB2 are similar to the proposed PostgreSQL feature. The main difference is in the access rights: DB2 uses `READ` and `WRITE`, while PostgreSQL uses `SELECT` and `UPDATE`. Because PostgreSQL already uses the `SET` command for GUCs, the `LET` command was introduced in the proposal (DB2 uses `SET`).&lt;br /&gt;
&lt;br /&gt;
Variables are visible in all sessions, but their values are private to each session. Variables are not transactional. Their usage is broader than in the proposal: they can be changed by `SET`, `SELECT INTO`, or used as OUT parameters of procedures. The search path (or a similar mechanism) applies to variables as well, but variables have lower priority than tables or columns.&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE myCounter INT DEFAULT 01;&lt;br /&gt;
    SELECT EMPNO, LASTNAME, CASE WHEN myCounter = 1 THEN SALARY ELSE NULL END&lt;br /&gt;
    FROM EMPLOYEE WHERE WORKDEPT = &#039;A00&#039;;&lt;br /&gt;
    SET myCounter = 29;&lt;br /&gt;
&lt;br /&gt;
There also appear to be other kinds of variables, accessed using the function `GETVARIABLE(&#039;name&#039;, &#039;default&#039;)`. These are very similar to PostgreSQL GUCs and the `current_setting` function. Such variables can be set via the connection string, are of type `VARCHAR`, and up to 10 values are allowed. Built-in session variables (configuration) can also be accessed using `GETVARIABLE`.&lt;br /&gt;
&lt;br /&gt;
=== MSSQL (T-SQL) ===&lt;br /&gt;
&lt;br /&gt;
MSSQL provides two types of variables:&lt;br /&gt;
&lt;br /&gt;
* Global variables — accessed with the `@@varname` syntax. These are similar to GUCs and provide state information such as `@@ERROR`, `@@ROWCOUNT`, or `@@IDENTITY`. &lt;br /&gt;
* Local variables — accessed with the `@varname` syntax. These must be declared with the `DECLARE` command before use. Their scope is limited to the batch, procedure, or function where they are executed.&lt;br /&gt;
&lt;br /&gt;
    DECLARE @TestVariable AS VARCHAR(100)&lt;br /&gt;
    SET @TestVariable = &#039;Think Green&#039;&lt;br /&gt;
    GO&lt;br /&gt;
    PRINT @TestVariable&lt;br /&gt;
&lt;br /&gt;
This script fails because `PRINT` is executed in a different batch. Therefore, it seems MSSQL does not truly support session variables.&lt;br /&gt;
&lt;br /&gt;
There are also mechanisms similar to PostgreSQL&#039;s custom GUCs and the use of `current_setting` and `set_config` functions. In general, MSSQL is fairly primitive in this area.&lt;br /&gt;
&lt;br /&gt;
    EXEC sp_set_session_context &#039;user_id&#039;, 4;&lt;br /&gt;
    SELECT SESSION_CONTEXT(N&#039;user_id&#039;);&lt;br /&gt;
&lt;br /&gt;
== ToDo ==&lt;br /&gt;
&lt;br /&gt;
* SECURITY LABEL support — enhance the `dummy_seclabel` module and the `sepgsql` extension. Update PostgreSQL documentation and ensure `SecLabelSupportsObjectType` includes session variables.&lt;/div&gt;</summary>
		<author><name>Okbobcz</name></author>
	</entry>
	<entry>
		<id>https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41836</id>
		<title>Implementation of declarative catalog session variables</title>
		<link rel="alternate" type="text/html" href="https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41836"/>
		<updated>2025-09-29T04:32:39Z</updated>

		<summary type="html">&lt;p&gt;Okbobcz: /* MSSQL (T-SQL) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Session variables are database objects that can hold a value across multiple queries inside a session. The design of session variables varies widely, and many databases implement more than one type of session variable.&lt;br /&gt;
&lt;br /&gt;
The SQL/PSM standard introduces modules and module-level variables. However, this part of the standard has not been implemented in any widely used product. As a result, each database system has developed its own proprietary design, syntax, and feature set.&lt;br /&gt;
&lt;br /&gt;
Session variables are often part of the stored procedure environment, but there are exceptions --- for example, MySQL session variables or client-side session variables in tools like psql. In most systems, session variables do not participate in transactions: once set, their value persists until explicitly changed, even if the surrounding transaction is rolled back. Conceptually, they behave much like variables in general-purpose programming languages.&lt;br /&gt;
&lt;br /&gt;
Because there is no common design, comparisons across systems are difficult. Each implementation has its own advantages and disadvantages, and in some cases a single system supports more than one kind of session variable.&lt;br /&gt;
&lt;br /&gt;
Kinds of session variables:&lt;br /&gt;
* client side (psql, pgadmin, ...) - mostly similar to shell variables (based on string operations)&lt;br /&gt;
* server side&lt;br /&gt;
** declarative&lt;br /&gt;
*** created only for batch (T-SQL)&lt;br /&gt;
*** created as an database object (or part of database object) (Oracle, DB2)&lt;br /&gt;
** created by usage (MySQL)&lt;br /&gt;
** with priprietary syntax for identifiers (MySQL, T-SQL)&lt;br /&gt;
** with ANSI SQL syntax for identifiers (Oracle, DB2)&lt;br /&gt;
&lt;br /&gt;
Possible usage of session variables:&lt;br /&gt;
&lt;br /&gt;
* usage of session variables as global variables in PL (tracing, debugging, hacking),&lt;br /&gt;
* holding configuration (for PL),&lt;br /&gt;
* holding some more used data in interactive session,&lt;br /&gt;
* holding credentials for some RLS and data securing,&lt;br /&gt;
* helping with migration from others systems (mainly from Oracle),&lt;br /&gt;
* allow anonymous block parametrization (only postgres).&lt;br /&gt;
&lt;br /&gt;
== SQL/PSM ==&lt;br /&gt;
&lt;br /&gt;
The SQL standard introduces the concept of modules, which can be associated with schemas (partially). Variables in modules behave like PL/pgSQL variables but are only local. The only objects allowed at the module level are temporary tables, which can be accessed only from routines assigned to that module. Modules can encapsulate private objects (functions, procedure, temp tables), that are not visible outside the module. Inside the module, a  private objects shadows other objects. What I know the modules (and generally SQL/PSM) doesn&#039;t cover a deployment of code, so there is not similarity with PostgreSQL extensions. SQL/PSM Modules are modules like modules from modular languages like Modula2 or Oberon. There is some similarity with Postgres schemas (both are containers) with significant differences (there is not a concept of SEARCH_PATH (on PSM side) or there is not a concept of private or public objects (on Postgres schema side)).&lt;br /&gt;
&lt;br /&gt;
SQL/PSM variables are not transactional (they are used exactly like in PL/pgSQL). The precedence between variables and columns is not specified.&lt;br /&gt;
&lt;br /&gt;
== Implementation Challenges ==&lt;br /&gt;
&lt;br /&gt;
* Possible collisions between session variables and other database objects.&lt;br /&gt;
* Memory cleanup after dropping a session variable, especially when DDL operations can be reverted (Postgres has not support for global temp objects).&lt;br /&gt;
* Memory invalidation: if a variable is dropped by one session, other sessions should not continue using it (Postgres has not support for global temp objects).&lt;br /&gt;
&lt;br /&gt;
== Proposed Implementations ==&lt;br /&gt;
&lt;br /&gt;
It is clean so no one design is perfect for all uses cases. MySQL is good enough and maybe handy for interactive work,&lt;br /&gt;
but cannot be used for storing any sensitive data, and it is unsafe. PostgreSQL GUC are perfect for configuration, but&lt;br /&gt;
are not friendly for interactive work, and are very limited for usage in PL as global variables. So no one design is&lt;br /&gt;
perfect, and I can imagine more different designs in one system, that can support some different use cases better.&lt;br /&gt;
&lt;br /&gt;
== Proposal for Postgres - design session variables as database global temporary typed objects with ANSI SQL syntax for identifiers ==&lt;br /&gt;
&lt;br /&gt;
I searched solution that can work with specific Postgres environment:&lt;br /&gt;
&lt;br /&gt;
* Postgres has more than one widely used language for stored procedures PL/pgSQL + PL/Python or PL/Perl&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL is strongly reduced PL/SQL, and PL/SQL is modified ADA language. I am pretty happy with the current scope of PL/SQL - it is domain specific language and it works well. It is a very simple language, and it is easy to learn it. PL/pgSQL has no packages or modules. Instead PostgreSQL schemas are used. But Postgres schemas are specific database objects. Schemas are independent on PL. &lt;br /&gt;
&lt;br /&gt;
So I wanted design that can supports more PL languages, didn&#039;t introduce new complexity to relative simple PL/pgSQL and didn&#039;t introduce functionality that can be redundant with already existing objects.&lt;br /&gt;
I wrote bigger application in PL/pgSQL. For maintaining of this application I wrote plpgsql_lint (later plpgsql_check). So every time I am searching solution that doesn&#039;t block code static analyse. Static analyse requires persistent objects, and then it is direct way to design session variables as database objects (with own system catalog). When session variables are designed as database objects, then using ACL is natural. Oracle package variables are well protected just by package scope. Unfortunately, postgres doesn&#039;t know schema scope. The locality of schema objects is defined by setting of SEARCH_PATH guc, and introduction of new mechanism can be messy. But with ACL the content of variables can be safe too:&lt;br /&gt;
&lt;br /&gt;
    CREATE TEMP VARIABLE x AS int;&lt;br /&gt;
     &lt;br /&gt;
    LET x = 10;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(x);&lt;br /&gt;
    END;&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    SELECT VARIABLE(x);&lt;br /&gt;
&lt;br /&gt;
==== Collisions between columns and variables identifiers ====&lt;br /&gt;
There is a new possibility of identifiers collisions between variables and columns identifiers. With ANSI SQL syntax for session variables identifiers there is a problem how to distinct between variable and column. This is known problem in PL/pgSQL or PL/SQL and can be solved by some prioritization or by prohibition of collisions. The prohibition of collisions is solution that almost cleaned this issue from PL/pgSQL. Unfortunately session variables are global objects and then the collision can be in any query, and then the new badly named variable can stop execution of any query. Prioritization variables or columns has own problems - quiet unwanted using variable or column.&lt;br /&gt;
&lt;br /&gt;
    CREATE TABLE tab(a int);&lt;br /&gt;
    CREATE VARIABLE a AS int;&lt;br /&gt;
    &lt;br /&gt;
    SELECT a FROM tab; -- with disallowed collisions, the execution of this query stops when there is a variable named &amp;quot;a&amp;quot;&lt;br /&gt;
    &lt;br /&gt;
    LET a = 1;&lt;br /&gt;
    DELETE FROM tab WHERE a = 1; -- with higher priority for variables all table can be deleted (nobody proposed higher priority for variables).&lt;br /&gt;
    SELECT a FROM tab; -- with higher priority for columns, the variable is not used (quietly)&lt;br /&gt;
&lt;br /&gt;
The prohibition of collision is not absolute protection against possible human errors:&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE _id AS int;&lt;br /&gt;
    LET _id = 10;&lt;br /&gt;
    CREATE TABLE foo(id int);&lt;br /&gt;
    DELETE FROM foo WHERE _id = 10; -- just type, but can have unwanted impact (all rows removed)&lt;br /&gt;
&lt;br /&gt;
These problems are not new. The developers in PL/pgSQL or PL/SQL knows it, but the scope of these risks are limited only to stored procedures. After very very long discussion and lot of designs I introduced variable fences (syntax `VARIABLE(varname)`). Inside of any query the variable can be used only in variable fence. This is 100% solution of collisions, and I think it can be good solution against human errors and unwanted usage of session variable. Now, variable fences are necessary every where inside a query or expressions, but I thinking about possibility to be optional inside PL/pgSQL (far future). This can reduce overhead with porting applications from Oracle, and generally the developers of stored procedures are experienced with described risks (this can be controlled by new `plpgsql.extra_checks`).&lt;br /&gt;
&lt;br /&gt;
A variable fence can be in collisions with custom function &amp;quot;variable&amp;quot;. There is similarity with usage of keywords `cube`. This can be fixed by usage prefix schema or by usage of parenthesis:&lt;br /&gt;
&lt;br /&gt;
    SELECT public.variable(...)&lt;br /&gt;
    SELECT &amp;quot;variable&amp;quot;(...)&lt;br /&gt;
&lt;br /&gt;
Why I don&#039;t propose usage of T-SQL (MSSQL, MySQL) syntax for session variables? There is more reasons (one is objective, last is subjective):&lt;br /&gt;
&lt;br /&gt;
* There is a collision with custom operators - operators `@` or `@@` are already used - usage of `@@` is common&lt;br /&gt;
&lt;br /&gt;
    (2025-09-28 06:57:30) postgres=# create table tab(a int);&lt;br /&gt;
    CREATE TABLE&lt;br /&gt;
    (2025-09-28 06:57:42) postgres=# insert into tab values(10);&lt;br /&gt;
    INSERT 0 1&lt;br /&gt;
    (2025-09-28 06:57:50) postgres=# select @a from tab;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │       10 │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
We can safely use only `:` prefix, but then we can break a psql variables. Similarly we can use `$` prefix, but then we can break query parameters syntax.&lt;br /&gt;
&lt;br /&gt;
* T-SQL variable names looks weird in Postgres (in queries, in PL/pgSQL). Postgres knows only ANSI/SQL identifiers, PL/pgSQL shares these rules too, and this are the PostgreSQL is consistent, and I missing motivation to introduce something new (with the knowledge so problems with possible compatibility breaks should be solved too). &lt;br /&gt;
&lt;br /&gt;
Note: There was a proposal to use table syntax as variable (like table (in memory) variables from T-SQL but only single row). Although it is very effective solution of collisions, I dislike this idea. It increase a complexity of simple expression (or increase inconsistency of some syntax rules). More looks weird for scalar variables (and can be handy for composite variables):&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(var); -- variable is not a table&lt;br /&gt;
    END&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      -- variable a a table (I don&#039;t like it)&lt;br /&gt;
      -- can we use DML? How much rows this kind of variable can hold&lt;br /&gt;
      -- value is row or not?&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, (SELECT var FROM var);&lt;br /&gt;
    END&lt;br /&gt;
    $$&lt;br /&gt;
&lt;br /&gt;
I am not against for introduction of inmemory tables - or more correctly for global temporary tables. Well implemented global temporary tables can be very nice and handy feature. But it is different feature, and should be designed separately. There can be some performance problems - plpgsql can use simple expression evaluation (when expression has not usage of any table). This optimization can be changed or enhanced, but its is change inside complex sensitive code, and it is not consistent. I very like a global temporary tables (that are not implemented in Postgres). Table (and related operations) should to looks like table, variable (and related operations) should to looks like variable. Common perspective how session variables should to looks is based on variables from PL environment. PostgreSQL internally supports &amp;lt;i&amp;gt;transition tables&amp;lt;/i&amp;gt;, and I can imagine to allow this kind of tables outside of triggers too. This is valid use case (and valid functionality), but it is something different than session variables (although there can be some overlap). The implementation of declared tables inside PL/pgSQL can be more easy from high design perspective (interaction between SQL tables and PL/pgSQL is well defined already), but difficult from low level perspective (where to store column statistics and other important data for optimization - same problems like with global temporary tables (without some fake proxy local temp tables)).&lt;br /&gt;
&lt;br /&gt;
   CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
   AS $$&lt;br /&gt;
   -- not implemented yet, not part of this proposal&lt;br /&gt;
   DECLARE t TABLE(a int, b int); -- this table is invisible outside fx&lt;br /&gt;
   BEGIN&lt;br /&gt;
     INSERT INTO t VALUES(10,20);&lt;br /&gt;
     INSERT INTO t VALUES(30,40);&lt;br /&gt;
     RAISE NOTICE &#039;%&#039;, (SELECT count(*) FROM t);&lt;br /&gt;
   END;&lt;br /&gt;
   $$ LANGUAGE plpgsql;&lt;br /&gt;
&lt;br /&gt;
Some data languages defined relational variables - common concept of session variables has zero intersection with this concept.&lt;br /&gt;
&lt;br /&gt;
== Variabes in PostgreSQL (as of v18) ==&lt;br /&gt;
&lt;br /&gt;
PostgreSQL provides relatively advanced client-side session variables in psql --- these variables use the `:` prefix. The implementation is pretty much straightforward: `psql`&#039;s parser reads tokens from input and, when it encounters a token related to a variable, replaces it with the variable&#039;s value. The syntax supports literal and identifier escaping, and variables can be used as arguments of the `\bind` command.&lt;br /&gt;
&lt;br /&gt;
`psql` variables are typeless client-side variables, available only inside `psql` or `pgbench`.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:40:18) postgres=# \set myvar ahoj&lt;br /&gt;
    (2025-09-25 09:40:33) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:14) postgres=# select :&#039;myvar&#039;;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:24) postgres=# select $1 \bind :myvar \g&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
The `\set` command allows only simple string concatenation, but it also supports execution of shell commands. When a query is executed with the `\gset` command, its result can be stored in a `psql` variable.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:41:40) postgres=# \set ctime `date`&lt;br /&gt;
    (2025-09-25 09:43:55) postgres=# \echo :ctime&lt;br /&gt;
    Thu Sep 25 09:43:55 CEST 2025&lt;br /&gt;
&lt;br /&gt;
The `\set` command is fairly limited, making complex operations unreadable or non-portable.&lt;br /&gt;
&lt;br /&gt;
This mechanism is good enough for writing tests, but it is not generally available (most users do not need it), and it is not accessible from the server side (e.g., from stored procedures). There is no simple way to access `psql` variables from an anonymous block --- a proxy custom GUC variable must be used:&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:58:38) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    (2025-09-25 09:58:43) postgres=# select set_config(&#039;myvars.myvar&#039;, :&#039;myvar&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ ahoj       │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 10:00:27) postgres=# do $$ begin raise notice &#039;%&#039;, current_setting(&#039;myvars.myvar&#039;); end $$;&lt;br /&gt;
    NOTICE:  ahoj&lt;br /&gt;
    DO&lt;br /&gt;
&lt;br /&gt;
PostgreSQL also has configuration variables (GUC - Grand Unified Configuration). These are primarily designed for PostgreSQL and extension configuration, but they can be used as session variables. GUC variables can be set with the `SET` command or the `set_config` function, and read with `SHOW` or `current_setting`.&lt;br /&gt;
&lt;br /&gt;
GUC variables are meant for holding configuration parameters. When created through the internal API, they can be restricted to superusers, hidden from regular users, and assigned specific types (boolean, memory, number, interval, string, enum) with validation. This type system is separate from the SQL type system and is initialized before PostgreSQL’s SQL types.&lt;br /&gt;
&lt;br /&gt;
Variables created from SQL must be &amp;quot;custom&amp;quot; variables, using a prefix in their name --- these are always strings. Such custom GUCs are often used as a workaround for missing server-side session variables in PostgreSQL, but they are typeless, unprotected, and unsafe.&lt;br /&gt;
&lt;br /&gt;
A notable feature of PostgreSQL GUCs (built-in or custom) is that they can have different scopes: transaction, session, or function execution. They can also be assigned default values at specific events --- configuration loading, role login, database login, or function start. These features are useful for configuration, but problematic when GUCs are repurposed as session variables.&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
    RETURNS void AS $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, current_setting(&#039;my.x&#039;);&lt;br /&gt;
    END $$ LANGUAGE plpgsql SET my.x = 20;&lt;br /&gt;
    CREATE FUNCTION&lt;br /&gt;
    (2025-09-27 06:10:37) postgres=# SET my.x = 0; SELECT fx();&lt;br /&gt;
    SET&lt;br /&gt;
    NOTICE:  20&lt;br /&gt;
    ┌────┐&lt;br /&gt;
    │ fx │&lt;br /&gt;
    ╞════╡&lt;br /&gt;
    │    │&lt;br /&gt;
    └────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    (2025-09-27 06:10:39) postgres=# SHOW my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 0    │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
GUC variables are transactional, and this behavior cannot be disabled.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-27 06:17:31) postgres=# set my.x = 10;&lt;br /&gt;
    SET&lt;br /&gt;
    (2025-09-27 06:19:42) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:19:46) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, true);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:19:55) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:01) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:20:11) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:13) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:20:38) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:45) postgres=# rollback;&lt;br /&gt;
    ROLLBACK&lt;br /&gt;
    (2025-09-27 06:20:52) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:55) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:22:36) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:22:39) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:22:42) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
As a result, there is no reliable way to store details such as error information in custom GUCs.&lt;br /&gt;
&lt;br /&gt;
== Session Variables in Other Systems ==&lt;br /&gt;
&lt;br /&gt;
This section surveys how session variables are implemented in several widely used database systems. Each subsection includes code examples to illustrate syntax, behaviour, and scope.&lt;br /&gt;
&lt;br /&gt;
=== MySQL ===&lt;br /&gt;
&lt;br /&gt;
Session variables in MySQL have syntax similar to the better-known T-SQL variables (though other details are MySQL-specific). MySQL distinguishes two kinds of variables:&lt;br /&gt;
&lt;br /&gt;
* system variables - referenced using the `@@` prefix (or `@@scope.name`)&lt;br /&gt;
* user-defined variables - referenced using the `@` prefix&lt;br /&gt;
&lt;br /&gt;
System variables are modified with the `SET` statement. The `SET` statement accepts the modifiers `GLOBAL`, `SESSION`, or `PERSIST`. Some variables can only be set using the `GLOBAL` or `SESSION` modifier; when the variable name is specified with the `@@` prefix, it is not necessary to explicitly indicate the scope. System variables are defined by MySQL or by MySQL plugins.&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL some_config = 100;&lt;br /&gt;
    SET @@same_config = 100;&lt;br /&gt;
    &lt;br /&gt;
    SET SESSION sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@SESSION.sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
&lt;br /&gt;
Persistent values are stored in `mysqld-auto.cnf` (in the server data directory).&lt;br /&gt;
&lt;br /&gt;
Resetting system variables can be done by assigning DEFAULT or by copying from the GLOBAL namespace:&lt;br /&gt;
&lt;br /&gt;
    SET @@sql_mode = DEFAULT;&lt;br /&gt;
    SET @@sql_mode = @@GLOBAL.sql_mode&lt;br /&gt;
&lt;br /&gt;
PostgreSQL equivalents:&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL ; -- there is not similar command in Postgres&lt;br /&gt;
    SET SESSION ; -- SET&lt;br /&gt;
    SET PERSIST ; -- ALTER SYSTEM; SELECT pg_reload_conf();&lt;br /&gt;
    SELECT @@var; -- SELECT current_setting(&#039;var);&lt;br /&gt;
&lt;br /&gt;
User-defined variables are created by assignment:&lt;br /&gt;
&lt;br /&gt;
    SET @var = expr [, @var2 = expr [ ... ]];&lt;br /&gt;
&lt;br /&gt;
User-defined variables can only be of type integer, decimal, float, binary, non-binary string, or NULL. Casting between these types is implicit and hidden. User-defined variables cannot be used as table or column names.&lt;br /&gt;
&lt;br /&gt;
An advantage of the T-SQL-style syntax (with special prefixes) is that it provides a simple solution for avoiding identifier collisions. MySQL variables are convenient, and the user experience can be good for people already familiar with T-SQL (MSSQL or Sybase). On the other hand, prefix-based syntax can be confusing for users coming from systems other than MSSQL. The type system is perhaps too simple and can be unsafe. In general, the performance of MySQL (or MariaDB) stored procedures is not good, and stored procedures are therefore not frequently used. There is no protection against typographical errors. Unassigned user variables can be used without error (they default to NULL), which makes static checking of stored procedures impossible in this area.&lt;br /&gt;
&lt;br /&gt;
There is no way to restrict access to user-defined variables in MySQL. They cannot be protected against unintended usage. The only possible safeguard is a naming convention, since all user-defined variables share a single namespace.&lt;br /&gt;
&lt;br /&gt;
=== Oracle ===&lt;br /&gt;
&lt;br /&gt;
Oracle PL/SQL allows the use of package variables. PL/SQL is based on the Ada language, and package variables act as &amp;quot;global&amp;quot; variables. They are not directly visible from SQL, but Oracle provides a reduced syntax for functions without arguments, so you typically write a wrapper:&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE PACKAGE my_package&lt;br /&gt;
    AS&lt;br /&gt;
      FUNCTION get_a RETURN NUMBER;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    /&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE PACKAGE BODY my_package&lt;br /&gt;
    AS&lt;br /&gt;
      a  NUMBER(20);&lt;br /&gt;
&lt;br /&gt;
      FUNCTION get_a&lt;br /&gt;
      RETURN NUMBER&lt;br /&gt;
      IS&lt;br /&gt;
      BEGIN&lt;br /&gt;
        RETURN a;&lt;br /&gt;
      END get_a;&lt;br /&gt;
    END my_package;&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
Inside SQL, SQL identifiers take precedence. Inside non-SQL commands (such as CALL or PL/SQL blocks), package identifiers take precedence.&lt;br /&gt;
&lt;br /&gt;
Oracle allows both syntaxes for calling a function with zero arguments:&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a() FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
This reduces the risk of naming collisions. Package variables persist for the duration of a session.&lt;br /&gt;
&lt;br /&gt;
Another possibility is to use variables in SQL*Plus (similar to `psql` variables, but with the ability to define the type on the server side).&lt;br /&gt;
&lt;br /&gt;
A variable is declared with the `VARIABLE` command and can be accessed in the session using the `:varname` syntax (a prior declaration step may be optional):&lt;br /&gt;
&lt;br /&gt;
    VARIABLE bv_variable_name VARCHAR2(30)&lt;br /&gt;
    BEGIN&lt;br /&gt;
      :bv_variable_name := &#039;Some Value&#039;;&lt;br /&gt;
    END;&lt;br /&gt;
    &lt;br /&gt;
    SELECT column_name&lt;br /&gt;
      FROM   table_name&lt;br /&gt;
     WHERE  column_name = :bv_variable_name;&lt;br /&gt;
&lt;br /&gt;
=== DB2 ===&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;user-defined global variables&amp;quot; in DB2 are similar to the proposed PostgreSQL feature. The main difference is in the access rights: DB2 uses `READ` and `WRITE`, while PostgreSQL uses `SELECT` and `UPDATE`. Because PostgreSQL already uses the `SET` command for GUCs, the `LET` command was introduced in the proposal (DB2 uses `SET`).&lt;br /&gt;
&lt;br /&gt;
Variables are visible in all sessions, but their values are private to each session. Variables are not transactional. Their usage is broader than in the proposal: they can be changed by `SET`, `SELECT INTO`, or used as OUT parameters of procedures. The search path (or a similar mechanism) applies to variables as well, but variables have lower priority than tables or columns.&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE myCounter INT DEFAULT 01;&lt;br /&gt;
    SELECT EMPNO, LASTNAME, CASE WHEN myCounter = 1 THEN SALARY ELSE NULL END&lt;br /&gt;
    FROM EMPLOYEE WHERE WORKDEPT = &#039;A00&#039;;&lt;br /&gt;
    SET myCounter = 29;&lt;br /&gt;
&lt;br /&gt;
There also appear to be other kinds of variables, accessed using the function `GETVARIABLE(&#039;name&#039;, &#039;default&#039;)`. These are very similar to PostgreSQL GUCs and the `current_setting` function. Such variables can be set via the connection string, are of type `VARCHAR`, and up to 10 values are allowed. Built-in session variables (configuration) can also be accessed using `GETVARIABLE`.&lt;br /&gt;
&lt;br /&gt;
=== MSSQL (T-SQL) ===&lt;br /&gt;
&lt;br /&gt;
MSSQL provides two types of variables:&lt;br /&gt;
&lt;br /&gt;
* Global variables — accessed with the `@@varname` syntax. These are similar to GUCs and provide state information such as `@@ERROR`, `@@ROWCOUNT`, or `@@IDENTITY`. &lt;br /&gt;
* Local variables — accessed with the `@varname` syntax. These must be declared with the `DECLARE` command before use. Their scope is limited to the batch, procedure, or function where they are executed.&lt;br /&gt;
&lt;br /&gt;
    DECLARE @TestVariable AS VARCHAR(100)&lt;br /&gt;
    SET @TestVariable = &#039;Think Green&#039;&lt;br /&gt;
    GO&lt;br /&gt;
    PRINT @TestVariable&lt;br /&gt;
&lt;br /&gt;
This script fails because `PRINT` is executed in a different batch. Therefore, it seems MSSQL does not truly support session variables.&lt;br /&gt;
&lt;br /&gt;
There are also mechanisms similar to PostgreSQL&#039;s custom GUCs and the use of `current_setting` and `set_config` functions. In general, MSSQL is fairly primitive in this area.&lt;br /&gt;
&lt;br /&gt;
    EXEC sp_set_session_context &#039;user_id&#039;, 4;&lt;br /&gt;
    SELECT SESSION_CONTEXT(N&#039;user_id&#039;);&lt;br /&gt;
&lt;br /&gt;
== ToDo ==&lt;br /&gt;
&lt;br /&gt;
* SECURITY LABEL support — enhance the `dummy_seclabel` module and the `sepgsql` extension. Update PostgreSQL documentation and ensure `SecLabelSupportsObjectType` includes session variables.&lt;/div&gt;</summary>
		<author><name>Okbobcz</name></author>
	</entry>
	<entry>
		<id>https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41835</id>
		<title>Implementation of declarative catalog session variables</title>
		<link rel="alternate" type="text/html" href="https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41835"/>
		<updated>2025-09-29T04:30:53Z</updated>

		<summary type="html">&lt;p&gt;Okbobcz: /* Introduction */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Session variables are database objects that can hold a value across multiple queries inside a session. The design of session variables varies widely, and many databases implement more than one type of session variable.&lt;br /&gt;
&lt;br /&gt;
The SQL/PSM standard introduces modules and module-level variables. However, this part of the standard has not been implemented in any widely used product. As a result, each database system has developed its own proprietary design, syntax, and feature set.&lt;br /&gt;
&lt;br /&gt;
Session variables are often part of the stored procedure environment, but there are exceptions --- for example, MySQL session variables or client-side session variables in tools like psql. In most systems, session variables do not participate in transactions: once set, their value persists until explicitly changed, even if the surrounding transaction is rolled back. Conceptually, they behave much like variables in general-purpose programming languages.&lt;br /&gt;
&lt;br /&gt;
Because there is no common design, comparisons across systems are difficult. Each implementation has its own advantages and disadvantages, and in some cases a single system supports more than one kind of session variable.&lt;br /&gt;
&lt;br /&gt;
Kinds of session variables:&lt;br /&gt;
* client side (psql, pgadmin, ...) - mostly similar to shell variables (based on string operations)&lt;br /&gt;
* server side&lt;br /&gt;
** declarative&lt;br /&gt;
*** created only for batch (T-SQL)&lt;br /&gt;
*** created as an database object (or part of database object) (Oracle, DB2)&lt;br /&gt;
** created by usage (MySQL)&lt;br /&gt;
** with priprietary syntax for identifiers (MySQL, T-SQL)&lt;br /&gt;
** with ANSI SQL syntax for identifiers (Oracle, DB2)&lt;br /&gt;
&lt;br /&gt;
Possible usage of session variables:&lt;br /&gt;
&lt;br /&gt;
* usage of session variables as global variables in PL (tracing, debugging, hacking),&lt;br /&gt;
* holding configuration (for PL),&lt;br /&gt;
* holding some more used data in interactive session,&lt;br /&gt;
* holding credentials for some RLS and data securing,&lt;br /&gt;
* helping with migration from others systems (mainly from Oracle),&lt;br /&gt;
* allow anonymous block parametrization (only postgres).&lt;br /&gt;
&lt;br /&gt;
== SQL/PSM ==&lt;br /&gt;
&lt;br /&gt;
The SQL standard introduces the concept of modules, which can be associated with schemas (partially). Variables in modules behave like PL/pgSQL variables but are only local. The only objects allowed at the module level are temporary tables, which can be accessed only from routines assigned to that module. Modules can encapsulate private objects (functions, procedure, temp tables), that are not visible outside the module. Inside the module, a  private objects shadows other objects. What I know the modules (and generally SQL/PSM) doesn&#039;t cover a deployment of code, so there is not similarity with PostgreSQL extensions. SQL/PSM Modules are modules like modules from modular languages like Modula2 or Oberon. There is some similarity with Postgres schemas (both are containers) with significant differences (there is not a concept of SEARCH_PATH (on PSM side) or there is not a concept of private or public objects (on Postgres schema side)).&lt;br /&gt;
&lt;br /&gt;
SQL/PSM variables are not transactional (they are used exactly like in PL/pgSQL). The precedence between variables and columns is not specified.&lt;br /&gt;
&lt;br /&gt;
== Implementation Challenges ==&lt;br /&gt;
&lt;br /&gt;
* Possible collisions between session variables and other database objects.&lt;br /&gt;
* Memory cleanup after dropping a session variable, especially when DDL operations can be reverted (Postgres has not support for global temp objects).&lt;br /&gt;
* Memory invalidation: if a variable is dropped by one session, other sessions should not continue using it (Postgres has not support for global temp objects).&lt;br /&gt;
&lt;br /&gt;
== Proposed Implementations ==&lt;br /&gt;
&lt;br /&gt;
It is clean so no one design is perfect for all uses cases. MySQL is good enough and maybe handy for interactive work,&lt;br /&gt;
but cannot be used for storing any sensitive data, and it is unsafe. PostgreSQL GUC are perfect for configuration, but&lt;br /&gt;
are not friendly for interactive work, and are very limited for usage in PL as global variables. So no one design is&lt;br /&gt;
perfect, and I can imagine more different designs in one system, that can support some different use cases better.&lt;br /&gt;
&lt;br /&gt;
== Proposal for Postgres - design session variables as database global temporary typed objects with ANSI SQL syntax for identifiers ==&lt;br /&gt;
&lt;br /&gt;
I searched solution that can work with specific Postgres environment:&lt;br /&gt;
&lt;br /&gt;
* Postgres has more than one widely used language for stored procedures PL/pgSQL + PL/Python or PL/Perl&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL is strongly reduced PL/SQL, and PL/SQL is modified ADA language. I am pretty happy with the current scope of PL/SQL - it is domain specific language and it works well. It is a very simple language, and it is easy to learn it. PL/pgSQL has no packages or modules. Instead PostgreSQL schemas are used. But Postgres schemas are specific database objects. Schemas are independent on PL. &lt;br /&gt;
&lt;br /&gt;
So I wanted design that can supports more PL languages, didn&#039;t introduce new complexity to relative simple PL/pgSQL and didn&#039;t introduce functionality that can be redundant with already existing objects.&lt;br /&gt;
I wrote bigger application in PL/pgSQL. For maintaining of this application I wrote plpgsql_lint (later plpgsql_check). So every time I am searching solution that doesn&#039;t block code static analyse. Static analyse requires persistent objects, and then it is direct way to design session variables as database objects (with own system catalog). When session variables are designed as database objects, then using ACL is natural. Oracle package variables are well protected just by package scope. Unfortunately, postgres doesn&#039;t know schema scope. The locality of schema objects is defined by setting of SEARCH_PATH guc, and introduction of new mechanism can be messy. But with ACL the content of variables can be safe too:&lt;br /&gt;
&lt;br /&gt;
    CREATE TEMP VARIABLE x AS int;&lt;br /&gt;
     &lt;br /&gt;
    LET x = 10;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(x);&lt;br /&gt;
    END;&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    SELECT VARIABLE(x);&lt;br /&gt;
&lt;br /&gt;
==== Collisions between columns and variables identifiers ====&lt;br /&gt;
There is a new possibility of identifiers collisions between variables and columns identifiers. With ANSI SQL syntax for session variables identifiers there is a problem how to distinct between variable and column. This is known problem in PL/pgSQL or PL/SQL and can be solved by some prioritization or by prohibition of collisions. The prohibition of collisions is solution that almost cleaned this issue from PL/pgSQL. Unfortunately session variables are global objects and then the collision can be in any query, and then the new badly named variable can stop execution of any query. Prioritization variables or columns has own problems - quiet unwanted using variable or column.&lt;br /&gt;
&lt;br /&gt;
    CREATE TABLE tab(a int);&lt;br /&gt;
    CREATE VARIABLE a AS int;&lt;br /&gt;
    &lt;br /&gt;
    SELECT a FROM tab; -- with disallowed collisions, the execution of this query stops when there is a variable named &amp;quot;a&amp;quot;&lt;br /&gt;
    &lt;br /&gt;
    LET a = 1;&lt;br /&gt;
    DELETE FROM tab WHERE a = 1; -- with higher priority for variables all table can be deleted (nobody proposed higher priority for variables).&lt;br /&gt;
    SELECT a FROM tab; -- with higher priority for columns, the variable is not used (quietly)&lt;br /&gt;
&lt;br /&gt;
The prohibition of collision is not absolute protection against possible human errors:&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE _id AS int;&lt;br /&gt;
    LET _id = 10;&lt;br /&gt;
    CREATE TABLE foo(id int);&lt;br /&gt;
    DELETE FROM foo WHERE _id = 10; -- just type, but can have unwanted impact (all rows removed)&lt;br /&gt;
&lt;br /&gt;
These problems are not new. The developers in PL/pgSQL or PL/SQL knows it, but the scope of these risks are limited only to stored procedures. After very very long discussion and lot of designs I introduced variable fences (syntax `VARIABLE(varname)`). Inside of any query the variable can be used only in variable fence. This is 100% solution of collisions, and I think it can be good solution against human errors and unwanted usage of session variable. Now, variable fences are necessary every where inside a query or expressions, but I thinking about possibility to be optional inside PL/pgSQL (far future). This can reduce overhead with porting applications from Oracle, and generally the developers of stored procedures are experienced with described risks (this can be controlled by new `plpgsql.extra_checks`).&lt;br /&gt;
&lt;br /&gt;
A variable fence can be in collisions with custom function &amp;quot;variable&amp;quot;. There is similarity with usage of keywords `cube`. This can be fixed by usage prefix schema or by usage of parenthesis:&lt;br /&gt;
&lt;br /&gt;
    SELECT public.variable(...)&lt;br /&gt;
    SELECT &amp;quot;variable&amp;quot;(...)&lt;br /&gt;
&lt;br /&gt;
Why I don&#039;t propose usage of T-SQL (MSSQL, MySQL) syntax for session variables? There is more reasons (one is objective, last is subjective):&lt;br /&gt;
&lt;br /&gt;
* There is a collision with custom operators - operators `@` or `@@` are already used - usage of `@@` is common&lt;br /&gt;
&lt;br /&gt;
    (2025-09-28 06:57:30) postgres=# create table tab(a int);&lt;br /&gt;
    CREATE TABLE&lt;br /&gt;
    (2025-09-28 06:57:42) postgres=# insert into tab values(10);&lt;br /&gt;
    INSERT 0 1&lt;br /&gt;
    (2025-09-28 06:57:50) postgres=# select @a from tab;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │       10 │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
We can safely use only `:` prefix, but then we can break a psql variables. Similarly we can use `$` prefix, but then we can break query parameters syntax.&lt;br /&gt;
&lt;br /&gt;
* T-SQL variable names looks weird in Postgres (in queries, in PL/pgSQL). Postgres knows only ANSI/SQL identifiers, PL/pgSQL shares these rules too, and this are the PostgreSQL is consistent, and I missing motivation to introduce something new (with the knowledge so problems with possible compatibility breaks should be solved too). &lt;br /&gt;
&lt;br /&gt;
Note: There was a proposal to use table syntax as variable (like table (in memory) variables from T-SQL but only single row). Although it is very effective solution of collisions, I dislike this idea. It increase a complexity of simple expression (or increase inconsistency of some syntax rules). More looks weird for scalar variables (and can be handy for composite variables):&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(var); -- variable is not a table&lt;br /&gt;
    END&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      -- variable a a table (I don&#039;t like it)&lt;br /&gt;
      -- can we use DML? How much rows this kind of variable can hold&lt;br /&gt;
      -- value is row or not?&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, (SELECT var FROM var);&lt;br /&gt;
    END&lt;br /&gt;
    $$&lt;br /&gt;
&lt;br /&gt;
I am not against for introduction of inmemory tables - or more correctly for global temporary tables. Well implemented global temporary tables can be very nice and handy feature. But it is different feature, and should be designed separately. There can be some performance problems - plpgsql can use simple expression evaluation (when expression has not usage of any table). This optimization can be changed or enhanced, but its is change inside complex sensitive code, and it is not consistent. I very like a global temporary tables (that are not implemented in Postgres). Table (and related operations) should to looks like table, variable (and related operations) should to looks like variable. Common perspective how session variables should to looks is based on variables from PL environment. PostgreSQL internally supports &amp;lt;i&amp;gt;transition tables&amp;lt;/i&amp;gt;, and I can imagine to allow this kind of tables outside of triggers too. This is valid use case (and valid functionality), but it is something different than session variables (although there can be some overlap). The implementation of declared tables inside PL/pgSQL can be more easy from high design perspective (interaction between SQL tables and PL/pgSQL is well defined already), but difficult from low level perspective (where to store column statistics and other important data for optimization - same problems like with global temporary tables (without some fake proxy local temp tables)).&lt;br /&gt;
&lt;br /&gt;
   CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
   AS $$&lt;br /&gt;
   -- not implemented yet, not part of this proposal&lt;br /&gt;
   DECLARE t TABLE(a int, b int); -- this table is invisible outside fx&lt;br /&gt;
   BEGIN&lt;br /&gt;
     INSERT INTO t VALUES(10,20);&lt;br /&gt;
     INSERT INTO t VALUES(30,40);&lt;br /&gt;
     RAISE NOTICE &#039;%&#039;, (SELECT count(*) FROM t);&lt;br /&gt;
   END;&lt;br /&gt;
   $$ LANGUAGE plpgsql;&lt;br /&gt;
&lt;br /&gt;
Some data languages defined relational variables - common concept of session variables has zero intersection with this concept.&lt;br /&gt;
&lt;br /&gt;
== Variabes in PostgreSQL (as of v18) ==&lt;br /&gt;
&lt;br /&gt;
PostgreSQL provides relatively advanced client-side session variables in psql --- these variables use the `:` prefix. The implementation is pretty much straightforward: `psql`&#039;s parser reads tokens from input and, when it encounters a token related to a variable, replaces it with the variable&#039;s value. The syntax supports literal and identifier escaping, and variables can be used as arguments of the `\bind` command.&lt;br /&gt;
&lt;br /&gt;
`psql` variables are typeless client-side variables, available only inside `psql` or `pgbench`.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:40:18) postgres=# \set myvar ahoj&lt;br /&gt;
    (2025-09-25 09:40:33) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:14) postgres=# select :&#039;myvar&#039;;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:24) postgres=# select $1 \bind :myvar \g&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
The `\set` command allows only simple string concatenation, but it also supports execution of shell commands. When a query is executed with the `\gset` command, its result can be stored in a `psql` variable.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:41:40) postgres=# \set ctime `date`&lt;br /&gt;
    (2025-09-25 09:43:55) postgres=# \echo :ctime&lt;br /&gt;
    Thu Sep 25 09:43:55 CEST 2025&lt;br /&gt;
&lt;br /&gt;
The `\set` command is fairly limited, making complex operations unreadable or non-portable.&lt;br /&gt;
&lt;br /&gt;
This mechanism is good enough for writing tests, but it is not generally available (most users do not need it), and it is not accessible from the server side (e.g., from stored procedures). There is no simple way to access `psql` variables from an anonymous block --- a proxy custom GUC variable must be used:&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:58:38) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    (2025-09-25 09:58:43) postgres=# select set_config(&#039;myvars.myvar&#039;, :&#039;myvar&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ ahoj       │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 10:00:27) postgres=# do $$ begin raise notice &#039;%&#039;, current_setting(&#039;myvars.myvar&#039;); end $$;&lt;br /&gt;
    NOTICE:  ahoj&lt;br /&gt;
    DO&lt;br /&gt;
&lt;br /&gt;
PostgreSQL also has configuration variables (GUC - Grand Unified Configuration). These are primarily designed for PostgreSQL and extension configuration, but they can be used as session variables. GUC variables can be set with the `SET` command or the `set_config` function, and read with `SHOW` or `current_setting`.&lt;br /&gt;
&lt;br /&gt;
GUC variables are meant for holding configuration parameters. When created through the internal API, they can be restricted to superusers, hidden from regular users, and assigned specific types (boolean, memory, number, interval, string, enum) with validation. This type system is separate from the SQL type system and is initialized before PostgreSQL’s SQL types.&lt;br /&gt;
&lt;br /&gt;
Variables created from SQL must be &amp;quot;custom&amp;quot; variables, using a prefix in their name --- these are always strings. Such custom GUCs are often used as a workaround for missing server-side session variables in PostgreSQL, but they are typeless, unprotected, and unsafe.&lt;br /&gt;
&lt;br /&gt;
A notable feature of PostgreSQL GUCs (built-in or custom) is that they can have different scopes: transaction, session, or function execution. They can also be assigned default values at specific events --- configuration loading, role login, database login, or function start. These features are useful for configuration, but problematic when GUCs are repurposed as session variables.&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
    RETURNS void AS $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, current_setting(&#039;my.x&#039;);&lt;br /&gt;
    END $$ LANGUAGE plpgsql SET my.x = 20;&lt;br /&gt;
    CREATE FUNCTION&lt;br /&gt;
    (2025-09-27 06:10:37) postgres=# SET my.x = 0; SELECT fx();&lt;br /&gt;
    SET&lt;br /&gt;
    NOTICE:  20&lt;br /&gt;
    ┌────┐&lt;br /&gt;
    │ fx │&lt;br /&gt;
    ╞════╡&lt;br /&gt;
    │    │&lt;br /&gt;
    └────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    (2025-09-27 06:10:39) postgres=# SHOW my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 0    │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
GUC variables are transactional, and this behavior cannot be disabled.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-27 06:17:31) postgres=# set my.x = 10;&lt;br /&gt;
    SET&lt;br /&gt;
    (2025-09-27 06:19:42) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:19:46) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, true);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:19:55) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:01) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:20:11) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:13) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:20:38) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:45) postgres=# rollback;&lt;br /&gt;
    ROLLBACK&lt;br /&gt;
    (2025-09-27 06:20:52) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:55) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:22:36) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:22:39) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:22:42) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
As a result, there is no reliable way to store details such as error information in custom GUCs.&lt;br /&gt;
&lt;br /&gt;
== Session Variables in Other Systems ==&lt;br /&gt;
&lt;br /&gt;
This section surveys how session variables are implemented in several widely used database systems. Each subsection includes code examples to illustrate syntax, behaviour, and scope.&lt;br /&gt;
&lt;br /&gt;
=== MySQL ===&lt;br /&gt;
&lt;br /&gt;
Session variables in MySQL have syntax similar to the better-known T-SQL variables (though other details are MySQL-specific). MySQL distinguishes two kinds of variables:&lt;br /&gt;
&lt;br /&gt;
* system variables - referenced using the `@@` prefix (or `@@scope.name`)&lt;br /&gt;
* user-defined variables - referenced using the `@` prefix&lt;br /&gt;
&lt;br /&gt;
System variables are modified with the `SET` statement. The `SET` statement accepts the modifiers `GLOBAL`, `SESSION`, or `PERSIST`. Some variables can only be set using the `GLOBAL` or `SESSION` modifier; when the variable name is specified with the `@@` prefix, it is not necessary to explicitly indicate the scope. System variables are defined by MySQL or by MySQL plugins.&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL some_config = 100;&lt;br /&gt;
    SET @@same_config = 100;&lt;br /&gt;
    &lt;br /&gt;
    SET SESSION sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@SESSION.sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
&lt;br /&gt;
Persistent values are stored in `mysqld-auto.cnf` (in the server data directory).&lt;br /&gt;
&lt;br /&gt;
Resetting system variables can be done by assigning DEFAULT or by copying from the GLOBAL namespace:&lt;br /&gt;
&lt;br /&gt;
    SET @@sql_mode = DEFAULT;&lt;br /&gt;
    SET @@sql_mode = @@GLOBAL.sql_mode&lt;br /&gt;
&lt;br /&gt;
PostgreSQL equivalents:&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL ; -- there is not similar command in Postgres&lt;br /&gt;
    SET SESSION ; -- SET&lt;br /&gt;
    SET PERSIST ; -- ALTER SYSTEM; SELECT pg_reload_conf();&lt;br /&gt;
    SELECT @@var; -- SELECT current_setting(&#039;var);&lt;br /&gt;
&lt;br /&gt;
User-defined variables are created by assignment:&lt;br /&gt;
&lt;br /&gt;
    SET @var = expr [, @var2 = expr [ ... ]];&lt;br /&gt;
&lt;br /&gt;
User-defined variables can only be of type integer, decimal, float, binary, non-binary string, or NULL. Casting between these types is implicit and hidden. User-defined variables cannot be used as table or column names.&lt;br /&gt;
&lt;br /&gt;
An advantage of the T-SQL-style syntax (with special prefixes) is that it provides a simple solution for avoiding identifier collisions. MySQL variables are convenient, and the user experience can be good for people already familiar with T-SQL (MSSQL or Sybase). On the other hand, prefix-based syntax can be confusing for users coming from systems other than MSSQL. The type system is perhaps too simple and can be unsafe. In general, the performance of MySQL (or MariaDB) stored procedures is not good, and stored procedures are therefore not frequently used. There is no protection against typographical errors. Unassigned user variables can be used without error (they default to NULL), which makes static checking of stored procedures impossible in this area.&lt;br /&gt;
&lt;br /&gt;
There is no way to restrict access to user-defined variables in MySQL. They cannot be protected against unintended usage. The only possible safeguard is a naming convention, since all user-defined variables share a single namespace.&lt;br /&gt;
&lt;br /&gt;
=== Oracle ===&lt;br /&gt;
&lt;br /&gt;
Oracle PL/SQL allows the use of package variables. PL/SQL is based on the Ada language, and package variables act as &amp;quot;global&amp;quot; variables. They are not directly visible from SQL, but Oracle provides a reduced syntax for functions without arguments, so you typically write a wrapper:&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE PACKAGE my_package&lt;br /&gt;
    AS&lt;br /&gt;
      FUNCTION get_a RETURN NUMBER;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    /&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE PACKAGE BODY my_package&lt;br /&gt;
    AS&lt;br /&gt;
      a  NUMBER(20);&lt;br /&gt;
&lt;br /&gt;
      FUNCTION get_a&lt;br /&gt;
      RETURN NUMBER&lt;br /&gt;
      IS&lt;br /&gt;
      BEGIN&lt;br /&gt;
        RETURN a;&lt;br /&gt;
      END get_a;&lt;br /&gt;
    END my_package;&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
Inside SQL, SQL identifiers take precedence. Inside non-SQL commands (such as CALL or PL/SQL blocks), package identifiers take precedence.&lt;br /&gt;
&lt;br /&gt;
Oracle allows both syntaxes for calling a function with zero arguments:&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a() FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
This reduces the risk of naming collisions. Package variables persist for the duration of a session.&lt;br /&gt;
&lt;br /&gt;
Another possibility is to use variables in SQL*Plus (similar to `psql` variables, but with the ability to define the type on the server side).&lt;br /&gt;
&lt;br /&gt;
A variable is declared with the `VARIABLE` command and can be accessed in the session using the `:varname` syntax (a prior declaration step may be optional):&lt;br /&gt;
&lt;br /&gt;
    VARIABLE bv_variable_name VARCHAR2(30)&lt;br /&gt;
    BEGIN&lt;br /&gt;
      :bv_variable_name := &#039;Some Value&#039;;&lt;br /&gt;
    END;&lt;br /&gt;
    &lt;br /&gt;
    SELECT column_name&lt;br /&gt;
      FROM   table_name&lt;br /&gt;
     WHERE  column_name = :bv_variable_name;&lt;br /&gt;
&lt;br /&gt;
=== DB2 ===&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;user-defined global variables&amp;quot; in DB2 are similar to the proposed PostgreSQL feature. The main difference is in the access rights: DB2 uses `READ` and `WRITE`, while PostgreSQL uses `SELECT` and `UPDATE`. Because PostgreSQL already uses the `SET` command for GUCs, the `LET` command was introduced in the proposal (DB2 uses `SET`).&lt;br /&gt;
&lt;br /&gt;
Variables are visible in all sessions, but their values are private to each session. Variables are not transactional. Their usage is broader than in the proposal: they can be changed by `SET`, `SELECT INTO`, or used as OUT parameters of procedures. The search path (or a similar mechanism) applies to variables as well, but variables have lower priority than tables or columns.&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE myCounter INT DEFAULT 01;&lt;br /&gt;
    SELECT EMPNO, LASTNAME, CASE WHEN myCounter = 1 THEN SALARY ELSE NULL END&lt;br /&gt;
    FROM EMPLOYEE WHERE WORKDEPT = &#039;A00&#039;;&lt;br /&gt;
    SET myCounter = 29;&lt;br /&gt;
&lt;br /&gt;
There also appear to be other kinds of variables, accessed using the function `GETVARIABLE(&#039;name&#039;, &#039;default&#039;)`. These are very similar to PostgreSQL GUCs and the `current_setting` function. Such variables can be set via the connection string, are of type `VARCHAR`, and up to 10 values are allowed. Built-in session variables (configuration) can also be accessed using `GETVARIABLE`.&lt;br /&gt;
&lt;br /&gt;
=== MSSQL (T-SQL) ===&lt;br /&gt;
&lt;br /&gt;
MSSQL provides two types of variables:&lt;br /&gt;
&lt;br /&gt;
* Global variables — accessed with the `@@varname` syntax. These are similar to GUCs and provide state information such as `@@ERROR`, `@@ROWCOUNT`, or `@@IDENTITY`. &lt;br /&gt;
* Local variables** — accessed with the `@varname` syntax. These must be declared with the `DECLARE` command before use. Their scope is limited to the batch, procedure, or function where they are executed.&lt;br /&gt;
&lt;br /&gt;
    DECLARE @TestVariable AS VARCHAR(100)&lt;br /&gt;
    SET @TestVariable = &#039;Think Green&#039;&lt;br /&gt;
    GO&lt;br /&gt;
    PRINT @TestVariable&lt;br /&gt;
&lt;br /&gt;
This script fails because `PRINT` is executed in a different batch. Therefore, it seems MSSQL does not truly support session variables.&lt;br /&gt;
&lt;br /&gt;
There are also mechanisms similar to PostgreSQL&#039;s custom GUCs and the use of `current_setting` and `set_config` functions. In general, MSSQL is fairly primitive in this area.&lt;br /&gt;
&lt;br /&gt;
    EXEC sp_set_session_context &#039;user_id&#039;, 4;&lt;br /&gt;
    SELECT SESSION_CONTEXT(N&#039;user_id&#039;);&lt;br /&gt;
&lt;br /&gt;
== ToDo ==&lt;br /&gt;
&lt;br /&gt;
* SECURITY LABEL support — enhance the `dummy_seclabel` module and the `sepgsql` extension. Update PostgreSQL documentation and ensure `SecLabelSupportsObjectType` includes session variables.&lt;/div&gt;</summary>
		<author><name>Okbobcz</name></author>
	</entry>
	<entry>
		<id>https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41834</id>
		<title>Implementation of declarative catalog session variables</title>
		<link rel="alternate" type="text/html" href="https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41834"/>
		<updated>2025-09-29T04:30:04Z</updated>

		<summary type="html">&lt;p&gt;Okbobcz: reaply Jim&amp;#039;s editorial work and some reorganization&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Session variables are database objects that can hold a value across multiple queries inside a session. The design of session variables varies widely, and many databases implement more than one type of session variable.&lt;br /&gt;
&lt;br /&gt;
The SQL/PSM standard introduces modules and module-level variables. However, this part of the standard has not been implemented in any widely used product. As a result, each database system has developed its own proprietary design, syntax, and feature set.&lt;br /&gt;
&lt;br /&gt;
Session variables are often part of the stored procedure environment, but there are exceptions --- for example, MySQL session variables or client-side session variables in tools like psql. In most systems, session variables do not participate in transactions: once set, their value persists until explicitly changed, even if the surrounding transaction is rolled back. Conceptually, they behave much like variables in general-purpose programming languages.&lt;br /&gt;
&lt;br /&gt;
Because there is no common design, comparisons across systems are difficult. Each implementation has its own advantages and disadvantages, and in some cases a single system supports more than one kind of session variable.&lt;br /&gt;
&lt;br /&gt;
Kinds of session variables:&lt;br /&gt;
* client side (psql, pgadmin, ...) - mostly similar to shell variables (based on string operations)&lt;br /&gt;
* server side&lt;br /&gt;
** declarative&lt;br /&gt;
*** created only for batch (T-SQL)&lt;br /&gt;
*** created as an database object (or part of database object) (Oracle, DB2)&lt;br /&gt;
** created by usage (MySQL)&lt;br /&gt;
** with special syntax (MySQL, T-SQL)&lt;br /&gt;
** with ANSI SQL syntax for identifiers (Oracle, DB2)&lt;br /&gt;
&lt;br /&gt;
Possible usage of session variables:&lt;br /&gt;
&lt;br /&gt;
* usage of session variables as global variables in PL (tracing, debugging, hacking),&lt;br /&gt;
* holding configuration (for PL),&lt;br /&gt;
* holding some more used data in interactive session,&lt;br /&gt;
* holding credentials for some RLS and data securing,&lt;br /&gt;
* helping with migration from others systems (mainly from Oracle),&lt;br /&gt;
* allow anonymous block parametrization (only postgres).&lt;br /&gt;
&lt;br /&gt;
== SQL/PSM ==&lt;br /&gt;
&lt;br /&gt;
The SQL standard introduces the concept of modules, which can be associated with schemas (partially). Variables in modules behave like PL/pgSQL variables but are only local. The only objects allowed at the module level are temporary tables, which can be accessed only from routines assigned to that module. Modules can encapsulate private objects (functions, procedure, temp tables), that are not visible outside the module. Inside the module, a  private objects shadows other objects. What I know the modules (and generally SQL/PSM) doesn&#039;t cover a deployment of code, so there is not similarity with PostgreSQL extensions. SQL/PSM Modules are modules like modules from modular languages like Modula2 or Oberon. There is some similarity with Postgres schemas (both are containers) with significant differences (there is not a concept of SEARCH_PATH (on PSM side) or there is not a concept of private or public objects (on Postgres schema side)).&lt;br /&gt;
&lt;br /&gt;
SQL/PSM variables are not transactional (they are used exactly like in PL/pgSQL). The precedence between variables and columns is not specified.&lt;br /&gt;
&lt;br /&gt;
== Implementation Challenges ==&lt;br /&gt;
&lt;br /&gt;
* Possible collisions between session variables and other database objects.&lt;br /&gt;
* Memory cleanup after dropping a session variable, especially when DDL operations can be reverted (Postgres has not support for global temp objects).&lt;br /&gt;
* Memory invalidation: if a variable is dropped by one session, other sessions should not continue using it (Postgres has not support for global temp objects).&lt;br /&gt;
&lt;br /&gt;
== Proposed Implementations ==&lt;br /&gt;
&lt;br /&gt;
It is clean so no one design is perfect for all uses cases. MySQL is good enough and maybe handy for interactive work,&lt;br /&gt;
but cannot be used for storing any sensitive data, and it is unsafe. PostgreSQL GUC are perfect for configuration, but&lt;br /&gt;
are not friendly for interactive work, and are very limited for usage in PL as global variables. So no one design is&lt;br /&gt;
perfect, and I can imagine more different designs in one system, that can support some different use cases better.&lt;br /&gt;
&lt;br /&gt;
== Proposal for Postgres - design session variables as database global temporary typed objects with ANSI SQL syntax for identifiers ==&lt;br /&gt;
&lt;br /&gt;
I searched solution that can work with specific Postgres environment:&lt;br /&gt;
&lt;br /&gt;
* Postgres has more than one widely used language for stored procedures PL/pgSQL + PL/Python or PL/Perl&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL is strongly reduced PL/SQL, and PL/SQL is modified ADA language. I am pretty happy with the current scope of PL/SQL - it is domain specific language and it works well. It is a very simple language, and it is easy to learn it. PL/pgSQL has no packages or modules. Instead PostgreSQL schemas are used. But Postgres schemas are specific database objects. Schemas are independent on PL. &lt;br /&gt;
&lt;br /&gt;
So I wanted design that can supports more PL languages, didn&#039;t introduce new complexity to relative simple PL/pgSQL and didn&#039;t introduce functionality that can be redundant with already existing objects.&lt;br /&gt;
I wrote bigger application in PL/pgSQL. For maintaining of this application I wrote plpgsql_lint (later plpgsql_check). So every time I am searching solution that doesn&#039;t block code static analyse. Static analyse requires persistent objects, and then it is direct way to design session variables as database objects (with own system catalog). When session variables are designed as database objects, then using ACL is natural. Oracle package variables are well protected just by package scope. Unfortunately, postgres doesn&#039;t know schema scope. The locality of schema objects is defined by setting of SEARCH_PATH guc, and introduction of new mechanism can be messy. But with ACL the content of variables can be safe too:&lt;br /&gt;
&lt;br /&gt;
    CREATE TEMP VARIABLE x AS int;&lt;br /&gt;
     &lt;br /&gt;
    LET x = 10;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(x);&lt;br /&gt;
    END;&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    SELECT VARIABLE(x);&lt;br /&gt;
&lt;br /&gt;
==== Collisions between columns and variables identifiers ====&lt;br /&gt;
There is a new possibility of identifiers collisions between variables and columns identifiers. With ANSI SQL syntax for session variables identifiers there is a problem how to distinct between variable and column. This is known problem in PL/pgSQL or PL/SQL and can be solved by some prioritization or by prohibition of collisions. The prohibition of collisions is solution that almost cleaned this issue from PL/pgSQL. Unfortunately session variables are global objects and then the collision can be in any query, and then the new badly named variable can stop execution of any query. Prioritization variables or columns has own problems - quiet unwanted using variable or column.&lt;br /&gt;
&lt;br /&gt;
    CREATE TABLE tab(a int);&lt;br /&gt;
    CREATE VARIABLE a AS int;&lt;br /&gt;
    &lt;br /&gt;
    SELECT a FROM tab; -- with disallowed collisions, the execution of this query stops when there is a variable named &amp;quot;a&amp;quot;&lt;br /&gt;
    &lt;br /&gt;
    LET a = 1;&lt;br /&gt;
    DELETE FROM tab WHERE a = 1; -- with higher priority for variables all table can be deleted (nobody proposed higher priority for variables).&lt;br /&gt;
    SELECT a FROM tab; -- with higher priority for columns, the variable is not used (quietly)&lt;br /&gt;
&lt;br /&gt;
The prohibition of collision is not absolute protection against possible human errors:&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE _id AS int;&lt;br /&gt;
    LET _id = 10;&lt;br /&gt;
    CREATE TABLE foo(id int);&lt;br /&gt;
    DELETE FROM foo WHERE _id = 10; -- just type, but can have unwanted impact (all rows removed)&lt;br /&gt;
&lt;br /&gt;
These problems are not new. The developers in PL/pgSQL or PL/SQL knows it, but the scope of these risks are limited only to stored procedures. After very very long discussion and lot of designs I introduced variable fences (syntax `VARIABLE(varname)`). Inside of any query the variable can be used only in variable fence. This is 100% solution of collisions, and I think it can be good solution against human errors and unwanted usage of session variable. Now, variable fences are necessary every where inside a query or expressions, but I thinking about possibility to be optional inside PL/pgSQL (far future). This can reduce overhead with porting applications from Oracle, and generally the developers of stored procedures are experienced with described risks (this can be controlled by new `plpgsql.extra_checks`).&lt;br /&gt;
&lt;br /&gt;
A variable fence can be in collisions with custom function &amp;quot;variable&amp;quot;. There is similarity with usage of keywords `cube`. This can be fixed by usage prefix schema or by usage of parenthesis:&lt;br /&gt;
&lt;br /&gt;
    SELECT public.variable(...)&lt;br /&gt;
    SELECT &amp;quot;variable&amp;quot;(...)&lt;br /&gt;
&lt;br /&gt;
Why I don&#039;t propose usage of T-SQL (MSSQL, MySQL) syntax for session variables? There is more reasons (one is objective, last is subjective):&lt;br /&gt;
&lt;br /&gt;
* There is a collision with custom operators - operators `@` or `@@` are already used - usage of `@@` is common&lt;br /&gt;
&lt;br /&gt;
    (2025-09-28 06:57:30) postgres=# create table tab(a int);&lt;br /&gt;
    CREATE TABLE&lt;br /&gt;
    (2025-09-28 06:57:42) postgres=# insert into tab values(10);&lt;br /&gt;
    INSERT 0 1&lt;br /&gt;
    (2025-09-28 06:57:50) postgres=# select @a from tab;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │       10 │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
We can safely use only `:` prefix, but then we can break a psql variables. Similarly we can use `$` prefix, but then we can break query parameters syntax.&lt;br /&gt;
&lt;br /&gt;
* T-SQL variable names looks weird in Postgres (in queries, in PL/pgSQL). Postgres knows only ANSI/SQL identifiers, PL/pgSQL shares these rules too, and this are the PostgreSQL is consistent, and I missing motivation to introduce something new (with the knowledge so problems with possible compatibility breaks should be solved too). &lt;br /&gt;
&lt;br /&gt;
Note: There was a proposal to use table syntax as variable (like table (in memory) variables from T-SQL but only single row). Although it is very effective solution of collisions, I dislike this idea. It increase a complexity of simple expression (or increase inconsistency of some syntax rules). More looks weird for scalar variables (and can be handy for composite variables):&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(var); -- variable is not a table&lt;br /&gt;
    END&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      -- variable a a table (I don&#039;t like it)&lt;br /&gt;
      -- can we use DML? How much rows this kind of variable can hold&lt;br /&gt;
      -- value is row or not?&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, (SELECT var FROM var);&lt;br /&gt;
    END&lt;br /&gt;
    $$&lt;br /&gt;
&lt;br /&gt;
I am not against for introduction of inmemory tables - or more correctly for global temporary tables. Well implemented global temporary tables can be very nice and handy feature. But it is different feature, and should be designed separately. There can be some performance problems - plpgsql can use simple expression evaluation (when expression has not usage of any table). This optimization can be changed or enhanced, but its is change inside complex sensitive code, and it is not consistent. I very like a global temporary tables (that are not implemented in Postgres). Table (and related operations) should to looks like table, variable (and related operations) should to looks like variable. Common perspective how session variables should to looks is based on variables from PL environment. PostgreSQL internally supports &amp;lt;i&amp;gt;transition tables&amp;lt;/i&amp;gt;, and I can imagine to allow this kind of tables outside of triggers too. This is valid use case (and valid functionality), but it is something different than session variables (although there can be some overlap). The implementation of declared tables inside PL/pgSQL can be more easy from high design perspective (interaction between SQL tables and PL/pgSQL is well defined already), but difficult from low level perspective (where to store column statistics and other important data for optimization - same problems like with global temporary tables (without some fake proxy local temp tables)).&lt;br /&gt;
&lt;br /&gt;
   CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
   AS $$&lt;br /&gt;
   -- not implemented yet, not part of this proposal&lt;br /&gt;
   DECLARE t TABLE(a int, b int); -- this table is invisible outside fx&lt;br /&gt;
   BEGIN&lt;br /&gt;
     INSERT INTO t VALUES(10,20);&lt;br /&gt;
     INSERT INTO t VALUES(30,40);&lt;br /&gt;
     RAISE NOTICE &#039;%&#039;, (SELECT count(*) FROM t);&lt;br /&gt;
   END;&lt;br /&gt;
   $$ LANGUAGE plpgsql;&lt;br /&gt;
&lt;br /&gt;
Some data languages defined relational variables - common concept of session variables has zero intersection with this concept.&lt;br /&gt;
&lt;br /&gt;
== Variabes in PostgreSQL (as of v18) ==&lt;br /&gt;
&lt;br /&gt;
PostgreSQL provides relatively advanced client-side session variables in psql --- these variables use the `:` prefix. The implementation is pretty much straightforward: `psql`&#039;s parser reads tokens from input and, when it encounters a token related to a variable, replaces it with the variable&#039;s value. The syntax supports literal and identifier escaping, and variables can be used as arguments of the `\bind` command.&lt;br /&gt;
&lt;br /&gt;
`psql` variables are typeless client-side variables, available only inside `psql` or `pgbench`.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:40:18) postgres=# \set myvar ahoj&lt;br /&gt;
    (2025-09-25 09:40:33) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:14) postgres=# select :&#039;myvar&#039;;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:24) postgres=# select $1 \bind :myvar \g&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
The `\set` command allows only simple string concatenation, but it also supports execution of shell commands. When a query is executed with the `\gset` command, its result can be stored in a `psql` variable.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:41:40) postgres=# \set ctime `date`&lt;br /&gt;
    (2025-09-25 09:43:55) postgres=# \echo :ctime&lt;br /&gt;
    Thu Sep 25 09:43:55 CEST 2025&lt;br /&gt;
&lt;br /&gt;
The `\set` command is fairly limited, making complex operations unreadable or non-portable.&lt;br /&gt;
&lt;br /&gt;
This mechanism is good enough for writing tests, but it is not generally available (most users do not need it), and it is not accessible from the server side (e.g., from stored procedures). There is no simple way to access `psql` variables from an anonymous block --- a proxy custom GUC variable must be used:&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:58:38) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    (2025-09-25 09:58:43) postgres=# select set_config(&#039;myvars.myvar&#039;, :&#039;myvar&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ ahoj       │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 10:00:27) postgres=# do $$ begin raise notice &#039;%&#039;, current_setting(&#039;myvars.myvar&#039;); end $$;&lt;br /&gt;
    NOTICE:  ahoj&lt;br /&gt;
    DO&lt;br /&gt;
&lt;br /&gt;
PostgreSQL also has configuration variables (GUC - Grand Unified Configuration). These are primarily designed for PostgreSQL and extension configuration, but they can be used as session variables. GUC variables can be set with the `SET` command or the `set_config` function, and read with `SHOW` or `current_setting`.&lt;br /&gt;
&lt;br /&gt;
GUC variables are meant for holding configuration parameters. When created through the internal API, they can be restricted to superusers, hidden from regular users, and assigned specific types (boolean, memory, number, interval, string, enum) with validation. This type system is separate from the SQL type system and is initialized before PostgreSQL’s SQL types.&lt;br /&gt;
&lt;br /&gt;
Variables created from SQL must be &amp;quot;custom&amp;quot; variables, using a prefix in their name --- these are always strings. Such custom GUCs are often used as a workaround for missing server-side session variables in PostgreSQL, but they are typeless, unprotected, and unsafe.&lt;br /&gt;
&lt;br /&gt;
A notable feature of PostgreSQL GUCs (built-in or custom) is that they can have different scopes: transaction, session, or function execution. They can also be assigned default values at specific events --- configuration loading, role login, database login, or function start. These features are useful for configuration, but problematic when GUCs are repurposed as session variables.&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
    RETURNS void AS $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, current_setting(&#039;my.x&#039;);&lt;br /&gt;
    END $$ LANGUAGE plpgsql SET my.x = 20;&lt;br /&gt;
    CREATE FUNCTION&lt;br /&gt;
    (2025-09-27 06:10:37) postgres=# SET my.x = 0; SELECT fx();&lt;br /&gt;
    SET&lt;br /&gt;
    NOTICE:  20&lt;br /&gt;
    ┌────┐&lt;br /&gt;
    │ fx │&lt;br /&gt;
    ╞════╡&lt;br /&gt;
    │    │&lt;br /&gt;
    └────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    (2025-09-27 06:10:39) postgres=# SHOW my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 0    │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
GUC variables are transactional, and this behavior cannot be disabled.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-27 06:17:31) postgres=# set my.x = 10;&lt;br /&gt;
    SET&lt;br /&gt;
    (2025-09-27 06:19:42) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:19:46) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, true);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:19:55) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:01) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:20:11) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:13) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:20:38) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:45) postgres=# rollback;&lt;br /&gt;
    ROLLBACK&lt;br /&gt;
    (2025-09-27 06:20:52) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:55) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:22:36) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:22:39) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:22:42) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
As a result, there is no reliable way to store details such as error information in custom GUCs.&lt;br /&gt;
&lt;br /&gt;
== Session Variables in Other Systems ==&lt;br /&gt;
&lt;br /&gt;
This section surveys how session variables are implemented in several widely used database systems. Each subsection includes code examples to illustrate syntax, behaviour, and scope.&lt;br /&gt;
&lt;br /&gt;
=== MySQL ===&lt;br /&gt;
&lt;br /&gt;
Session variables in MySQL have syntax similar to the better-known T-SQL variables (though other details are MySQL-specific). MySQL distinguishes two kinds of variables:&lt;br /&gt;
&lt;br /&gt;
* system variables - referenced using the `@@` prefix (or `@@scope.name`)&lt;br /&gt;
* user-defined variables - referenced using the `@` prefix&lt;br /&gt;
&lt;br /&gt;
System variables are modified with the `SET` statement. The `SET` statement accepts the modifiers `GLOBAL`, `SESSION`, or `PERSIST`. Some variables can only be set using the `GLOBAL` or `SESSION` modifier; when the variable name is specified with the `@@` prefix, it is not necessary to explicitly indicate the scope. System variables are defined by MySQL or by MySQL plugins.&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL some_config = 100;&lt;br /&gt;
    SET @@same_config = 100;&lt;br /&gt;
    &lt;br /&gt;
    SET SESSION sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@SESSION.sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
&lt;br /&gt;
Persistent values are stored in `mysqld-auto.cnf` (in the server data directory).&lt;br /&gt;
&lt;br /&gt;
Resetting system variables can be done by assigning DEFAULT or by copying from the GLOBAL namespace:&lt;br /&gt;
&lt;br /&gt;
    SET @@sql_mode = DEFAULT;&lt;br /&gt;
    SET @@sql_mode = @@GLOBAL.sql_mode&lt;br /&gt;
&lt;br /&gt;
PostgreSQL equivalents:&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL ; -- there is not similar command in Postgres&lt;br /&gt;
    SET SESSION ; -- SET&lt;br /&gt;
    SET PERSIST ; -- ALTER SYSTEM; SELECT pg_reload_conf();&lt;br /&gt;
    SELECT @@var; -- SELECT current_setting(&#039;var);&lt;br /&gt;
&lt;br /&gt;
User-defined variables are created by assignment:&lt;br /&gt;
&lt;br /&gt;
    SET @var = expr [, @var2 = expr [ ... ]];&lt;br /&gt;
&lt;br /&gt;
User-defined variables can only be of type integer, decimal, float, binary, non-binary string, or NULL. Casting between these types is implicit and hidden. User-defined variables cannot be used as table or column names.&lt;br /&gt;
&lt;br /&gt;
An advantage of the T-SQL-style syntax (with special prefixes) is that it provides a simple solution for avoiding identifier collisions. MySQL variables are convenient, and the user experience can be good for people already familiar with T-SQL (MSSQL or Sybase). On the other hand, prefix-based syntax can be confusing for users coming from systems other than MSSQL. The type system is perhaps too simple and can be unsafe. In general, the performance of MySQL (or MariaDB) stored procedures is not good, and stored procedures are therefore not frequently used. There is no protection against typographical errors. Unassigned user variables can be used without error (they default to NULL), which makes static checking of stored procedures impossible in this area.&lt;br /&gt;
&lt;br /&gt;
There is no way to restrict access to user-defined variables in MySQL. They cannot be protected against unintended usage. The only possible safeguard is a naming convention, since all user-defined variables share a single namespace.&lt;br /&gt;
&lt;br /&gt;
=== Oracle ===&lt;br /&gt;
&lt;br /&gt;
Oracle PL/SQL allows the use of package variables. PL/SQL is based on the Ada language, and package variables act as &amp;quot;global&amp;quot; variables. They are not directly visible from SQL, but Oracle provides a reduced syntax for functions without arguments, so you typically write a wrapper:&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE PACKAGE my_package&lt;br /&gt;
    AS&lt;br /&gt;
      FUNCTION get_a RETURN NUMBER;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    /&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE PACKAGE BODY my_package&lt;br /&gt;
    AS&lt;br /&gt;
      a  NUMBER(20);&lt;br /&gt;
&lt;br /&gt;
      FUNCTION get_a&lt;br /&gt;
      RETURN NUMBER&lt;br /&gt;
      IS&lt;br /&gt;
      BEGIN&lt;br /&gt;
        RETURN a;&lt;br /&gt;
      END get_a;&lt;br /&gt;
    END my_package;&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
Inside SQL, SQL identifiers take precedence. Inside non-SQL commands (such as CALL or PL/SQL blocks), package identifiers take precedence.&lt;br /&gt;
&lt;br /&gt;
Oracle allows both syntaxes for calling a function with zero arguments:&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a() FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
This reduces the risk of naming collisions. Package variables persist for the duration of a session.&lt;br /&gt;
&lt;br /&gt;
Another possibility is to use variables in SQL*Plus (similar to `psql` variables, but with the ability to define the type on the server side).&lt;br /&gt;
&lt;br /&gt;
A variable is declared with the `VARIABLE` command and can be accessed in the session using the `:varname` syntax (a prior declaration step may be optional):&lt;br /&gt;
&lt;br /&gt;
    VARIABLE bv_variable_name VARCHAR2(30)&lt;br /&gt;
    BEGIN&lt;br /&gt;
      :bv_variable_name := &#039;Some Value&#039;;&lt;br /&gt;
    END;&lt;br /&gt;
    &lt;br /&gt;
    SELECT column_name&lt;br /&gt;
      FROM   table_name&lt;br /&gt;
     WHERE  column_name = :bv_variable_name;&lt;br /&gt;
&lt;br /&gt;
=== DB2 ===&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;user-defined global variables&amp;quot; in DB2 are similar to the proposed PostgreSQL feature. The main difference is in the access rights: DB2 uses `READ` and `WRITE`, while PostgreSQL uses `SELECT` and `UPDATE`. Because PostgreSQL already uses the `SET` command for GUCs, the `LET` command was introduced in the proposal (DB2 uses `SET`).&lt;br /&gt;
&lt;br /&gt;
Variables are visible in all sessions, but their values are private to each session. Variables are not transactional. Their usage is broader than in the proposal: they can be changed by `SET`, `SELECT INTO`, or used as OUT parameters of procedures. The search path (or a similar mechanism) applies to variables as well, but variables have lower priority than tables or columns.&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE myCounter INT DEFAULT 01;&lt;br /&gt;
    SELECT EMPNO, LASTNAME, CASE WHEN myCounter = 1 THEN SALARY ELSE NULL END&lt;br /&gt;
    FROM EMPLOYEE WHERE WORKDEPT = &#039;A00&#039;;&lt;br /&gt;
    SET myCounter = 29;&lt;br /&gt;
&lt;br /&gt;
There also appear to be other kinds of variables, accessed using the function `GETVARIABLE(&#039;name&#039;, &#039;default&#039;)`. These are very similar to PostgreSQL GUCs and the `current_setting` function. Such variables can be set via the connection string, are of type `VARCHAR`, and up to 10 values are allowed. Built-in session variables (configuration) can also be accessed using `GETVARIABLE`.&lt;br /&gt;
&lt;br /&gt;
=== MSSQL (T-SQL) ===&lt;br /&gt;
&lt;br /&gt;
MSSQL provides two types of variables:&lt;br /&gt;
&lt;br /&gt;
* Global variables — accessed with the `@@varname` syntax. These are similar to GUCs and provide state information such as `@@ERROR`, `@@ROWCOUNT`, or `@@IDENTITY`. &lt;br /&gt;
* Local variables** — accessed with the `@varname` syntax. These must be declared with the `DECLARE` command before use. Their scope is limited to the batch, procedure, or function where they are executed.&lt;br /&gt;
&lt;br /&gt;
    DECLARE @TestVariable AS VARCHAR(100)&lt;br /&gt;
    SET @TestVariable = &#039;Think Green&#039;&lt;br /&gt;
    GO&lt;br /&gt;
    PRINT @TestVariable&lt;br /&gt;
&lt;br /&gt;
This script fails because `PRINT` is executed in a different batch. Therefore, it seems MSSQL does not truly support session variables.&lt;br /&gt;
&lt;br /&gt;
There are also mechanisms similar to PostgreSQL&#039;s custom GUCs and the use of `current_setting` and `set_config` functions. In general, MSSQL is fairly primitive in this area.&lt;br /&gt;
&lt;br /&gt;
    EXEC sp_set_session_context &#039;user_id&#039;, 4;&lt;br /&gt;
    SELECT SESSION_CONTEXT(N&#039;user_id&#039;);&lt;br /&gt;
&lt;br /&gt;
== ToDo ==&lt;br /&gt;
&lt;br /&gt;
* SECURITY LABEL support — enhance the `dummy_seclabel` module and the `sepgsql` extension. Update PostgreSQL documentation and ensure `SecLabelSupportsObjectType` includes session variables.&lt;/div&gt;</summary>
		<author><name>Okbobcz</name></author>
	</entry>
	<entry>
		<id>https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41833</id>
		<title>Implementation of declarative catalog session variables</title>
		<link rel="alternate" type="text/html" href="https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41833"/>
		<updated>2025-09-29T03:56:03Z</updated>

		<summary type="html">&lt;p&gt;Okbobcz: Undo revision 41829 by Jimus (talk)&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
&lt;br /&gt;
Session variables are database objects that can hold a value across multiple queries inside a session. The design of session variables varies widely, and many databases implement more than one type of session variable.&lt;br /&gt;
&lt;br /&gt;
The SQL/PSM standard introduces modules and module-level variables. However, this part of the standard has not been implemented in any widely used product. As a result, each database system has developed its own proprietary design, syntax, and feature set.&lt;br /&gt;
&lt;br /&gt;
Session variables are often part of the stored procedure environment, but there are exceptions --- for example, MySQL session variables or client-side session variables in tools like psql. In most systems, session variables do not participate in transactions: once set, their value persists until explicitly changed, even if the surrounding transaction is rolled back. Conceptually, they behave much like variables in general-purpose programming languages.&lt;br /&gt;
&lt;br /&gt;
Because there is no common design, comparisons across systems are difficult. Each implementation has its own advantages and disadvantages, and in some cases a single system supports more than one kind of session variable.&lt;br /&gt;
&lt;br /&gt;
== SQL/PSM ==&lt;br /&gt;
&lt;br /&gt;
The SQL standard introduces the concept of modules, which can be associated with schemas. Variables in modules behave like PL/pgSQL variables but are only local. The only objects allowed at the module level are temporary tables, which can be accessed only from routines assigned to that module. Modules are essentially declarative versions of extensions (if I understand correctly; I did not find an implementation). They allow you to override the search path for routines assigned to the module. Variables are not transactional. The precedence between variables and columns is not specified.&lt;br /&gt;
&lt;br /&gt;
== Implementation Challenges ==&lt;br /&gt;
&lt;br /&gt;
* Possible collisions between session variables and other database objects.&lt;br /&gt;
* Memory cleanup after dropping a session variable, especially when DDL operations can be reverted.&lt;br /&gt;
* Memory invalidation: if a variable is dropped by one session, other sessions should not continue using it.&lt;br /&gt;
&lt;br /&gt;
== Proposed Implementations ==&lt;br /&gt;
&lt;br /&gt;
===  Catalog Schema Variables ===&lt;br /&gt;
&lt;br /&gt;
== Variabes in PostgreSQL (as of v18) ==&lt;br /&gt;
&lt;br /&gt;
PostgreSQL provides relatively advanced client-side session variables in psql --- these variables use the `:` prefix. The implementation is pretty much straightforward: `psql`&#039;s parser reads tokens from input and, when it encounters a token related to a variable, replaces it with the variable&#039;s value. The syntax supports literal and identifier escaping, and variables can be used as arguments of the `\bind` command.&lt;br /&gt;
&lt;br /&gt;
`psql` variables are typeless client-side variables, available only inside `psql` or `pgbench`.&lt;br /&gt;
&lt;br /&gt;
   (2025-09-25 09:40:18) postgres=# \set myvar ahoj&lt;br /&gt;
   (2025-09-25 09:40:33) postgres=# \echo :myvar&lt;br /&gt;
   ahoj&lt;br /&gt;
&lt;br /&gt;
   (2025-09-25 09:41:14) postgres=# select :&#039;myvar&#039;;&lt;br /&gt;
   ┌──────────┐&lt;br /&gt;
   │ ?column? │&lt;br /&gt;
   ╞══════════╡&lt;br /&gt;
   │ ahoj     │&lt;br /&gt;
   └──────────┘&lt;br /&gt;
   (1 row)&lt;br /&gt;
&lt;br /&gt;
   (2025-09-25 09:41:24) postgres=# select $1 \bind :myvar \g&lt;br /&gt;
   ┌──────────┐&lt;br /&gt;
   │ ?column? │&lt;br /&gt;
   ╞══════════╡&lt;br /&gt;
   │ ahoj     │&lt;br /&gt;
   └──────────┘&lt;br /&gt;
   (1 row)&lt;br /&gt;
&lt;br /&gt;
The `\set` command allows only simple string concatenation, but it also supports execution of shell commands. When a query is executed with the `\gset` command, its result can be stored in a `psql` variable.&lt;br /&gt;
&lt;br /&gt;
   (2025-09-25 09:41:40) postgres=# \set ctime `date`&lt;br /&gt;
   (2025-09-25 09:43:55) postgres=# \echo :ctime&lt;br /&gt;
   čt 25. září 2025, 09:43:55 CEST&lt;br /&gt;
&lt;br /&gt;
The `\set` command is fairly limited, making complex operations unreadable or non-portable.&lt;br /&gt;
&lt;br /&gt;
This mechanism is good enough for writing tests, but it is not generally available (most users do not need it), and it is not accessible from the server side (e.g., from stored procedures). There is no simple way to access `psql` variables from an anonymous block --- a proxy custom GUC variable must be used:&lt;br /&gt;
&lt;br /&gt;
   (2025-09-25 09:58:38) postgres=# \echo :myvar&lt;br /&gt;
   ahoj&lt;br /&gt;
   (2025-09-25 09:58:43) postgres=# select set_config(&#039;myvars.myvar&#039;,&lt;br /&gt;
:&#039;myvar&#039;, false);&lt;br /&gt;
   ┌────────────┐&lt;br /&gt;
   │ set_config │&lt;br /&gt;
   ╞════════════╡&lt;br /&gt;
   │ ahoj       │&lt;br /&gt;
   └────────────┘&lt;br /&gt;
   (1 row)&lt;br /&gt;
&lt;br /&gt;
   (2025-09-25 10:00:27) postgres=# do $$ begin raise notice &#039;%&#039;, current_setting(&#039;myvars.myvar&#039;); end $$;&lt;br /&gt;
   NOTICE:  ahoj&lt;br /&gt;
   DO&lt;br /&gt;
&lt;br /&gt;
PostgreSQL also has configuration variables (GUC - Grand Unified Configuration). These are primarily designed for PostgreSQL and&lt;br /&gt;
extension configuration, but they can be used as session variables. GUC variables can be set with the `SET` command or the `set_config` function, and read with `SHOW` or `current_setting`.&lt;br /&gt;
&lt;br /&gt;
GUC variables are meant for holding configuration parameters. When created through the internal API, they can be restricted to superusers, hidden from regular users, and assigned specific types (boolean, memory, number, interval, string, enum) with validation. This type system is separate from the SQL type system and is initialized before PostgreSQL’s SQL types.&lt;br /&gt;
&lt;br /&gt;
Variables created from SQL must be &amp;quot;custom&amp;quot; variables, using a prefix in their name --- these are always strings. Such custom GUCs are often used as a workaround for missing server-side session variables in PostgreSQL, but they are typeless, unprotected, and unsafe.&lt;br /&gt;
&lt;br /&gt;
A notable feature of PostgreSQL GUCs (built-in or custom) is that they can have different scopes: transaction, session, or function execution. They can also be assigned default values at specific events --- configuration loading, role login, database login, or function start. These features are useful for configuration, but problematic when GUCs are repurposed as session variables.&lt;br /&gt;
&lt;br /&gt;
   CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
   RETURNS void AS $$&lt;br /&gt;
   BEGIN&lt;br /&gt;
     RAISE NOTICE &#039;%&#039;, current_setting(&#039;my.x&#039;);&lt;br /&gt;
   END $$ LANGUAGE plpgsql SET my.x = 20;&lt;br /&gt;
   CREATE FUNCTION&lt;br /&gt;
   (2025-09-27 06:10:37) postgres=# SET my.x = 0; SELECT fx();&lt;br /&gt;
   SET&lt;br /&gt;
   NOTICE:  20&lt;br /&gt;
   ┌────┐&lt;br /&gt;
   │ fx │&lt;br /&gt;
   ╞════╡&lt;br /&gt;
   │    │&lt;br /&gt;
   └────┘&lt;br /&gt;
   (1 row)&lt;br /&gt;
   (2025-09-27 06:10:39) postgres=# SHOW my.x;&lt;br /&gt;
   ┌──────┐&lt;br /&gt;
   │ my.x │&lt;br /&gt;
   ╞══════╡&lt;br /&gt;
   │ 0    │&lt;br /&gt;
   └──────┘&lt;br /&gt;
   (1 row)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
GUC variables are transactional, and this behavior cannot be disabled.&lt;br /&gt;
&lt;br /&gt;
   (2025-09-27 06:17:31) postgres=# set my.x = 10;&lt;br /&gt;
   SET&lt;br /&gt;
   (2025-09-27 06:19:42) postgres=# begin;&lt;br /&gt;
   BEGIN&lt;br /&gt;
   (2025-09-27 06:19:46) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, true);&lt;br /&gt;
   ┌────────────┐&lt;br /&gt;
   │ set_config │&lt;br /&gt;
   ╞════════════╡&lt;br /&gt;
   │ 20         │&lt;br /&gt;
   └────────────┘&lt;br /&gt;
   (1 row)&lt;br /&gt;
&lt;br /&gt;
   (2025-09-27 06:19:55) postgres=# show my.x;&lt;br /&gt;
   ┌──────┐&lt;br /&gt;
   │ my.x │&lt;br /&gt;
   ╞══════╡&lt;br /&gt;
   │ 20   │&lt;br /&gt;
   └──────┘&lt;br /&gt;
   (1 row)&lt;br /&gt;
&lt;br /&gt;
   (2025-09-27 06:20:01) postgres=# commit;&lt;br /&gt;
   COMMIT&lt;br /&gt;
   (2025-09-27 06:20:11) postgres=# show my.x;&lt;br /&gt;
   ┌──────┐&lt;br /&gt;
   │ my.x │&lt;br /&gt;
   ╞══════╡&lt;br /&gt;
   │ 10   │&lt;br /&gt;
   └──────┘&lt;br /&gt;
   (1 row)&lt;br /&gt;
&lt;br /&gt;
   (2025-09-27 06:20:13) postgres=# begin;&lt;br /&gt;
   BEGIN&lt;br /&gt;
   (2025-09-27 06:20:38) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
   ┌────────────┐&lt;br /&gt;
   │ set_config │&lt;br /&gt;
   ╞════════════╡&lt;br /&gt;
   │ 20         │&lt;br /&gt;
   └────────────┘&lt;br /&gt;
   (1 row)&lt;br /&gt;
&lt;br /&gt;
   (2025-09-27 06:20:45) postgres=# rollback;&lt;br /&gt;
   ROLLBACK&lt;br /&gt;
   (2025-09-27 06:20:52) postgres=# show my.x;&lt;br /&gt;
   ┌──────┐&lt;br /&gt;
   │ my.x │&lt;br /&gt;
   ╞══════╡&lt;br /&gt;
   │ 10   │&lt;br /&gt;
   └──────┘&lt;br /&gt;
   (1 row)&lt;br /&gt;
&lt;br /&gt;
   (2025-09-27 06:20:55) postgres=# begin;&lt;br /&gt;
   BEGIN&lt;br /&gt;
   (2025-09-27 06:22:36) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
   ┌────────────┐&lt;br /&gt;
   │ set_config │&lt;br /&gt;
   ╞════════════╡&lt;br /&gt;
   │ 20         │&lt;br /&gt;
   └────────────┘&lt;br /&gt;
   (1 row)&lt;br /&gt;
&lt;br /&gt;
   (2025-09-27 06:22:39) postgres=# commit;&lt;br /&gt;
   COMMIT&lt;br /&gt;
   (2025-09-27 06:22:42) postgres=# show my.x;&lt;br /&gt;
   ┌──────┐&lt;br /&gt;
   │ my.x │&lt;br /&gt;
   ╞══════╡&lt;br /&gt;
   │ 20   │&lt;br /&gt;
   └──────┘&lt;br /&gt;
   (1 row)&lt;br /&gt;
&lt;br /&gt;
As a result, there is no reliable way to store details such as error information in custom GUCs.&lt;br /&gt;
&lt;br /&gt;
== Session Variables in Other Systems ==&lt;br /&gt;
&lt;br /&gt;
This section surveys how session variables are implemented in several widely used database systems. Each subsection includes code examples to illustrate syntax, behavior, and scope.&lt;br /&gt;
&lt;br /&gt;
=== MySQL ===&lt;br /&gt;
&lt;br /&gt;
Session variables in MySQL have syntax similar to the better-known T-SQL variables (though other details are MySQL-specific). MySQL distinguishes two kinds of variables:&lt;br /&gt;
&lt;br /&gt;
    system variables - referenced using the `@@` prefix (or `@@scope.name`)&lt;br /&gt;
    user-defined variables - referenced using the `@` prefix&lt;br /&gt;
&lt;br /&gt;
System variables are modified with the `SET` statement. The `SET` statement accepts the modifiers `GLOBAL`, `SESSION`, or `PERSIST`. Some variables can only be set using the `GLOBAL` or `SESSION` modifier; when the variable name is specified with the `@@` prefix, it is not necessary to explicitly indicate the scope. System variables are defined by MySQL or by MySQL plugins.&lt;br /&gt;
&lt;br /&gt;
   SET GLOBAL some_config = 100;&lt;br /&gt;
   SET @@same_config = 100;&lt;br /&gt;
&lt;br /&gt;
   SET SESSION sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
   SET @@sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
   SET @@SESSION.sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
   SET sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
&lt;br /&gt;
Persistent values are stored in `mysqld-auto.cnf` (in the server data directory).&lt;br /&gt;
&lt;br /&gt;
Resetting system variables can be done by assigning DEFAULT or by copying from the GLOBAL namespace:&lt;br /&gt;
&lt;br /&gt;
   SET @@sql_mode = DEFAULT;&lt;br /&gt;
   SET @@sql_mode = @@GLOBAL.sql_mode&lt;br /&gt;
&lt;br /&gt;
PostgreSQL equivalents:&lt;br /&gt;
&lt;br /&gt;
   SET GLOBAL ; -- there is not similar command in Postgres&lt;br /&gt;
   SET SESSION ; -- SET&lt;br /&gt;
   SET PERSIST ; -- ALTER SYSTEM; SELECT pg_reload_conf();&lt;br /&gt;
   SELECT @@var; -- SELECT current_setting(&#039;var);&lt;br /&gt;
&lt;br /&gt;
User-defined variables are created by assignment:&lt;br /&gt;
&lt;br /&gt;
   SET @var = expr [, @var2 = expr [ ... ]];&lt;br /&gt;
&lt;br /&gt;
User-defined variables can only be of type integer, decimal, float, binary, non-binary string, or NULL. Casting between these types is implicit and hidden. User-defined variables cannot be used as table or column names.&lt;br /&gt;
&lt;br /&gt;
An advantage of the T-SQL-style syntax (with special prefixes) is that it provides a simple solution for avoiding identifier collisions. MySQL variables are convenient, and the user experience can be good for people already familiar with T-SQL (MSSQL or Sybase). On the other hand, prefix-based syntax can be confusing for users coming from systems other than MSSQL. The type system is perhaps too simple and can be unsafe. In general, the performance of MySQL (or MariaDB) stored procedures is not good, and stored procedures are therefore not frequently used. There is no protection against typographical errors. Unassigned user variables can be used without error (they default to NULL), which makes static checking of stored procedures impossible in this area.&lt;br /&gt;
&lt;br /&gt;
There is no way to restrict access to user-defined variables in MySQL. They cannot be protected against unintended usage. The only possible safeguard is a naming convention, since all user-defined variables share a single namespace.&lt;br /&gt;
&lt;br /&gt;
=== Oracle ===&lt;br /&gt;
&lt;br /&gt;
Oracle PL/SQL allows the use of package variables. PL/SQL is based on the Ada language, and package variables act as &amp;quot;global&amp;quot; variables. They are not directly visible from SQL, but Oracle provides a reduced syntax for functions without arguments, so you typically write a wrapper:&lt;br /&gt;
&lt;br /&gt;
   CREATE OR REPLACE PACKAGE my_package&lt;br /&gt;
   AS&lt;br /&gt;
     FUNCTION get_a RETURN NUMBER;&lt;br /&gt;
   END my_package;&lt;br /&gt;
   /&lt;br /&gt;
&lt;br /&gt;
   CREATE OR REPLACE PACKAGE BODY my_package&lt;br /&gt;
   AS&lt;br /&gt;
     a  NUMBER(20);&lt;br /&gt;
&lt;br /&gt;
     FUNCTION get_a&lt;br /&gt;
     RETURN NUMBER&lt;br /&gt;
     IS&lt;br /&gt;
     BEGIN&lt;br /&gt;
       RETURN a;&lt;br /&gt;
     END get_a;&lt;br /&gt;
   END my_package;&lt;br /&gt;
&lt;br /&gt;
   SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
Inside SQL, SQL identifiers take precedence. Inside non-SQL commands (such as CALL or PL/SQL blocks), package identifiers take precedence.&lt;br /&gt;
&lt;br /&gt;
Oracle allows both syntaxes for calling a function with zero arguments:&lt;br /&gt;
&lt;br /&gt;
   SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
   SELECT my_package.get_a() FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
This reduces the risk of naming collisions. Package variables persist for the duration of a session.&lt;br /&gt;
&lt;br /&gt;
Another possibility is to use variables in SQL*Plus (similar to `psql` variables, but with the ability to define the type on the server side).&lt;br /&gt;
&lt;br /&gt;
A variable is declared with the `VARIABLE` command and can be accessed in the session using the `:varname` syntax (a prior declaration step may be optional):&lt;br /&gt;
&lt;br /&gt;
   VARIABLE bv_variable_name VARCHAR2(30)&lt;br /&gt;
   BEGIN&lt;br /&gt;
     :bv_variable_name := &#039;Some Value&#039;;&lt;br /&gt;
   END;&lt;br /&gt;
&lt;br /&gt;
   SELECT column_name&lt;br /&gt;
     FROM   table_name&lt;br /&gt;
    WHERE  column_name = :bv_variable_name;&lt;br /&gt;
&lt;br /&gt;
=== DB2 ===&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;user-defined global variables&amp;quot; in DB2 are similar to the proposed PostgreSQL feature. The main difference is in the access rights: DB2 uses `READ` and `WRITE`, while PostgreSQL uses `SELECT` and `UPDATE`. Because PostgreSQL already uses the `SET` command for GUCs, the `LET` command was introduced in the proposal (DB2 uses `SET`).&lt;br /&gt;
&lt;br /&gt;
Variables are visible in all sessions, but their values are private to each session. Variables are not transactional. Their usage is broader than in the proposal: they can be changed by `SET`, `SELECT INTO`, or used as OUT parameters of procedures. The search path (or a similar mechanism) applies to variables as well, but variables have lower priority than tables or columns.&lt;br /&gt;
&lt;br /&gt;
   CREATE VARIABLE myCounter INT DEFAULT 01;&lt;br /&gt;
   SELECT EMPNO, LASTNAME, CASE WHEN myCounter = 1 THEN SALARY ELSE NULL END&lt;br /&gt;
   FROM EMPLOYEE WHERE WORKDEPT = &#039;A00&#039;;&lt;br /&gt;
   SET myCounter = 29;&lt;br /&gt;
&lt;br /&gt;
There also appear to be other kinds of variables, accessed using the function `GETVARIABLE(&#039;name&#039;, &#039;default&#039;)`. These are very similar to PostgreSQL GUCs and the `current_setting` function. Such variables can be set via the connection string, are of type `VARCHAR`, and up to 10 values are allowed. Built-in session variables (configuration) can also be accessed using `GETVARIABLE`.&lt;br /&gt;
&lt;br /&gt;
=== MSSQL (T-SQL) ===&lt;br /&gt;
&lt;br /&gt;
MSSQL provides two types of variables:&lt;br /&gt;
&lt;br /&gt;
* Global variables — accessed with the `@@varname` syntax. These are similar to GUCs and provide state information such as `@@ERROR`, `@@ROWCOUNT`, or `@@IDENTITY`. &lt;br /&gt;
* Local variables** — accessed with the `@varname` syntax. These must be declared with the `DECLARE` command before use. Their scope is limited to the batch, procedure, or function where they are executed.&lt;br /&gt;
&lt;br /&gt;
   DECLARE @TestVariable AS VARCHAR(100)&lt;br /&gt;
   SET @TestVariable = &#039;Think Green&#039;&lt;br /&gt;
   GO&lt;br /&gt;
   PRINT @TestVariable&lt;br /&gt;
&lt;br /&gt;
This script fails because `PRINT` is executed in a different batch. Therefore, it seems MSSQL does not truly support session variables.&lt;br /&gt;
&lt;br /&gt;
There are also mechanisms similar to PostgreSQL&#039;s custom GUCs and the use of `current_setting` and `set_config` functions. In general, MSSQL is fairly primitive in this area.&lt;br /&gt;
&lt;br /&gt;
   EXEC sp_set_session_context &#039;user_id&#039;, 4;&lt;br /&gt;
   SELECT SESSION_CONTEXT(N&#039;user_id&#039;);&lt;br /&gt;
&lt;br /&gt;
== ToDo ==&lt;br /&gt;
&lt;br /&gt;
* SECURITY LABEL support — enhance the `dummy_seclabel` module and the `sepgsql` extension. Update PostgreSQL documentation and ensure `SecLabelSupportsObjectType` includes session variables.&lt;br /&gt;
&lt;br /&gt;
= Types of different session variables =&lt;br /&gt;
* client side (psql, pgadmin, ...) - mostly similar to shell variables (based on string operations)&lt;br /&gt;
* server side&lt;br /&gt;
** declarative&lt;br /&gt;
*** created only for batch (T-SQL)&lt;br /&gt;
*** created as an database object (or part of database object) (Oracle, DB2)&lt;br /&gt;
** created by usage (MySQL)&lt;br /&gt;
** with special syntax (MySQL, T-SQL)&lt;br /&gt;
** with ANSI SQL syntax for identifiers (Oracle, DB2)&lt;br /&gt;
&lt;br /&gt;
= Implementation of session variables in Postgres =&lt;br /&gt;
&lt;br /&gt;
== Possible use cases ==&lt;br /&gt;
* usage of session variables as global variables in PL&lt;br /&gt;
* holding configuration (for PL)&lt;br /&gt;
* holding some more used data in interactive session&lt;br /&gt;
* holding credentials for some RLS and data securing&lt;br /&gt;
* helping with migration from others systems (mainly from Oracle)&lt;br /&gt;
* allow anonymous block parametrization (only postgres)&lt;br /&gt;
&lt;br /&gt;
It is clean so no one design is perfect for all uses cases. MySQL is good enough and maybe handy for interactive work,&lt;br /&gt;
but cannot be used for storing any sensitive data, and it is unsafe. PostgreSQL GUC are perfect for configuration, but&lt;br /&gt;
are not friendly for interactive work, and are very limited for usage in PL as global variables. So no one design is&lt;br /&gt;
perfect, and I can imagine more different designs in one system, that can support some different use cases better.&lt;br /&gt;
&lt;br /&gt;
== Proposal for Postgres - design session variables as database global temporary typed objects with ANSI SQL syntax for identifiers ==&lt;br /&gt;
&lt;br /&gt;
I searched solution that can work with specific Postgres environment:&lt;br /&gt;
&lt;br /&gt;
* Postgres has more than one widely used language for stored procedures PL/pgSQL + PL/Python or PL/Perl&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL is strongly reduced PL/SQL, and PL/SQL is modified ADA language. I am pretty happy with the current scope of PL/SQL - it is domain specific language and it works well. It is a very simple language, and it is easy to learn it. PL/pgSQL has no packages or modules. Instead PostgreSQL schemas are used. But Postgres schemas are specific database objects. Schemas are independent on PL. &lt;br /&gt;
&lt;br /&gt;
So I wanted design that can supports more PL languages, didn&#039;t introduce new complexity to relative simple PL/pgSQL and didn&#039;t introduce functionality that can be redundant with already existing objects.&lt;br /&gt;
I wrote bigger application in PL/pgSQL. For maintaining of this application I wrote plpgsql_lint (later plpgsql_check). So every time I am searching solution that doesn&#039;t block code static analyse. Static analyse requires persistent objects, and then it is direct way to design session variables as database objects (with own system catalog). When session variables are designed as database objects, then using ACL is natural. Oracle package variables are well protected just by package scope. Unfortunately, postgres doesn&#039;t know schema scope. The locality of schema objects is defined by setting of SEARCH_PATH guc, and introduction of new mechanism can be messy. But with ACL the content of variables can be safe too:&lt;br /&gt;
&lt;br /&gt;
    CREATE TEMP VARIABLE x AS int;&lt;br /&gt;
     &lt;br /&gt;
    LET x = 10;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(x);&lt;br /&gt;
    END;&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    SELECT VARIABLE(x);&lt;br /&gt;
&lt;br /&gt;
== Implementation issues ==&lt;br /&gt;
=== Collisions between columns and variables identifiers ===&lt;br /&gt;
There is a new possibility of identifiers collisions between variables and columns identifiers. With ANSI SQL syntax for session variables identifiers there is a problem how to distinct between variable and column. This is known problem in PL/pgSQL or PL/SQL and can be solved by some prioritization or by prohibition of collisions. The prohibition of collisions is solution that almost cleaned this issue from PL/pgSQL. Unfortunately session variables are global objects and then the collision can be in any query, and then the new badly named variable can stop execution of any query. Prioritization variables or columns has own problems - quiet unwanted using variable or column.&lt;br /&gt;
&lt;br /&gt;
    CREATE TABLE tab(a int);&lt;br /&gt;
    CREATE VARIABLE a AS int;&lt;br /&gt;
    &lt;br /&gt;
    SELECT a FROM tab; -- with disallowed collisions, the execution of this query stops when there is a variable named &amp;quot;a&amp;quot;&lt;br /&gt;
    &lt;br /&gt;
    LET a = 1;&lt;br /&gt;
    DELETE FROM tab WHERE a = 1; -- with higher priority for variables all table can be deleted (nobody proposed higher priority for variables).&lt;br /&gt;
    SELECT a FROM tab; -- with higher priority for columns, the variable is not used (quietly)&lt;br /&gt;
&lt;br /&gt;
The prohibition of collision is not absolute protection against possible human errors:&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE _id AS int;&lt;br /&gt;
    LET _id = 10;&lt;br /&gt;
    CREATE TABLE foo(id int);&lt;br /&gt;
    DELETE FROM foo WHERE _id = 10; -- just type, but can have unwanted impact (all rows removed)&lt;br /&gt;
&lt;br /&gt;
These problems are not new. The developers in PL/pgSQL or PL/SQL knows it, but the scope of these risks are limited only to stored procedures. After very very long discussion and lot of designs I introduced variable fences (syntax `VARIABLE(varname)`). Inside of any query the variable can be used only in variable fence. This is 100% solution of collisions, and I think it can be good solution against human errors and unwanted usage of session variable. Now, variable fences are necessary every where inside a query or expressions, but I thinking about possibility to be optional inside PL/pgSQL (far future). This can reduce overhead with porting applications from Oracle, and generally the developers of stored procedures are experienced with described risks (this can be controlled by new `plpgsql.extra_checks`).&lt;br /&gt;
&lt;br /&gt;
A variable fence can be in collisions with custom function &amp;quot;variable&amp;quot;. There is similarity with usage of keywords `cube`. This can be fixed by usage prefix schema or by usage of parenthesis:&lt;br /&gt;
&lt;br /&gt;
    SELECT public.variable(...)&lt;br /&gt;
    SELECT &amp;quot;variable&amp;quot;(...)&lt;br /&gt;
&lt;br /&gt;
Why I don&#039;t propose usage of T-SQL (MSSQL, MySQL) syntax for session variables? There is more reasons (one is objective, last is subjective):&lt;br /&gt;
&lt;br /&gt;
* There is a collision with custom operators - operators `@` or `@@` are already used - usage of `@@` is common&lt;br /&gt;
&lt;br /&gt;
    (2025-09-28 06:57:30) postgres=# create table tab(a int);&lt;br /&gt;
    CREATE TABLE&lt;br /&gt;
    (2025-09-28 06:57:42) postgres=# insert into tab values(10);&lt;br /&gt;
    INSERT 0 1&lt;br /&gt;
    (2025-09-28 06:57:50) postgres=# select @a from tab;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │       10 │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
We can safely use only `:` prefix, but then we can break a psql variables. Similarly we can use `$` prefix, but then we can break query parameters syntax.&lt;br /&gt;
&lt;br /&gt;
* T-SQL variable names looks weird in Postgres (in queries, in PL/pgSQL). Postgres knows only ANSI/SQL identifiers, PL/pgSQL shares these rules too, and this are the PostgreSQL is consistent, and I missing motivation to introduce something new (with the knowledge so problems with possible compatibility breaks should be solved too). &lt;br /&gt;
&lt;br /&gt;
Note: There was a proposal to use table syntax as variable (like table (in memory) variables from T-SQL but only single row). Although it is very effective solution of collisions, I dislike this idea. It increase a complexity of simple expression (or increase inconsistency of some syntax rules). More looks weird for scalar variables (and can be handy for composite variables):&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(var); -- variable is not a table&lt;br /&gt;
    END&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      -- variable a a table (I don&#039;t like it)&lt;br /&gt;
      -- can we use DML? How much rows this kind of variable can hold&lt;br /&gt;
      -- value is row or not?&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, (SELECT var FROM var);&lt;br /&gt;
    END&lt;br /&gt;
    $$&lt;br /&gt;
&lt;br /&gt;
I am not against for introduction of inmemory tables - or more correctly for global temporary tables. Well implemented global temporary tables can be very nice and handy feature. But it is different feature, and should be designed separately. There can be some performance problems - plpgsql can use simple expression evaluation (when expression has not usage of any table). This optimization can be changed or enhanced, but its is change inside complex sensitive code, and it is not consistent. I very like a global temporary tables (that are not implemented in Postgres). Table (and related operations) should to looks like table, variable (and related operations) should to looks like variable. Common perspective how session variables should to looks is based on variables from PL environment. PostgreSQL internally supports &amp;lt;i&amp;gt;transition tables&amp;lt;/i&amp;gt;, and I can imagine to allow this kind of tables outside of triggers too. This is valid use case (and valid functionality), but it is something different than session variables (although there can be some overlap). The implementation of declared tables inside PL/pgSQL can be more easy from high design perspective (interaction between SQL tables and PL/pgSQL is well defined already), but difficult from low level perspective (where to store column statistics and other important data for optimization - same problems like with global temporary tables (without some fake proxy local temp tables)).&lt;br /&gt;
&lt;br /&gt;
   CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
   AS $$&lt;br /&gt;
   -- not implemented yet, not part of this proposal&lt;br /&gt;
   DECLARE t TABLE(a int, b int); -- this table is invisible outside fx&lt;br /&gt;
   BEGIN&lt;br /&gt;
     INSERT INTO t VALUES(10,20);&lt;br /&gt;
     INSERT INTO t VALUES(30,40);&lt;br /&gt;
     RAISE NOTICE &#039;%&#039;, (SELECT count(*) FROM t);&lt;br /&gt;
   END;&lt;br /&gt;
   $$ LANGUAGE plpgsql;&lt;br /&gt;
&lt;br /&gt;
Some data languages defined relational variables - common concept of session variables has zero intersection with this concept.&lt;br /&gt;
&lt;br /&gt;
=Issues that any implementation should to solve =&lt;br /&gt;
* possible collision between session variables and other database objects&lt;br /&gt;
* memory cleaning after dropping session variable (when DDL can be reverted)&lt;br /&gt;
* memory invalidation (variable is dropped by one session, but still used in second session)&lt;br /&gt;
&lt;br /&gt;
= ToDo =&lt;br /&gt;
* SECURITY LABEL support - enhancing dummy_seclabel module, sepgsql extension + pg doc and SecLabelSupportsObjectType&lt;/div&gt;</summary>
		<author><name>Okbobcz</name></author>
	</entry>
	<entry>
		<id>https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41827</id>
		<title>Implementation of declarative catalog session variables</title>
		<link rel="alternate" type="text/html" href="https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41827"/>
		<updated>2025-09-28T06:00:41Z</updated>

		<summary type="html">&lt;p&gt;Okbobcz: /* Implementation issues */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Session variables =&lt;br /&gt;
Session variables are database objects that can hold a value across multiple queries inside a session. A design of session variables is varied and more times more databases implement more than just one type of session variables. Unfortunately standard SQL/PSM is not commonly accepted, and a part of this standard related to modules and module&#039;s variables was not implemented (in any widely used product). Very often session variables are a part of the stored procedures environment - but there are exceptions (MySQL session variables or any client side session variables). What I know is that session variables don&#039;t support transactions (for values). It is a plus minus just variable like we know from programming languages.&lt;br /&gt;
&lt;br /&gt;
There is no common design of session variables. Currently any database system has its own proprietary design with proprietary syntax and features. Some systems have more than one different design of session variables. It is hard to compare different designs - any design has different advantages and disadvantages.&lt;br /&gt;
&lt;br /&gt;
== MySQL ==&lt;br /&gt;
Session variables in MySQL have the same syntax like more known T-SQL variables (but all other things are proprietary). MySQL knows two kinds of session variables:&lt;br /&gt;
&lt;br /&gt;
* system variables - use the prefix `@@` or `@@xxx.`&lt;br /&gt;
* user defined variables - use the prefix `@`&lt;br /&gt;
&lt;br /&gt;
System variables can be modified by command `SET`. After keyword `SET` keywords `GLOBAL`, `SESSION` or `PERSIST` can be used. &lt;br /&gt;
Some variables can be used just with keyword `GLOBAL` or `SESSION` and in this case, when the name is prefixed by `@@`, is not&lt;br /&gt;
necessary to specify scope. System variables are defined by MySQL or by an mysql&#039;s plugins.&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL some_config = 100;&lt;br /&gt;
    SET @@same_config = 100;&lt;br /&gt;
&lt;br /&gt;
    SET SESSION sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@SESSION.sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
&lt;br /&gt;
Persistent values are stored in `mysql-auto.conf`.&lt;br /&gt;
&lt;br /&gt;
Reset of system variables can be done by setting to default or assigning from global namespace:&lt;br /&gt;
&lt;br /&gt;
    SET @@sql_mode = DEFAULT;&lt;br /&gt;
    SET @@sql_mode = @@GLOBAL.sql_mode&lt;br /&gt;
&lt;br /&gt;
PostgreSQL equivalent:&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL ; -- there is not similar command in Postgres&lt;br /&gt;
    SET SESSION ; -- SET&lt;br /&gt;
    SET PERSIST ; -- ALTER SYSTEM; SELECT pg_reload_conf();&lt;br /&gt;
    SELECT @@var; -- SELECT current_setting(&#039;var);&lt;br /&gt;
&lt;br /&gt;
User defined variables are defined by assignment&lt;br /&gt;
&lt;br /&gt;
    SET @var = expr [, @var2 = expr [ ... ]];&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
User defined variables can be only int, decimal, float, binary, nonbinary string or NULL - casting from/to any types is implicit and hidden.&lt;br /&gt;
UDV cannot be used as table or column names.&lt;br /&gt;
&lt;br /&gt;
An advantage of T-SQL syntax (with special prefixes) is simple solution of possible identifier collisions. MySQL&lt;br /&gt;
variables are very handy too, and user experience can be good for people with knowledge of T-SQL (MSSQL or Sybase).&lt;br /&gt;
On the other hand, prefix based syntax can be disturbing for users who work with different systems than MSSQL. Type system&lt;br /&gt;
is maybe too simple and can be usafe. Generally the performance of MySQL (or MariaDB) stored procedures is not good,&lt;br /&gt;
and stored procedures are not frequently used. There is not any protection against typo errors. Unassigned user variables&lt;br /&gt;
can be used without error (have  NULL value), so static check of stored procedures is not possible (in this area).&lt;br /&gt;
&lt;br /&gt;
There is not any possibility how to limit an access to user defined variables in MySQL. UDV cannot be protected&lt;br /&gt;
against unwanted usage. Some protection can be only naming convention (all UDV share one name space).&lt;br /&gt;
&lt;br /&gt;
==Postgres==&lt;br /&gt;
Postgres has relatively advanced client side session variables in `psql`. `psql`&#039;s variables use prefix &#039;:&#039;. The implementation is simple. psql&#039;s parser reads tokens from input. When it gets a token related to a variable, then this token is replaced by a value of variable. Implemented syntax supports literal and identifier escaping. psql&#039;s variables can be used as an argument of the `\bind` command.&lt;br /&gt;
&lt;br /&gt;
psql&#039;s variables are typeless client side variables available only inside `psql` or `pgbench`. &lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:40:18) postgres=# \set myvar ahoj&lt;br /&gt;
    (2025-09-25 09:40:33) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:14) postgres=# select :&#039;myvar&#039;;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:24) postgres=# select $1 \bind :myvar \g&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
psql&#039;s command `\set` allows just simply string concatenation - but allows to execute shell command. When&lt;br /&gt;
query is executed by `\gset` command, then result is stored in psql variable.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:41:40) postgres=# \set ctime `date`&lt;br /&gt;
    (2025-09-25 09:43:55) postgres=# \echo :ctime&lt;br /&gt;
    čt 25. září 2025, 09:43:55 CEST&lt;br /&gt;
&lt;br /&gt;
&#039;\set` command is too simple, and writing some complex operations is unreadable or not portable.&lt;br /&gt;
&lt;br /&gt;
It is good enough for writing tests, but it is not generally available (probably nobody expects it), and it is not&lt;br /&gt;
accessible from server side (from stored procedures). There is not simple way, how to access psql variables from&lt;br /&gt;
anonymous  block (proxy custom GUC variable is needed):&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:58:38) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    (2025-09-25 09:58:43) postgres=# select set_config(&#039;myvars.myvar&#039;, :&#039;myvar&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ ahoj       │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 10:00:27) postgres=# do $$ begin raise notice &#039;%&#039;, current_setting(&#039;myvars.myvar&#039;); end $$;&lt;br /&gt;
    NOTICE:  ahoj&lt;br /&gt;
    DO&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PostgreSQL has configuration GUC (Grand Unified Configuration) variables. These variables are primarily designed for PostgreSQL and PostgreSQL extensions configuration, but can be used as session variables (implicit, explicit, typeless). These variables can be set by command `SET` or by function `set_config`, and can be read by command `SHOW` or by function `current_setting`. GUC variables are designed for holding configuration parameters. When they are created from an internal API, then they can be protected for super users, hidden to common users, and can have assigned types (bool, memory, number, interval, string, enum) and values that can be checked before assign. The type system is independent of the PostgreSQL SQL type system - it is used for configuration and that is processed before postgresql sql type system is initialized.&lt;br /&gt;
&lt;br /&gt;
Variables created from SQL should be only &amp;quot;custom&amp;quot;. The name of these variables have to use a prefix and these variables are string only. These variables are commonly used as a workaround for missing server side session variables in Postgres (typeless unprotected unsecure session variables).&lt;br /&gt;
&lt;br /&gt;
Specific feature of Postgres GUC (build-in or custom) is the possibility to specify scope transaction, session or function execution. Another functionality is to assign a default value of a specific parameter with some event - loading configuration, login to role, login to database, start some function. These features are nice for work with configuration, but can be very problematic when GUC variables are used as session variables.&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE FUNCTION fx() &lt;br /&gt;
    RETURNS void AS $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, current_setting(&#039;my.x&#039;);&lt;br /&gt;
    END $$ LANGUAGE plpgsql SET my.x = 20;&lt;br /&gt;
    CREATE FUNCTION&lt;br /&gt;
    (2025-09-27 06:10:37) postgres=# SET my.x = 0; SELECT fx();&lt;br /&gt;
    SET&lt;br /&gt;
    NOTICE:  20&lt;br /&gt;
    ┌────┐&lt;br /&gt;
    │ fx │&lt;br /&gt;
    ╞════╡&lt;br /&gt;
    │    │&lt;br /&gt;
    └────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    (2025-09-27 06:10:39) postgres=# SHOW my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 0    │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
GUC variables are transactional - without possibility to disable it.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-27 06:17:31) postgres=# set my.x = 10;&lt;br /&gt;
    SET&lt;br /&gt;
    (2025-09-27 06:19:42) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:19:46) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, true);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:19:55) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:01) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:20:11) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:13) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:20:38) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:45) postgres=# rollback;&lt;br /&gt;
    ROLLBACK&lt;br /&gt;
    (2025-09-27 06:20:52) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:55) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:22:36) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:22:39) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:22:42) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
So there is no possibility to store to custom GUC some details about an error.&lt;br /&gt;
&lt;br /&gt;
== Oracle ==&lt;br /&gt;
Oracle PL/SQL allows the use of package variables. PL/SQL is based on ADA&lt;br /&gt;
language - and package variables are &amp;quot;global&amp;quot; variables. They are not&lt;br /&gt;
directly visible from SQL, but Oracle allows reduced syntax for functions&lt;br /&gt;
without arguments, so you need to write a wrapper&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE PACKAGE my_package&lt;br /&gt;
    AS&lt;br /&gt;
      FUNCTION get_a RETURN NUMBER;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    /&lt;br /&gt;
    &lt;br /&gt;
    CREATE OR REPLACE PACKAGE BODY my_package&lt;br /&gt;
    AS&lt;br /&gt;
      a  NUMBER(20);&lt;br /&gt;
     &lt;br /&gt;
      FUNCTION get_a&lt;br /&gt;
      RETURN NUMBER&lt;br /&gt;
      IS&lt;br /&gt;
      BEGIN&lt;br /&gt;
        RETURN a;&lt;br /&gt;
      END get_a;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    &lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
Inside SQL the higher priority has SQL, inside non SQL commands like CALL&lt;br /&gt;
or some PL/SQL command, the higher priority has packages.&lt;br /&gt;
&lt;br /&gt;
The Oracle allows both syntax for calling function with zero arguments so&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a() FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Then there is less risk reduction of collision. Package variables persist&lt;br /&gt;
in session&lt;br /&gt;
&lt;br /&gt;
Another possibility is using variables in SQL*Plus (looks like our psql&lt;br /&gt;
variables, with possibility to define type on server side)&lt;br /&gt;
&lt;br /&gt;
The variable should be declared by command VARIABLE and can be accessed by&lt;br /&gt;
syntax :varname in session before usage (maybe this step is optional)&lt;br /&gt;
&lt;br /&gt;
    VARIABLE bv_variable_name VARCHAR2(30)&lt;br /&gt;
    BEGIN&lt;br /&gt;
     :bv_variable_name := &#039;Some Value&#039;;&lt;br /&gt;
    END;&lt;br /&gt;
    &lt;br /&gt;
    SELECT column_name&lt;br /&gt;
      FROM   table_name&lt;br /&gt;
     WHERE  column_name = :bv_variable_name;&lt;br /&gt;
&lt;br /&gt;
== DB2 ==&lt;br /&gt;
The &amp;quot;user defined global variables&amp;quot; are similar to my proposal. The&lt;br /&gt;
differences are different access rights &amp;quot;READ, WRITE&amp;quot; x &amp;quot;SELECT, UPDATE&amp;quot;.&lt;br /&gt;
Because PostgreSQL has SET command for GUC, I introduced LET command (DB2&lt;br /&gt;
uses SET)&lt;br /&gt;
&lt;br /&gt;
Variables are visible in all sessions, but value is private per session.&lt;br /&gt;
Variables are not transactional. The usage is wider than my proposal. Then&lt;br /&gt;
can be changed by commands SET, SELECT INTO or they can be used like OUT&lt;br /&gt;
parameters of procedures. The search path (or some like that) is used for&lt;br /&gt;
variables too, but the variables has less priority than tables/columns.&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE myCounter INT DEFAULT 01;&lt;br /&gt;
    SELECT EMPNO, LASTNAME, CASE WHEN myCounter = 1 THEN SALARY ELSE NULL END&lt;br /&gt;
    FROM EMPLOYEE WHERE WORKDEPT = ’A00’;&lt;br /&gt;
    SET myCounter = 29;&lt;br /&gt;
&lt;br /&gt;
There are (I think) different kinds of variables - accessed by the function&lt;br /&gt;
GETVARIABLE(&#039;name&#039;, &#039;default) - it looks very similar ro our GUC and&lt;br /&gt;
`current_setting` function. These variables can be set by connection&lt;br /&gt;
string, are of varchar type and 10 values are allowed. Built-in session&lt;br /&gt;
variables (configuration) can be accessed by the function GETVARIABLE too.&lt;br /&gt;
&lt;br /&gt;
== MSSQL (T-SQL) ==&lt;br /&gt;
global variables - the access syntax is @@varname, they are used like GUC&lt;br /&gt;
and little bit more - some state informations are there like @@ERROR,&lt;br /&gt;
@@ROWCOUNT or @@IDENTITY&lt;br /&gt;
&lt;br /&gt;
local variables - the access syntax is @varname, and should be declared&lt;br /&gt;
before usage by DECLARE command. The scope is limited to batch or procedure&lt;br /&gt;
or function, where DECLARE command was executed.&lt;br /&gt;
&lt;br /&gt;
    DECLARE @TestVariable AS VARCHAR(100)&lt;br /&gt;
    SET @TestVariable = &#039;Think Green&#039;&lt;br /&gt;
    GO&lt;br /&gt;
    PRINT @TestVariable&lt;br /&gt;
&lt;br /&gt;
This script fails, because PRINT is executed in another batch. So I think&lt;br /&gt;
so MSSQL doesn&#039;t support session variables&lt;br /&gt;
&lt;br /&gt;
There are similar mechanisms like our custom GUC and usage current_setting&lt;br /&gt;
and set_config functions. Generally, in this area is MSSQL very primitive&lt;br /&gt;
&lt;br /&gt;
    EXEC sp_set_session_context &#039;user_id&#039;, 4;&lt;br /&gt;
    SELECT SESSION_CONTEXT(N&#039;user_id&#039;);&lt;br /&gt;
&lt;br /&gt;
== SQL/PSM ==&lt;br /&gt;
Standard introduces a concept of modules that can be joined with schemas.&lt;br /&gt;
The variables are like PLpgSQL, but only local - the only temp tables can&lt;br /&gt;
be defined on module levels. These tables can be accessed only from&lt;br /&gt;
routines assigned to modules. Modules are declarative versions of our&lt;br /&gt;
extensions (if I understand well, I didn&#039;t find any implementation). It&lt;br /&gt;
allows you to overwrite the search patch for routines assigned in the&lt;br /&gt;
module. Variables are not transactional, the priority - variables/columns&lt;br /&gt;
is not specified.&lt;br /&gt;
&lt;br /&gt;
= Types of different session variables =&lt;br /&gt;
* client side (psql, pgadmin, ...) - mostly similar to shell variables (based on string operations)&lt;br /&gt;
* server side&lt;br /&gt;
** declarative&lt;br /&gt;
*** created only for batch (T-SQL)&lt;br /&gt;
*** created as an database object (or part of database object) (Oracle, DB2)&lt;br /&gt;
** created by usage (MySQL)&lt;br /&gt;
** with special syntax (MySQL, T-SQL)&lt;br /&gt;
** with ANSI SQL syntax for identifiers (Oracle, DB2)&lt;br /&gt;
&lt;br /&gt;
= Implementation of session variables in Postgres =&lt;br /&gt;
&lt;br /&gt;
== Possible use cases ==&lt;br /&gt;
* usage of session variables as global variables in PL&lt;br /&gt;
* holding configuration (for PL)&lt;br /&gt;
* holding some more used data in interactive session&lt;br /&gt;
* holding credentials for some RLS and data securing&lt;br /&gt;
* helping with migration from others systems (mainly from Oracle)&lt;br /&gt;
* allow anonymous block parametrization (only postgres)&lt;br /&gt;
&lt;br /&gt;
It is clean so no one design is perfect for all uses cases. MySQL is good enough and maybe handy for interactive work,&lt;br /&gt;
but cannot be used for storing any sensitive data, and it is unsafe. PostgreSQL GUC are perfect for configuration, but&lt;br /&gt;
are not friendly for interactive work, and are very limited for usage in PL as global variables. So no one design is&lt;br /&gt;
perfect, and I can imagine more different designs in one system, that can support some different use cases better.&lt;br /&gt;
&lt;br /&gt;
== Proposal for Postgres - design session variables as database global temporary typed objects with ANSI SQL syntax for identifiers ==&lt;br /&gt;
&lt;br /&gt;
I searched solution that can work with specific Postgres environment:&lt;br /&gt;
&lt;br /&gt;
* Postgres has more than one widely used language for stored procedures PL/pgSQL + PL/Python or PL/Perl&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL is strongly reduced PL/SQL, and PL/SQL is modified ADA language. I am pretty happy with the current scope of PL/SQL - it is domain specific language and it works well. It is a very simple language, and it is easy to learn it. PL/pgSQL has no packages or modules. Instead PostgreSQL schemas are used. But Postgres schemas are specific database objects. Schemas are independent on PL. &lt;br /&gt;
&lt;br /&gt;
So I wanted design that can supports more PL languages, didn&#039;t introduce new complexity to relative simple PL/pgSQL and didn&#039;t introduce functionality that can be redundant with already existing objects.&lt;br /&gt;
I wrote bigger application in PL/pgSQL. For maintaining of this application I wrote plpgsql_lint (later plpgsql_check). So every time I am searching solution that doesn&#039;t block code static analyse. Static analyse requires persistent objects, and then it is direct way to design session variables as database objects (with own system catalog). When session variables are designed as database objects, then using ACL is natural. Oracle package variables are well protected just by package scope. Unfortunately, postgres doesn&#039;t know schema scope. The locality of schema objects is defined by setting of SEARCH_PATH guc, and introduction of new mechanism can be messy. But with ACL the content of variables can be safe too:&lt;br /&gt;
&lt;br /&gt;
    CREATE TEMP VARIABLE x AS int;&lt;br /&gt;
     &lt;br /&gt;
    LET x = 10;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(x);&lt;br /&gt;
    END;&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    SELECT VARIABLE(x);&lt;br /&gt;
&lt;br /&gt;
== Implementation issues ==&lt;br /&gt;
=== Collisions between columns and variables identifiers ===&lt;br /&gt;
There is a new possibility of identifiers collisions between variables and columns identifiers. With ANSI SQL syntax for session variables identifiers there is a problem how to distinct between variable and column. This is known problem in PL/pgSQL or PL/SQL and can be solved by some prioritization or by prohibition of collisions. The prohibition of collisions is solution that almost cleaned this issue from PL/pgSQL. Unfortunately session variables are global objects and then the collision can be in any query, and then the new badly named variable can stop execution of any query. Prioritization variables or columns has own problems - quiet unwanted using variable or column.&lt;br /&gt;
&lt;br /&gt;
    CREATE TABLE tab(a int);&lt;br /&gt;
    CREATE VARIABLE a AS int;&lt;br /&gt;
    &lt;br /&gt;
    SELECT a FROM tab; -- with disallowed collisions, the execution of this query stops when there is a variable named &amp;quot;a&amp;quot;&lt;br /&gt;
    &lt;br /&gt;
    LET a = 1;&lt;br /&gt;
    DELETE FROM tab WHERE a = 1; -- with higher priority for variables all table can be deleted (nobody proposed higher priority for variables).&lt;br /&gt;
    SELECT a FROM tab; -- with higher priority for columns, the variable is not used (quietly)&lt;br /&gt;
&lt;br /&gt;
The prohibition of collision is not absolute protection against possible human errors:&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE _id AS int;&lt;br /&gt;
    LET _id = 10;&lt;br /&gt;
    CREATE TABLE foo(id int);&lt;br /&gt;
    DELETE FROM foo WHERE _id = 10; -- just type, but can have unwanted impact (all rows removed)&lt;br /&gt;
&lt;br /&gt;
These problems are not new. The developers in PL/pgSQL or PL/SQL knows it, but the scope of these risks are limited only to stored procedures. After very very long discussion and lot of designs I introduced variable fences (syntax `VARIABLE(varname)`). Inside of any query the variable can be used only in variable fence. This is 100% solution of collisions, and I think it can be good solution against human errors and unwanted usage of session variable. Now, variable fences are necessary every where inside a query or expressions, but I thinking about possibility to be optional inside PL/pgSQL (far future). This can reduce overhead with porting applications from Oracle, and generally the developers of stored procedures are experienced with described risks (this can be controlled by new `plpgsql.extra_checks`).&lt;br /&gt;
&lt;br /&gt;
A variable fence can be in collisions with custom function &amp;quot;variable&amp;quot;. There is similarity with usage of keywords `cube`. This can be fixed by usage prefix schema or by usage of parenthesis:&lt;br /&gt;
&lt;br /&gt;
    SELECT public.variable(...)&lt;br /&gt;
    SELECT &amp;quot;variable&amp;quot;(...)&lt;br /&gt;
&lt;br /&gt;
Why I don&#039;t propose usage of T-SQL (MSSQL, MySQL) syntax for session variables? There is more reasons (one is objective, last is subjective):&lt;br /&gt;
&lt;br /&gt;
* There is a collision with custom operators - operators `@` or `@@` are already used - usage of `@@` is common&lt;br /&gt;
&lt;br /&gt;
    (2025-09-28 06:57:30) postgres=# create table tab(a int);&lt;br /&gt;
    CREATE TABLE&lt;br /&gt;
    (2025-09-28 06:57:42) postgres=# insert into tab values(10);&lt;br /&gt;
    INSERT 0 1&lt;br /&gt;
    (2025-09-28 06:57:50) postgres=# select @a from tab;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │       10 │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
We can safely use only `:` prefix, but then we can break a psql variables. Similarly we can use `$` prefix, but then we can break query parameters syntax.&lt;br /&gt;
&lt;br /&gt;
* T-SQL variable names looks weird in Postgres (in queries, in PL/pgSQL). Postgres knows only ANSI/SQL identifiers, PL/pgSQL shares these rules too, and this are the PostgreSQL is consistent, and I missing motivation to introduce something new (with the knowledge so problems with possible compatibility breaks should be solved too). &lt;br /&gt;
&lt;br /&gt;
Note: There was a proposal to use table syntax as variable (like table (in memory) variables from T-SQL but only single row). Although it is very effective solution of collisions, I dislike this idea. It increase a complexity of simple expression (or increase inconsistency of some syntax rules). More looks weird for scalar variables (and can be handy for composite variables):&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(var); -- variable is not a table&lt;br /&gt;
    END&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      -- variable a a table (I don&#039;t like it)&lt;br /&gt;
      -- can we use DML? How much rows this kind of variable can hold&lt;br /&gt;
      -- value is row or not?&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, (SELECT var FROM var);&lt;br /&gt;
    END&lt;br /&gt;
    $$&lt;br /&gt;
&lt;br /&gt;
I am not against for introduction of inmemory tables - or more correctly for global temporary tables. Well implemented global temporary tables can be very nice and handy feature. But it is different feature, and should be designed separately. There can be some performance problems - plpgsql can use simple expression evaluation (when expression has not usage of any table). This optimization can be changed or enhanced, but its is change inside complex sensitive code, and it is not consistent. I very like a global temporary tables (that are not implemented in Postgres). Table (and related operations) should to looks like table, variable (and related operations) should to looks like variable. Common perspective how session variables should to looks is based on variables from PL environment. PostgreSQL internally supports &amp;lt;i&amp;gt;transition tables&amp;lt;/i&amp;gt;, and I can imagine to allow this kind of tables outside of triggers too. This is valid use case (and valid functionality), but it is something different than session variables (although there can be some overlap). The implementation of declared tables inside PL/pgSQL can be more easy from high design perspective (interaction between SQL tables and PL/pgSQL is well defined already), but difficult from low level perspective (where to store column statistics and other important data for optimization - same problems like with global temporary tables (without some fake proxy local temp tables)).&lt;br /&gt;
&lt;br /&gt;
   CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
   AS $$&lt;br /&gt;
   -- not implemented yet, not part of this proposal&lt;br /&gt;
   DECLARE t TABLE(a int, b int); -- this table is invisible outside fx&lt;br /&gt;
   BEGIN&lt;br /&gt;
     INSERT INTO t VALUES(10,20);&lt;br /&gt;
     INSERT INTO t VALUES(30,40);&lt;br /&gt;
     RAISE NOTICE &#039;%&#039;, (SELECT count(*) FROM t);&lt;br /&gt;
   END;&lt;br /&gt;
   $$ LANGUAGE plpgsql;&lt;br /&gt;
&lt;br /&gt;
Some data languages defined relational variables - common concept of session variables has zero intersection with this concept.&lt;br /&gt;
&lt;br /&gt;
=Issues that any implementation should to solve =&lt;br /&gt;
* possible collision between session variables and other database objects&lt;br /&gt;
* memory cleaning after dropping session variable (when DDL can be reverted)&lt;br /&gt;
* memory invalidation (variable is dropped by one session, but still used in second session)&lt;br /&gt;
&lt;br /&gt;
= ToDo =&lt;br /&gt;
* SECURITY LABEL support - enhancing dummy_seclabel module, sepgsql extension + pg doc and SecLabelSupportsObjectType&lt;/div&gt;</summary>
		<author><name>Okbobcz</name></author>
	</entry>
	<entry>
		<id>https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41826</id>
		<title>Implementation of declarative catalog session variables</title>
		<link rel="alternate" type="text/html" href="https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41826"/>
		<updated>2025-09-28T05:59:55Z</updated>

		<summary type="html">&lt;p&gt;Okbobcz: /* Collisions between columns and variables identifiers */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Session variables =&lt;br /&gt;
Session variables are database objects that can hold a value across multiple queries inside a session. A design of session variables is varied and more times more databases implement more than just one type of session variables. Unfortunately standard SQL/PSM is not commonly accepted, and a part of this standard related to modules and module&#039;s variables was not implemented (in any widely used product). Very often session variables are a part of the stored procedures environment - but there are exceptions (MySQL session variables or any client side session variables). What I know is that session variables don&#039;t support transactions (for values). It is a plus minus just variable like we know from programming languages.&lt;br /&gt;
&lt;br /&gt;
There is no common design of session variables. Currently any database system has its own proprietary design with proprietary syntax and features. Some systems have more than one different design of session variables. It is hard to compare different designs - any design has different advantages and disadvantages.&lt;br /&gt;
&lt;br /&gt;
== MySQL ==&lt;br /&gt;
Session variables in MySQL have the same syntax like more known T-SQL variables (but all other things are proprietary). MySQL knows two kinds of session variables:&lt;br /&gt;
&lt;br /&gt;
* system variables - use the prefix `@@` or `@@xxx.`&lt;br /&gt;
* user defined variables - use the prefix `@`&lt;br /&gt;
&lt;br /&gt;
System variables can be modified by command `SET`. After keyword `SET` keywords `GLOBAL`, `SESSION` or `PERSIST` can be used. &lt;br /&gt;
Some variables can be used just with keyword `GLOBAL` or `SESSION` and in this case, when the name is prefixed by `@@`, is not&lt;br /&gt;
necessary to specify scope. System variables are defined by MySQL or by an mysql&#039;s plugins.&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL some_config = 100;&lt;br /&gt;
    SET @@same_config = 100;&lt;br /&gt;
&lt;br /&gt;
    SET SESSION sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@SESSION.sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
&lt;br /&gt;
Persistent values are stored in `mysql-auto.conf`.&lt;br /&gt;
&lt;br /&gt;
Reset of system variables can be done by setting to default or assigning from global namespace:&lt;br /&gt;
&lt;br /&gt;
    SET @@sql_mode = DEFAULT;&lt;br /&gt;
    SET @@sql_mode = @@GLOBAL.sql_mode&lt;br /&gt;
&lt;br /&gt;
PostgreSQL equivalent:&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL ; -- there is not similar command in Postgres&lt;br /&gt;
    SET SESSION ; -- SET&lt;br /&gt;
    SET PERSIST ; -- ALTER SYSTEM; SELECT pg_reload_conf();&lt;br /&gt;
    SELECT @@var; -- SELECT current_setting(&#039;var);&lt;br /&gt;
&lt;br /&gt;
User defined variables are defined by assignment&lt;br /&gt;
&lt;br /&gt;
    SET @var = expr [, @var2 = expr [ ... ]];&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
User defined variables can be only int, decimal, float, binary, nonbinary string or NULL - casting from/to any types is implicit and hidden.&lt;br /&gt;
UDV cannot be used as table or column names.&lt;br /&gt;
&lt;br /&gt;
An advantage of T-SQL syntax (with special prefixes) is simple solution of possible identifier collisions. MySQL&lt;br /&gt;
variables are very handy too, and user experience can be good for people with knowledge of T-SQL (MSSQL or Sybase).&lt;br /&gt;
On the other hand, prefix based syntax can be disturbing for users who work with different systems than MSSQL. Type system&lt;br /&gt;
is maybe too simple and can be usafe. Generally the performance of MySQL (or MariaDB) stored procedures is not good,&lt;br /&gt;
and stored procedures are not frequently used. There is not any protection against typo errors. Unassigned user variables&lt;br /&gt;
can be used without error (have  NULL value), so static check of stored procedures is not possible (in this area).&lt;br /&gt;
&lt;br /&gt;
There is not any possibility how to limit an access to user defined variables in MySQL. UDV cannot be protected&lt;br /&gt;
against unwanted usage. Some protection can be only naming convention (all UDV share one name space).&lt;br /&gt;
&lt;br /&gt;
==Postgres==&lt;br /&gt;
Postgres has relatively advanced client side session variables in `psql`. `psql`&#039;s variables use prefix &#039;:&#039;. The implementation is simple. psql&#039;s parser reads tokens from input. When it gets a token related to a variable, then this token is replaced by a value of variable. Implemented syntax supports literal and identifier escaping. psql&#039;s variables can be used as an argument of the `\bind` command.&lt;br /&gt;
&lt;br /&gt;
psql&#039;s variables are typeless client side variables available only inside `psql` or `pgbench`. &lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:40:18) postgres=# \set myvar ahoj&lt;br /&gt;
    (2025-09-25 09:40:33) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:14) postgres=# select :&#039;myvar&#039;;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:24) postgres=# select $1 \bind :myvar \g&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
psql&#039;s command `\set` allows just simply string concatenation - but allows to execute shell command. When&lt;br /&gt;
query is executed by `\gset` command, then result is stored in psql variable.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:41:40) postgres=# \set ctime `date`&lt;br /&gt;
    (2025-09-25 09:43:55) postgres=# \echo :ctime&lt;br /&gt;
    čt 25. září 2025, 09:43:55 CEST&lt;br /&gt;
&lt;br /&gt;
&#039;\set` command is too simple, and writing some complex operations is unreadable or not portable.&lt;br /&gt;
&lt;br /&gt;
It is good enough for writing tests, but it is not generally available (probably nobody expects it), and it is not&lt;br /&gt;
accessible from server side (from stored procedures). There is not simple way, how to access psql variables from&lt;br /&gt;
anonymous  block (proxy custom GUC variable is needed):&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:58:38) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    (2025-09-25 09:58:43) postgres=# select set_config(&#039;myvars.myvar&#039;, :&#039;myvar&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ ahoj       │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 10:00:27) postgres=# do $$ begin raise notice &#039;%&#039;, current_setting(&#039;myvars.myvar&#039;); end $$;&lt;br /&gt;
    NOTICE:  ahoj&lt;br /&gt;
    DO&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PostgreSQL has configuration GUC (Grand Unified Configuration) variables. These variables are primarily designed for PostgreSQL and PostgreSQL extensions configuration, but can be used as session variables (implicit, explicit, typeless). These variables can be set by command `SET` or by function `set_config`, and can be read by command `SHOW` or by function `current_setting`. GUC variables are designed for holding configuration parameters. When they are created from an internal API, then they can be protected for super users, hidden to common users, and can have assigned types (bool, memory, number, interval, string, enum) and values that can be checked before assign. The type system is independent of the PostgreSQL SQL type system - it is used for configuration and that is processed before postgresql sql type system is initialized.&lt;br /&gt;
&lt;br /&gt;
Variables created from SQL should be only &amp;quot;custom&amp;quot;. The name of these variables have to use a prefix and these variables are string only. These variables are commonly used as a workaround for missing server side session variables in Postgres (typeless unprotected unsecure session variables).&lt;br /&gt;
&lt;br /&gt;
Specific feature of Postgres GUC (build-in or custom) is the possibility to specify scope transaction, session or function execution. Another functionality is to assign a default value of a specific parameter with some event - loading configuration, login to role, login to database, start some function. These features are nice for work with configuration, but can be very problematic when GUC variables are used as session variables.&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE FUNCTION fx() &lt;br /&gt;
    RETURNS void AS $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, current_setting(&#039;my.x&#039;);&lt;br /&gt;
    END $$ LANGUAGE plpgsql SET my.x = 20;&lt;br /&gt;
    CREATE FUNCTION&lt;br /&gt;
    (2025-09-27 06:10:37) postgres=# SET my.x = 0; SELECT fx();&lt;br /&gt;
    SET&lt;br /&gt;
    NOTICE:  20&lt;br /&gt;
    ┌────┐&lt;br /&gt;
    │ fx │&lt;br /&gt;
    ╞════╡&lt;br /&gt;
    │    │&lt;br /&gt;
    └────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    (2025-09-27 06:10:39) postgres=# SHOW my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 0    │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
GUC variables are transactional - without possibility to disable it.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-27 06:17:31) postgres=# set my.x = 10;&lt;br /&gt;
    SET&lt;br /&gt;
    (2025-09-27 06:19:42) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:19:46) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, true);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:19:55) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:01) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:20:11) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:13) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:20:38) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:45) postgres=# rollback;&lt;br /&gt;
    ROLLBACK&lt;br /&gt;
    (2025-09-27 06:20:52) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:55) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:22:36) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:22:39) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:22:42) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
So there is no possibility to store to custom GUC some details about an error.&lt;br /&gt;
&lt;br /&gt;
== Oracle ==&lt;br /&gt;
Oracle PL/SQL allows the use of package variables. PL/SQL is based on ADA&lt;br /&gt;
language - and package variables are &amp;quot;global&amp;quot; variables. They are not&lt;br /&gt;
directly visible from SQL, but Oracle allows reduced syntax for functions&lt;br /&gt;
without arguments, so you need to write a wrapper&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE PACKAGE my_package&lt;br /&gt;
    AS&lt;br /&gt;
      FUNCTION get_a RETURN NUMBER;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    /&lt;br /&gt;
    &lt;br /&gt;
    CREATE OR REPLACE PACKAGE BODY my_package&lt;br /&gt;
    AS&lt;br /&gt;
      a  NUMBER(20);&lt;br /&gt;
     &lt;br /&gt;
      FUNCTION get_a&lt;br /&gt;
      RETURN NUMBER&lt;br /&gt;
      IS&lt;br /&gt;
      BEGIN&lt;br /&gt;
        RETURN a;&lt;br /&gt;
      END get_a;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    &lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
Inside SQL the higher priority has SQL, inside non SQL commands like CALL&lt;br /&gt;
or some PL/SQL command, the higher priority has packages.&lt;br /&gt;
&lt;br /&gt;
The Oracle allows both syntax for calling function with zero arguments so&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a() FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Then there is less risk reduction of collision. Package variables persist&lt;br /&gt;
in session&lt;br /&gt;
&lt;br /&gt;
Another possibility is using variables in SQL*Plus (looks like our psql&lt;br /&gt;
variables, with possibility to define type on server side)&lt;br /&gt;
&lt;br /&gt;
The variable should be declared by command VARIABLE and can be accessed by&lt;br /&gt;
syntax :varname in session before usage (maybe this step is optional)&lt;br /&gt;
&lt;br /&gt;
    VARIABLE bv_variable_name VARCHAR2(30)&lt;br /&gt;
    BEGIN&lt;br /&gt;
     :bv_variable_name := &#039;Some Value&#039;;&lt;br /&gt;
    END;&lt;br /&gt;
    &lt;br /&gt;
    SELECT column_name&lt;br /&gt;
      FROM   table_name&lt;br /&gt;
     WHERE  column_name = :bv_variable_name;&lt;br /&gt;
&lt;br /&gt;
== DB2 ==&lt;br /&gt;
The &amp;quot;user defined global variables&amp;quot; are similar to my proposal. The&lt;br /&gt;
differences are different access rights &amp;quot;READ, WRITE&amp;quot; x &amp;quot;SELECT, UPDATE&amp;quot;.&lt;br /&gt;
Because PostgreSQL has SET command for GUC, I introduced LET command (DB2&lt;br /&gt;
uses SET)&lt;br /&gt;
&lt;br /&gt;
Variables are visible in all sessions, but value is private per session.&lt;br /&gt;
Variables are not transactional. The usage is wider than my proposal. Then&lt;br /&gt;
can be changed by commands SET, SELECT INTO or they can be used like OUT&lt;br /&gt;
parameters of procedures. The search path (or some like that) is used for&lt;br /&gt;
variables too, but the variables has less priority than tables/columns.&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE myCounter INT DEFAULT 01;&lt;br /&gt;
    SELECT EMPNO, LASTNAME, CASE WHEN myCounter = 1 THEN SALARY ELSE NULL END&lt;br /&gt;
    FROM EMPLOYEE WHERE WORKDEPT = ’A00’;&lt;br /&gt;
    SET myCounter = 29;&lt;br /&gt;
&lt;br /&gt;
There are (I think) different kinds of variables - accessed by the function&lt;br /&gt;
GETVARIABLE(&#039;name&#039;, &#039;default) - it looks very similar ro our GUC and&lt;br /&gt;
`current_setting` function. These variables can be set by connection&lt;br /&gt;
string, are of varchar type and 10 values are allowed. Built-in session&lt;br /&gt;
variables (configuration) can be accessed by the function GETVARIABLE too.&lt;br /&gt;
&lt;br /&gt;
== MSSQL (T-SQL) ==&lt;br /&gt;
global variables - the access syntax is @@varname, they are used like GUC&lt;br /&gt;
and little bit more - some state informations are there like @@ERROR,&lt;br /&gt;
@@ROWCOUNT or @@IDENTITY&lt;br /&gt;
&lt;br /&gt;
local variables - the access syntax is @varname, and should be declared&lt;br /&gt;
before usage by DECLARE command. The scope is limited to batch or procedure&lt;br /&gt;
or function, where DECLARE command was executed.&lt;br /&gt;
&lt;br /&gt;
    DECLARE @TestVariable AS VARCHAR(100)&lt;br /&gt;
    SET @TestVariable = &#039;Think Green&#039;&lt;br /&gt;
    GO&lt;br /&gt;
    PRINT @TestVariable&lt;br /&gt;
&lt;br /&gt;
This script fails, because PRINT is executed in another batch. So I think&lt;br /&gt;
so MSSQL doesn&#039;t support session variables&lt;br /&gt;
&lt;br /&gt;
There are similar mechanisms like our custom GUC and usage current_setting&lt;br /&gt;
and set_config functions. Generally, in this area is MSSQL very primitive&lt;br /&gt;
&lt;br /&gt;
    EXEC sp_set_session_context &#039;user_id&#039;, 4;&lt;br /&gt;
    SELECT SESSION_CONTEXT(N&#039;user_id&#039;);&lt;br /&gt;
&lt;br /&gt;
== SQL/PSM ==&lt;br /&gt;
Standard introduces a concept of modules that can be joined with schemas.&lt;br /&gt;
The variables are like PLpgSQL, but only local - the only temp tables can&lt;br /&gt;
be defined on module levels. These tables can be accessed only from&lt;br /&gt;
routines assigned to modules. Modules are declarative versions of our&lt;br /&gt;
extensions (if I understand well, I didn&#039;t find any implementation). It&lt;br /&gt;
allows you to overwrite the search patch for routines assigned in the&lt;br /&gt;
module. Variables are not transactional, the priority - variables/columns&lt;br /&gt;
is not specified.&lt;br /&gt;
&lt;br /&gt;
= Types of different session variables =&lt;br /&gt;
* client side (psql, pgadmin, ...) - mostly similar to shell variables (based on string operations)&lt;br /&gt;
* server side&lt;br /&gt;
** declarative&lt;br /&gt;
*** created only for batch (T-SQL)&lt;br /&gt;
*** created as an database object (or part of database object) (Oracle, DB2)&lt;br /&gt;
** created by usage (MySQL)&lt;br /&gt;
** with special syntax (MySQL, T-SQL)&lt;br /&gt;
** with ANSI SQL syntax for identifiers (Oracle, DB2)&lt;br /&gt;
&lt;br /&gt;
= Implementation of session variables in Postgres =&lt;br /&gt;
&lt;br /&gt;
== Possible use cases ==&lt;br /&gt;
* usage of session variables as global variables in PL&lt;br /&gt;
* holding configuration (for PL)&lt;br /&gt;
* holding some more used data in interactive session&lt;br /&gt;
* holding credentials for some RLS and data securing&lt;br /&gt;
* helping with migration from others systems (mainly from Oracle)&lt;br /&gt;
* allow anonymous block parametrization (only postgres)&lt;br /&gt;
&lt;br /&gt;
It is clean so no one design is perfect for all uses cases. MySQL is good enough and maybe handy for interactive work,&lt;br /&gt;
but cannot be used for storing any sensitive data, and it is unsafe. PostgreSQL GUC are perfect for configuration, but&lt;br /&gt;
are not friendly for interactive work, and are very limited for usage in PL as global variables. So no one design is&lt;br /&gt;
perfect, and I can imagine more different designs in one system, that can support some different use cases better.&lt;br /&gt;
&lt;br /&gt;
== Proposal for Postgres - design session variables as database global temporary typed objects with ANSI SQL syntax for identifiers ==&lt;br /&gt;
&lt;br /&gt;
I searched solution that can work with specific Postgres environment:&lt;br /&gt;
&lt;br /&gt;
* Postgres has more than one widely used language for stored procedures PL/pgSQL + PL/Python or PL/Perl&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL is strongly reduced PL/SQL, and PL/SQL is modified ADA language. I am pretty happy with the current scope of PL/SQL - it is domain specific language and it works well. It is a very simple language, and it is easy to learn it. PL/pgSQL has no packages or modules. Instead PostgreSQL schemas are used. But Postgres schemas are specific database objects. Schemas are independent on PL. &lt;br /&gt;
&lt;br /&gt;
So I wanted design that can supports more PL languages, didn&#039;t introduce new complexity to relative simple PL/pgSQL and didn&#039;t introduce functionality that can be redundant with already existing objects.&lt;br /&gt;
I wrote bigger application in PL/pgSQL. For maintaining of this application I wrote plpgsql_lint (later plpgsql_check). So every time I am searching solution that doesn&#039;t block code static analyse. Static analyse requires persistent objects, and then it is direct way to design session variables as database objects (with own system catalog). When session variables are designed as database objects, then using ACL is natural. Oracle package variables are well protected just by package scope. Unfortunately, postgres doesn&#039;t know schema scope. The locality of schema objects is defined by setting of SEARCH_PATH guc, and introduction of new mechanism can be messy. But with ACL the content of variables can be safe too:&lt;br /&gt;
&lt;br /&gt;
    CREATE TEMP VARIABLE x AS int;&lt;br /&gt;
     &lt;br /&gt;
    LET x = 10;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(x);&lt;br /&gt;
    END;&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    SELECT VARIABLE(x);&lt;br /&gt;
&lt;br /&gt;
== Implementation issues ==&lt;br /&gt;
=== Collisions between columns and variables identifiers ===&lt;br /&gt;
There is a new possibility of identifiers collisions between variables and columns identifiers. With ANSI SQL syntax for session variables identifiers there is a problem how to distinct between variable and column. This is known problem in PL/pgSQL or PL/SQL and can be solved by some prioritization or by prohibition of collisions. The prohibition of collisions is solution that almost cleaned this issue from PL/pgSQL. Unfortunately session variables are global objects and then the collision can be in any query, and then the new badly named variable can stop execution of any query. Prioritization variables or columns has own problems - quiet unwanted using variable or column.&lt;br /&gt;
&lt;br /&gt;
    CREATE TABLE tab(a int);&lt;br /&gt;
    CREATE VARIABLE a AS int;&lt;br /&gt;
    &lt;br /&gt;
    SELECT a FROM tab; -- with disallowed collisions, the execution of this query stops when there is a variable named &amp;quot;a&amp;quot;&lt;br /&gt;
    &lt;br /&gt;
    LET a = 1;&lt;br /&gt;
    DELETE FROM tab WHERE a = 1; -- with higher priority for variables all table can be deleted (nobody proposed higher priority for variables).&lt;br /&gt;
    SELECT a FROM tab; -- with higher priority for columns, the variable is not used (quietly)&lt;br /&gt;
&lt;br /&gt;
The prohibition of collision is not absolute protection against possible human errors:&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE _id AS int;&lt;br /&gt;
    LET _id = 10;&lt;br /&gt;
    CREATE TABLE foo(id int);&lt;br /&gt;
    DELETE FROM foo WHERE _id = 10; -- just type, but can have unwanted impact (all rows removed)&lt;br /&gt;
&lt;br /&gt;
These problems are not new. The developers in PL/pgSQL or PL/SQL knows it, but the scope of these risks are limited only to stored procedures. After very very long discussion and lot of designs I introduced variable fences (syntax `VARIABLE(varname)`). Inside of any query the variable can be used only in variable fence. This is 100% solution of collisions, and I think it can be good solution against human errors and unwanted usage of session variable. Now, variable fences are necessary every where inside a query or expressions, but I thinking about possibility to be optional inside PL/pgSQL (far future). This can reduce overhead with porting applications from Oracle, and generally the developers of stored procedures are experienced with described risks (this can be controlled by new `plpgsql.extra_checks`).&lt;br /&gt;
&lt;br /&gt;
A variable fence can be in collisions with custom function &amp;quot;variable&amp;quot;. There is similarity with usage of keywords `cube`. This can be fixed by usage prefix schema or by usage of parenthesis:&lt;br /&gt;
&lt;br /&gt;
    SELECT public.variable(...)&lt;br /&gt;
    SELECT &amp;quot;variable&amp;quot;(...)&lt;br /&gt;
&lt;br /&gt;
Why I don&#039;t propose usage of T-SQL (MSSQL, MySQL) syntax for session variables? There is more reasons (one is objective, last is subjective):&lt;br /&gt;
&lt;br /&gt;
* There is a collision with custom operators - operators `@` or `@@` are already used - usage of `@@` is common&lt;br /&gt;
&lt;br /&gt;
    (2025-09-28 06:57:30) postgres=# create table tab(a int);&lt;br /&gt;
    CREATE TABLE&lt;br /&gt;
    (2025-09-28 06:57:42) postgres=# insert into tab values(10);&lt;br /&gt;
    INSERT 0 1&lt;br /&gt;
    (2025-09-28 06:57:50) postgres=# select @a from tab;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │       10 │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
We can safely use only `:` prefix, but then we can break a psql variables. Similarly we can use `$` prefix, but then we can break query parameters syntax.&lt;br /&gt;
&lt;br /&gt;
* T-SQL variable names looks weird in Postgres (in queries, in PL/pgSQL). Postgres knows only ANSI/SQL identifiers, PL/pgSQL shares these rules too, and this are the PostgreSQL is consistent, and I missing motivation to introduce something new (with the knowledge so problems with possible compatibility breaks should be solved too). &lt;br /&gt;
&lt;br /&gt;
Note: There was a proposal to use table syntax as variable (like table (in memory) variables from T-SQL but only single row). Although it is very effective solution of collisions, I dislike this idea. It increase a complexity of simple expression (or increase inconsistency of some syntax rules). More looks weird for scalar variables (and can be handy for composite variables):&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(var); -- variable is not a table&lt;br /&gt;
    END&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      -- variable a a table (I don&#039;t like it)&lt;br /&gt;
      -- can we use DML? How much rows this kind of variable can hold&lt;br /&gt;
      -- value is row or not?&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, (SELECT var FROM var);&lt;br /&gt;
    END&lt;br /&gt;
    $$&lt;br /&gt;
&lt;br /&gt;
I am not against for introduction of inmemory tables - or more correctly for global temporary tables. Well implemented global temporary tables can be very nice and handy feature. But it is different feature, and should be designed separately. There can be some performance problems - plpgsql can use simple expression evaluation (when expression has not usage of any table). This optimization can be changed or enhanced, but its is change inside complex sensitive code, and it is not consistent. I very like a global temporary tables (that are not implemented in Postgres). Table (and related operations) should to looks like table, variable (and related operations) should to looks like variable. Common perspective how session variables should to looks is based on variables from PL environment. PostgreSQL internally supports &amp;lt;i&amp;gt;transition tables&amp;lt;/i&amp;gt;, and I can imagine to allow this kind of tables outside of triggers too. This is valid use case (and valid functionality), but it is something different than session variables (although there can be some overlap). The implementation of declared tables inside PL/pgSQL can be more easy from high design perspective (interaction between SQL tables and PL/pgSQL is well defined already), but difficult from low level perspective (where to store column statistics and other important data for optimization - same problems like with global temporary tables (without some fake proxy local temp tables)).&lt;br /&gt;
&lt;br /&gt;
   CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
   AS $$&lt;br /&gt;
   DECLARE t TABLE(a int, b int); -- this table is invisible outside fx&lt;br /&gt;
   BEGIN&lt;br /&gt;
     INSERT INTO t VALUES(10,20);&lt;br /&gt;
     INSERT INTO t VALUES(30,40);&lt;br /&gt;
     RAISE NOTICE &#039;%&#039;, (SELECT count(*) FROM t);&lt;br /&gt;
   END;&lt;br /&gt;
   $$ LANGUAGE plpgsql;&lt;br /&gt;
&lt;br /&gt;
Some data languages defined relational variables - common concept of session variables has zero intersection with this concept.&lt;br /&gt;
&lt;br /&gt;
=Issues that any implementation should to solve =&lt;br /&gt;
* possible collision between session variables and other database objects&lt;br /&gt;
* memory cleaning after dropping session variable (when DDL can be reverted)&lt;br /&gt;
* memory invalidation (variable is dropped by one session, but still used in second session)&lt;br /&gt;
&lt;br /&gt;
= ToDo =&lt;br /&gt;
* SECURITY LABEL support - enhancing dummy_seclabel module, sepgsql extension + pg doc and SecLabelSupportsObjectType&lt;/div&gt;</summary>
		<author><name>Okbobcz</name></author>
	</entry>
	<entry>
		<id>https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41825</id>
		<title>Implementation of declarative catalog session variables</title>
		<link rel="alternate" type="text/html" href="https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41825"/>
		<updated>2025-09-28T05:55:53Z</updated>

		<summary type="html">&lt;p&gt;Okbobcz: /* Implementation issues */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Session variables =&lt;br /&gt;
Session variables are database objects that can hold a value across multiple queries inside a session. A design of session variables is varied and more times more databases implement more than just one type of session variables. Unfortunately standard SQL/PSM is not commonly accepted, and a part of this standard related to modules and module&#039;s variables was not implemented (in any widely used product). Very often session variables are a part of the stored procedures environment - but there are exceptions (MySQL session variables or any client side session variables). What I know is that session variables don&#039;t support transactions (for values). It is a plus minus just variable like we know from programming languages.&lt;br /&gt;
&lt;br /&gt;
There is no common design of session variables. Currently any database system has its own proprietary design with proprietary syntax and features. Some systems have more than one different design of session variables. It is hard to compare different designs - any design has different advantages and disadvantages.&lt;br /&gt;
&lt;br /&gt;
== MySQL ==&lt;br /&gt;
Session variables in MySQL have the same syntax like more known T-SQL variables (but all other things are proprietary). MySQL knows two kinds of session variables:&lt;br /&gt;
&lt;br /&gt;
* system variables - use the prefix `@@` or `@@xxx.`&lt;br /&gt;
* user defined variables - use the prefix `@`&lt;br /&gt;
&lt;br /&gt;
System variables can be modified by command `SET`. After keyword `SET` keywords `GLOBAL`, `SESSION` or `PERSIST` can be used. &lt;br /&gt;
Some variables can be used just with keyword `GLOBAL` or `SESSION` and in this case, when the name is prefixed by `@@`, is not&lt;br /&gt;
necessary to specify scope. System variables are defined by MySQL or by an mysql&#039;s plugins.&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL some_config = 100;&lt;br /&gt;
    SET @@same_config = 100;&lt;br /&gt;
&lt;br /&gt;
    SET SESSION sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@SESSION.sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
&lt;br /&gt;
Persistent values are stored in `mysql-auto.conf`.&lt;br /&gt;
&lt;br /&gt;
Reset of system variables can be done by setting to default or assigning from global namespace:&lt;br /&gt;
&lt;br /&gt;
    SET @@sql_mode = DEFAULT;&lt;br /&gt;
    SET @@sql_mode = @@GLOBAL.sql_mode&lt;br /&gt;
&lt;br /&gt;
PostgreSQL equivalent:&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL ; -- there is not similar command in Postgres&lt;br /&gt;
    SET SESSION ; -- SET&lt;br /&gt;
    SET PERSIST ; -- ALTER SYSTEM; SELECT pg_reload_conf();&lt;br /&gt;
    SELECT @@var; -- SELECT current_setting(&#039;var);&lt;br /&gt;
&lt;br /&gt;
User defined variables are defined by assignment&lt;br /&gt;
&lt;br /&gt;
    SET @var = expr [, @var2 = expr [ ... ]];&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
User defined variables can be only int, decimal, float, binary, nonbinary string or NULL - casting from/to any types is implicit and hidden.&lt;br /&gt;
UDV cannot be used as table or column names.&lt;br /&gt;
&lt;br /&gt;
An advantage of T-SQL syntax (with special prefixes) is simple solution of possible identifier collisions. MySQL&lt;br /&gt;
variables are very handy too, and user experience can be good for people with knowledge of T-SQL (MSSQL or Sybase).&lt;br /&gt;
On the other hand, prefix based syntax can be disturbing for users who work with different systems than MSSQL. Type system&lt;br /&gt;
is maybe too simple and can be usafe. Generally the performance of MySQL (or MariaDB) stored procedures is not good,&lt;br /&gt;
and stored procedures are not frequently used. There is not any protection against typo errors. Unassigned user variables&lt;br /&gt;
can be used without error (have  NULL value), so static check of stored procedures is not possible (in this area).&lt;br /&gt;
&lt;br /&gt;
There is not any possibility how to limit an access to user defined variables in MySQL. UDV cannot be protected&lt;br /&gt;
against unwanted usage. Some protection can be only naming convention (all UDV share one name space).&lt;br /&gt;
&lt;br /&gt;
==Postgres==&lt;br /&gt;
Postgres has relatively advanced client side session variables in `psql`. `psql`&#039;s variables use prefix &#039;:&#039;. The implementation is simple. psql&#039;s parser reads tokens from input. When it gets a token related to a variable, then this token is replaced by a value of variable. Implemented syntax supports literal and identifier escaping. psql&#039;s variables can be used as an argument of the `\bind` command.&lt;br /&gt;
&lt;br /&gt;
psql&#039;s variables are typeless client side variables available only inside `psql` or `pgbench`. &lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:40:18) postgres=# \set myvar ahoj&lt;br /&gt;
    (2025-09-25 09:40:33) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:14) postgres=# select :&#039;myvar&#039;;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:24) postgres=# select $1 \bind :myvar \g&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
psql&#039;s command `\set` allows just simply string concatenation - but allows to execute shell command. When&lt;br /&gt;
query is executed by `\gset` command, then result is stored in psql variable.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:41:40) postgres=# \set ctime `date`&lt;br /&gt;
    (2025-09-25 09:43:55) postgres=# \echo :ctime&lt;br /&gt;
    čt 25. září 2025, 09:43:55 CEST&lt;br /&gt;
&lt;br /&gt;
&#039;\set` command is too simple, and writing some complex operations is unreadable or not portable.&lt;br /&gt;
&lt;br /&gt;
It is good enough for writing tests, but it is not generally available (probably nobody expects it), and it is not&lt;br /&gt;
accessible from server side (from stored procedures). There is not simple way, how to access psql variables from&lt;br /&gt;
anonymous  block (proxy custom GUC variable is needed):&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:58:38) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    (2025-09-25 09:58:43) postgres=# select set_config(&#039;myvars.myvar&#039;, :&#039;myvar&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ ahoj       │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 10:00:27) postgres=# do $$ begin raise notice &#039;%&#039;, current_setting(&#039;myvars.myvar&#039;); end $$;&lt;br /&gt;
    NOTICE:  ahoj&lt;br /&gt;
    DO&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PostgreSQL has configuration GUC (Grand Unified Configuration) variables. These variables are primarily designed for PostgreSQL and PostgreSQL extensions configuration, but can be used as session variables (implicit, explicit, typeless). These variables can be set by command `SET` or by function `set_config`, and can be read by command `SHOW` or by function `current_setting`. GUC variables are designed for holding configuration parameters. When they are created from an internal API, then they can be protected for super users, hidden to common users, and can have assigned types (bool, memory, number, interval, string, enum) and values that can be checked before assign. The type system is independent of the PostgreSQL SQL type system - it is used for configuration and that is processed before postgresql sql type system is initialized.&lt;br /&gt;
&lt;br /&gt;
Variables created from SQL should be only &amp;quot;custom&amp;quot;. The name of these variables have to use a prefix and these variables are string only. These variables are commonly used as a workaround for missing server side session variables in Postgres (typeless unprotected unsecure session variables).&lt;br /&gt;
&lt;br /&gt;
Specific feature of Postgres GUC (build-in or custom) is the possibility to specify scope transaction, session or function execution. Another functionality is to assign a default value of a specific parameter with some event - loading configuration, login to role, login to database, start some function. These features are nice for work with configuration, but can be very problematic when GUC variables are used as session variables.&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE FUNCTION fx() &lt;br /&gt;
    RETURNS void AS $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, current_setting(&#039;my.x&#039;);&lt;br /&gt;
    END $$ LANGUAGE plpgsql SET my.x = 20;&lt;br /&gt;
    CREATE FUNCTION&lt;br /&gt;
    (2025-09-27 06:10:37) postgres=# SET my.x = 0; SELECT fx();&lt;br /&gt;
    SET&lt;br /&gt;
    NOTICE:  20&lt;br /&gt;
    ┌────┐&lt;br /&gt;
    │ fx │&lt;br /&gt;
    ╞════╡&lt;br /&gt;
    │    │&lt;br /&gt;
    └────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    (2025-09-27 06:10:39) postgres=# SHOW my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 0    │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
GUC variables are transactional - without possibility to disable it.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-27 06:17:31) postgres=# set my.x = 10;&lt;br /&gt;
    SET&lt;br /&gt;
    (2025-09-27 06:19:42) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:19:46) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, true);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:19:55) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:01) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:20:11) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:13) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:20:38) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:45) postgres=# rollback;&lt;br /&gt;
    ROLLBACK&lt;br /&gt;
    (2025-09-27 06:20:52) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:55) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:22:36) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:22:39) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:22:42) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
So there is no possibility to store to custom GUC some details about an error.&lt;br /&gt;
&lt;br /&gt;
== Oracle ==&lt;br /&gt;
Oracle PL/SQL allows the use of package variables. PL/SQL is based on ADA&lt;br /&gt;
language - and package variables are &amp;quot;global&amp;quot; variables. They are not&lt;br /&gt;
directly visible from SQL, but Oracle allows reduced syntax for functions&lt;br /&gt;
without arguments, so you need to write a wrapper&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE PACKAGE my_package&lt;br /&gt;
    AS&lt;br /&gt;
      FUNCTION get_a RETURN NUMBER;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    /&lt;br /&gt;
    &lt;br /&gt;
    CREATE OR REPLACE PACKAGE BODY my_package&lt;br /&gt;
    AS&lt;br /&gt;
      a  NUMBER(20);&lt;br /&gt;
     &lt;br /&gt;
      FUNCTION get_a&lt;br /&gt;
      RETURN NUMBER&lt;br /&gt;
      IS&lt;br /&gt;
      BEGIN&lt;br /&gt;
        RETURN a;&lt;br /&gt;
      END get_a;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    &lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
Inside SQL the higher priority has SQL, inside non SQL commands like CALL&lt;br /&gt;
or some PL/SQL command, the higher priority has packages.&lt;br /&gt;
&lt;br /&gt;
The Oracle allows both syntax for calling function with zero arguments so&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a() FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Then there is less risk reduction of collision. Package variables persist&lt;br /&gt;
in session&lt;br /&gt;
&lt;br /&gt;
Another possibility is using variables in SQL*Plus (looks like our psql&lt;br /&gt;
variables, with possibility to define type on server side)&lt;br /&gt;
&lt;br /&gt;
The variable should be declared by command VARIABLE and can be accessed by&lt;br /&gt;
syntax :varname in session before usage (maybe this step is optional)&lt;br /&gt;
&lt;br /&gt;
    VARIABLE bv_variable_name VARCHAR2(30)&lt;br /&gt;
    BEGIN&lt;br /&gt;
     :bv_variable_name := &#039;Some Value&#039;;&lt;br /&gt;
    END;&lt;br /&gt;
    &lt;br /&gt;
    SELECT column_name&lt;br /&gt;
      FROM   table_name&lt;br /&gt;
     WHERE  column_name = :bv_variable_name;&lt;br /&gt;
&lt;br /&gt;
== DB2 ==&lt;br /&gt;
The &amp;quot;user defined global variables&amp;quot; are similar to my proposal. The&lt;br /&gt;
differences are different access rights &amp;quot;READ, WRITE&amp;quot; x &amp;quot;SELECT, UPDATE&amp;quot;.&lt;br /&gt;
Because PostgreSQL has SET command for GUC, I introduced LET command (DB2&lt;br /&gt;
uses SET)&lt;br /&gt;
&lt;br /&gt;
Variables are visible in all sessions, but value is private per session.&lt;br /&gt;
Variables are not transactional. The usage is wider than my proposal. Then&lt;br /&gt;
can be changed by commands SET, SELECT INTO or they can be used like OUT&lt;br /&gt;
parameters of procedures. The search path (or some like that) is used for&lt;br /&gt;
variables too, but the variables has less priority than tables/columns.&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE myCounter INT DEFAULT 01;&lt;br /&gt;
    SELECT EMPNO, LASTNAME, CASE WHEN myCounter = 1 THEN SALARY ELSE NULL END&lt;br /&gt;
    FROM EMPLOYEE WHERE WORKDEPT = ’A00’;&lt;br /&gt;
    SET myCounter = 29;&lt;br /&gt;
&lt;br /&gt;
There are (I think) different kinds of variables - accessed by the function&lt;br /&gt;
GETVARIABLE(&#039;name&#039;, &#039;default) - it looks very similar ro our GUC and&lt;br /&gt;
`current_setting` function. These variables can be set by connection&lt;br /&gt;
string, are of varchar type and 10 values are allowed. Built-in session&lt;br /&gt;
variables (configuration) can be accessed by the function GETVARIABLE too.&lt;br /&gt;
&lt;br /&gt;
== MSSQL (T-SQL) ==&lt;br /&gt;
global variables - the access syntax is @@varname, they are used like GUC&lt;br /&gt;
and little bit more - some state informations are there like @@ERROR,&lt;br /&gt;
@@ROWCOUNT or @@IDENTITY&lt;br /&gt;
&lt;br /&gt;
local variables - the access syntax is @varname, and should be declared&lt;br /&gt;
before usage by DECLARE command. The scope is limited to batch or procedure&lt;br /&gt;
or function, where DECLARE command was executed.&lt;br /&gt;
&lt;br /&gt;
    DECLARE @TestVariable AS VARCHAR(100)&lt;br /&gt;
    SET @TestVariable = &#039;Think Green&#039;&lt;br /&gt;
    GO&lt;br /&gt;
    PRINT @TestVariable&lt;br /&gt;
&lt;br /&gt;
This script fails, because PRINT is executed in another batch. So I think&lt;br /&gt;
so MSSQL doesn&#039;t support session variables&lt;br /&gt;
&lt;br /&gt;
There are similar mechanisms like our custom GUC and usage current_setting&lt;br /&gt;
and set_config functions. Generally, in this area is MSSQL very primitive&lt;br /&gt;
&lt;br /&gt;
    EXEC sp_set_session_context &#039;user_id&#039;, 4;&lt;br /&gt;
    SELECT SESSION_CONTEXT(N&#039;user_id&#039;);&lt;br /&gt;
&lt;br /&gt;
== SQL/PSM ==&lt;br /&gt;
Standard introduces a concept of modules that can be joined with schemas.&lt;br /&gt;
The variables are like PLpgSQL, but only local - the only temp tables can&lt;br /&gt;
be defined on module levels. These tables can be accessed only from&lt;br /&gt;
routines assigned to modules. Modules are declarative versions of our&lt;br /&gt;
extensions (if I understand well, I didn&#039;t find any implementation). It&lt;br /&gt;
allows you to overwrite the search patch for routines assigned in the&lt;br /&gt;
module. Variables are not transactional, the priority - variables/columns&lt;br /&gt;
is not specified.&lt;br /&gt;
&lt;br /&gt;
= Types of different session variables =&lt;br /&gt;
* client side (psql, pgadmin, ...) - mostly similar to shell variables (based on string operations)&lt;br /&gt;
* server side&lt;br /&gt;
** declarative&lt;br /&gt;
*** created only for batch (T-SQL)&lt;br /&gt;
*** created as an database object (or part of database object) (Oracle, DB2)&lt;br /&gt;
** created by usage (MySQL)&lt;br /&gt;
** with special syntax (MySQL, T-SQL)&lt;br /&gt;
** with ANSI SQL syntax for identifiers (Oracle, DB2)&lt;br /&gt;
&lt;br /&gt;
= Implementation of session variables in Postgres =&lt;br /&gt;
&lt;br /&gt;
== Possible use cases ==&lt;br /&gt;
* usage of session variables as global variables in PL&lt;br /&gt;
* holding configuration (for PL)&lt;br /&gt;
* holding some more used data in interactive session&lt;br /&gt;
* holding credentials for some RLS and data securing&lt;br /&gt;
* helping with migration from others systems (mainly from Oracle)&lt;br /&gt;
* allow anonymous block parametrization (only postgres)&lt;br /&gt;
&lt;br /&gt;
It is clean so no one design is perfect for all uses cases. MySQL is good enough and maybe handy for interactive work,&lt;br /&gt;
but cannot be used for storing any sensitive data, and it is unsafe. PostgreSQL GUC are perfect for configuration, but&lt;br /&gt;
are not friendly for interactive work, and are very limited for usage in PL as global variables. So no one design is&lt;br /&gt;
perfect, and I can imagine more different designs in one system, that can support some different use cases better.&lt;br /&gt;
&lt;br /&gt;
== Proposal for Postgres - design session variables as database global temporary typed objects with ANSI SQL syntax for identifiers ==&lt;br /&gt;
&lt;br /&gt;
I searched solution that can work with specific Postgres environment:&lt;br /&gt;
&lt;br /&gt;
* Postgres has more than one widely used language for stored procedures PL/pgSQL + PL/Python or PL/Perl&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL is strongly reduced PL/SQL, and PL/SQL is modified ADA language. I am pretty happy with the current scope of PL/SQL - it is domain specific language and it works well. It is a very simple language, and it is easy to learn it. PL/pgSQL has no packages or modules. Instead PostgreSQL schemas are used. But Postgres schemas are specific database objects. Schemas are independent on PL. &lt;br /&gt;
&lt;br /&gt;
So I wanted design that can supports more PL languages, didn&#039;t introduce new complexity to relative simple PL/pgSQL and didn&#039;t introduce functionality that can be redundant with already existing objects.&lt;br /&gt;
I wrote bigger application in PL/pgSQL. For maintaining of this application I wrote plpgsql_lint (later plpgsql_check). So every time I am searching solution that doesn&#039;t block code static analyse. Static analyse requires persistent objects, and then it is direct way to design session variables as database objects (with own system catalog). When session variables are designed as database objects, then using ACL is natural. Oracle package variables are well protected just by package scope. Unfortunately, postgres doesn&#039;t know schema scope. The locality of schema objects is defined by setting of SEARCH_PATH guc, and introduction of new mechanism can be messy. But with ACL the content of variables can be safe too:&lt;br /&gt;
&lt;br /&gt;
    CREATE TEMP VARIABLE x AS int;&lt;br /&gt;
     &lt;br /&gt;
    LET x = 10;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(x);&lt;br /&gt;
    END;&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    SELECT VARIABLE(x);&lt;br /&gt;
&lt;br /&gt;
== Implementation issues ==&lt;br /&gt;
=== Collisions between columns and variables identifiers ===&lt;br /&gt;
There is a new possibility of identifiers collisions between variables and columns identifiers. With ANSI SQL syntax for session variables identifiers there is a problem how to distinct between variable and column. This is known problem in PL/pgSQL or PL/SQL and can be solved by some prioritization or by prohibition of collisions. The prohibition of collisions is solution that almost cleaned this issue from PL/pgSQL. Unfortunately session variables are global objects and then the collision can be in any query, and then the new badly named variable can stop execution of any query. Prioritization variables or columns has own problems - quiet unwanted using variable or column.&lt;br /&gt;
&lt;br /&gt;
    CREATE TABLE tab(a int);&lt;br /&gt;
    CREATE VARIABLE a AS int;&lt;br /&gt;
    &lt;br /&gt;
    SELECT a FROM tab; -- with disallowed collisions, the execution of this query stops when there is a variable named &amp;quot;a&amp;quot;&lt;br /&gt;
    &lt;br /&gt;
    LET a = 1;&lt;br /&gt;
    DELETE FROM tab WHERE a = 1; -- with higher priority for variables all table can be deleted (nobody proposed higher priority for variables).&lt;br /&gt;
    SELECT a FROM tab; -- with higher priority for columns, the variable is not used (quietly)&lt;br /&gt;
&lt;br /&gt;
The prohibition of collision is not absolute protection against possible human errors:&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE _id AS int;&lt;br /&gt;
    LET _id = 10;&lt;br /&gt;
    CREATE TABLE foo(id int);&lt;br /&gt;
    DELETE FROM foo WHERE _id = 10; -- just type, but can have unwanted impact (all rows removed)&lt;br /&gt;
&lt;br /&gt;
These problems are not new. The developers in PL/pgSQL or PL/SQL knows it, but the scope of these risks are limited only to stored procedures. After very very long discussion and lot of designs I introduced variable fences (syntax `VARIABLE(varname)`). Inside of any query the variable can be used only in variable fence. This is 100% solution of collisions, and I think it can be good solution against human errors and unwanted usage of session variable. Now, variable fences are necessary every where inside a query or expressions, but I thinking about possibility to be optional inside PL/pgSQL (far future). This can reduce overhead with porting applications from Oracle, and generally the developers of stored procedures are experienced with described risks (this can be controlled by new `plpgsql.extra_checks`).&lt;br /&gt;
&lt;br /&gt;
A variable fence can be in collisions with custom function &amp;quot;variable&amp;quot;. There is similarity with usage of keywords `cube`. This can be fixed by usage prefix schema or by usage of parenthesis:&lt;br /&gt;
&lt;br /&gt;
    SELECT public.variable(...)&lt;br /&gt;
    SELECT &amp;quot;variable&amp;quot;(...)&lt;br /&gt;
&lt;br /&gt;
Why I don&#039;t propose usage of T-SQL (MSSQL, MySQL) syntax for session variables? There is more reasons (one is objective, last is subjective):&lt;br /&gt;
&lt;br /&gt;
* There is a collision with custom operators - operators `@` or `@@` are already used - usage of `@@` is common&lt;br /&gt;
&lt;br /&gt;
    (2025-09-28 06:57:30) postgres=# create table tab(a int);&lt;br /&gt;
    CREATE TABLE&lt;br /&gt;
    (2025-09-28 06:57:42) postgres=# insert into tab values(10);&lt;br /&gt;
    INSERT 0 1&lt;br /&gt;
    (2025-09-28 06:57:50) postgres=# select @a from tab;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │       10 │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
We can safely use only `:` prefix, but then we can break a psql variables. Similarly we can use `$` prefix, but then we can break query parameters syntax.&lt;br /&gt;
&lt;br /&gt;
* T-SQL variable names looks weird in Postgres (in queries, in PL/pgSQL). Postgres knows only ANSI/SQL identifiers, PL/pgSQL shares these rules too, and this are the PostgreSQL is consistent, and I missing motivation to introduce something new (with the knowledge so problems with possible compatibility breaks should be solved too). &lt;br /&gt;
&lt;br /&gt;
Note: There was a proposal to use table syntax as variable (like table (in memory) variables from T-SQL but only single row). Although it is very effective solution of collisions, I dislike this idea. It increase a complexity of simple expression (or increase inconsistency of some syntax rules). More looks weird for scalar variables (and can be handy for composite variables):&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(var); -- variable is not a table&lt;br /&gt;
    END&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      -- variable a a table (I don&#039;t like it)&lt;br /&gt;
      -- can we use DML? How much rows this kind of variable can hold&lt;br /&gt;
      -- value is row or not?&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, (SELECT var FROM var);&lt;br /&gt;
    END&lt;br /&gt;
    $$&lt;br /&gt;
&lt;br /&gt;
I am not against for introduction of inmemory tables - or more correctly for global temporary tables. Well implemented global temporary tables can be very nice and handy feature. But it is different feature, and should be designed separately. There can be some performance problems - plpgsql can use simple expression evaluation (when expression has not usage of any table). This optimization can be changed or enhanced, but its is change inside complex sensitive code, and it is not consistent. I very like a global temporary tables (that are not implemented in Postgres). Table (and related operations) should to looks like table, variable (and related operations) should to looks like variable. Common perspective how session variables should to looks is based on variables from PL environment. PostgreSQL internally supports &amp;lt;i&amp;gt;transition tables&amp;lt;/i&amp;gt;, and I can imagine to allow this kind of tables outside of triggers too. This is valid use case (and valid functionality), but it is something different than session variables (although there can be some overlap). &lt;br /&gt;
&lt;br /&gt;
   CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
   AS $$&lt;br /&gt;
   DECLARE t TABLE(a int, b int); -- this table is invisible outside fx&lt;br /&gt;
   BEGIN&lt;br /&gt;
     INSERT INTO t VALUES(10,20);&lt;br /&gt;
     INSERT INTO t VALUES(30,40);&lt;br /&gt;
     RAISE NOTICE &#039;%&#039;, (SELECT count(*) FROM t);&lt;br /&gt;
   END;&lt;br /&gt;
   $$ LANGUAGE plpgsql;&lt;br /&gt;
&lt;br /&gt;
Some data languages defined relational variables - common concept of session variables has zero intersection with this concept.&lt;br /&gt;
&lt;br /&gt;
=Issues that any implementation should to solve =&lt;br /&gt;
* possible collision between session variables and other database objects&lt;br /&gt;
* memory cleaning after dropping session variable (when DDL can be reverted)&lt;br /&gt;
* memory invalidation (variable is dropped by one session, but still used in second session)&lt;br /&gt;
&lt;br /&gt;
= ToDo =&lt;br /&gt;
* SECURITY LABEL support - enhancing dummy_seclabel module, sepgsql extension + pg doc and SecLabelSupportsObjectType&lt;/div&gt;</summary>
		<author><name>Okbobcz</name></author>
	</entry>
	<entry>
		<id>https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41824</id>
		<title>Implementation of declarative catalog session variables</title>
		<link rel="alternate" type="text/html" href="https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41824"/>
		<updated>2025-09-28T05:54:46Z</updated>

		<summary type="html">&lt;p&gt;Okbobcz: /* Collisions between columns and variables identifiers */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Session variables =&lt;br /&gt;
Session variables are database objects that can hold a value across multiple queries inside a session. A design of session variables is varied and more times more databases implement more than just one type of session variables. Unfortunately standard SQL/PSM is not commonly accepted, and a part of this standard related to modules and module&#039;s variables was not implemented (in any widely used product). Very often session variables are a part of the stored procedures environment - but there are exceptions (MySQL session variables or any client side session variables). What I know is that session variables don&#039;t support transactions (for values). It is a plus minus just variable like we know from programming languages.&lt;br /&gt;
&lt;br /&gt;
There is no common design of session variables. Currently any database system has its own proprietary design with proprietary syntax and features. Some systems have more than one different design of session variables. It is hard to compare different designs - any design has different advantages and disadvantages.&lt;br /&gt;
&lt;br /&gt;
== MySQL ==&lt;br /&gt;
Session variables in MySQL have the same syntax like more known T-SQL variables (but all other things are proprietary). MySQL knows two kinds of session variables:&lt;br /&gt;
&lt;br /&gt;
* system variables - use the prefix `@@` or `@@xxx.`&lt;br /&gt;
* user defined variables - use the prefix `@`&lt;br /&gt;
&lt;br /&gt;
System variables can be modified by command `SET`. After keyword `SET` keywords `GLOBAL`, `SESSION` or `PERSIST` can be used. &lt;br /&gt;
Some variables can be used just with keyword `GLOBAL` or `SESSION` and in this case, when the name is prefixed by `@@`, is not&lt;br /&gt;
necessary to specify scope. System variables are defined by MySQL or by an mysql&#039;s plugins.&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL some_config = 100;&lt;br /&gt;
    SET @@same_config = 100;&lt;br /&gt;
&lt;br /&gt;
    SET SESSION sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@SESSION.sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
&lt;br /&gt;
Persistent values are stored in `mysql-auto.conf`.&lt;br /&gt;
&lt;br /&gt;
Reset of system variables can be done by setting to default or assigning from global namespace:&lt;br /&gt;
&lt;br /&gt;
    SET @@sql_mode = DEFAULT;&lt;br /&gt;
    SET @@sql_mode = @@GLOBAL.sql_mode&lt;br /&gt;
&lt;br /&gt;
PostgreSQL equivalent:&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL ; -- there is not similar command in Postgres&lt;br /&gt;
    SET SESSION ; -- SET&lt;br /&gt;
    SET PERSIST ; -- ALTER SYSTEM; SELECT pg_reload_conf();&lt;br /&gt;
    SELECT @@var; -- SELECT current_setting(&#039;var);&lt;br /&gt;
&lt;br /&gt;
User defined variables are defined by assignment&lt;br /&gt;
&lt;br /&gt;
    SET @var = expr [, @var2 = expr [ ... ]];&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
User defined variables can be only int, decimal, float, binary, nonbinary string or NULL - casting from/to any types is implicit and hidden.&lt;br /&gt;
UDV cannot be used as table or column names.&lt;br /&gt;
&lt;br /&gt;
An advantage of T-SQL syntax (with special prefixes) is simple solution of possible identifier collisions. MySQL&lt;br /&gt;
variables are very handy too, and user experience can be good for people with knowledge of T-SQL (MSSQL or Sybase).&lt;br /&gt;
On the other hand, prefix based syntax can be disturbing for users who work with different systems than MSSQL. Type system&lt;br /&gt;
is maybe too simple and can be usafe. Generally the performance of MySQL (or MariaDB) stored procedures is not good,&lt;br /&gt;
and stored procedures are not frequently used. There is not any protection against typo errors. Unassigned user variables&lt;br /&gt;
can be used without error (have  NULL value), so static check of stored procedures is not possible (in this area).&lt;br /&gt;
&lt;br /&gt;
There is not any possibility how to limit an access to user defined variables in MySQL. UDV cannot be protected&lt;br /&gt;
against unwanted usage. Some protection can be only naming convention (all UDV share one name space).&lt;br /&gt;
&lt;br /&gt;
==Postgres==&lt;br /&gt;
Postgres has relatively advanced client side session variables in `psql`. `psql`&#039;s variables use prefix &#039;:&#039;. The implementation is simple. psql&#039;s parser reads tokens from input. When it gets a token related to a variable, then this token is replaced by a value of variable. Implemented syntax supports literal and identifier escaping. psql&#039;s variables can be used as an argument of the `\bind` command.&lt;br /&gt;
&lt;br /&gt;
psql&#039;s variables are typeless client side variables available only inside `psql` or `pgbench`. &lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:40:18) postgres=# \set myvar ahoj&lt;br /&gt;
    (2025-09-25 09:40:33) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:14) postgres=# select :&#039;myvar&#039;;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:24) postgres=# select $1 \bind :myvar \g&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
psql&#039;s command `\set` allows just simply string concatenation - but allows to execute shell command. When&lt;br /&gt;
query is executed by `\gset` command, then result is stored in psql variable.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:41:40) postgres=# \set ctime `date`&lt;br /&gt;
    (2025-09-25 09:43:55) postgres=# \echo :ctime&lt;br /&gt;
    čt 25. září 2025, 09:43:55 CEST&lt;br /&gt;
&lt;br /&gt;
&#039;\set` command is too simple, and writing some complex operations is unreadable or not portable.&lt;br /&gt;
&lt;br /&gt;
It is good enough for writing tests, but it is not generally available (probably nobody expects it), and it is not&lt;br /&gt;
accessible from server side (from stored procedures). There is not simple way, how to access psql variables from&lt;br /&gt;
anonymous  block (proxy custom GUC variable is needed):&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:58:38) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    (2025-09-25 09:58:43) postgres=# select set_config(&#039;myvars.myvar&#039;, :&#039;myvar&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ ahoj       │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 10:00:27) postgres=# do $$ begin raise notice &#039;%&#039;, current_setting(&#039;myvars.myvar&#039;); end $$;&lt;br /&gt;
    NOTICE:  ahoj&lt;br /&gt;
    DO&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PostgreSQL has configuration GUC (Grand Unified Configuration) variables. These variables are primarily designed for PostgreSQL and PostgreSQL extensions configuration, but can be used as session variables (implicit, explicit, typeless). These variables can be set by command `SET` or by function `set_config`, and can be read by command `SHOW` or by function `current_setting`. GUC variables are designed for holding configuration parameters. When they are created from an internal API, then they can be protected for super users, hidden to common users, and can have assigned types (bool, memory, number, interval, string, enum) and values that can be checked before assign. The type system is independent of the PostgreSQL SQL type system - it is used for configuration and that is processed before postgresql sql type system is initialized.&lt;br /&gt;
&lt;br /&gt;
Variables created from SQL should be only &amp;quot;custom&amp;quot;. The name of these variables have to use a prefix and these variables are string only. These variables are commonly used as a workaround for missing server side session variables in Postgres (typeless unprotected unsecure session variables).&lt;br /&gt;
&lt;br /&gt;
Specific feature of Postgres GUC (build-in or custom) is the possibility to specify scope transaction, session or function execution. Another functionality is to assign a default value of a specific parameter with some event - loading configuration, login to role, login to database, start some function. These features are nice for work with configuration, but can be very problematic when GUC variables are used as session variables.&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE FUNCTION fx() &lt;br /&gt;
    RETURNS void AS $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, current_setting(&#039;my.x&#039;);&lt;br /&gt;
    END $$ LANGUAGE plpgsql SET my.x = 20;&lt;br /&gt;
    CREATE FUNCTION&lt;br /&gt;
    (2025-09-27 06:10:37) postgres=# SET my.x = 0; SELECT fx();&lt;br /&gt;
    SET&lt;br /&gt;
    NOTICE:  20&lt;br /&gt;
    ┌────┐&lt;br /&gt;
    │ fx │&lt;br /&gt;
    ╞════╡&lt;br /&gt;
    │    │&lt;br /&gt;
    └────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    (2025-09-27 06:10:39) postgres=# SHOW my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 0    │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
GUC variables are transactional - without possibility to disable it.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-27 06:17:31) postgres=# set my.x = 10;&lt;br /&gt;
    SET&lt;br /&gt;
    (2025-09-27 06:19:42) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:19:46) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, true);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:19:55) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:01) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:20:11) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:13) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:20:38) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:45) postgres=# rollback;&lt;br /&gt;
    ROLLBACK&lt;br /&gt;
    (2025-09-27 06:20:52) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:55) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:22:36) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:22:39) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:22:42) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
So there is no possibility to store to custom GUC some details about an error.&lt;br /&gt;
&lt;br /&gt;
== Oracle ==&lt;br /&gt;
Oracle PL/SQL allows the use of package variables. PL/SQL is based on ADA&lt;br /&gt;
language - and package variables are &amp;quot;global&amp;quot; variables. They are not&lt;br /&gt;
directly visible from SQL, but Oracle allows reduced syntax for functions&lt;br /&gt;
without arguments, so you need to write a wrapper&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE PACKAGE my_package&lt;br /&gt;
    AS&lt;br /&gt;
      FUNCTION get_a RETURN NUMBER;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    /&lt;br /&gt;
    &lt;br /&gt;
    CREATE OR REPLACE PACKAGE BODY my_package&lt;br /&gt;
    AS&lt;br /&gt;
      a  NUMBER(20);&lt;br /&gt;
     &lt;br /&gt;
      FUNCTION get_a&lt;br /&gt;
      RETURN NUMBER&lt;br /&gt;
      IS&lt;br /&gt;
      BEGIN&lt;br /&gt;
        RETURN a;&lt;br /&gt;
      END get_a;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    &lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
Inside SQL the higher priority has SQL, inside non SQL commands like CALL&lt;br /&gt;
or some PL/SQL command, the higher priority has packages.&lt;br /&gt;
&lt;br /&gt;
The Oracle allows both syntax for calling function with zero arguments so&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a() FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Then there is less risk reduction of collision. Package variables persist&lt;br /&gt;
in session&lt;br /&gt;
&lt;br /&gt;
Another possibility is using variables in SQL*Plus (looks like our psql&lt;br /&gt;
variables, with possibility to define type on server side)&lt;br /&gt;
&lt;br /&gt;
The variable should be declared by command VARIABLE and can be accessed by&lt;br /&gt;
syntax :varname in session before usage (maybe this step is optional)&lt;br /&gt;
&lt;br /&gt;
    VARIABLE bv_variable_name VARCHAR2(30)&lt;br /&gt;
    BEGIN&lt;br /&gt;
     :bv_variable_name := &#039;Some Value&#039;;&lt;br /&gt;
    END;&lt;br /&gt;
    &lt;br /&gt;
    SELECT column_name&lt;br /&gt;
      FROM   table_name&lt;br /&gt;
     WHERE  column_name = :bv_variable_name;&lt;br /&gt;
&lt;br /&gt;
== DB2 ==&lt;br /&gt;
The &amp;quot;user defined global variables&amp;quot; are similar to my proposal. The&lt;br /&gt;
differences are different access rights &amp;quot;READ, WRITE&amp;quot; x &amp;quot;SELECT, UPDATE&amp;quot;.&lt;br /&gt;
Because PostgreSQL has SET command for GUC, I introduced LET command (DB2&lt;br /&gt;
uses SET)&lt;br /&gt;
&lt;br /&gt;
Variables are visible in all sessions, but value is private per session.&lt;br /&gt;
Variables are not transactional. The usage is wider than my proposal. Then&lt;br /&gt;
can be changed by commands SET, SELECT INTO or they can be used like OUT&lt;br /&gt;
parameters of procedures. The search path (or some like that) is used for&lt;br /&gt;
variables too, but the variables has less priority than tables/columns.&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE myCounter INT DEFAULT 01;&lt;br /&gt;
    SELECT EMPNO, LASTNAME, CASE WHEN myCounter = 1 THEN SALARY ELSE NULL END&lt;br /&gt;
    FROM EMPLOYEE WHERE WORKDEPT = ’A00’;&lt;br /&gt;
    SET myCounter = 29;&lt;br /&gt;
&lt;br /&gt;
There are (I think) different kinds of variables - accessed by the function&lt;br /&gt;
GETVARIABLE(&#039;name&#039;, &#039;default) - it looks very similar ro our GUC and&lt;br /&gt;
`current_setting` function. These variables can be set by connection&lt;br /&gt;
string, are of varchar type and 10 values are allowed. Built-in session&lt;br /&gt;
variables (configuration) can be accessed by the function GETVARIABLE too.&lt;br /&gt;
&lt;br /&gt;
== MSSQL (T-SQL) ==&lt;br /&gt;
global variables - the access syntax is @@varname, they are used like GUC&lt;br /&gt;
and little bit more - some state informations are there like @@ERROR,&lt;br /&gt;
@@ROWCOUNT or @@IDENTITY&lt;br /&gt;
&lt;br /&gt;
local variables - the access syntax is @varname, and should be declared&lt;br /&gt;
before usage by DECLARE command. The scope is limited to batch or procedure&lt;br /&gt;
or function, where DECLARE command was executed.&lt;br /&gt;
&lt;br /&gt;
    DECLARE @TestVariable AS VARCHAR(100)&lt;br /&gt;
    SET @TestVariable = &#039;Think Green&#039;&lt;br /&gt;
    GO&lt;br /&gt;
    PRINT @TestVariable&lt;br /&gt;
&lt;br /&gt;
This script fails, because PRINT is executed in another batch. So I think&lt;br /&gt;
so MSSQL doesn&#039;t support session variables&lt;br /&gt;
&lt;br /&gt;
There are similar mechanisms like our custom GUC and usage current_setting&lt;br /&gt;
and set_config functions. Generally, in this area is MSSQL very primitive&lt;br /&gt;
&lt;br /&gt;
    EXEC sp_set_session_context &#039;user_id&#039;, 4;&lt;br /&gt;
    SELECT SESSION_CONTEXT(N&#039;user_id&#039;);&lt;br /&gt;
&lt;br /&gt;
== SQL/PSM ==&lt;br /&gt;
Standard introduces a concept of modules that can be joined with schemas.&lt;br /&gt;
The variables are like PLpgSQL, but only local - the only temp tables can&lt;br /&gt;
be defined on module levels. These tables can be accessed only from&lt;br /&gt;
routines assigned to modules. Modules are declarative versions of our&lt;br /&gt;
extensions (if I understand well, I didn&#039;t find any implementation). It&lt;br /&gt;
allows you to overwrite the search patch for routines assigned in the&lt;br /&gt;
module. Variables are not transactional, the priority - variables/columns&lt;br /&gt;
is not specified.&lt;br /&gt;
&lt;br /&gt;
= Types of different session variables =&lt;br /&gt;
* client side (psql, pgadmin, ...) - mostly similar to shell variables (based on string operations)&lt;br /&gt;
* server side&lt;br /&gt;
** declarative&lt;br /&gt;
*** created only for batch (T-SQL)&lt;br /&gt;
*** created as an database object (or part of database object) (Oracle, DB2)&lt;br /&gt;
** created by usage (MySQL)&lt;br /&gt;
** with special syntax (MySQL, T-SQL)&lt;br /&gt;
** with ANSI SQL syntax for identifiers (Oracle, DB2)&lt;br /&gt;
&lt;br /&gt;
= Implementation of session variables in Postgres =&lt;br /&gt;
&lt;br /&gt;
== Possible use cases ==&lt;br /&gt;
* usage of session variables as global variables in PL&lt;br /&gt;
* holding configuration (for PL)&lt;br /&gt;
* holding some more used data in interactive session&lt;br /&gt;
* holding credentials for some RLS and data securing&lt;br /&gt;
* helping with migration from others systems (mainly from Oracle)&lt;br /&gt;
* allow anonymous block parametrization (only postgres)&lt;br /&gt;
&lt;br /&gt;
It is clean so no one design is perfect for all uses cases. MySQL is good enough and maybe handy for interactive work,&lt;br /&gt;
but cannot be used for storing any sensitive data, and it is unsafe. PostgreSQL GUC are perfect for configuration, but&lt;br /&gt;
are not friendly for interactive work, and are very limited for usage in PL as global variables. So no one design is&lt;br /&gt;
perfect, and I can imagine more different designs in one system, that can support some different use cases better.&lt;br /&gt;
&lt;br /&gt;
== Proposal for Postgres - design session variables as database global temporary typed objects with ANSI SQL syntax for identifiers ==&lt;br /&gt;
&lt;br /&gt;
I searched solution that can work with specific Postgres environment:&lt;br /&gt;
&lt;br /&gt;
* Postgres has more than one widely used language for stored procedures PL/pgSQL + PL/Python or PL/Perl&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL is strongly reduced PL/SQL, and PL/SQL is modified ADA language. I am pretty happy with the current scope of PL/SQL - it is domain specific language and it works well. It is a very simple language, and it is easy to learn it. PL/pgSQL has no packages or modules. Instead PostgreSQL schemas are used. But Postgres schemas are specific database objects. Schemas are independent on PL. &lt;br /&gt;
&lt;br /&gt;
So I wanted design that can supports more PL languages, didn&#039;t introduce new complexity to relative simple PL/pgSQL and didn&#039;t introduce functionality that can be redundant with already existing objects.&lt;br /&gt;
I wrote bigger application in PL/pgSQL. For maintaining of this application I wrote plpgsql_lint (later plpgsql_check). So every time I am searching solution that doesn&#039;t block code static analyse. Static analyse requires persistent objects, and then it is direct way to design session variables as database objects (with own system catalog). When session variables are designed as database objects, then using ACL is natural. Oracle package variables are well protected just by package scope. Unfortunately, postgres doesn&#039;t know schema scope. The locality of schema objects is defined by setting of SEARCH_PATH guc, and introduction of new mechanism can be messy. But with ACL the content of variables can be safe too:&lt;br /&gt;
&lt;br /&gt;
    CREATE TEMP VARIABLE x AS int;&lt;br /&gt;
     &lt;br /&gt;
    LET x = 10;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(x);&lt;br /&gt;
    END;&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    SELECT VARIABLE(x);&lt;br /&gt;
&lt;br /&gt;
== Implementation issues ==&lt;br /&gt;
=== Collisions between columns and variables identifiers ===&lt;br /&gt;
There is a new possibility of identifiers collisions between variables and columns identifiers. With ANSI SQL syntax for session variables identifiers there is a problem how to distinct between variable and column. This is known problem in PL/pgSQL or PL/SQL and can be solved by some prioritization or by prohibition of collisions. The prohibition of collisions is solution that almost cleaned this issue from PL/pgSQL. Unfortunately session variables are global objects and then the collision can be in any query, and then the new badly named variable can stop execution of any query. Prioritization variables or columns has own problems - quiet unwanted using variable or column.&lt;br /&gt;
&lt;br /&gt;
    CREATE TABLE tab(a int);&lt;br /&gt;
    CREATE VARIABLE a AS int;&lt;br /&gt;
    &lt;br /&gt;
    SELECT a FROM tab; -- with disallowed collisions, the execution of this query stops when there is a variable named &amp;quot;a&amp;quot;&lt;br /&gt;
    &lt;br /&gt;
    LET a = 1;&lt;br /&gt;
    DELETE FROM tab WHERE a = 1; -- with higher priority for variables all table can be deleted (nobody proposed higher priority for variables).&lt;br /&gt;
    SELECT a FROM tab; -- with higher priority for columns, the variable is not used (quietly)&lt;br /&gt;
&lt;br /&gt;
The prohibition of collision is not absolute protection against possible human errors:&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE _id AS int;&lt;br /&gt;
    LET _id = 10;&lt;br /&gt;
    CREATE TABLE foo(id int);&lt;br /&gt;
    DELETE FROM foo WHERE _id = 10; -- just type, but can have unwanted impact (all rows removed)&lt;br /&gt;
&lt;br /&gt;
These problems are not new. The developers in PL/pgSQL or PL/SQL knows it, but the scope of these risks are limited only to stored procedures. After very very long discussion and lot of designs I introduced variable fences (syntax `VARIABLE(varname)`). Inside of any query the variable can be used only in variable fence. This is 100% solution of collisions, and I think it can be good solution against human errors and unwanted usage of session variable. Now, variable fences are necessary every where inside a query or expressions, but I thinking about possibility to be optional inside PL/pgSQL (far future). This can reduce overhead with porting applications from Oracle, and generally the developers of stored procedures are experienced with described risks (this can be controlled by new `plpgsql.extra_checks`).&lt;br /&gt;
&lt;br /&gt;
A variable fence can be in collisions with custom function &amp;quot;variable&amp;quot;. There is similarity with usage of keywords `cube`. This can be fixed by usage prefix schema or by usage of parenthesis:&lt;br /&gt;
&lt;br /&gt;
    SELECT public.variable(...)&lt;br /&gt;
    SELECT &amp;quot;variable&amp;quot;(...)&lt;br /&gt;
&lt;br /&gt;
Why I don&#039;t propose usage of T-SQL (MSSQL, MySQL) syntax for session variables? There is more reasons (one is objective, last is subjective):&lt;br /&gt;
&lt;br /&gt;
* There is a collision with custom operators - operators `@` or `@@` are already used - usage of `@@` is common&lt;br /&gt;
&lt;br /&gt;
    (2025-09-28 06:57:30) postgres=# create table tab(a int);&lt;br /&gt;
    CREATE TABLE&lt;br /&gt;
    (2025-09-28 06:57:42) postgres=# insert into tab values(10);&lt;br /&gt;
    INSERT 0 1&lt;br /&gt;
    (2025-09-28 06:57:50) postgres=# select @a from tab;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │       10 │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
We can safely use only `:` prefix, but then we can break a psql variables. Similarly we can use `$` prefix, but then we can break query parameters syntax.&lt;br /&gt;
&lt;br /&gt;
* T-SQL variable names looks weird in Postgres (in queries, in PL/pgSQL). Postgres knows only ANSI/SQL identifiers, PL/pgSQL shares these rules too, and this are the PostgreSQL is consistent, and I missing motivation to introduce something new (with the knowledge so problems with possible compatibility breaks should be solved too). &lt;br /&gt;
&lt;br /&gt;
Note: There was a proposal to use table syntax as variable (like table (in memory) variables from T-SQL but only single row). Although it is very effective solution of collisions, I dislike this idea. It increase a complexity of simple expression (or increase inconsistency of some syntax rules). More looks weird for scalar variables (and can be handy for composite variables):&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(var); -- variable is not a table&lt;br /&gt;
    END&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      -- variable a a table (I don&#039;t like it)&lt;br /&gt;
      -- can we use DML? How much rows this kind of variable can hold&lt;br /&gt;
      -- value is row or not?&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, (SELECT var FROM var);&lt;br /&gt;
    END&lt;br /&gt;
    $$&lt;br /&gt;
&lt;br /&gt;
I am not against for introduction of inmemory tables - or more correctly for global temporary tables. Well implemented global temporary tables can be very nice and handy feature. But it is different feature, and should be designed separately. There can be some performance problems - plpgsql can use simple expression evaluation (when expression has not usage of any table). This optimization can be changed or enhanced, but its is change inside complex sensitive code, and it is not consistent. I very like a global temporary tables (that are not implemented in Postgres). Table (and related operations) should to looks like table, variable (and related operations) should to looks like variable. Common perspective how session variables should to looks is based on variables from PL environment. PostgreSQL internally supports transition tables, and I can imagine to allow this kind of tables outside of triggers too. This is valid use case (and valid functionality), but it is something different than session variables (although there can be some overlap). &lt;br /&gt;
&lt;br /&gt;
   CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
   AS $$&lt;br /&gt;
   DECLARE t TABLE(a int, b int); -- this table is invisible outside fx&lt;br /&gt;
   BEGIN&lt;br /&gt;
     INSERT INTO t VALUES(10,20);&lt;br /&gt;
     INSERT INTO t VALUES(30,40);&lt;br /&gt;
     RAISE NOTICE &#039;%&#039;, (SELECT count(*) FROM t);&lt;br /&gt;
   END;&lt;br /&gt;
   $$ LANGUAGE plpgsql;&lt;br /&gt;
&lt;br /&gt;
Some data languages defined relational variables - common concept of session variables has zero intersection with this concept.&lt;br /&gt;
&lt;br /&gt;
=Issues that any implementation should to solve =&lt;br /&gt;
* possible collision between session variables and other database objects&lt;br /&gt;
* memory cleaning after dropping session variable (when DDL can be reverted)&lt;br /&gt;
* memory invalidation (variable is dropped by one session, but still used in second session)&lt;br /&gt;
&lt;br /&gt;
= ToDo =&lt;br /&gt;
* SECURITY LABEL support - enhancing dummy_seclabel module, sepgsql extension + pg doc and SecLabelSupportsObjectType&lt;/div&gt;</summary>
		<author><name>Okbobcz</name></author>
	</entry>
	<entry>
		<id>https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41823</id>
		<title>Implementation of declarative catalog session variables</title>
		<link rel="alternate" type="text/html" href="https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41823"/>
		<updated>2025-09-28T05:53:22Z</updated>

		<summary type="html">&lt;p&gt;Okbobcz: /* Collisions between columns and variables identifiers */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Session variables =&lt;br /&gt;
Session variables are database objects that can hold a value across multiple queries inside a session. A design of session variables is varied and more times more databases implement more than just one type of session variables. Unfortunately standard SQL/PSM is not commonly accepted, and a part of this standard related to modules and module&#039;s variables was not implemented (in any widely used product). Very often session variables are a part of the stored procedures environment - but there are exceptions (MySQL session variables or any client side session variables). What I know is that session variables don&#039;t support transactions (for values). It is a plus minus just variable like we know from programming languages.&lt;br /&gt;
&lt;br /&gt;
There is no common design of session variables. Currently any database system has its own proprietary design with proprietary syntax and features. Some systems have more than one different design of session variables. It is hard to compare different designs - any design has different advantages and disadvantages.&lt;br /&gt;
&lt;br /&gt;
== MySQL ==&lt;br /&gt;
Session variables in MySQL have the same syntax like more known T-SQL variables (but all other things are proprietary). MySQL knows two kinds of session variables:&lt;br /&gt;
&lt;br /&gt;
* system variables - use the prefix `@@` or `@@xxx.`&lt;br /&gt;
* user defined variables - use the prefix `@`&lt;br /&gt;
&lt;br /&gt;
System variables can be modified by command `SET`. After keyword `SET` keywords `GLOBAL`, `SESSION` or `PERSIST` can be used. &lt;br /&gt;
Some variables can be used just with keyword `GLOBAL` or `SESSION` and in this case, when the name is prefixed by `@@`, is not&lt;br /&gt;
necessary to specify scope. System variables are defined by MySQL or by an mysql&#039;s plugins.&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL some_config = 100;&lt;br /&gt;
    SET @@same_config = 100;&lt;br /&gt;
&lt;br /&gt;
    SET SESSION sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@SESSION.sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
&lt;br /&gt;
Persistent values are stored in `mysql-auto.conf`.&lt;br /&gt;
&lt;br /&gt;
Reset of system variables can be done by setting to default or assigning from global namespace:&lt;br /&gt;
&lt;br /&gt;
    SET @@sql_mode = DEFAULT;&lt;br /&gt;
    SET @@sql_mode = @@GLOBAL.sql_mode&lt;br /&gt;
&lt;br /&gt;
PostgreSQL equivalent:&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL ; -- there is not similar command in Postgres&lt;br /&gt;
    SET SESSION ; -- SET&lt;br /&gt;
    SET PERSIST ; -- ALTER SYSTEM; SELECT pg_reload_conf();&lt;br /&gt;
    SELECT @@var; -- SELECT current_setting(&#039;var);&lt;br /&gt;
&lt;br /&gt;
User defined variables are defined by assignment&lt;br /&gt;
&lt;br /&gt;
    SET @var = expr [, @var2 = expr [ ... ]];&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
User defined variables can be only int, decimal, float, binary, nonbinary string or NULL - casting from/to any types is implicit and hidden.&lt;br /&gt;
UDV cannot be used as table or column names.&lt;br /&gt;
&lt;br /&gt;
An advantage of T-SQL syntax (with special prefixes) is simple solution of possible identifier collisions. MySQL&lt;br /&gt;
variables are very handy too, and user experience can be good for people with knowledge of T-SQL (MSSQL or Sybase).&lt;br /&gt;
On the other hand, prefix based syntax can be disturbing for users who work with different systems than MSSQL. Type system&lt;br /&gt;
is maybe too simple and can be usafe. Generally the performance of MySQL (or MariaDB) stored procedures is not good,&lt;br /&gt;
and stored procedures are not frequently used. There is not any protection against typo errors. Unassigned user variables&lt;br /&gt;
can be used without error (have  NULL value), so static check of stored procedures is not possible (in this area).&lt;br /&gt;
&lt;br /&gt;
There is not any possibility how to limit an access to user defined variables in MySQL. UDV cannot be protected&lt;br /&gt;
against unwanted usage. Some protection can be only naming convention (all UDV share one name space).&lt;br /&gt;
&lt;br /&gt;
==Postgres==&lt;br /&gt;
Postgres has relatively advanced client side session variables in `psql`. `psql`&#039;s variables use prefix &#039;:&#039;. The implementation is simple. psql&#039;s parser reads tokens from input. When it gets a token related to a variable, then this token is replaced by a value of variable. Implemented syntax supports literal and identifier escaping. psql&#039;s variables can be used as an argument of the `\bind` command.&lt;br /&gt;
&lt;br /&gt;
psql&#039;s variables are typeless client side variables available only inside `psql` or `pgbench`. &lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:40:18) postgres=# \set myvar ahoj&lt;br /&gt;
    (2025-09-25 09:40:33) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:14) postgres=# select :&#039;myvar&#039;;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:24) postgres=# select $1 \bind :myvar \g&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
psql&#039;s command `\set` allows just simply string concatenation - but allows to execute shell command. When&lt;br /&gt;
query is executed by `\gset` command, then result is stored in psql variable.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:41:40) postgres=# \set ctime `date`&lt;br /&gt;
    (2025-09-25 09:43:55) postgres=# \echo :ctime&lt;br /&gt;
    čt 25. září 2025, 09:43:55 CEST&lt;br /&gt;
&lt;br /&gt;
&#039;\set` command is too simple, and writing some complex operations is unreadable or not portable.&lt;br /&gt;
&lt;br /&gt;
It is good enough for writing tests, but it is not generally available (probably nobody expects it), and it is not&lt;br /&gt;
accessible from server side (from stored procedures). There is not simple way, how to access psql variables from&lt;br /&gt;
anonymous  block (proxy custom GUC variable is needed):&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:58:38) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    (2025-09-25 09:58:43) postgres=# select set_config(&#039;myvars.myvar&#039;, :&#039;myvar&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ ahoj       │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 10:00:27) postgres=# do $$ begin raise notice &#039;%&#039;, current_setting(&#039;myvars.myvar&#039;); end $$;&lt;br /&gt;
    NOTICE:  ahoj&lt;br /&gt;
    DO&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PostgreSQL has configuration GUC (Grand Unified Configuration) variables. These variables are primarily designed for PostgreSQL and PostgreSQL extensions configuration, but can be used as session variables (implicit, explicit, typeless). These variables can be set by command `SET` or by function `set_config`, and can be read by command `SHOW` or by function `current_setting`. GUC variables are designed for holding configuration parameters. When they are created from an internal API, then they can be protected for super users, hidden to common users, and can have assigned types (bool, memory, number, interval, string, enum) and values that can be checked before assign. The type system is independent of the PostgreSQL SQL type system - it is used for configuration and that is processed before postgresql sql type system is initialized.&lt;br /&gt;
&lt;br /&gt;
Variables created from SQL should be only &amp;quot;custom&amp;quot;. The name of these variables have to use a prefix and these variables are string only. These variables are commonly used as a workaround for missing server side session variables in Postgres (typeless unprotected unsecure session variables).&lt;br /&gt;
&lt;br /&gt;
Specific feature of Postgres GUC (build-in or custom) is the possibility to specify scope transaction, session or function execution. Another functionality is to assign a default value of a specific parameter with some event - loading configuration, login to role, login to database, start some function. These features are nice for work with configuration, but can be very problematic when GUC variables are used as session variables.&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE FUNCTION fx() &lt;br /&gt;
    RETURNS void AS $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, current_setting(&#039;my.x&#039;);&lt;br /&gt;
    END $$ LANGUAGE plpgsql SET my.x = 20;&lt;br /&gt;
    CREATE FUNCTION&lt;br /&gt;
    (2025-09-27 06:10:37) postgres=# SET my.x = 0; SELECT fx();&lt;br /&gt;
    SET&lt;br /&gt;
    NOTICE:  20&lt;br /&gt;
    ┌────┐&lt;br /&gt;
    │ fx │&lt;br /&gt;
    ╞════╡&lt;br /&gt;
    │    │&lt;br /&gt;
    └────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    (2025-09-27 06:10:39) postgres=# SHOW my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 0    │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
GUC variables are transactional - without possibility to disable it.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-27 06:17:31) postgres=# set my.x = 10;&lt;br /&gt;
    SET&lt;br /&gt;
    (2025-09-27 06:19:42) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:19:46) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, true);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:19:55) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:01) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:20:11) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:13) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:20:38) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:45) postgres=# rollback;&lt;br /&gt;
    ROLLBACK&lt;br /&gt;
    (2025-09-27 06:20:52) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:55) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:22:36) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:22:39) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:22:42) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
So there is no possibility to store to custom GUC some details about an error.&lt;br /&gt;
&lt;br /&gt;
== Oracle ==&lt;br /&gt;
Oracle PL/SQL allows the use of package variables. PL/SQL is based on ADA&lt;br /&gt;
language - and package variables are &amp;quot;global&amp;quot; variables. They are not&lt;br /&gt;
directly visible from SQL, but Oracle allows reduced syntax for functions&lt;br /&gt;
without arguments, so you need to write a wrapper&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE PACKAGE my_package&lt;br /&gt;
    AS&lt;br /&gt;
      FUNCTION get_a RETURN NUMBER;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    /&lt;br /&gt;
    &lt;br /&gt;
    CREATE OR REPLACE PACKAGE BODY my_package&lt;br /&gt;
    AS&lt;br /&gt;
      a  NUMBER(20);&lt;br /&gt;
     &lt;br /&gt;
      FUNCTION get_a&lt;br /&gt;
      RETURN NUMBER&lt;br /&gt;
      IS&lt;br /&gt;
      BEGIN&lt;br /&gt;
        RETURN a;&lt;br /&gt;
      END get_a;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    &lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
Inside SQL the higher priority has SQL, inside non SQL commands like CALL&lt;br /&gt;
or some PL/SQL command, the higher priority has packages.&lt;br /&gt;
&lt;br /&gt;
The Oracle allows both syntax for calling function with zero arguments so&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a() FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Then there is less risk reduction of collision. Package variables persist&lt;br /&gt;
in session&lt;br /&gt;
&lt;br /&gt;
Another possibility is using variables in SQL*Plus (looks like our psql&lt;br /&gt;
variables, with possibility to define type on server side)&lt;br /&gt;
&lt;br /&gt;
The variable should be declared by command VARIABLE and can be accessed by&lt;br /&gt;
syntax :varname in session before usage (maybe this step is optional)&lt;br /&gt;
&lt;br /&gt;
    VARIABLE bv_variable_name VARCHAR2(30)&lt;br /&gt;
    BEGIN&lt;br /&gt;
     :bv_variable_name := &#039;Some Value&#039;;&lt;br /&gt;
    END;&lt;br /&gt;
    &lt;br /&gt;
    SELECT column_name&lt;br /&gt;
      FROM   table_name&lt;br /&gt;
     WHERE  column_name = :bv_variable_name;&lt;br /&gt;
&lt;br /&gt;
== DB2 ==&lt;br /&gt;
The &amp;quot;user defined global variables&amp;quot; are similar to my proposal. The&lt;br /&gt;
differences are different access rights &amp;quot;READ, WRITE&amp;quot; x &amp;quot;SELECT, UPDATE&amp;quot;.&lt;br /&gt;
Because PostgreSQL has SET command for GUC, I introduced LET command (DB2&lt;br /&gt;
uses SET)&lt;br /&gt;
&lt;br /&gt;
Variables are visible in all sessions, but value is private per session.&lt;br /&gt;
Variables are not transactional. The usage is wider than my proposal. Then&lt;br /&gt;
can be changed by commands SET, SELECT INTO or they can be used like OUT&lt;br /&gt;
parameters of procedures. The search path (or some like that) is used for&lt;br /&gt;
variables too, but the variables has less priority than tables/columns.&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE myCounter INT DEFAULT 01;&lt;br /&gt;
    SELECT EMPNO, LASTNAME, CASE WHEN myCounter = 1 THEN SALARY ELSE NULL END&lt;br /&gt;
    FROM EMPLOYEE WHERE WORKDEPT = ’A00’;&lt;br /&gt;
    SET myCounter = 29;&lt;br /&gt;
&lt;br /&gt;
There are (I think) different kinds of variables - accessed by the function&lt;br /&gt;
GETVARIABLE(&#039;name&#039;, &#039;default) - it looks very similar ro our GUC and&lt;br /&gt;
`current_setting` function. These variables can be set by connection&lt;br /&gt;
string, are of varchar type and 10 values are allowed. Built-in session&lt;br /&gt;
variables (configuration) can be accessed by the function GETVARIABLE too.&lt;br /&gt;
&lt;br /&gt;
== MSSQL (T-SQL) ==&lt;br /&gt;
global variables - the access syntax is @@varname, they are used like GUC&lt;br /&gt;
and little bit more - some state informations are there like @@ERROR,&lt;br /&gt;
@@ROWCOUNT or @@IDENTITY&lt;br /&gt;
&lt;br /&gt;
local variables - the access syntax is @varname, and should be declared&lt;br /&gt;
before usage by DECLARE command. The scope is limited to batch or procedure&lt;br /&gt;
or function, where DECLARE command was executed.&lt;br /&gt;
&lt;br /&gt;
    DECLARE @TestVariable AS VARCHAR(100)&lt;br /&gt;
    SET @TestVariable = &#039;Think Green&#039;&lt;br /&gt;
    GO&lt;br /&gt;
    PRINT @TestVariable&lt;br /&gt;
&lt;br /&gt;
This script fails, because PRINT is executed in another batch. So I think&lt;br /&gt;
so MSSQL doesn&#039;t support session variables&lt;br /&gt;
&lt;br /&gt;
There are similar mechanisms like our custom GUC and usage current_setting&lt;br /&gt;
and set_config functions. Generally, in this area is MSSQL very primitive&lt;br /&gt;
&lt;br /&gt;
    EXEC sp_set_session_context &#039;user_id&#039;, 4;&lt;br /&gt;
    SELECT SESSION_CONTEXT(N&#039;user_id&#039;);&lt;br /&gt;
&lt;br /&gt;
== SQL/PSM ==&lt;br /&gt;
Standard introduces a concept of modules that can be joined with schemas.&lt;br /&gt;
The variables are like PLpgSQL, but only local - the only temp tables can&lt;br /&gt;
be defined on module levels. These tables can be accessed only from&lt;br /&gt;
routines assigned to modules. Modules are declarative versions of our&lt;br /&gt;
extensions (if I understand well, I didn&#039;t find any implementation). It&lt;br /&gt;
allows you to overwrite the search patch for routines assigned in the&lt;br /&gt;
module. Variables are not transactional, the priority - variables/columns&lt;br /&gt;
is not specified.&lt;br /&gt;
&lt;br /&gt;
= Types of different session variables =&lt;br /&gt;
* client side (psql, pgadmin, ...) - mostly similar to shell variables (based on string operations)&lt;br /&gt;
* server side&lt;br /&gt;
** declarative&lt;br /&gt;
*** created only for batch (T-SQL)&lt;br /&gt;
*** created as an database object (or part of database object) (Oracle, DB2)&lt;br /&gt;
** created by usage (MySQL)&lt;br /&gt;
** with special syntax (MySQL, T-SQL)&lt;br /&gt;
** with ANSI SQL syntax for identifiers (Oracle, DB2)&lt;br /&gt;
&lt;br /&gt;
= Implementation of session variables in Postgres =&lt;br /&gt;
&lt;br /&gt;
== Possible use cases ==&lt;br /&gt;
* usage of session variables as global variables in PL&lt;br /&gt;
* holding configuration (for PL)&lt;br /&gt;
* holding some more used data in interactive session&lt;br /&gt;
* holding credentials for some RLS and data securing&lt;br /&gt;
* helping with migration from others systems (mainly from Oracle)&lt;br /&gt;
* allow anonymous block parametrization (only postgres)&lt;br /&gt;
&lt;br /&gt;
It is clean so no one design is perfect for all uses cases. MySQL is good enough and maybe handy for interactive work,&lt;br /&gt;
but cannot be used for storing any sensitive data, and it is unsafe. PostgreSQL GUC are perfect for configuration, but&lt;br /&gt;
are not friendly for interactive work, and are very limited for usage in PL as global variables. So no one design is&lt;br /&gt;
perfect, and I can imagine more different designs in one system, that can support some different use cases better.&lt;br /&gt;
&lt;br /&gt;
== Proposal for Postgres - design session variables as database global temporary typed objects with ANSI SQL syntax for identifiers ==&lt;br /&gt;
&lt;br /&gt;
I searched solution that can work with specific Postgres environment:&lt;br /&gt;
&lt;br /&gt;
* Postgres has more than one widely used language for stored procedures PL/pgSQL + PL/Python or PL/Perl&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL is strongly reduced PL/SQL, and PL/SQL is modified ADA language. I am pretty happy with the current scope of PL/SQL - it is domain specific language and it works well. It is a very simple language, and it is easy to learn it. PL/pgSQL has no packages or modules. Instead PostgreSQL schemas are used. But Postgres schemas are specific database objects. Schemas are independent on PL. &lt;br /&gt;
&lt;br /&gt;
So I wanted design that can supports more PL languages, didn&#039;t introduce new complexity to relative simple PL/pgSQL and didn&#039;t introduce functionality that can be redundant with already existing objects.&lt;br /&gt;
I wrote bigger application in PL/pgSQL. For maintaining of this application I wrote plpgsql_lint (later plpgsql_check). So every time I am searching solution that doesn&#039;t block code static analyse. Static analyse requires persistent objects, and then it is direct way to design session variables as database objects (with own system catalog). When session variables are designed as database objects, then using ACL is natural. Oracle package variables are well protected just by package scope. Unfortunately, postgres doesn&#039;t know schema scope. The locality of schema objects is defined by setting of SEARCH_PATH guc, and introduction of new mechanism can be messy. But with ACL the content of variables can be safe too:&lt;br /&gt;
&lt;br /&gt;
    CREATE TEMP VARIABLE x AS int;&lt;br /&gt;
     &lt;br /&gt;
    LET x = 10;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(x);&lt;br /&gt;
    END;&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    SELECT VARIABLE(x);&lt;br /&gt;
&lt;br /&gt;
== Implementation issues ==&lt;br /&gt;
=== Collisions between columns and variables identifiers ===&lt;br /&gt;
There is a new possibility of identifiers collisions between variables and columns identifiers. With ANSI SQL syntax for session variables identifiers there is a problem how to distinct between variable and column. This is known problem in PL/pgSQL or PL/SQL and can be solved by some prioritization or by prohibition of collisions. The prohibition of collisions is solution that almost cleaned this issue from PL/pgSQL. Unfortunately session variables are global objects and then the collision can be in any query, and then the new badly named variable can stop execution of any query. Prioritization variables or columns has own problems - quiet unwanted using variable or column.&lt;br /&gt;
&lt;br /&gt;
    CREATE TABLE tab(a int);&lt;br /&gt;
    CREATE VARIABLE a AS int;&lt;br /&gt;
    &lt;br /&gt;
    SELECT a FROM tab; -- with disallowed collisions, the execution of this query stops when there is a variable named &amp;quot;a&amp;quot;&lt;br /&gt;
    &lt;br /&gt;
    LET a = 1;&lt;br /&gt;
    DELETE FROM tab WHERE a = 1; -- with higher priority for variables all table can be deleted (nobody proposed higher priority for variables).&lt;br /&gt;
    SELECT a FROM tab; -- with higher priority for columns, the variable is not used (quietly)&lt;br /&gt;
&lt;br /&gt;
The prohibition of collision is not absolute protection against possible human errors:&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE _id AS int;&lt;br /&gt;
    LET _id = 10;&lt;br /&gt;
    CREATE TABLE foo(id int);&lt;br /&gt;
    DELETE FROM foo WHERE _id = 10; -- just type, but can have unwanted impact (all rows removed)&lt;br /&gt;
&lt;br /&gt;
These problems are not new. The developers in PL/pgSQL or PL/SQL knows it, but the scope of these risks are limited only to stored procedures. After very very long discussion and lot of designs I introduced variable fences (syntax `VARIABLE(varname)`). Inside of any query the variable can be used only in variable fence. This is 100% solution of collisions, and I think it can be good solution against human errors and unwanted usage of session variable. Now, variable fences are necessary every where inside a query or expressions, but I thinking about possibility to be optional inside PL/pgSQL (far future). This can reduce overhead with porting applications from Oracle, and generally the developers of stored procedures are experienced with described risks (this can be controlled by new `plpgsql.extra_checks`).&lt;br /&gt;
&lt;br /&gt;
A variable fence can be in collisions with custom function &amp;quot;variable&amp;quot;. There is similarity with usage of keywords `cube`. This can be fixed by usage prefix schema or by usage of parenthesis:&lt;br /&gt;
&lt;br /&gt;
    SELECT public.variable(...)&lt;br /&gt;
    SELECT &amp;quot;variable&amp;quot;(...)&lt;br /&gt;
&lt;br /&gt;
Why I don&#039;t propose usage of T-SQL (MSSQL, MySQL) syntax for session variables? There is more reasons (one is objective, last is subjective):&lt;br /&gt;
&lt;br /&gt;
* There is a collision with custom operators - operators `@` or `@@` are already used - usage of `@@` is common&lt;br /&gt;
&lt;br /&gt;
    (2025-09-28 06:57:30) postgres=# create table tab(a int);&lt;br /&gt;
    CREATE TABLE&lt;br /&gt;
    (2025-09-28 06:57:42) postgres=# insert into tab values(10);&lt;br /&gt;
    INSERT 0 1&lt;br /&gt;
    (2025-09-28 06:57:50) postgres=# select @a from tab;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │       10 │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
We can safely use only `:` prefix, but then we can break a psql variables. Similarly we can use `$` prefix, but then we can break query parameters syntax.&lt;br /&gt;
&lt;br /&gt;
* T-SQL variable names looks weird in Postgres (in queries, in PL/pgSQL). Postgres knows only ANSI/SQL identifiers, PL/pgSQL shares these rules too, and this are the PostgreSQL is consistent, and I missing motivation to introduce something new (with the knowledge so problems with possible compatibility breaks should be solved too). &lt;br /&gt;
&lt;br /&gt;
Note: There was a proposal to use table syntax as variable (like table (in memory) variables from T-SQL but only single row). Although it is very effective solution of collisions, I dislike this idea. It increase a complexity of simple expression (or increase inconsistency of some syntax rules). More looks weird for scalar variables (and can be handy for composite variables):&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(var); -- variable is not a table&lt;br /&gt;
    END&lt;br /&gt;
    $$;&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE var AS int;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      -- variable a a table (I don&#039;t like it)&lt;br /&gt;
      -- can we use DML? How much rows this kind of variable can hold&lt;br /&gt;
      -- value is row or not?&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, (SELECT var FROM var);&lt;br /&gt;
    END&lt;br /&gt;
    $$&lt;br /&gt;
&lt;br /&gt;
I am not against for introduction of inmemory tables - or more correctly for global temporary tables. Well implemented global temporary tables can be very nice and handy feature. But it is different feature, and should be designed separately. There can be some performance problems - plpgsql can use simple expression evaluation (when expression has not usage of any table). This optimization can be changed or enhanced, but its is change inside complex sensitive code, and it is not consistent. I very like a global temporary tables (that are not implemented in Postgres). Table (and related operations) should to looks like table, variable (and related operations) should to looks like variable. Common perspective how session variables should to looks is based on variables from PL environment. PostgreSQL internally supports transition tables, and I can imagine to allow this kind of tables outside of triggers too. This is valid use case (and valid functionality), but it is something different than session variables (although there can be some overlap). &lt;br /&gt;
&lt;br /&gt;
   CREATE OR REPLACE FUNCTION fx()&lt;br /&gt;
   AS $$&lt;br /&gt;
   DECLARE t TABLE(a int, b int); -- this table is invisible outside fx&lt;br /&gt;
   BEGIN&lt;br /&gt;
     INSERT INTO t VALUES(10,20);&lt;br /&gt;
     INSERT INTO t VALUES(30,40);&lt;br /&gt;
     RAISE NOTICE &#039;%&#039;, (SELECT count(*) FROM t);&lt;br /&gt;
   END;&lt;br /&gt;
   $$ LANGUAGE plpgsql;&lt;br /&gt;
&lt;br /&gt;
Some data languages defined relational variables - common concept of session variables has zero intersection with this concept.&lt;br /&gt;
&lt;br /&gt;
=Issues that any implementation should to solve =&lt;br /&gt;
* possible collision between session variables and other database objects&lt;br /&gt;
* memory cleaning after dropping session variable (when DDL can be reverted)&lt;br /&gt;
* memory invalidation (variable is dropped by one session, but still used in second session)&lt;br /&gt;
&lt;br /&gt;
= ToDo =&lt;br /&gt;
* SECURITY LABEL support - enhancing dummy_seclabel module, sepgsql extension + pg doc and SecLabelSupportsObjectType&lt;/div&gt;</summary>
		<author><name>Okbobcz</name></author>
	</entry>
	<entry>
		<id>https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41822</id>
		<title>Implementation of declarative catalog session variables</title>
		<link rel="alternate" type="text/html" href="https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41822"/>
		<updated>2025-09-28T05:16:18Z</updated>

		<summary type="html">&lt;p&gt;Okbobcz: /* Proposal for Postgres - design session variables as database global temporary typed objects with ANSI SQL syntax for identifiers */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Session variables =&lt;br /&gt;
Session variables are database objects that can hold a value across multiple queries inside a session. A design of session variables is varied and more times more databases implement more than just one type of session variables. Unfortunately standard SQL/PSM is not commonly accepted, and a part of this standard related to modules and module&#039;s variables was not implemented (in any widely used product). Very often session variables are a part of the stored procedures environment - but there are exceptions (MySQL session variables or any client side session variables). What I know is that session variables don&#039;t support transactions (for values). It is a plus minus just variable like we know from programming languages.&lt;br /&gt;
&lt;br /&gt;
There is no common design of session variables. Currently any database system has its own proprietary design with proprietary syntax and features. Some systems have more than one different design of session variables. It is hard to compare different designs - any design has different advantages and disadvantages.&lt;br /&gt;
&lt;br /&gt;
== MySQL ==&lt;br /&gt;
Session variables in MySQL have the same syntax like more known T-SQL variables (but all other things are proprietary). MySQL knows two kinds of session variables:&lt;br /&gt;
&lt;br /&gt;
* system variables - use the prefix `@@` or `@@xxx.`&lt;br /&gt;
* user defined variables - use the prefix `@`&lt;br /&gt;
&lt;br /&gt;
System variables can be modified by command `SET`. After keyword `SET` keywords `GLOBAL`, `SESSION` or `PERSIST` can be used. &lt;br /&gt;
Some variables can be used just with keyword `GLOBAL` or `SESSION` and in this case, when the name is prefixed by `@@`, is not&lt;br /&gt;
necessary to specify scope. System variables are defined by MySQL or by an mysql&#039;s plugins.&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL some_config = 100;&lt;br /&gt;
    SET @@same_config = 100;&lt;br /&gt;
&lt;br /&gt;
    SET SESSION sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@SESSION.sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
&lt;br /&gt;
Persistent values are stored in `mysql-auto.conf`.&lt;br /&gt;
&lt;br /&gt;
Reset of system variables can be done by setting to default or assigning from global namespace:&lt;br /&gt;
&lt;br /&gt;
    SET @@sql_mode = DEFAULT;&lt;br /&gt;
    SET @@sql_mode = @@GLOBAL.sql_mode&lt;br /&gt;
&lt;br /&gt;
PostgreSQL equivalent:&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL ; -- there is not similar command in Postgres&lt;br /&gt;
    SET SESSION ; -- SET&lt;br /&gt;
    SET PERSIST ; -- ALTER SYSTEM; SELECT pg_reload_conf();&lt;br /&gt;
    SELECT @@var; -- SELECT current_setting(&#039;var);&lt;br /&gt;
&lt;br /&gt;
User defined variables are defined by assignment&lt;br /&gt;
&lt;br /&gt;
    SET @var = expr [, @var2 = expr [ ... ]];&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
User defined variables can be only int, decimal, float, binary, nonbinary string or NULL - casting from/to any types is implicit and hidden.&lt;br /&gt;
UDV cannot be used as table or column names.&lt;br /&gt;
&lt;br /&gt;
An advantage of T-SQL syntax (with special prefixes) is simple solution of possible identifier collisions. MySQL&lt;br /&gt;
variables are very handy too, and user experience can be good for people with knowledge of T-SQL (MSSQL or Sybase).&lt;br /&gt;
On the other hand, prefix based syntax can be disturbing for users who work with different systems than MSSQL. Type system&lt;br /&gt;
is maybe too simple and can be usafe. Generally the performance of MySQL (or MariaDB) stored procedures is not good,&lt;br /&gt;
and stored procedures are not frequently used. There is not any protection against typo errors. Unassigned user variables&lt;br /&gt;
can be used without error (have  NULL value), so static check of stored procedures is not possible (in this area).&lt;br /&gt;
&lt;br /&gt;
There is not any possibility how to limit an access to user defined variables in MySQL. UDV cannot be protected&lt;br /&gt;
against unwanted usage. Some protection can be only naming convention (all UDV share one name space).&lt;br /&gt;
&lt;br /&gt;
==Postgres==&lt;br /&gt;
Postgres has relatively advanced client side session variables in `psql`. `psql`&#039;s variables use prefix &#039;:&#039;. The implementation is simple. psql&#039;s parser reads tokens from input. When it gets a token related to a variable, then this token is replaced by a value of variable. Implemented syntax supports literal and identifier escaping. psql&#039;s variables can be used as an argument of the `\bind` command.&lt;br /&gt;
&lt;br /&gt;
psql&#039;s variables are typeless client side variables available only inside `psql` or `pgbench`. &lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:40:18) postgres=# \set myvar ahoj&lt;br /&gt;
    (2025-09-25 09:40:33) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:14) postgres=# select :&#039;myvar&#039;;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:24) postgres=# select $1 \bind :myvar \g&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
psql&#039;s command `\set` allows just simply string concatenation - but allows to execute shell command. When&lt;br /&gt;
query is executed by `\gset` command, then result is stored in psql variable.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:41:40) postgres=# \set ctime `date`&lt;br /&gt;
    (2025-09-25 09:43:55) postgres=# \echo :ctime&lt;br /&gt;
    čt 25. září 2025, 09:43:55 CEST&lt;br /&gt;
&lt;br /&gt;
&#039;\set` command is too simple, and writing some complex operations is unreadable or not portable.&lt;br /&gt;
&lt;br /&gt;
It is good enough for writing tests, but it is not generally available (probably nobody expects it), and it is not&lt;br /&gt;
accessible from server side (from stored procedures). There is not simple way, how to access psql variables from&lt;br /&gt;
anonymous  block (proxy custom GUC variable is needed):&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:58:38) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    (2025-09-25 09:58:43) postgres=# select set_config(&#039;myvars.myvar&#039;, :&#039;myvar&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ ahoj       │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 10:00:27) postgres=# do $$ begin raise notice &#039;%&#039;, current_setting(&#039;myvars.myvar&#039;); end $$;&lt;br /&gt;
    NOTICE:  ahoj&lt;br /&gt;
    DO&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PostgreSQL has configuration GUC (Grand Unified Configuration) variables. These variables are primarily designed for PostgreSQL and PostgreSQL extensions configuration, but can be used as session variables (implicit, explicit, typeless). These variables can be set by command `SET` or by function `set_config`, and can be read by command `SHOW` or by function `current_setting`. GUC variables are designed for holding configuration parameters. When they are created from an internal API, then they can be protected for super users, hidden to common users, and can have assigned types (bool, memory, number, interval, string, enum) and values that can be checked before assign. The type system is independent of the PostgreSQL SQL type system - it is used for configuration and that is processed before postgresql sql type system is initialized.&lt;br /&gt;
&lt;br /&gt;
Variables created from SQL should be only &amp;quot;custom&amp;quot;. The name of these variables have to use a prefix and these variables are string only. These variables are commonly used as a workaround for missing server side session variables in Postgres (typeless unprotected unsecure session variables).&lt;br /&gt;
&lt;br /&gt;
Specific feature of Postgres GUC (build-in or custom) is the possibility to specify scope transaction, session or function execution. Another functionality is to assign a default value of a specific parameter with some event - loading configuration, login to role, login to database, start some function. These features are nice for work with configuration, but can be very problematic when GUC variables are used as session variables.&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE FUNCTION fx() &lt;br /&gt;
    RETURNS void AS $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, current_setting(&#039;my.x&#039;);&lt;br /&gt;
    END $$ LANGUAGE plpgsql SET my.x = 20;&lt;br /&gt;
    CREATE FUNCTION&lt;br /&gt;
    (2025-09-27 06:10:37) postgres=# SET my.x = 0; SELECT fx();&lt;br /&gt;
    SET&lt;br /&gt;
    NOTICE:  20&lt;br /&gt;
    ┌────┐&lt;br /&gt;
    │ fx │&lt;br /&gt;
    ╞════╡&lt;br /&gt;
    │    │&lt;br /&gt;
    └────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    (2025-09-27 06:10:39) postgres=# SHOW my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 0    │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
GUC variables are transactional - without possibility to disable it.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-27 06:17:31) postgres=# set my.x = 10;&lt;br /&gt;
    SET&lt;br /&gt;
    (2025-09-27 06:19:42) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:19:46) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, true);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:19:55) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:01) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:20:11) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:13) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:20:38) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:45) postgres=# rollback;&lt;br /&gt;
    ROLLBACK&lt;br /&gt;
    (2025-09-27 06:20:52) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:55) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:22:36) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:22:39) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:22:42) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
So there is no possibility to store to custom GUC some details about an error.&lt;br /&gt;
&lt;br /&gt;
== Oracle ==&lt;br /&gt;
Oracle PL/SQL allows the use of package variables. PL/SQL is based on ADA&lt;br /&gt;
language - and package variables are &amp;quot;global&amp;quot; variables. They are not&lt;br /&gt;
directly visible from SQL, but Oracle allows reduced syntax for functions&lt;br /&gt;
without arguments, so you need to write a wrapper&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE PACKAGE my_package&lt;br /&gt;
    AS&lt;br /&gt;
      FUNCTION get_a RETURN NUMBER;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    /&lt;br /&gt;
    &lt;br /&gt;
    CREATE OR REPLACE PACKAGE BODY my_package&lt;br /&gt;
    AS&lt;br /&gt;
      a  NUMBER(20);&lt;br /&gt;
     &lt;br /&gt;
      FUNCTION get_a&lt;br /&gt;
      RETURN NUMBER&lt;br /&gt;
      IS&lt;br /&gt;
      BEGIN&lt;br /&gt;
        RETURN a;&lt;br /&gt;
      END get_a;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    &lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
Inside SQL the higher priority has SQL, inside non SQL commands like CALL&lt;br /&gt;
or some PL/SQL command, the higher priority has packages.&lt;br /&gt;
&lt;br /&gt;
The Oracle allows both syntax for calling function with zero arguments so&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a() FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Then there is less risk reduction of collision. Package variables persist&lt;br /&gt;
in session&lt;br /&gt;
&lt;br /&gt;
Another possibility is using variables in SQL*Plus (looks like our psql&lt;br /&gt;
variables, with possibility to define type on server side)&lt;br /&gt;
&lt;br /&gt;
The variable should be declared by command VARIABLE and can be accessed by&lt;br /&gt;
syntax :varname in session before usage (maybe this step is optional)&lt;br /&gt;
&lt;br /&gt;
    VARIABLE bv_variable_name VARCHAR2(30)&lt;br /&gt;
    BEGIN&lt;br /&gt;
     :bv_variable_name := &#039;Some Value&#039;;&lt;br /&gt;
    END;&lt;br /&gt;
    &lt;br /&gt;
    SELECT column_name&lt;br /&gt;
      FROM   table_name&lt;br /&gt;
     WHERE  column_name = :bv_variable_name;&lt;br /&gt;
&lt;br /&gt;
== DB2 ==&lt;br /&gt;
The &amp;quot;user defined global variables&amp;quot; are similar to my proposal. The&lt;br /&gt;
differences are different access rights &amp;quot;READ, WRITE&amp;quot; x &amp;quot;SELECT, UPDATE&amp;quot;.&lt;br /&gt;
Because PostgreSQL has SET command for GUC, I introduced LET command (DB2&lt;br /&gt;
uses SET)&lt;br /&gt;
&lt;br /&gt;
Variables are visible in all sessions, but value is private per session.&lt;br /&gt;
Variables are not transactional. The usage is wider than my proposal. Then&lt;br /&gt;
can be changed by commands SET, SELECT INTO or they can be used like OUT&lt;br /&gt;
parameters of procedures. The search path (or some like that) is used for&lt;br /&gt;
variables too, but the variables has less priority than tables/columns.&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE myCounter INT DEFAULT 01;&lt;br /&gt;
    SELECT EMPNO, LASTNAME, CASE WHEN myCounter = 1 THEN SALARY ELSE NULL END&lt;br /&gt;
    FROM EMPLOYEE WHERE WORKDEPT = ’A00’;&lt;br /&gt;
    SET myCounter = 29;&lt;br /&gt;
&lt;br /&gt;
There are (I think) different kinds of variables - accessed by the function&lt;br /&gt;
GETVARIABLE(&#039;name&#039;, &#039;default) - it looks very similar ro our GUC and&lt;br /&gt;
`current_setting` function. These variables can be set by connection&lt;br /&gt;
string, are of varchar type and 10 values are allowed. Built-in session&lt;br /&gt;
variables (configuration) can be accessed by the function GETVARIABLE too.&lt;br /&gt;
&lt;br /&gt;
== MSSQL (T-SQL) ==&lt;br /&gt;
global variables - the access syntax is @@varname, they are used like GUC&lt;br /&gt;
and little bit more - some state informations are there like @@ERROR,&lt;br /&gt;
@@ROWCOUNT or @@IDENTITY&lt;br /&gt;
&lt;br /&gt;
local variables - the access syntax is @varname, and should be declared&lt;br /&gt;
before usage by DECLARE command. The scope is limited to batch or procedure&lt;br /&gt;
or function, where DECLARE command was executed.&lt;br /&gt;
&lt;br /&gt;
    DECLARE @TestVariable AS VARCHAR(100)&lt;br /&gt;
    SET @TestVariable = &#039;Think Green&#039;&lt;br /&gt;
    GO&lt;br /&gt;
    PRINT @TestVariable&lt;br /&gt;
&lt;br /&gt;
This script fails, because PRINT is executed in another batch. So I think&lt;br /&gt;
so MSSQL doesn&#039;t support session variables&lt;br /&gt;
&lt;br /&gt;
There are similar mechanisms like our custom GUC and usage current_setting&lt;br /&gt;
and set_config functions. Generally, in this area is MSSQL very primitive&lt;br /&gt;
&lt;br /&gt;
    EXEC sp_set_session_context &#039;user_id&#039;, 4;&lt;br /&gt;
    SELECT SESSION_CONTEXT(N&#039;user_id&#039;);&lt;br /&gt;
&lt;br /&gt;
== SQL/PSM ==&lt;br /&gt;
Standard introduces a concept of modules that can be joined with schemas.&lt;br /&gt;
The variables are like PLpgSQL, but only local - the only temp tables can&lt;br /&gt;
be defined on module levels. These tables can be accessed only from&lt;br /&gt;
routines assigned to modules. Modules are declarative versions of our&lt;br /&gt;
extensions (if I understand well, I didn&#039;t find any implementation). It&lt;br /&gt;
allows you to overwrite the search patch for routines assigned in the&lt;br /&gt;
module. Variables are not transactional, the priority - variables/columns&lt;br /&gt;
is not specified.&lt;br /&gt;
&lt;br /&gt;
= Types of different session variables =&lt;br /&gt;
* client side (psql, pgadmin, ...) - mostly similar to shell variables (based on string operations)&lt;br /&gt;
* server side&lt;br /&gt;
** declarative&lt;br /&gt;
*** created only for batch (T-SQL)&lt;br /&gt;
*** created as an database object (or part of database object) (Oracle, DB2)&lt;br /&gt;
** created by usage (MySQL)&lt;br /&gt;
** with special syntax (MySQL, T-SQL)&lt;br /&gt;
** with ANSI SQL syntax for identifiers (Oracle, DB2)&lt;br /&gt;
&lt;br /&gt;
= Implementation of session variables in Postgres =&lt;br /&gt;
&lt;br /&gt;
== Possible use cases ==&lt;br /&gt;
* usage of session variables as global variables in PL&lt;br /&gt;
* holding configuration (for PL)&lt;br /&gt;
* holding some more used data in interactive session&lt;br /&gt;
* holding credentials for some RLS and data securing&lt;br /&gt;
* helping with migration from others systems (mainly from Oracle)&lt;br /&gt;
* allow anonymous block parametrization (only postgres)&lt;br /&gt;
&lt;br /&gt;
It is clean so no one design is perfect for all uses cases. MySQL is good enough and maybe handy for interactive work,&lt;br /&gt;
but cannot be used for storing any sensitive data, and it is unsafe. PostgreSQL GUC are perfect for configuration, but&lt;br /&gt;
are not friendly for interactive work, and are very limited for usage in PL as global variables. So no one design is&lt;br /&gt;
perfect, and I can imagine more different designs in one system, that can support some different use cases better.&lt;br /&gt;
&lt;br /&gt;
== Proposal for Postgres - design session variables as database global temporary typed objects with ANSI SQL syntax for identifiers ==&lt;br /&gt;
&lt;br /&gt;
I searched solution that can work with specific Postgres environment:&lt;br /&gt;
&lt;br /&gt;
* Postgres has more than one widely used language for stored procedures PL/pgSQL + PL/Python or PL/Perl&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL is strongly reduced PL/SQL, and PL/SQL is modified ADA language. I am pretty happy with the current scope of PL/SQL - it is domain specific language and it works well. It is a very simple language, and it is easy to learn it. PL/pgSQL has no packages or modules. Instead PostgreSQL schemas are used. But Postgres schemas are specific database objects. Schemas are independent on PL. &lt;br /&gt;
&lt;br /&gt;
So I wanted design that can supports more PL languages, didn&#039;t introduce new complexity to relative simple PL/pgSQL and didn&#039;t introduce functionality that can be redundant with already existing objects.&lt;br /&gt;
I wrote bigger application in PL/pgSQL. For maintaining of this application I wrote plpgsql_lint (later plpgsql_check). So every time I am searching solution that doesn&#039;t block code static analyse. Static analyse requires persistent objects, and then it is direct way to design session variables as database objects (with own system catalog). When session variables are designed as database objects, then using ACL is natural. Oracle package variables are well protected just by package scope. Unfortunately, postgres doesn&#039;t know schema scope. The locality of schema objects is defined by setting of SEARCH_PATH guc, and introduction of new mechanism can be messy. But with ACL the content of variables can be safe too:&lt;br /&gt;
&lt;br /&gt;
    CREATE TEMP VARIABLE x AS int;&lt;br /&gt;
     &lt;br /&gt;
    LET x = 10;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(x);&lt;br /&gt;
    END;&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    SELECT VARIABLE(x);&lt;br /&gt;
&lt;br /&gt;
== Implementation issues ==&lt;br /&gt;
=== Collisions between columns and variables identifiers ===&lt;br /&gt;
There is a new possibility of identifiers collisions between variables and columns identifiers. With ANSI SQL syntax for session variables identifiers there is a problem how to distinct between variable and column. This is known problem in PL/pgSQL or PL/SQL and can be solved by some prioritization or by prohibition of collisions. The prohibition of collisions is solution that almost cleaned this issue from PL/pgSQL. Unfortunately session variables are global objects and then the collision can be in any query, and then the new badly named variable can stop execution of any query. Prioritization variables or columns has own problems - quiet unwanted using variable or column.&lt;br /&gt;
&lt;br /&gt;
    CREATE TABLE tab(a int);&lt;br /&gt;
    CREATE VARIABLE a AS int;&lt;br /&gt;
    &lt;br /&gt;
    SELECT a FROM tab; -- with disallowed collisions, the execution of this query stops when there is a variable named &amp;quot;a&amp;quot;&lt;br /&gt;
    &lt;br /&gt;
    LET a = 1;&lt;br /&gt;
    DELETE FROM tab WHERE a = 1; -- with higher priority for variables all table can be deleted (nobody proposed higher priority for variables).&lt;br /&gt;
    SELECT a FROM tab; -- with higher priority for columns, the variable is not used (quietly)&lt;br /&gt;
&lt;br /&gt;
The prohibition of collision is not absolute protection against possible human errors:&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE _id AS int;&lt;br /&gt;
    LET _id = 10;&lt;br /&gt;
    CREATE TABLE foo(id int);&lt;br /&gt;
    DELETE FROM foo WHERE _id = 10; -- just type, but can have unwanted impact (all rows removed)&lt;br /&gt;
&lt;br /&gt;
These problems are not new. The developers in PL/pgSQL or PL/SQL knows it, but the scope of these risks are limited only to stored procedures. After very very long discussion and lot of designs I introduced variable fences (syntax `VARIABLE(varname)`). Inside of any query the variable can be used only in variable fence. This is 100% solution of collisions, and I think it can be good solution against human errors and unwanted usage of session variable. Now, variable fences are necessary every where inside a query or expressions, but I thinking about possibility to be optional inside PL/pgSQL (far future). This can reduce overhead with porting applications from Oracle, and generally the developers of stored procedures are experienced with described risks (this can be controlled by new `plpgsql.extra_checks`).&lt;br /&gt;
&lt;br /&gt;
Why I don&#039;t propose usage of T-SQL (MSSQL, MySQL) syntax for session variables? There is more reasons (one is objective, last is subjective):&lt;br /&gt;
&lt;br /&gt;
* There is a collision with custom operators - operators `@` or `@@` are already used - usage of `@@` is common&lt;br /&gt;
&lt;br /&gt;
    (2025-09-28 06:57:30) postgres=# create table tab(a int);&lt;br /&gt;
    CREATE TABLE&lt;br /&gt;
    (2025-09-28 06:57:42) postgres=# insert into tab values(10);&lt;br /&gt;
    INSERT 0 1&lt;br /&gt;
    (2025-09-28 06:57:50) postgres=# select @a from tab;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │       10 │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
We can safely use only `:` prefix, but then we can break a psql variables.&lt;br /&gt;
&lt;br /&gt;
* T-SQL variable names looks weird in Postgres (in queries, in PL/pgSQL). Postgres knows only ANSI/SQL identifiers, PL/pgSQL shares these rules too, and this are the PostgreSQL is consistent, and I missing motivation to introduce something new (with the knowledge so problems with possible compatibility breaks should be solved too).&lt;br /&gt;
&lt;br /&gt;
=Issues that any implementation should to solve =&lt;br /&gt;
* possible collision between session variables and other database objects&lt;br /&gt;
* memory cleaning after dropping session variable (when DDL can be reverted)&lt;br /&gt;
* memory invalidation (variable is dropped by one session, but still used in second session)&lt;br /&gt;
&lt;br /&gt;
= ToDo =&lt;br /&gt;
* SECURITY LABEL support - enhancing dummy_seclabel module, sepgsql extension + pg doc and SecLabelSupportsObjectType&lt;/div&gt;</summary>
		<author><name>Okbobcz</name></author>
	</entry>
	<entry>
		<id>https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41812</id>
		<title>Implementation of declarative catalog session variables</title>
		<link rel="alternate" type="text/html" href="https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41812"/>
		<updated>2025-09-27T19:24:05Z</updated>

		<summary type="html">&lt;p&gt;Okbobcz: /* Possible use cases */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Session variables =&lt;br /&gt;
Session variables are database objects that can hold a value across multiple queries inside a session. A design of session variables is varied and more times more databases implement more than just one type of session variables. Unfortunately standard SQL/PSM is not commonly accepted, and a part of this standard related to modules and module&#039;s variables was not implemented (in any widely used product). Very often session variables are a part of the stored procedures environment - but there are exceptions (MySQL session variables or any client side session variables). What I know is that session variables don&#039;t support transactions (for values). It is a plus minus just variable like we know from programming languages.&lt;br /&gt;
&lt;br /&gt;
There is no common design of session variables. Currently any database system has its own proprietary design with proprietary syntax and features. Some systems have more than one different design of session variables. It is hard to compare different designs - any design has different advantages and disadvantages.&lt;br /&gt;
&lt;br /&gt;
== MySQL ==&lt;br /&gt;
Session variables in MySQL have the same syntax like more known T-SQL variables (but all other things are proprietary). MySQL knows two kinds of session variables:&lt;br /&gt;
&lt;br /&gt;
* system variables - use the prefix `@@` or `@@xxx.`&lt;br /&gt;
* user defined variables - use the prefix `@`&lt;br /&gt;
&lt;br /&gt;
System variables can be modified by command `SET`. After keyword `SET` keywords `GLOBAL`, `SESSION` or `PERSIST` can be used. &lt;br /&gt;
Some variables can be used just with keyword `GLOBAL` or `SESSION` and in this case, when the name is prefixed by `@@`, is not&lt;br /&gt;
necessary to specify scope. System variables are defined by MySQL or by an mysql&#039;s plugins.&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL some_config = 100;&lt;br /&gt;
    SET @@same_config = 100;&lt;br /&gt;
&lt;br /&gt;
    SET SESSION sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@SESSION.sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
&lt;br /&gt;
Persistent values are stored in `mysql-auto.conf`.&lt;br /&gt;
&lt;br /&gt;
Reset of system variables can be done by setting to default or assigning from global namespace:&lt;br /&gt;
&lt;br /&gt;
    SET @@sql_mode = DEFAULT;&lt;br /&gt;
    SET @@sql_mode = @@GLOBAL.sql_mode&lt;br /&gt;
&lt;br /&gt;
PostgreSQL equivalent:&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL ; -- there is not similar command in Postgres&lt;br /&gt;
    SET SESSION ; -- SET&lt;br /&gt;
    SET PERSIST ; -- ALTER SYSTEM; SELECT pg_reload_conf();&lt;br /&gt;
    SELECT @@var; -- SELECT current_setting(&#039;var);&lt;br /&gt;
&lt;br /&gt;
User defined variables are defined by assignment&lt;br /&gt;
&lt;br /&gt;
    SET @var = expr [, @var2 = expr [ ... ]];&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
User defined variables can be only int, decimal, float, binary, nonbinary string or NULL - casting from/to any types is implicit and hidden.&lt;br /&gt;
UDV cannot be used as table or column names.&lt;br /&gt;
&lt;br /&gt;
An advantage of T-SQL syntax (with special prefixes) is simple solution of possible identifier collisions. MySQL&lt;br /&gt;
variables are very handy too, and user experience can be good for people with knowledge of T-SQL (MSSQL or Sybase).&lt;br /&gt;
On the other hand, prefix based syntax can be disturbing for users who work with different systems than MSSQL. Type system&lt;br /&gt;
is maybe too simple and can be usafe. Generally the performance of MySQL (or MariaDB) stored procedures is not good,&lt;br /&gt;
and stored procedures are not frequently used. There is not any protection against typo errors. Unassigned user variables&lt;br /&gt;
can be used without error (have  NULL value), so static check of stored procedures is not possible (in this area).&lt;br /&gt;
&lt;br /&gt;
There is not any possibility how to limit an access to user defined variables in MySQL. UDV cannot be protected&lt;br /&gt;
against unwanted usage. Some protection can be only naming convention (all UDV share one name space).&lt;br /&gt;
&lt;br /&gt;
==Postgres==&lt;br /&gt;
Postgres has relatively advanced client side session variables in `psql`. `psql`&#039;s variables use prefix &#039;:&#039;. The implementation is simple. psql&#039;s parser reads tokens from input. When it gets a token related to a variable, then this token is replaced by a value of variable. Implemented syntax supports literal and identifier escaping. psql&#039;s variables can be used as an argument of the `\bind` command.&lt;br /&gt;
&lt;br /&gt;
psql&#039;s variables are typeless client side variables available only inside `psql` or `pgbench`. &lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:40:18) postgres=# \set myvar ahoj&lt;br /&gt;
    (2025-09-25 09:40:33) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:14) postgres=# select :&#039;myvar&#039;;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:24) postgres=# select $1 \bind :myvar \g&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
psql&#039;s command `\set` allows just simply string concatenation - but allows to execute shell command. When&lt;br /&gt;
query is executed by `\gset` command, then result is stored in psql variable.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:41:40) postgres=# \set ctime `date`&lt;br /&gt;
    (2025-09-25 09:43:55) postgres=# \echo :ctime&lt;br /&gt;
    čt 25. září 2025, 09:43:55 CEST&lt;br /&gt;
&lt;br /&gt;
&#039;\set` command is too simple, and writing some complex operations is unreadable or not portable.&lt;br /&gt;
&lt;br /&gt;
It is good enough for writing tests, but it is not generally available (probably nobody expects it), and it is not&lt;br /&gt;
accessible from server side (from stored procedures). There is not simple way, how to access psql variables from&lt;br /&gt;
anonymous  block (proxy custom GUC variable is needed):&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:58:38) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    (2025-09-25 09:58:43) postgres=# select set_config(&#039;myvars.myvar&#039;, :&#039;myvar&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ ahoj       │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 10:00:27) postgres=# do $$ begin raise notice &#039;%&#039;, current_setting(&#039;myvars.myvar&#039;); end $$;&lt;br /&gt;
    NOTICE:  ahoj&lt;br /&gt;
    DO&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PostgreSQL has configuration GUC (Grand Unified Configuration) variables. These variables are primarily designed for PostgreSQL and PostgreSQL extensions configuration, but can be used as session variables (implicit, explicit, typeless). These variables can be set by command `SET` or by function `set_config`, and can be read by command `SHOW` or by function `current_setting`. GUC variables are designed for holding configuration parameters. When they are created from an internal API, then they can be protected for super users, hidden to common users, and can have assigned types (bool, memory, number, interval, string, enum) and values that can be checked before assign. The type system is independent of the PostgreSQL SQL type system - it is used for configuration and that is processed before postgresql sql type system is initialized.&lt;br /&gt;
&lt;br /&gt;
Variables created from SQL should be only &amp;quot;custom&amp;quot;. The name of these variables have to use a prefix and these variables are string only. These variables are commonly used as a workaround for missing server side session variables in Postgres (typeless unprotected unsecure session variables).&lt;br /&gt;
&lt;br /&gt;
Specific feature of Postgres GUC (build-in or custom) is the possibility to specify scope transaction, session or function execution. Another functionality is to assign a default value of a specific parameter with some event - loading configuration, login to role, login to database, start some function. These features are nice for work with configuration, but can be very problematic when GUC variables are used as session variables.&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE FUNCTION fx() &lt;br /&gt;
    RETURNS void AS $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, current_setting(&#039;my.x&#039;);&lt;br /&gt;
    END $$ LANGUAGE plpgsql SET my.x = 20;&lt;br /&gt;
    CREATE FUNCTION&lt;br /&gt;
    (2025-09-27 06:10:37) postgres=# SET my.x = 0; SELECT fx();&lt;br /&gt;
    SET&lt;br /&gt;
    NOTICE:  20&lt;br /&gt;
    ┌────┐&lt;br /&gt;
    │ fx │&lt;br /&gt;
    ╞════╡&lt;br /&gt;
    │    │&lt;br /&gt;
    └────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    (2025-09-27 06:10:39) postgres=# SHOW my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 0    │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
GUC variables are transactional - without possibility to disable it.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-27 06:17:31) postgres=# set my.x = 10;&lt;br /&gt;
    SET&lt;br /&gt;
    (2025-09-27 06:19:42) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:19:46) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, true);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:19:55) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:01) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:20:11) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:13) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:20:38) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:45) postgres=# rollback;&lt;br /&gt;
    ROLLBACK&lt;br /&gt;
    (2025-09-27 06:20:52) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:55) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:22:36) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:22:39) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:22:42) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
So there is no possibility to store to custom GUC some details about an error.&lt;br /&gt;
&lt;br /&gt;
== Oracle ==&lt;br /&gt;
Oracle PL/SQL allows the use of package variables. PL/SQL is based on ADA&lt;br /&gt;
language - and package variables are &amp;quot;global&amp;quot; variables. They are not&lt;br /&gt;
directly visible from SQL, but Oracle allows reduced syntax for functions&lt;br /&gt;
without arguments, so you need to write a wrapper&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE PACKAGE my_package&lt;br /&gt;
    AS&lt;br /&gt;
      FUNCTION get_a RETURN NUMBER;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    /&lt;br /&gt;
    &lt;br /&gt;
    CREATE OR REPLACE PACKAGE BODY my_package&lt;br /&gt;
    AS&lt;br /&gt;
      a  NUMBER(20);&lt;br /&gt;
     &lt;br /&gt;
      FUNCTION get_a&lt;br /&gt;
      RETURN NUMBER&lt;br /&gt;
      IS&lt;br /&gt;
      BEGIN&lt;br /&gt;
        RETURN a;&lt;br /&gt;
      END get_a;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    &lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
Inside SQL the higher priority has SQL, inside non SQL commands like CALL&lt;br /&gt;
or some PL/SQL command, the higher priority has packages.&lt;br /&gt;
&lt;br /&gt;
The Oracle allows both syntax for calling function with zero arguments so&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a() FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Then there is less risk reduction of collision. Package variables persist&lt;br /&gt;
in session&lt;br /&gt;
&lt;br /&gt;
Another possibility is using variables in SQL*Plus (looks like our psql&lt;br /&gt;
variables, with possibility to define type on server side)&lt;br /&gt;
&lt;br /&gt;
The variable should be declared by command VARIABLE and can be accessed by&lt;br /&gt;
syntax :varname in session before usage (maybe this step is optional)&lt;br /&gt;
&lt;br /&gt;
    VARIABLE bv_variable_name VARCHAR2(30)&lt;br /&gt;
    BEGIN&lt;br /&gt;
     :bv_variable_name := &#039;Some Value&#039;;&lt;br /&gt;
    END;&lt;br /&gt;
    &lt;br /&gt;
    SELECT column_name&lt;br /&gt;
      FROM   table_name&lt;br /&gt;
     WHERE  column_name = :bv_variable_name;&lt;br /&gt;
&lt;br /&gt;
== DB2 ==&lt;br /&gt;
The &amp;quot;user defined global variables&amp;quot; are similar to my proposal. The&lt;br /&gt;
differences are different access rights &amp;quot;READ, WRITE&amp;quot; x &amp;quot;SELECT, UPDATE&amp;quot;.&lt;br /&gt;
Because PostgreSQL has SET command for GUC, I introduced LET command (DB2&lt;br /&gt;
uses SET)&lt;br /&gt;
&lt;br /&gt;
Variables are visible in all sessions, but value is private per session.&lt;br /&gt;
Variables are not transactional. The usage is wider than my proposal. Then&lt;br /&gt;
can be changed by commands SET, SELECT INTO or they can be used like OUT&lt;br /&gt;
parameters of procedures. The search path (or some like that) is used for&lt;br /&gt;
variables too, but the variables has less priority than tables/columns.&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE myCounter INT DEFAULT 01;&lt;br /&gt;
    SELECT EMPNO, LASTNAME, CASE WHEN myCounter = 1 THEN SALARY ELSE NULL END&lt;br /&gt;
    FROM EMPLOYEE WHERE WORKDEPT = ’A00’;&lt;br /&gt;
    SET myCounter = 29;&lt;br /&gt;
&lt;br /&gt;
There are (I think) different kinds of variables - accessed by the function&lt;br /&gt;
GETVARIABLE(&#039;name&#039;, &#039;default) - it looks very similar ro our GUC and&lt;br /&gt;
`current_setting` function. These variables can be set by connection&lt;br /&gt;
string, are of varchar type and 10 values are allowed. Built-in session&lt;br /&gt;
variables (configuration) can be accessed by the function GETVARIABLE too.&lt;br /&gt;
&lt;br /&gt;
== MSSQL (T-SQL) ==&lt;br /&gt;
global variables - the access syntax is @@varname, they are used like GUC&lt;br /&gt;
and little bit more - some state informations are there like @@ERROR,&lt;br /&gt;
@@ROWCOUNT or @@IDENTITY&lt;br /&gt;
&lt;br /&gt;
local variables - the access syntax is @varname, and should be declared&lt;br /&gt;
before usage by DECLARE command. The scope is limited to batch or procedure&lt;br /&gt;
or function, where DECLARE command was executed.&lt;br /&gt;
&lt;br /&gt;
    DECLARE @TestVariable AS VARCHAR(100)&lt;br /&gt;
    SET @TestVariable = &#039;Think Green&#039;&lt;br /&gt;
    GO&lt;br /&gt;
    PRINT @TestVariable&lt;br /&gt;
&lt;br /&gt;
This script fails, because PRINT is executed in another batch. So I think&lt;br /&gt;
so MSSQL doesn&#039;t support session variables&lt;br /&gt;
&lt;br /&gt;
There are similar mechanisms like our custom GUC and usage current_setting&lt;br /&gt;
and set_config functions. Generally, in this area is MSSQL very primitive&lt;br /&gt;
&lt;br /&gt;
    EXEC sp_set_session_context &#039;user_id&#039;, 4;&lt;br /&gt;
    SELECT SESSION_CONTEXT(N&#039;user_id&#039;);&lt;br /&gt;
&lt;br /&gt;
== SQL/PSM ==&lt;br /&gt;
Standard introduces a concept of modules that can be joined with schemas.&lt;br /&gt;
The variables are like PLpgSQL, but only local - the only temp tables can&lt;br /&gt;
be defined on module levels. These tables can be accessed only from&lt;br /&gt;
routines assigned to modules. Modules are declarative versions of our&lt;br /&gt;
extensions (if I understand well, I didn&#039;t find any implementation). It&lt;br /&gt;
allows you to overwrite the search patch for routines assigned in the&lt;br /&gt;
module. Variables are not transactional, the priority - variables/columns&lt;br /&gt;
is not specified.&lt;br /&gt;
&lt;br /&gt;
= Types of different session variables =&lt;br /&gt;
* client side (psql, pgadmin, ...) - mostly similar to shell variables (based on string operations)&lt;br /&gt;
* server side&lt;br /&gt;
** declarative&lt;br /&gt;
*** created only for batch (T-SQL)&lt;br /&gt;
*** created as an database object (or part of database object) (Oracle, DB2)&lt;br /&gt;
** created by usage (MySQL)&lt;br /&gt;
** with special syntax (MySQL, T-SQL)&lt;br /&gt;
** with ANSI SQL syntax for identifiers (Oracle, DB2)&lt;br /&gt;
&lt;br /&gt;
= Implementation of session variables in Postgres =&lt;br /&gt;
&lt;br /&gt;
== Possible use cases ==&lt;br /&gt;
* usage of session variables as global variables in PL&lt;br /&gt;
* holding configuration (for PL)&lt;br /&gt;
* holding some more used data in interactive session&lt;br /&gt;
* holding credentials for some RLS and data securing&lt;br /&gt;
* helping with migration from others systems (mainly from Oracle)&lt;br /&gt;
* allow anonymous block parametrization (only postgres)&lt;br /&gt;
&lt;br /&gt;
It is clean so no one design is perfect for all uses cases. MySQL is good enough and maybe handy for interactive work,&lt;br /&gt;
but cannot be used for storing any sensitive data, and it is unsafe. PostgreSQL GUC are perfect for configuration, but&lt;br /&gt;
are not friendly for interactive work, and are very limited for usage in PL as global variables. So no one design is&lt;br /&gt;
perfect, and I can imagine more different designs in one system, that can support some different use cases better.&lt;br /&gt;
&lt;br /&gt;
== Proposal for Postgres - design session variables as database global temporary typed objects with ANSI SQL syntax for identifiers ==&lt;br /&gt;
&lt;br /&gt;
I searched solution that can work with specific Postgres environment:&lt;br /&gt;
&lt;br /&gt;
* Postgres has more than one widely used language for stored procedures PL/pgSQL + PL/Python or PL/Perl&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL is strongly reduced PL/SQL, and PL/SQL is modified ADA language. I am pretty happy with the current scope of PL/SQL - it is domain specific language and it works well. It is a very simple language, and it is easy to learn it. PL/pgSQL has no packages or modules. Instead PostgreSQL schemas are used. But Postgres schemas are specific database objects. Schemas are independent on PL. &lt;br /&gt;
&lt;br /&gt;
So I wanted design that can supports more PL languages, didn&#039;t introduce new complexity to relative simple PL/pgSQL and didn&#039;t introduce functionality that can be redundant with already existing objects.&lt;br /&gt;
I wrote bigger application in PL/pgSQL. For maintaining of this application I wrote plpgsql_lint (later plpgsql_check). So every time I am searching solution that doesn&#039;t block code static analyse. Static analyse requires persistent objects, and then it is direct way to design session variables as database objects (with own system catalog). When session variables are designed as database objects, then using ACL is natural. Oracle package variables are well protected just by package scope. Unfortunately, postgres doesn&#039;t know schema scope. The locality of schema objects is defined by setting of SEARCH_PATH guc, and introduction of new mechanism can be messy. But with ACL the content of variables can be safe too:&lt;br /&gt;
&lt;br /&gt;
    CREATE TEMP VARIABLE x AS int;&lt;br /&gt;
     &lt;br /&gt;
    LET x = 10;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(x);&lt;br /&gt;
    END;&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    SELECT VARIABLE(x);&lt;br /&gt;
&lt;br /&gt;
=Issues that any implementation should to solve =&lt;br /&gt;
* possible collision between session variables and other database objects&lt;br /&gt;
* memory cleaning after dropping session variable (when DDL can be reverted)&lt;br /&gt;
* memory invalidation (variable is dropped by one session, but still used in second session)&lt;br /&gt;
&lt;br /&gt;
= ToDo =&lt;br /&gt;
* SECURITY LABEL support - enhancing dummy_seclabel module, sepgsql extension + pg doc and SecLabelSupportsObjectType&lt;/div&gt;</summary>
		<author><name>Okbobcz</name></author>
	</entry>
	<entry>
		<id>https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41811</id>
		<title>Implementation of declarative catalog session variables</title>
		<link rel="alternate" type="text/html" href="https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41811"/>
		<updated>2025-09-27T19:13:39Z</updated>

		<summary type="html">&lt;p&gt;Okbobcz: /* Proposal for Postgres - design session variables as database global temporary typed objects with ANSI SQL syntax for identifiers */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Session variables =&lt;br /&gt;
Session variables are database objects that can hold a value across multiple queries inside a session. A design of session variables is varied and more times more databases implement more than just one type of session variables. Unfortunately standard SQL/PSM is not commonly accepted, and a part of this standard related to modules and module&#039;s variables was not implemented (in any widely used product). Very often session variables are a part of the stored procedures environment - but there are exceptions (MySQL session variables or any client side session variables). What I know is that session variables don&#039;t support transactions (for values). It is a plus minus just variable like we know from programming languages.&lt;br /&gt;
&lt;br /&gt;
There is no common design of session variables. Currently any database system has its own proprietary design with proprietary syntax and features. Some systems have more than one different design of session variables. It is hard to compare different designs - any design has different advantages and disadvantages.&lt;br /&gt;
&lt;br /&gt;
== MySQL ==&lt;br /&gt;
Session variables in MySQL have the same syntax like more known T-SQL variables (but all other things are proprietary). MySQL knows two kinds of session variables:&lt;br /&gt;
&lt;br /&gt;
* system variables - use the prefix `@@` or `@@xxx.`&lt;br /&gt;
* user defined variables - use the prefix `@`&lt;br /&gt;
&lt;br /&gt;
System variables can be modified by command `SET`. After keyword `SET` keywords `GLOBAL`, `SESSION` or `PERSIST` can be used. &lt;br /&gt;
Some variables can be used just with keyword `GLOBAL` or `SESSION` and in this case, when the name is prefixed by `@@`, is not&lt;br /&gt;
necessary to specify scope. System variables are defined by MySQL or by an mysql&#039;s plugins.&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL some_config = 100;&lt;br /&gt;
    SET @@same_config = 100;&lt;br /&gt;
&lt;br /&gt;
    SET SESSION sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@SESSION.sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
&lt;br /&gt;
Persistent values are stored in `mysql-auto.conf`.&lt;br /&gt;
&lt;br /&gt;
Reset of system variables can be done by setting to default or assigning from global namespace:&lt;br /&gt;
&lt;br /&gt;
    SET @@sql_mode = DEFAULT;&lt;br /&gt;
    SET @@sql_mode = @@GLOBAL.sql_mode&lt;br /&gt;
&lt;br /&gt;
PostgreSQL equivalent:&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL ; -- there is not similar command in Postgres&lt;br /&gt;
    SET SESSION ; -- SET&lt;br /&gt;
    SET PERSIST ; -- ALTER SYSTEM; SELECT pg_reload_conf();&lt;br /&gt;
    SELECT @@var; -- SELECT current_setting(&#039;var);&lt;br /&gt;
&lt;br /&gt;
User defined variables are defined by assignment&lt;br /&gt;
&lt;br /&gt;
    SET @var = expr [, @var2 = expr [ ... ]];&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
User defined variables can be only int, decimal, float, binary, nonbinary string or NULL - casting from/to any types is implicit and hidden.&lt;br /&gt;
UDV cannot be used as table or column names.&lt;br /&gt;
&lt;br /&gt;
An advantage of T-SQL syntax (with special prefixes) is simple solution of possible identifier collisions. MySQL&lt;br /&gt;
variables are very handy too, and user experience can be good for people with knowledge of T-SQL (MSSQL or Sybase).&lt;br /&gt;
On the other hand, prefix based syntax can be disturbing for users who work with different systems than MSSQL. Type system&lt;br /&gt;
is maybe too simple and can be usafe. Generally the performance of MySQL (or MariaDB) stored procedures is not good,&lt;br /&gt;
and stored procedures are not frequently used. There is not any protection against typo errors. Unassigned user variables&lt;br /&gt;
can be used without error (have  NULL value), so static check of stored procedures is not possible (in this area).&lt;br /&gt;
&lt;br /&gt;
There is not any possibility how to limit an access to user defined variables in MySQL. UDV cannot be protected&lt;br /&gt;
against unwanted usage. Some protection can be only naming convention (all UDV share one name space).&lt;br /&gt;
&lt;br /&gt;
==Postgres==&lt;br /&gt;
Postgres has relatively advanced client side session variables in `psql`. `psql`&#039;s variables use prefix &#039;:&#039;. The implementation is simple. psql&#039;s parser reads tokens from input. When it gets a token related to a variable, then this token is replaced by a value of variable. Implemented syntax supports literal and identifier escaping. psql&#039;s variables can be used as an argument of the `\bind` command.&lt;br /&gt;
&lt;br /&gt;
psql&#039;s variables are typeless client side variables available only inside `psql` or `pgbench`. &lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:40:18) postgres=# \set myvar ahoj&lt;br /&gt;
    (2025-09-25 09:40:33) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:14) postgres=# select :&#039;myvar&#039;;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:24) postgres=# select $1 \bind :myvar \g&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
psql&#039;s command `\set` allows just simply string concatenation - but allows to execute shell command. When&lt;br /&gt;
query is executed by `\gset` command, then result is stored in psql variable.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:41:40) postgres=# \set ctime `date`&lt;br /&gt;
    (2025-09-25 09:43:55) postgres=# \echo :ctime&lt;br /&gt;
    čt 25. září 2025, 09:43:55 CEST&lt;br /&gt;
&lt;br /&gt;
&#039;\set` command is too simple, and writing some complex operations is unreadable or not portable.&lt;br /&gt;
&lt;br /&gt;
It is good enough for writing tests, but it is not generally available (probably nobody expects it), and it is not&lt;br /&gt;
accessible from server side (from stored procedures). There is not simple way, how to access psql variables from&lt;br /&gt;
anonymous  block (proxy custom GUC variable is needed):&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:58:38) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    (2025-09-25 09:58:43) postgres=# select set_config(&#039;myvars.myvar&#039;, :&#039;myvar&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ ahoj       │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 10:00:27) postgres=# do $$ begin raise notice &#039;%&#039;, current_setting(&#039;myvars.myvar&#039;); end $$;&lt;br /&gt;
    NOTICE:  ahoj&lt;br /&gt;
    DO&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PostgreSQL has configuration GUC (Grand Unified Configuration) variables. These variables are primarily designed for PostgreSQL and PostgreSQL extensions configuration, but can be used as session variables (implicit, explicit, typeless). These variables can be set by command `SET` or by function `set_config`, and can be read by command `SHOW` or by function `current_setting`. GUC variables are designed for holding configuration parameters. When they are created from an internal API, then they can be protected for super users, hidden to common users, and can have assigned types (bool, memory, number, interval, string, enum) and values that can be checked before assign. The type system is independent of the PostgreSQL SQL type system - it is used for configuration and that is processed before postgresql sql type system is initialized.&lt;br /&gt;
&lt;br /&gt;
Variables created from SQL should be only &amp;quot;custom&amp;quot;. The name of these variables have to use a prefix and these variables are string only. These variables are commonly used as a workaround for missing server side session variables in Postgres (typeless unprotected unsecure session variables).&lt;br /&gt;
&lt;br /&gt;
Specific feature of Postgres GUC (build-in or custom) is the possibility to specify scope transaction, session or function execution. Another functionality is to assign a default value of a specific parameter with some event - loading configuration, login to role, login to database, start some function. These features are nice for work with configuration, but can be very problematic when GUC variables are used as session variables.&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE FUNCTION fx() &lt;br /&gt;
    RETURNS void AS $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, current_setting(&#039;my.x&#039;);&lt;br /&gt;
    END $$ LANGUAGE plpgsql SET my.x = 20;&lt;br /&gt;
    CREATE FUNCTION&lt;br /&gt;
    (2025-09-27 06:10:37) postgres=# SET my.x = 0; SELECT fx();&lt;br /&gt;
    SET&lt;br /&gt;
    NOTICE:  20&lt;br /&gt;
    ┌────┐&lt;br /&gt;
    │ fx │&lt;br /&gt;
    ╞════╡&lt;br /&gt;
    │    │&lt;br /&gt;
    └────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    (2025-09-27 06:10:39) postgres=# SHOW my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 0    │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
GUC variables are transactional - without possibility to disable it.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-27 06:17:31) postgres=# set my.x = 10;&lt;br /&gt;
    SET&lt;br /&gt;
    (2025-09-27 06:19:42) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:19:46) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, true);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:19:55) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:01) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:20:11) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:13) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:20:38) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:45) postgres=# rollback;&lt;br /&gt;
    ROLLBACK&lt;br /&gt;
    (2025-09-27 06:20:52) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:55) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:22:36) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:22:39) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:22:42) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
So there is no possibility to store to custom GUC some details about an error.&lt;br /&gt;
&lt;br /&gt;
== Oracle ==&lt;br /&gt;
Oracle PL/SQL allows the use of package variables. PL/SQL is based on ADA&lt;br /&gt;
language - and package variables are &amp;quot;global&amp;quot; variables. They are not&lt;br /&gt;
directly visible from SQL, but Oracle allows reduced syntax for functions&lt;br /&gt;
without arguments, so you need to write a wrapper&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE PACKAGE my_package&lt;br /&gt;
    AS&lt;br /&gt;
      FUNCTION get_a RETURN NUMBER;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    /&lt;br /&gt;
    &lt;br /&gt;
    CREATE OR REPLACE PACKAGE BODY my_package&lt;br /&gt;
    AS&lt;br /&gt;
      a  NUMBER(20);&lt;br /&gt;
     &lt;br /&gt;
      FUNCTION get_a&lt;br /&gt;
      RETURN NUMBER&lt;br /&gt;
      IS&lt;br /&gt;
      BEGIN&lt;br /&gt;
        RETURN a;&lt;br /&gt;
      END get_a;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    &lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
Inside SQL the higher priority has SQL, inside non SQL commands like CALL&lt;br /&gt;
or some PL/SQL command, the higher priority has packages.&lt;br /&gt;
&lt;br /&gt;
The Oracle allows both syntax for calling function with zero arguments so&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a() FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Then there is less risk reduction of collision. Package variables persist&lt;br /&gt;
in session&lt;br /&gt;
&lt;br /&gt;
Another possibility is using variables in SQL*Plus (looks like our psql&lt;br /&gt;
variables, with possibility to define type on server side)&lt;br /&gt;
&lt;br /&gt;
The variable should be declared by command VARIABLE and can be accessed by&lt;br /&gt;
syntax :varname in session before usage (maybe this step is optional)&lt;br /&gt;
&lt;br /&gt;
    VARIABLE bv_variable_name VARCHAR2(30)&lt;br /&gt;
    BEGIN&lt;br /&gt;
     :bv_variable_name := &#039;Some Value&#039;;&lt;br /&gt;
    END;&lt;br /&gt;
    &lt;br /&gt;
    SELECT column_name&lt;br /&gt;
      FROM   table_name&lt;br /&gt;
     WHERE  column_name = :bv_variable_name;&lt;br /&gt;
&lt;br /&gt;
== DB2 ==&lt;br /&gt;
The &amp;quot;user defined global variables&amp;quot; are similar to my proposal. The&lt;br /&gt;
differences are different access rights &amp;quot;READ, WRITE&amp;quot; x &amp;quot;SELECT, UPDATE&amp;quot;.&lt;br /&gt;
Because PostgreSQL has SET command for GUC, I introduced LET command (DB2&lt;br /&gt;
uses SET)&lt;br /&gt;
&lt;br /&gt;
Variables are visible in all sessions, but value is private per session.&lt;br /&gt;
Variables are not transactional. The usage is wider than my proposal. Then&lt;br /&gt;
can be changed by commands SET, SELECT INTO or they can be used like OUT&lt;br /&gt;
parameters of procedures. The search path (or some like that) is used for&lt;br /&gt;
variables too, but the variables has less priority than tables/columns.&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE myCounter INT DEFAULT 01;&lt;br /&gt;
    SELECT EMPNO, LASTNAME, CASE WHEN myCounter = 1 THEN SALARY ELSE NULL END&lt;br /&gt;
    FROM EMPLOYEE WHERE WORKDEPT = ’A00’;&lt;br /&gt;
    SET myCounter = 29;&lt;br /&gt;
&lt;br /&gt;
There are (I think) different kinds of variables - accessed by the function&lt;br /&gt;
GETVARIABLE(&#039;name&#039;, &#039;default) - it looks very similar ro our GUC and&lt;br /&gt;
`current_setting` function. These variables can be set by connection&lt;br /&gt;
string, are of varchar type and 10 values are allowed. Built-in session&lt;br /&gt;
variables (configuration) can be accessed by the function GETVARIABLE too.&lt;br /&gt;
&lt;br /&gt;
== MSSQL (T-SQL) ==&lt;br /&gt;
global variables - the access syntax is @@varname, they are used like GUC&lt;br /&gt;
and little bit more - some state informations are there like @@ERROR,&lt;br /&gt;
@@ROWCOUNT or @@IDENTITY&lt;br /&gt;
&lt;br /&gt;
local variables - the access syntax is @varname, and should be declared&lt;br /&gt;
before usage by DECLARE command. The scope is limited to batch or procedure&lt;br /&gt;
or function, where DECLARE command was executed.&lt;br /&gt;
&lt;br /&gt;
    DECLARE @TestVariable AS VARCHAR(100)&lt;br /&gt;
    SET @TestVariable = &#039;Think Green&#039;&lt;br /&gt;
    GO&lt;br /&gt;
    PRINT @TestVariable&lt;br /&gt;
&lt;br /&gt;
This script fails, because PRINT is executed in another batch. So I think&lt;br /&gt;
so MSSQL doesn&#039;t support session variables&lt;br /&gt;
&lt;br /&gt;
There are similar mechanisms like our custom GUC and usage current_setting&lt;br /&gt;
and set_config functions. Generally, in this area is MSSQL very primitive&lt;br /&gt;
&lt;br /&gt;
    EXEC sp_set_session_context &#039;user_id&#039;, 4;&lt;br /&gt;
    SELECT SESSION_CONTEXT(N&#039;user_id&#039;);&lt;br /&gt;
&lt;br /&gt;
== SQL/PSM ==&lt;br /&gt;
Standard introduces a concept of modules that can be joined with schemas.&lt;br /&gt;
The variables are like PLpgSQL, but only local - the only temp tables can&lt;br /&gt;
be defined on module levels. These tables can be accessed only from&lt;br /&gt;
routines assigned to modules. Modules are declarative versions of our&lt;br /&gt;
extensions (if I understand well, I didn&#039;t find any implementation). It&lt;br /&gt;
allows you to overwrite the search patch for routines assigned in the&lt;br /&gt;
module. Variables are not transactional, the priority - variables/columns&lt;br /&gt;
is not specified.&lt;br /&gt;
&lt;br /&gt;
= Types of different session variables =&lt;br /&gt;
* client side (psql, pgadmin, ...) - mostly similar to shell variables (based on string operations)&lt;br /&gt;
* server side&lt;br /&gt;
** declarative&lt;br /&gt;
*** created only for batch (T-SQL)&lt;br /&gt;
*** created as an database object (or part of database object) (Oracle, DB2)&lt;br /&gt;
** created by usage (MySQL)&lt;br /&gt;
** with special syntax (MySQL, T-SQL)&lt;br /&gt;
** with ANSI SQL syntax for identifiers (Oracle, DB2)&lt;br /&gt;
&lt;br /&gt;
= Implementation of session variables in Postgres =&lt;br /&gt;
&lt;br /&gt;
== Possible use cases ==&lt;br /&gt;
* usage of session variables as global variables in PL&lt;br /&gt;
* holding configuration (for PL)&lt;br /&gt;
* holding some more used data in interactive session&lt;br /&gt;
* holding credentials for some RLS and data securing&lt;br /&gt;
* helping with migration from others systems&lt;br /&gt;
* allow anonymous block parametrization (only postgres)&lt;br /&gt;
&lt;br /&gt;
It is clean so no one design is perfect for all uses cases. MySQL is good enough and maybe handy for interactive work,&lt;br /&gt;
but cannot be used for storing any sensitive data, and it is unsafe. PostgreSQL GUC are perfect for configuration, but&lt;br /&gt;
are not friendly for interactive work, and are very limited for usage in PL as global variables. So no one design is&lt;br /&gt;
perfect, and I can imagine more different designs in one system, that can support some different use cases better.&lt;br /&gt;
&lt;br /&gt;
== Proposal for Postgres - design session variables as database global temporary typed objects with ANSI SQL syntax for identifiers ==&lt;br /&gt;
&lt;br /&gt;
I searched solution that can work with specific Postgres environment:&lt;br /&gt;
&lt;br /&gt;
* Postgres has more than one widely used language for stored procedures PL/pgSQL + PL/Python or PL/Perl&lt;br /&gt;
&lt;br /&gt;
* PL/pgSQL is strongly reduced PL/SQL, and PL/SQL is modified ADA language. I am pretty happy with the current scope of PL/SQL - it is domain specific language and it works well. It is a very simple language, and it is easy to learn it. PL/pgSQL has no packages or modules. Instead PostgreSQL schemas are used. But Postgres schemas are specific database objects. Schemas are independent on PL. &lt;br /&gt;
&lt;br /&gt;
So I wanted design that can supports more PL languages, didn&#039;t introduce new complexity to relative simple PL/pgSQL and didn&#039;t introduce functionality that can be redundant with already existing objects.&lt;br /&gt;
I wrote bigger application in PL/pgSQL. For maintaining of this application I wrote plpgsql_lint (later plpgsql_check). So every time I am searching solution that doesn&#039;t block code static analyse. Static analyse requires persistent objects, and then it is direct way to design session variables as database objects (with own system catalog). When session variables are designed as database objects, then using ACL is natural. Oracle package variables are well protected just by package scope. Unfortunately, postgres doesn&#039;t know schema scope. The locality of schema objects is defined by setting of SEARCH_PATH guc, and introduction of new mechanism can be messy. But with ACL the content of variables can be safe too:&lt;br /&gt;
&lt;br /&gt;
    CREATE TEMP VARIABLE x AS int;&lt;br /&gt;
     &lt;br /&gt;
    LET x = 10;&lt;br /&gt;
    DO $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, VARIABLE(x);&lt;br /&gt;
    END;&lt;br /&gt;
    $$;&lt;br /&gt;
    &lt;br /&gt;
    SELECT VARIABLE(x);&lt;br /&gt;
&lt;br /&gt;
=Issues that any implementation should to solve =&lt;br /&gt;
* possible collision between session variables and other database objects&lt;br /&gt;
* memory cleaning after dropping session variable (when DDL can be reverted)&lt;br /&gt;
* memory invalidation (variable is dropped by one session, but still used in second session)&lt;br /&gt;
&lt;br /&gt;
= ToDo =&lt;br /&gt;
* SECURITY LABEL support - enhancing dummy_seclabel module, sepgsql extension + pg doc and SecLabelSupportsObjectType&lt;/div&gt;</summary>
		<author><name>Okbobcz</name></author>
	</entry>
	<entry>
		<id>https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41810</id>
		<title>Implementation of declarative catalog session variables</title>
		<link rel="alternate" type="text/html" href="https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41810"/>
		<updated>2025-09-27T18:46:08Z</updated>

		<summary type="html">&lt;p&gt;Okbobcz: /* Proposal for Postgres - design session variables as global temporary typed objects with ANSI SQL syntax for identifiers */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Session variables =&lt;br /&gt;
Session variables are database objects that can hold a value across multiple queries inside a session. A design of session variables is varied and more times more databases implement more than just one type of session variables. Unfortunately standard SQL/PSM is not commonly accepted, and a part of this standard related to modules and module&#039;s variables was not implemented (in any widely used product). Very often session variables are a part of the stored procedures environment - but there are exceptions (MySQL session variables or any client side session variables). What I know is that session variables don&#039;t support transactions (for values). It is a plus minus just variable like we know from programming languages.&lt;br /&gt;
&lt;br /&gt;
There is no common design of session variables. Currently any database system has its own proprietary design with proprietary syntax and features. Some systems have more than one different design of session variables. It is hard to compare different designs - any design has different advantages and disadvantages.&lt;br /&gt;
&lt;br /&gt;
== MySQL ==&lt;br /&gt;
Session variables in MySQL have the same syntax like more known T-SQL variables (but all other things are proprietary). MySQL knows two kinds of session variables:&lt;br /&gt;
&lt;br /&gt;
* system variables - use the prefix `@@` or `@@xxx.`&lt;br /&gt;
* user defined variables - use the prefix `@`&lt;br /&gt;
&lt;br /&gt;
System variables can be modified by command `SET`. After keyword `SET` keywords `GLOBAL`, `SESSION` or `PERSIST` can be used. &lt;br /&gt;
Some variables can be used just with keyword `GLOBAL` or `SESSION` and in this case, when the name is prefixed by `@@`, is not&lt;br /&gt;
necessary to specify scope. System variables are defined by MySQL or by an mysql&#039;s plugins.&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL some_config = 100;&lt;br /&gt;
    SET @@same_config = 100;&lt;br /&gt;
&lt;br /&gt;
    SET SESSION sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@SESSION.sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
&lt;br /&gt;
Persistent values are stored in `mysql-auto.conf`.&lt;br /&gt;
&lt;br /&gt;
Reset of system variables can be done by setting to default or assigning from global namespace:&lt;br /&gt;
&lt;br /&gt;
    SET @@sql_mode = DEFAULT;&lt;br /&gt;
    SET @@sql_mode = @@GLOBAL.sql_mode&lt;br /&gt;
&lt;br /&gt;
PostgreSQL equivalent:&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL ; -- there is not similar command in Postgres&lt;br /&gt;
    SET SESSION ; -- SET&lt;br /&gt;
    SET PERSIST ; -- ALTER SYSTEM; SELECT pg_reload_conf();&lt;br /&gt;
    SELECT @@var; -- SELECT current_setting(&#039;var);&lt;br /&gt;
&lt;br /&gt;
User defined variables are defined by assignment&lt;br /&gt;
&lt;br /&gt;
    SET @var = expr [, @var2 = expr [ ... ]];&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
User defined variables can be only int, decimal, float, binary, nonbinary string or NULL - casting from/to any types is implicit and hidden.&lt;br /&gt;
UDV cannot be used as table or column names.&lt;br /&gt;
&lt;br /&gt;
An advantage of T-SQL syntax (with special prefixes) is simple solution of possible identifier collisions. MySQL&lt;br /&gt;
variables are very handy too, and user experience can be good for people with knowledge of T-SQL (MSSQL or Sybase).&lt;br /&gt;
On the other hand, prefix based syntax can be disturbing for users who work with different systems than MSSQL. Type system&lt;br /&gt;
is maybe too simple and can be usafe. Generally the performance of MySQL (or MariaDB) stored procedures is not good,&lt;br /&gt;
and stored procedures are not frequently used. There is not any protection against typo errors. Unassigned user variables&lt;br /&gt;
can be used without error (have  NULL value), so static check of stored procedures is not possible (in this area).&lt;br /&gt;
&lt;br /&gt;
There is not any possibility how to limit an access to user defined variables in MySQL. UDV cannot be protected&lt;br /&gt;
against unwanted usage. Some protection can be only naming convention (all UDV share one name space).&lt;br /&gt;
&lt;br /&gt;
==Postgres==&lt;br /&gt;
Postgres has relatively advanced client side session variables in `psql`. `psql`&#039;s variables use prefix &#039;:&#039;. The implementation is simple. psql&#039;s parser reads tokens from input. When it gets a token related to a variable, then this token is replaced by a value of variable. Implemented syntax supports literal and identifier escaping. psql&#039;s variables can be used as an argument of the `\bind` command.&lt;br /&gt;
&lt;br /&gt;
psql&#039;s variables are typeless client side variables available only inside `psql` or `pgbench`. &lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:40:18) postgres=# \set myvar ahoj&lt;br /&gt;
    (2025-09-25 09:40:33) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:14) postgres=# select :&#039;myvar&#039;;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:24) postgres=# select $1 \bind :myvar \g&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
psql&#039;s command `\set` allows just simply string concatenation - but allows to execute shell command. When&lt;br /&gt;
query is executed by `\gset` command, then result is stored in psql variable.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:41:40) postgres=# \set ctime `date`&lt;br /&gt;
    (2025-09-25 09:43:55) postgres=# \echo :ctime&lt;br /&gt;
    čt 25. září 2025, 09:43:55 CEST&lt;br /&gt;
&lt;br /&gt;
&#039;\set` command is too simple, and writing some complex operations is unreadable or not portable.&lt;br /&gt;
&lt;br /&gt;
It is good enough for writing tests, but it is not generally available (probably nobody expects it), and it is not&lt;br /&gt;
accessible from server side (from stored procedures). There is not simple way, how to access psql variables from&lt;br /&gt;
anonymous  block (proxy custom GUC variable is needed):&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:58:38) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    (2025-09-25 09:58:43) postgres=# select set_config(&#039;myvars.myvar&#039;, :&#039;myvar&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ ahoj       │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 10:00:27) postgres=# do $$ begin raise notice &#039;%&#039;, current_setting(&#039;myvars.myvar&#039;); end $$;&lt;br /&gt;
    NOTICE:  ahoj&lt;br /&gt;
    DO&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PostgreSQL has configuration GUC (Grand Unified Configuration) variables. These variables are primarily designed for PostgreSQL and PostgreSQL extensions configuration, but can be used as session variables (implicit, explicit, typeless). These variables can be set by command `SET` or by function `set_config`, and can be read by command `SHOW` or by function `current_setting`. GUC variables are designed for holding configuration parameters. When they are created from an internal API, then they can be protected for super users, hidden to common users, and can have assigned types (bool, memory, number, interval, string, enum) and values that can be checked before assign. The type system is independent of the PostgreSQL SQL type system - it is used for configuration and that is processed before postgresql sql type system is initialized.&lt;br /&gt;
&lt;br /&gt;
Variables created from SQL should be only &amp;quot;custom&amp;quot;. The name of these variables have to use a prefix and these variables are string only. These variables are commonly used as a workaround for missing server side session variables in Postgres (typeless unprotected unsecure session variables).&lt;br /&gt;
&lt;br /&gt;
Specific feature of Postgres GUC (build-in or custom) is the possibility to specify scope transaction, session or function execution. Another functionality is to assign a default value of a specific parameter with some event - loading configuration, login to role, login to database, start some function. These features are nice for work with configuration, but can be very problematic when GUC variables are used as session variables.&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE FUNCTION fx() &lt;br /&gt;
    RETURNS void AS $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, current_setting(&#039;my.x&#039;);&lt;br /&gt;
    END $$ LANGUAGE plpgsql SET my.x = 20;&lt;br /&gt;
    CREATE FUNCTION&lt;br /&gt;
    (2025-09-27 06:10:37) postgres=# SET my.x = 0; SELECT fx();&lt;br /&gt;
    SET&lt;br /&gt;
    NOTICE:  20&lt;br /&gt;
    ┌────┐&lt;br /&gt;
    │ fx │&lt;br /&gt;
    ╞════╡&lt;br /&gt;
    │    │&lt;br /&gt;
    └────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    (2025-09-27 06:10:39) postgres=# SHOW my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 0    │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
GUC variables are transactional - without possibility to disable it.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-27 06:17:31) postgres=# set my.x = 10;&lt;br /&gt;
    SET&lt;br /&gt;
    (2025-09-27 06:19:42) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:19:46) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, true);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:19:55) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:01) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:20:11) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:13) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:20:38) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:45) postgres=# rollback;&lt;br /&gt;
    ROLLBACK&lt;br /&gt;
    (2025-09-27 06:20:52) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:55) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:22:36) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:22:39) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:22:42) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
So there is no possibility to store to custom GUC some details about an error.&lt;br /&gt;
&lt;br /&gt;
== Oracle ==&lt;br /&gt;
Oracle PL/SQL allows the use of package variables. PL/SQL is based on ADA&lt;br /&gt;
language - and package variables are &amp;quot;global&amp;quot; variables. They are not&lt;br /&gt;
directly visible from SQL, but Oracle allows reduced syntax for functions&lt;br /&gt;
without arguments, so you need to write a wrapper&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE PACKAGE my_package&lt;br /&gt;
    AS&lt;br /&gt;
      FUNCTION get_a RETURN NUMBER;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    /&lt;br /&gt;
    &lt;br /&gt;
    CREATE OR REPLACE PACKAGE BODY my_package&lt;br /&gt;
    AS&lt;br /&gt;
      a  NUMBER(20);&lt;br /&gt;
     &lt;br /&gt;
      FUNCTION get_a&lt;br /&gt;
      RETURN NUMBER&lt;br /&gt;
      IS&lt;br /&gt;
      BEGIN&lt;br /&gt;
        RETURN a;&lt;br /&gt;
      END get_a;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    &lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
Inside SQL the higher priority has SQL, inside non SQL commands like CALL&lt;br /&gt;
or some PL/SQL command, the higher priority has packages.&lt;br /&gt;
&lt;br /&gt;
The Oracle allows both syntax for calling function with zero arguments so&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a() FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Then there is less risk reduction of collision. Package variables persist&lt;br /&gt;
in session&lt;br /&gt;
&lt;br /&gt;
Another possibility is using variables in SQL*Plus (looks like our psql&lt;br /&gt;
variables, with possibility to define type on server side)&lt;br /&gt;
&lt;br /&gt;
The variable should be declared by command VARIABLE and can be accessed by&lt;br /&gt;
syntax :varname in session before usage (maybe this step is optional)&lt;br /&gt;
&lt;br /&gt;
    VARIABLE bv_variable_name VARCHAR2(30)&lt;br /&gt;
    BEGIN&lt;br /&gt;
     :bv_variable_name := &#039;Some Value&#039;;&lt;br /&gt;
    END;&lt;br /&gt;
    &lt;br /&gt;
    SELECT column_name&lt;br /&gt;
      FROM   table_name&lt;br /&gt;
     WHERE  column_name = :bv_variable_name;&lt;br /&gt;
&lt;br /&gt;
== DB2 ==&lt;br /&gt;
The &amp;quot;user defined global variables&amp;quot; are similar to my proposal. The&lt;br /&gt;
differences are different access rights &amp;quot;READ, WRITE&amp;quot; x &amp;quot;SELECT, UPDATE&amp;quot;.&lt;br /&gt;
Because PostgreSQL has SET command for GUC, I introduced LET command (DB2&lt;br /&gt;
uses SET)&lt;br /&gt;
&lt;br /&gt;
Variables are visible in all sessions, but value is private per session.&lt;br /&gt;
Variables are not transactional. The usage is wider than my proposal. Then&lt;br /&gt;
can be changed by commands SET, SELECT INTO or they can be used like OUT&lt;br /&gt;
parameters of procedures. The search path (or some like that) is used for&lt;br /&gt;
variables too, but the variables has less priority than tables/columns.&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE myCounter INT DEFAULT 01;&lt;br /&gt;
    SELECT EMPNO, LASTNAME, CASE WHEN myCounter = 1 THEN SALARY ELSE NULL END&lt;br /&gt;
    FROM EMPLOYEE WHERE WORKDEPT = ’A00’;&lt;br /&gt;
    SET myCounter = 29;&lt;br /&gt;
&lt;br /&gt;
There are (I think) different kinds of variables - accessed by the function&lt;br /&gt;
GETVARIABLE(&#039;name&#039;, &#039;default) - it looks very similar ro our GUC and&lt;br /&gt;
`current_setting` function. These variables can be set by connection&lt;br /&gt;
string, are of varchar type and 10 values are allowed. Built-in session&lt;br /&gt;
variables (configuration) can be accessed by the function GETVARIABLE too.&lt;br /&gt;
&lt;br /&gt;
== MSSQL (T-SQL) ==&lt;br /&gt;
global variables - the access syntax is @@varname, they are used like GUC&lt;br /&gt;
and little bit more - some state informations are there like @@ERROR,&lt;br /&gt;
@@ROWCOUNT or @@IDENTITY&lt;br /&gt;
&lt;br /&gt;
local variables - the access syntax is @varname, and should be declared&lt;br /&gt;
before usage by DECLARE command. The scope is limited to batch or procedure&lt;br /&gt;
or function, where DECLARE command was executed.&lt;br /&gt;
&lt;br /&gt;
    DECLARE @TestVariable AS VARCHAR(100)&lt;br /&gt;
    SET @TestVariable = &#039;Think Green&#039;&lt;br /&gt;
    GO&lt;br /&gt;
    PRINT @TestVariable&lt;br /&gt;
&lt;br /&gt;
This script fails, because PRINT is executed in another batch. So I think&lt;br /&gt;
so MSSQL doesn&#039;t support session variables&lt;br /&gt;
&lt;br /&gt;
There are similar mechanisms like our custom GUC and usage current_setting&lt;br /&gt;
and set_config functions. Generally, in this area is MSSQL very primitive&lt;br /&gt;
&lt;br /&gt;
    EXEC sp_set_session_context &#039;user_id&#039;, 4;&lt;br /&gt;
    SELECT SESSION_CONTEXT(N&#039;user_id&#039;);&lt;br /&gt;
&lt;br /&gt;
== SQL/PSM ==&lt;br /&gt;
Standard introduces a concept of modules that can be joined with schemas.&lt;br /&gt;
The variables are like PLpgSQL, but only local - the only temp tables can&lt;br /&gt;
be defined on module levels. These tables can be accessed only from&lt;br /&gt;
routines assigned to modules. Modules are declarative versions of our&lt;br /&gt;
extensions (if I understand well, I didn&#039;t find any implementation). It&lt;br /&gt;
allows you to overwrite the search patch for routines assigned in the&lt;br /&gt;
module. Variables are not transactional, the priority - variables/columns&lt;br /&gt;
is not specified.&lt;br /&gt;
&lt;br /&gt;
= Types of different session variables =&lt;br /&gt;
* client side (psql, pgadmin, ...) - mostly similar to shell variables (based on string operations)&lt;br /&gt;
* server side&lt;br /&gt;
** declarative&lt;br /&gt;
*** created only for batch (T-SQL)&lt;br /&gt;
*** created as an database object (or part of database object) (Oracle, DB2)&lt;br /&gt;
** created by usage (MySQL)&lt;br /&gt;
** with special syntax (MySQL, T-SQL)&lt;br /&gt;
** with ANSI SQL syntax for identifiers (Oracle, DB2)&lt;br /&gt;
&lt;br /&gt;
= Implementation of session variables in Postgres =&lt;br /&gt;
&lt;br /&gt;
== Possible use cases ==&lt;br /&gt;
* usage of session variables as global variables in PL&lt;br /&gt;
* holding configuration (for PL)&lt;br /&gt;
* holding some more used data in interactive session&lt;br /&gt;
* holding credentials for some RLS and data securing&lt;br /&gt;
* helping with migration from others systems&lt;br /&gt;
* allow anonymous block parametrization (only postgres)&lt;br /&gt;
&lt;br /&gt;
It is clean so no one design is perfect for all uses cases. MySQL is good enough and maybe handy for interactive work,&lt;br /&gt;
but cannot be used for storing any sensitive data, and it is unsafe. PostgreSQL GUC are perfect for configuration, but&lt;br /&gt;
are not friendly for interactive work, and are very limited for usage in PL as global variables. So no one design is&lt;br /&gt;
perfect, and I can imagine more different designs in one system, that can support some different use cases better.&lt;br /&gt;
&lt;br /&gt;
== Proposal for Postgres - design session variables as database global temporary typed objects with ANSI SQL syntax for identifiers ==&lt;br /&gt;
&lt;br /&gt;
=Issues that any implementation should to solve =&lt;br /&gt;
* possible collision between session variables and other database objects&lt;br /&gt;
* memory cleaning after dropping session variable (when DDL can be reverted)&lt;br /&gt;
* memory invalidation (variable is dropped by one session, but still used in second session)&lt;br /&gt;
&lt;br /&gt;
= ToDo =&lt;br /&gt;
* SECURITY LABEL support - enhancing dummy_seclabel module, sepgsql extension + pg doc and SecLabelSupportsObjectType&lt;/div&gt;</summary>
		<author><name>Okbobcz</name></author>
	</entry>
	<entry>
		<id>https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41809</id>
		<title>Implementation of declarative catalog session variables</title>
		<link rel="alternate" type="text/html" href="https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41809"/>
		<updated>2025-09-27T18:45:32Z</updated>

		<summary type="html">&lt;p&gt;Okbobcz: /* Possible use cases */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Session variables =&lt;br /&gt;
Session variables are database objects that can hold a value across multiple queries inside a session. A design of session variables is varied and more times more databases implement more than just one type of session variables. Unfortunately standard SQL/PSM is not commonly accepted, and a part of this standard related to modules and module&#039;s variables was not implemented (in any widely used product). Very often session variables are a part of the stored procedures environment - but there are exceptions (MySQL session variables or any client side session variables). What I know is that session variables don&#039;t support transactions (for values). It is a plus minus just variable like we know from programming languages.&lt;br /&gt;
&lt;br /&gt;
There is no common design of session variables. Currently any database system has its own proprietary design with proprietary syntax and features. Some systems have more than one different design of session variables. It is hard to compare different designs - any design has different advantages and disadvantages.&lt;br /&gt;
&lt;br /&gt;
== MySQL ==&lt;br /&gt;
Session variables in MySQL have the same syntax like more known T-SQL variables (but all other things are proprietary). MySQL knows two kinds of session variables:&lt;br /&gt;
&lt;br /&gt;
* system variables - use the prefix `@@` or `@@xxx.`&lt;br /&gt;
* user defined variables - use the prefix `@`&lt;br /&gt;
&lt;br /&gt;
System variables can be modified by command `SET`. After keyword `SET` keywords `GLOBAL`, `SESSION` or `PERSIST` can be used. &lt;br /&gt;
Some variables can be used just with keyword `GLOBAL` or `SESSION` and in this case, when the name is prefixed by `@@`, is not&lt;br /&gt;
necessary to specify scope. System variables are defined by MySQL or by an mysql&#039;s plugins.&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL some_config = 100;&lt;br /&gt;
    SET @@same_config = 100;&lt;br /&gt;
&lt;br /&gt;
    SET SESSION sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@SESSION.sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
&lt;br /&gt;
Persistent values are stored in `mysql-auto.conf`.&lt;br /&gt;
&lt;br /&gt;
Reset of system variables can be done by setting to default or assigning from global namespace:&lt;br /&gt;
&lt;br /&gt;
    SET @@sql_mode = DEFAULT;&lt;br /&gt;
    SET @@sql_mode = @@GLOBAL.sql_mode&lt;br /&gt;
&lt;br /&gt;
PostgreSQL equivalent:&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL ; -- there is not similar command in Postgres&lt;br /&gt;
    SET SESSION ; -- SET&lt;br /&gt;
    SET PERSIST ; -- ALTER SYSTEM; SELECT pg_reload_conf();&lt;br /&gt;
    SELECT @@var; -- SELECT current_setting(&#039;var);&lt;br /&gt;
&lt;br /&gt;
User defined variables are defined by assignment&lt;br /&gt;
&lt;br /&gt;
    SET @var = expr [, @var2 = expr [ ... ]];&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
User defined variables can be only int, decimal, float, binary, nonbinary string or NULL - casting from/to any types is implicit and hidden.&lt;br /&gt;
UDV cannot be used as table or column names.&lt;br /&gt;
&lt;br /&gt;
An advantage of T-SQL syntax (with special prefixes) is simple solution of possible identifier collisions. MySQL&lt;br /&gt;
variables are very handy too, and user experience can be good for people with knowledge of T-SQL (MSSQL or Sybase).&lt;br /&gt;
On the other hand, prefix based syntax can be disturbing for users who work with different systems than MSSQL. Type system&lt;br /&gt;
is maybe too simple and can be usafe. Generally the performance of MySQL (or MariaDB) stored procedures is not good,&lt;br /&gt;
and stored procedures are not frequently used. There is not any protection against typo errors. Unassigned user variables&lt;br /&gt;
can be used without error (have  NULL value), so static check of stored procedures is not possible (in this area).&lt;br /&gt;
&lt;br /&gt;
There is not any possibility how to limit an access to user defined variables in MySQL. UDV cannot be protected&lt;br /&gt;
against unwanted usage. Some protection can be only naming convention (all UDV share one name space).&lt;br /&gt;
&lt;br /&gt;
==Postgres==&lt;br /&gt;
Postgres has relatively advanced client side session variables in `psql`. `psql`&#039;s variables use prefix &#039;:&#039;. The implementation is simple. psql&#039;s parser reads tokens from input. When it gets a token related to a variable, then this token is replaced by a value of variable. Implemented syntax supports literal and identifier escaping. psql&#039;s variables can be used as an argument of the `\bind` command.&lt;br /&gt;
&lt;br /&gt;
psql&#039;s variables are typeless client side variables available only inside `psql` or `pgbench`. &lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:40:18) postgres=# \set myvar ahoj&lt;br /&gt;
    (2025-09-25 09:40:33) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:14) postgres=# select :&#039;myvar&#039;;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:24) postgres=# select $1 \bind :myvar \g&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
psql&#039;s command `\set` allows just simply string concatenation - but allows to execute shell command. When&lt;br /&gt;
query is executed by `\gset` command, then result is stored in psql variable.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:41:40) postgres=# \set ctime `date`&lt;br /&gt;
    (2025-09-25 09:43:55) postgres=# \echo :ctime&lt;br /&gt;
    čt 25. září 2025, 09:43:55 CEST&lt;br /&gt;
&lt;br /&gt;
&#039;\set` command is too simple, and writing some complex operations is unreadable or not portable.&lt;br /&gt;
&lt;br /&gt;
It is good enough for writing tests, but it is not generally available (probably nobody expects it), and it is not&lt;br /&gt;
accessible from server side (from stored procedures). There is not simple way, how to access psql variables from&lt;br /&gt;
anonymous  block (proxy custom GUC variable is needed):&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:58:38) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    (2025-09-25 09:58:43) postgres=# select set_config(&#039;myvars.myvar&#039;, :&#039;myvar&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ ahoj       │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 10:00:27) postgres=# do $$ begin raise notice &#039;%&#039;, current_setting(&#039;myvars.myvar&#039;); end $$;&lt;br /&gt;
    NOTICE:  ahoj&lt;br /&gt;
    DO&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PostgreSQL has configuration GUC (Grand Unified Configuration) variables. These variables are primarily designed for PostgreSQL and PostgreSQL extensions configuration, but can be used as session variables (implicit, explicit, typeless). These variables can be set by command `SET` or by function `set_config`, and can be read by command `SHOW` or by function `current_setting`. GUC variables are designed for holding configuration parameters. When they are created from an internal API, then they can be protected for super users, hidden to common users, and can have assigned types (bool, memory, number, interval, string, enum) and values that can be checked before assign. The type system is independent of the PostgreSQL SQL type system - it is used for configuration and that is processed before postgresql sql type system is initialized.&lt;br /&gt;
&lt;br /&gt;
Variables created from SQL should be only &amp;quot;custom&amp;quot;. The name of these variables have to use a prefix and these variables are string only. These variables are commonly used as a workaround for missing server side session variables in Postgres (typeless unprotected unsecure session variables).&lt;br /&gt;
&lt;br /&gt;
Specific feature of Postgres GUC (build-in or custom) is the possibility to specify scope transaction, session or function execution. Another functionality is to assign a default value of a specific parameter with some event - loading configuration, login to role, login to database, start some function. These features are nice for work with configuration, but can be very problematic when GUC variables are used as session variables.&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE FUNCTION fx() &lt;br /&gt;
    RETURNS void AS $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, current_setting(&#039;my.x&#039;);&lt;br /&gt;
    END $$ LANGUAGE plpgsql SET my.x = 20;&lt;br /&gt;
    CREATE FUNCTION&lt;br /&gt;
    (2025-09-27 06:10:37) postgres=# SET my.x = 0; SELECT fx();&lt;br /&gt;
    SET&lt;br /&gt;
    NOTICE:  20&lt;br /&gt;
    ┌────┐&lt;br /&gt;
    │ fx │&lt;br /&gt;
    ╞════╡&lt;br /&gt;
    │    │&lt;br /&gt;
    └────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    (2025-09-27 06:10:39) postgres=# SHOW my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 0    │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
GUC variables are transactional - without possibility to disable it.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-27 06:17:31) postgres=# set my.x = 10;&lt;br /&gt;
    SET&lt;br /&gt;
    (2025-09-27 06:19:42) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:19:46) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, true);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:19:55) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:01) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:20:11) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:13) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:20:38) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:45) postgres=# rollback;&lt;br /&gt;
    ROLLBACK&lt;br /&gt;
    (2025-09-27 06:20:52) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:55) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:22:36) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:22:39) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:22:42) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
So there is no possibility to store to custom GUC some details about an error.&lt;br /&gt;
&lt;br /&gt;
== Oracle ==&lt;br /&gt;
Oracle PL/SQL allows the use of package variables. PL/SQL is based on ADA&lt;br /&gt;
language - and package variables are &amp;quot;global&amp;quot; variables. They are not&lt;br /&gt;
directly visible from SQL, but Oracle allows reduced syntax for functions&lt;br /&gt;
without arguments, so you need to write a wrapper&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE PACKAGE my_package&lt;br /&gt;
    AS&lt;br /&gt;
      FUNCTION get_a RETURN NUMBER;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    /&lt;br /&gt;
    &lt;br /&gt;
    CREATE OR REPLACE PACKAGE BODY my_package&lt;br /&gt;
    AS&lt;br /&gt;
      a  NUMBER(20);&lt;br /&gt;
     &lt;br /&gt;
      FUNCTION get_a&lt;br /&gt;
      RETURN NUMBER&lt;br /&gt;
      IS&lt;br /&gt;
      BEGIN&lt;br /&gt;
        RETURN a;&lt;br /&gt;
      END get_a;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    &lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
Inside SQL the higher priority has SQL, inside non SQL commands like CALL&lt;br /&gt;
or some PL/SQL command, the higher priority has packages.&lt;br /&gt;
&lt;br /&gt;
The Oracle allows both syntax for calling function with zero arguments so&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a() FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Then there is less risk reduction of collision. Package variables persist&lt;br /&gt;
in session&lt;br /&gt;
&lt;br /&gt;
Another possibility is using variables in SQL*Plus (looks like our psql&lt;br /&gt;
variables, with possibility to define type on server side)&lt;br /&gt;
&lt;br /&gt;
The variable should be declared by command VARIABLE and can be accessed by&lt;br /&gt;
syntax :varname in session before usage (maybe this step is optional)&lt;br /&gt;
&lt;br /&gt;
    VARIABLE bv_variable_name VARCHAR2(30)&lt;br /&gt;
    BEGIN&lt;br /&gt;
     :bv_variable_name := &#039;Some Value&#039;;&lt;br /&gt;
    END;&lt;br /&gt;
    &lt;br /&gt;
    SELECT column_name&lt;br /&gt;
      FROM   table_name&lt;br /&gt;
     WHERE  column_name = :bv_variable_name;&lt;br /&gt;
&lt;br /&gt;
== DB2 ==&lt;br /&gt;
The &amp;quot;user defined global variables&amp;quot; are similar to my proposal. The&lt;br /&gt;
differences are different access rights &amp;quot;READ, WRITE&amp;quot; x &amp;quot;SELECT, UPDATE&amp;quot;.&lt;br /&gt;
Because PostgreSQL has SET command for GUC, I introduced LET command (DB2&lt;br /&gt;
uses SET)&lt;br /&gt;
&lt;br /&gt;
Variables are visible in all sessions, but value is private per session.&lt;br /&gt;
Variables are not transactional. The usage is wider than my proposal. Then&lt;br /&gt;
can be changed by commands SET, SELECT INTO or they can be used like OUT&lt;br /&gt;
parameters of procedures. The search path (or some like that) is used for&lt;br /&gt;
variables too, but the variables has less priority than tables/columns.&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE myCounter INT DEFAULT 01;&lt;br /&gt;
    SELECT EMPNO, LASTNAME, CASE WHEN myCounter = 1 THEN SALARY ELSE NULL END&lt;br /&gt;
    FROM EMPLOYEE WHERE WORKDEPT = ’A00’;&lt;br /&gt;
    SET myCounter = 29;&lt;br /&gt;
&lt;br /&gt;
There are (I think) different kinds of variables - accessed by the function&lt;br /&gt;
GETVARIABLE(&#039;name&#039;, &#039;default) - it looks very similar ro our GUC and&lt;br /&gt;
`current_setting` function. These variables can be set by connection&lt;br /&gt;
string, are of varchar type and 10 values are allowed. Built-in session&lt;br /&gt;
variables (configuration) can be accessed by the function GETVARIABLE too.&lt;br /&gt;
&lt;br /&gt;
== MSSQL (T-SQL) ==&lt;br /&gt;
global variables - the access syntax is @@varname, they are used like GUC&lt;br /&gt;
and little bit more - some state informations are there like @@ERROR,&lt;br /&gt;
@@ROWCOUNT or @@IDENTITY&lt;br /&gt;
&lt;br /&gt;
local variables - the access syntax is @varname, and should be declared&lt;br /&gt;
before usage by DECLARE command. The scope is limited to batch or procedure&lt;br /&gt;
or function, where DECLARE command was executed.&lt;br /&gt;
&lt;br /&gt;
    DECLARE @TestVariable AS VARCHAR(100)&lt;br /&gt;
    SET @TestVariable = &#039;Think Green&#039;&lt;br /&gt;
    GO&lt;br /&gt;
    PRINT @TestVariable&lt;br /&gt;
&lt;br /&gt;
This script fails, because PRINT is executed in another batch. So I think&lt;br /&gt;
so MSSQL doesn&#039;t support session variables&lt;br /&gt;
&lt;br /&gt;
There are similar mechanisms like our custom GUC and usage current_setting&lt;br /&gt;
and set_config functions. Generally, in this area is MSSQL very primitive&lt;br /&gt;
&lt;br /&gt;
    EXEC sp_set_session_context &#039;user_id&#039;, 4;&lt;br /&gt;
    SELECT SESSION_CONTEXT(N&#039;user_id&#039;);&lt;br /&gt;
&lt;br /&gt;
== SQL/PSM ==&lt;br /&gt;
Standard introduces a concept of modules that can be joined with schemas.&lt;br /&gt;
The variables are like PLpgSQL, but only local - the only temp tables can&lt;br /&gt;
be defined on module levels. These tables can be accessed only from&lt;br /&gt;
routines assigned to modules. Modules are declarative versions of our&lt;br /&gt;
extensions (if I understand well, I didn&#039;t find any implementation). It&lt;br /&gt;
allows you to overwrite the search patch for routines assigned in the&lt;br /&gt;
module. Variables are not transactional, the priority - variables/columns&lt;br /&gt;
is not specified.&lt;br /&gt;
&lt;br /&gt;
= Types of different session variables =&lt;br /&gt;
* client side (psql, pgadmin, ...) - mostly similar to shell variables (based on string operations)&lt;br /&gt;
* server side&lt;br /&gt;
** declarative&lt;br /&gt;
*** created only for batch (T-SQL)&lt;br /&gt;
*** created as an database object (or part of database object) (Oracle, DB2)&lt;br /&gt;
** created by usage (MySQL)&lt;br /&gt;
** with special syntax (MySQL, T-SQL)&lt;br /&gt;
** with ANSI SQL syntax for identifiers (Oracle, DB2)&lt;br /&gt;
&lt;br /&gt;
= Implementation of session variables in Postgres =&lt;br /&gt;
&lt;br /&gt;
== Possible use cases ==&lt;br /&gt;
* usage of session variables as global variables in PL&lt;br /&gt;
* holding configuration (for PL)&lt;br /&gt;
* holding some more used data in interactive session&lt;br /&gt;
* holding credentials for some RLS and data securing&lt;br /&gt;
* helping with migration from others systems&lt;br /&gt;
* allow anonymous block parametrization (only postgres)&lt;br /&gt;
&lt;br /&gt;
It is clean so no one design is perfect for all uses cases. MySQL is good enough and maybe handy for interactive work,&lt;br /&gt;
but cannot be used for storing any sensitive data, and it is unsafe. PostgreSQL GUC are perfect for configuration, but&lt;br /&gt;
are not friendly for interactive work, and are very limited for usage in PL as global variables. So no one design is&lt;br /&gt;
perfect, and I can imagine more different designs in one system, that can support some different use cases better.&lt;br /&gt;
&lt;br /&gt;
== Proposal for Postgres - design session variables as global temporary typed objects with ANSI SQL syntax for identifiers ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=Issues that any implementation should to solve =&lt;br /&gt;
* possible collision between session variables and other database objects&lt;br /&gt;
* memory cleaning after dropping session variable (when DDL can be reverted)&lt;br /&gt;
* memory invalidation (variable is dropped by one session, but still used in second session)&lt;br /&gt;
&lt;br /&gt;
= ToDo =&lt;br /&gt;
* SECURITY LABEL support - enhancing dummy_seclabel module, sepgsql extension + pg doc and SecLabelSupportsObjectType&lt;/div&gt;</summary>
		<author><name>Okbobcz</name></author>
	</entry>
	<entry>
		<id>https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41808</id>
		<title>Implementation of declarative catalog session variables</title>
		<link rel="alternate" type="text/html" href="https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41808"/>
		<updated>2025-09-27T18:41:07Z</updated>

		<summary type="html">&lt;p&gt;Okbobcz: /* Possible use cases */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Session variables =&lt;br /&gt;
Session variables are database objects that can hold a value across multiple queries inside a session. A design of session variables is varied and more times more databases implement more than just one type of session variables. Unfortunately standard SQL/PSM is not commonly accepted, and a part of this standard related to modules and module&#039;s variables was not implemented (in any widely used product). Very often session variables are a part of the stored procedures environment - but there are exceptions (MySQL session variables or any client side session variables). What I know is that session variables don&#039;t support transactions (for values). It is a plus minus just variable like we know from programming languages.&lt;br /&gt;
&lt;br /&gt;
There is no common design of session variables. Currently any database system has its own proprietary design with proprietary syntax and features. Some systems have more than one different design of session variables. It is hard to compare different designs - any design has different advantages and disadvantages.&lt;br /&gt;
&lt;br /&gt;
== MySQL ==&lt;br /&gt;
Session variables in MySQL have the same syntax like more known T-SQL variables (but all other things are proprietary). MySQL knows two kinds of session variables:&lt;br /&gt;
&lt;br /&gt;
* system variables - use the prefix `@@` or `@@xxx.`&lt;br /&gt;
* user defined variables - use the prefix `@`&lt;br /&gt;
&lt;br /&gt;
System variables can be modified by command `SET`. After keyword `SET` keywords `GLOBAL`, `SESSION` or `PERSIST` can be used. &lt;br /&gt;
Some variables can be used just with keyword `GLOBAL` or `SESSION` and in this case, when the name is prefixed by `@@`, is not&lt;br /&gt;
necessary to specify scope. System variables are defined by MySQL or by an mysql&#039;s plugins.&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL some_config = 100;&lt;br /&gt;
    SET @@same_config = 100;&lt;br /&gt;
&lt;br /&gt;
    SET SESSION sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@SESSION.sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
&lt;br /&gt;
Persistent values are stored in `mysql-auto.conf`.&lt;br /&gt;
&lt;br /&gt;
Reset of system variables can be done by setting to default or assigning from global namespace:&lt;br /&gt;
&lt;br /&gt;
    SET @@sql_mode = DEFAULT;&lt;br /&gt;
    SET @@sql_mode = @@GLOBAL.sql_mode&lt;br /&gt;
&lt;br /&gt;
PostgreSQL equivalent:&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL ; -- there is not similar command in Postgres&lt;br /&gt;
    SET SESSION ; -- SET&lt;br /&gt;
    SET PERSIST ; -- ALTER SYSTEM; SELECT pg_reload_conf();&lt;br /&gt;
    SELECT @@var; -- SELECT current_setting(&#039;var);&lt;br /&gt;
&lt;br /&gt;
User defined variables are defined by assignment&lt;br /&gt;
&lt;br /&gt;
    SET @var = expr [, @var2 = expr [ ... ]];&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
User defined variables can be only int, decimal, float, binary, nonbinary string or NULL - casting from/to any types is implicit and hidden.&lt;br /&gt;
UDV cannot be used as table or column names.&lt;br /&gt;
&lt;br /&gt;
An advantage of T-SQL syntax (with special prefixes) is simple solution of possible identifier collisions. MySQL&lt;br /&gt;
variables are very handy too, and user experience can be good for people with knowledge of T-SQL (MSSQL or Sybase).&lt;br /&gt;
On the other hand, prefix based syntax can be disturbing for users who work with different systems than MSSQL. Type system&lt;br /&gt;
is maybe too simple and can be usafe. Generally the performance of MySQL (or MariaDB) stored procedures is not good,&lt;br /&gt;
and stored procedures are not frequently used. There is not any protection against typo errors. Unassigned user variables&lt;br /&gt;
can be used without error (have  NULL value), so static check of stored procedures is not possible (in this area).&lt;br /&gt;
&lt;br /&gt;
There is not any possibility how to limit an access to user defined variables in MySQL. UDV cannot be protected&lt;br /&gt;
against unwanted usage. Some protection can be only naming convention (all UDV share one name space).&lt;br /&gt;
&lt;br /&gt;
==Postgres==&lt;br /&gt;
Postgres has relatively advanced client side session variables in `psql`. `psql`&#039;s variables use prefix &#039;:&#039;. The implementation is simple. psql&#039;s parser reads tokens from input. When it gets a token related to a variable, then this token is replaced by a value of variable. Implemented syntax supports literal and identifier escaping. psql&#039;s variables can be used as an argument of the `\bind` command.&lt;br /&gt;
&lt;br /&gt;
psql&#039;s variables are typeless client side variables available only inside `psql` or `pgbench`. &lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:40:18) postgres=# \set myvar ahoj&lt;br /&gt;
    (2025-09-25 09:40:33) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:14) postgres=# select :&#039;myvar&#039;;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:24) postgres=# select $1 \bind :myvar \g&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
psql&#039;s command `\set` allows just simply string concatenation - but allows to execute shell command. When&lt;br /&gt;
query is executed by `\gset` command, then result is stored in psql variable.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:41:40) postgres=# \set ctime `date`&lt;br /&gt;
    (2025-09-25 09:43:55) postgres=# \echo :ctime&lt;br /&gt;
    čt 25. září 2025, 09:43:55 CEST&lt;br /&gt;
&lt;br /&gt;
&#039;\set` command is too simple, and writing some complex operations is unreadable or not portable.&lt;br /&gt;
&lt;br /&gt;
It is good enough for writing tests, but it is not generally available (probably nobody expects it), and it is not&lt;br /&gt;
accessible from server side (from stored procedures). There is not simple way, how to access psql variables from&lt;br /&gt;
anonymous  block (proxy custom GUC variable is needed):&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:58:38) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    (2025-09-25 09:58:43) postgres=# select set_config(&#039;myvars.myvar&#039;, :&#039;myvar&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ ahoj       │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 10:00:27) postgres=# do $$ begin raise notice &#039;%&#039;, current_setting(&#039;myvars.myvar&#039;); end $$;&lt;br /&gt;
    NOTICE:  ahoj&lt;br /&gt;
    DO&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PostgreSQL has configuration GUC (Grand Unified Configuration) variables. These variables are primarily designed for PostgreSQL and PostgreSQL extensions configuration, but can be used as session variables (implicit, explicit, typeless). These variables can be set by command `SET` or by function `set_config`, and can be read by command `SHOW` or by function `current_setting`. GUC variables are designed for holding configuration parameters. When they are created from an internal API, then they can be protected for super users, hidden to common users, and can have assigned types (bool, memory, number, interval, string, enum) and values that can be checked before assign. The type system is independent of the PostgreSQL SQL type system - it is used for configuration and that is processed before postgresql sql type system is initialized.&lt;br /&gt;
&lt;br /&gt;
Variables created from SQL should be only &amp;quot;custom&amp;quot;. The name of these variables have to use a prefix and these variables are string only. These variables are commonly used as a workaround for missing server side session variables in Postgres (typeless unprotected unsecure session variables).&lt;br /&gt;
&lt;br /&gt;
Specific feature of Postgres GUC (build-in or custom) is the possibility to specify scope transaction, session or function execution. Another functionality is to assign a default value of a specific parameter with some event - loading configuration, login to role, login to database, start some function. These features are nice for work with configuration, but can be very problematic when GUC variables are used as session variables.&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE FUNCTION fx() &lt;br /&gt;
    RETURNS void AS $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, current_setting(&#039;my.x&#039;);&lt;br /&gt;
    END $$ LANGUAGE plpgsql SET my.x = 20;&lt;br /&gt;
    CREATE FUNCTION&lt;br /&gt;
    (2025-09-27 06:10:37) postgres=# SET my.x = 0; SELECT fx();&lt;br /&gt;
    SET&lt;br /&gt;
    NOTICE:  20&lt;br /&gt;
    ┌────┐&lt;br /&gt;
    │ fx │&lt;br /&gt;
    ╞════╡&lt;br /&gt;
    │    │&lt;br /&gt;
    └────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    (2025-09-27 06:10:39) postgres=# SHOW my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 0    │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
GUC variables are transactional - without possibility to disable it.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-27 06:17:31) postgres=# set my.x = 10;&lt;br /&gt;
    SET&lt;br /&gt;
    (2025-09-27 06:19:42) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:19:46) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, true);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:19:55) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:01) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:20:11) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:13) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:20:38) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:45) postgres=# rollback;&lt;br /&gt;
    ROLLBACK&lt;br /&gt;
    (2025-09-27 06:20:52) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:55) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:22:36) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:22:39) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:22:42) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
So there is no possibility to store to custom GUC some details about an error.&lt;br /&gt;
&lt;br /&gt;
== Oracle ==&lt;br /&gt;
Oracle PL/SQL allows the use of package variables. PL/SQL is based on ADA&lt;br /&gt;
language - and package variables are &amp;quot;global&amp;quot; variables. They are not&lt;br /&gt;
directly visible from SQL, but Oracle allows reduced syntax for functions&lt;br /&gt;
without arguments, so you need to write a wrapper&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE PACKAGE my_package&lt;br /&gt;
    AS&lt;br /&gt;
      FUNCTION get_a RETURN NUMBER;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    /&lt;br /&gt;
    &lt;br /&gt;
    CREATE OR REPLACE PACKAGE BODY my_package&lt;br /&gt;
    AS&lt;br /&gt;
      a  NUMBER(20);&lt;br /&gt;
     &lt;br /&gt;
      FUNCTION get_a&lt;br /&gt;
      RETURN NUMBER&lt;br /&gt;
      IS&lt;br /&gt;
      BEGIN&lt;br /&gt;
        RETURN a;&lt;br /&gt;
      END get_a;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    &lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
Inside SQL the higher priority has SQL, inside non SQL commands like CALL&lt;br /&gt;
or some PL/SQL command, the higher priority has packages.&lt;br /&gt;
&lt;br /&gt;
The Oracle allows both syntax for calling function with zero arguments so&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a() FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Then there is less risk reduction of collision. Package variables persist&lt;br /&gt;
in session&lt;br /&gt;
&lt;br /&gt;
Another possibility is using variables in SQL*Plus (looks like our psql&lt;br /&gt;
variables, with possibility to define type on server side)&lt;br /&gt;
&lt;br /&gt;
The variable should be declared by command VARIABLE and can be accessed by&lt;br /&gt;
syntax :varname in session before usage (maybe this step is optional)&lt;br /&gt;
&lt;br /&gt;
    VARIABLE bv_variable_name VARCHAR2(30)&lt;br /&gt;
    BEGIN&lt;br /&gt;
     :bv_variable_name := &#039;Some Value&#039;;&lt;br /&gt;
    END;&lt;br /&gt;
    &lt;br /&gt;
    SELECT column_name&lt;br /&gt;
      FROM   table_name&lt;br /&gt;
     WHERE  column_name = :bv_variable_name;&lt;br /&gt;
&lt;br /&gt;
== DB2 ==&lt;br /&gt;
The &amp;quot;user defined global variables&amp;quot; are similar to my proposal. The&lt;br /&gt;
differences are different access rights &amp;quot;READ, WRITE&amp;quot; x &amp;quot;SELECT, UPDATE&amp;quot;.&lt;br /&gt;
Because PostgreSQL has SET command for GUC, I introduced LET command (DB2&lt;br /&gt;
uses SET)&lt;br /&gt;
&lt;br /&gt;
Variables are visible in all sessions, but value is private per session.&lt;br /&gt;
Variables are not transactional. The usage is wider than my proposal. Then&lt;br /&gt;
can be changed by commands SET, SELECT INTO or they can be used like OUT&lt;br /&gt;
parameters of procedures. The search path (or some like that) is used for&lt;br /&gt;
variables too, but the variables has less priority than tables/columns.&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE myCounter INT DEFAULT 01;&lt;br /&gt;
    SELECT EMPNO, LASTNAME, CASE WHEN myCounter = 1 THEN SALARY ELSE NULL END&lt;br /&gt;
    FROM EMPLOYEE WHERE WORKDEPT = ’A00’;&lt;br /&gt;
    SET myCounter = 29;&lt;br /&gt;
&lt;br /&gt;
There are (I think) different kinds of variables - accessed by the function&lt;br /&gt;
GETVARIABLE(&#039;name&#039;, &#039;default) - it looks very similar ro our GUC and&lt;br /&gt;
`current_setting` function. These variables can be set by connection&lt;br /&gt;
string, are of varchar type and 10 values are allowed. Built-in session&lt;br /&gt;
variables (configuration) can be accessed by the function GETVARIABLE too.&lt;br /&gt;
&lt;br /&gt;
== MSSQL (T-SQL) ==&lt;br /&gt;
global variables - the access syntax is @@varname, they are used like GUC&lt;br /&gt;
and little bit more - some state informations are there like @@ERROR,&lt;br /&gt;
@@ROWCOUNT or @@IDENTITY&lt;br /&gt;
&lt;br /&gt;
local variables - the access syntax is @varname, and should be declared&lt;br /&gt;
before usage by DECLARE command. The scope is limited to batch or procedure&lt;br /&gt;
or function, where DECLARE command was executed.&lt;br /&gt;
&lt;br /&gt;
    DECLARE @TestVariable AS VARCHAR(100)&lt;br /&gt;
    SET @TestVariable = &#039;Think Green&#039;&lt;br /&gt;
    GO&lt;br /&gt;
    PRINT @TestVariable&lt;br /&gt;
&lt;br /&gt;
This script fails, because PRINT is executed in another batch. So I think&lt;br /&gt;
so MSSQL doesn&#039;t support session variables&lt;br /&gt;
&lt;br /&gt;
There are similar mechanisms like our custom GUC and usage current_setting&lt;br /&gt;
and set_config functions. Generally, in this area is MSSQL very primitive&lt;br /&gt;
&lt;br /&gt;
    EXEC sp_set_session_context &#039;user_id&#039;, 4;&lt;br /&gt;
    SELECT SESSION_CONTEXT(N&#039;user_id&#039;);&lt;br /&gt;
&lt;br /&gt;
== SQL/PSM ==&lt;br /&gt;
Standard introduces a concept of modules that can be joined with schemas.&lt;br /&gt;
The variables are like PLpgSQL, but only local - the only temp tables can&lt;br /&gt;
be defined on module levels. These tables can be accessed only from&lt;br /&gt;
routines assigned to modules. Modules are declarative versions of our&lt;br /&gt;
extensions (if I understand well, I didn&#039;t find any implementation). It&lt;br /&gt;
allows you to overwrite the search patch for routines assigned in the&lt;br /&gt;
module. Variables are not transactional, the priority - variables/columns&lt;br /&gt;
is not specified.&lt;br /&gt;
&lt;br /&gt;
= Types of different session variables =&lt;br /&gt;
* client side (psql, pgadmin, ...) - mostly similar to shell variables (based on string operations)&lt;br /&gt;
* server side&lt;br /&gt;
** declarative&lt;br /&gt;
*** created only for batch (T-SQL)&lt;br /&gt;
*** created as an database object (or part of database object) (Oracle, DB2)&lt;br /&gt;
** created by usage (MySQL)&lt;br /&gt;
** with special syntax (MySQL, T-SQL)&lt;br /&gt;
** with ANSI SQL syntax for identifiers (Oracle, DB2)&lt;br /&gt;
&lt;br /&gt;
= Implementation of session variables in Postgres =&lt;br /&gt;
&lt;br /&gt;
== Possible use cases ==&lt;br /&gt;
* usage of session variables as global variables in PL&lt;br /&gt;
* holding configuration (for PL)&lt;br /&gt;
* holding some more used data in interactive session&lt;br /&gt;
* holding credentials for some RLS and data securing&lt;br /&gt;
* helping with migration from others systems&lt;br /&gt;
* allow anonymous block parametrization (only postgres)&lt;br /&gt;
&lt;br /&gt;
It is clean so no one design is perfect for all uses cases. MySQL is good enough and maybe handy for interactive work,&lt;br /&gt;
but cannot be used for storing any sensitive data, and it unsafe. PostgreSQL GUC are perfect for configuration, but&lt;br /&gt;
are not friendly for interactive work, and are very limited for usage in PL as global variables. So no one design is&lt;br /&gt;
perfect, and I can imagine more different designs in one system, that can support some different use cases better.&lt;br /&gt;
&lt;br /&gt;
== Proposal for Postgres - design session variables as global temporary typed objects with ANSI SQL syntax for identifiers ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=Issues that any implementation should to solve =&lt;br /&gt;
* possible collision between session variables and other database objects&lt;br /&gt;
* memory cleaning after dropping session variable (when DDL can be reverted)&lt;br /&gt;
* memory invalidation (variable is dropped by one session, but still used in second session)&lt;br /&gt;
&lt;br /&gt;
= ToDo =&lt;br /&gt;
* SECURITY LABEL support - enhancing dummy_seclabel module, sepgsql extension + pg doc and SecLabelSupportsObjectType&lt;/div&gt;</summary>
		<author><name>Okbobcz</name></author>
	</entry>
	<entry>
		<id>https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41807</id>
		<title>Implementation of declarative catalog session variables</title>
		<link rel="alternate" type="text/html" href="https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41807"/>
		<updated>2025-09-27T18:39:52Z</updated>

		<summary type="html">&lt;p&gt;Okbobcz: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Session variables =&lt;br /&gt;
Session variables are database objects that can hold a value across multiple queries inside a session. A design of session variables is varied and more times more databases implement more than just one type of session variables. Unfortunately standard SQL/PSM is not commonly accepted, and a part of this standard related to modules and module&#039;s variables was not implemented (in any widely used product). Very often session variables are a part of the stored procedures environment - but there are exceptions (MySQL session variables or any client side session variables). What I know is that session variables don&#039;t support transactions (for values). It is a plus minus just variable like we know from programming languages.&lt;br /&gt;
&lt;br /&gt;
There is no common design of session variables. Currently any database system has its own proprietary design with proprietary syntax and features. Some systems have more than one different design of session variables. It is hard to compare different designs - any design has different advantages and disadvantages.&lt;br /&gt;
&lt;br /&gt;
== MySQL ==&lt;br /&gt;
Session variables in MySQL have the same syntax like more known T-SQL variables (but all other things are proprietary). MySQL knows two kinds of session variables:&lt;br /&gt;
&lt;br /&gt;
* system variables - use the prefix `@@` or `@@xxx.`&lt;br /&gt;
* user defined variables - use the prefix `@`&lt;br /&gt;
&lt;br /&gt;
System variables can be modified by command `SET`. After keyword `SET` keywords `GLOBAL`, `SESSION` or `PERSIST` can be used. &lt;br /&gt;
Some variables can be used just with keyword `GLOBAL` or `SESSION` and in this case, when the name is prefixed by `@@`, is not&lt;br /&gt;
necessary to specify scope. System variables are defined by MySQL or by an mysql&#039;s plugins.&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL some_config = 100;&lt;br /&gt;
    SET @@same_config = 100;&lt;br /&gt;
&lt;br /&gt;
    SET SESSION sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@SESSION.sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
&lt;br /&gt;
Persistent values are stored in `mysql-auto.conf`.&lt;br /&gt;
&lt;br /&gt;
Reset of system variables can be done by setting to default or assigning from global namespace:&lt;br /&gt;
&lt;br /&gt;
    SET @@sql_mode = DEFAULT;&lt;br /&gt;
    SET @@sql_mode = @@GLOBAL.sql_mode&lt;br /&gt;
&lt;br /&gt;
PostgreSQL equivalent:&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL ; -- there is not similar command in Postgres&lt;br /&gt;
    SET SESSION ; -- SET&lt;br /&gt;
    SET PERSIST ; -- ALTER SYSTEM; SELECT pg_reload_conf();&lt;br /&gt;
    SELECT @@var; -- SELECT current_setting(&#039;var);&lt;br /&gt;
&lt;br /&gt;
User defined variables are defined by assignment&lt;br /&gt;
&lt;br /&gt;
    SET @var = expr [, @var2 = expr [ ... ]];&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
User defined variables can be only int, decimal, float, binary, nonbinary string or NULL - casting from/to any types is implicit and hidden.&lt;br /&gt;
UDV cannot be used as table or column names.&lt;br /&gt;
&lt;br /&gt;
An advantage of T-SQL syntax (with special prefixes) is simple solution of possible identifier collisions. MySQL&lt;br /&gt;
variables are very handy too, and user experience can be good for people with knowledge of T-SQL (MSSQL or Sybase).&lt;br /&gt;
On the other hand, prefix based syntax can be disturbing for users who work with different systems than MSSQL. Type system&lt;br /&gt;
is maybe too simple and can be usafe. Generally the performance of MySQL (or MariaDB) stored procedures is not good,&lt;br /&gt;
and stored procedures are not frequently used. There is not any protection against typo errors. Unassigned user variables&lt;br /&gt;
can be used without error (have  NULL value), so static check of stored procedures is not possible (in this area).&lt;br /&gt;
&lt;br /&gt;
There is not any possibility how to limit an access to user defined variables in MySQL. UDV cannot be protected&lt;br /&gt;
against unwanted usage. Some protection can be only naming convention (all UDV share one name space).&lt;br /&gt;
&lt;br /&gt;
==Postgres==&lt;br /&gt;
Postgres has relatively advanced client side session variables in `psql`. `psql`&#039;s variables use prefix &#039;:&#039;. The implementation is simple. psql&#039;s parser reads tokens from input. When it gets a token related to a variable, then this token is replaced by a value of variable. Implemented syntax supports literal and identifier escaping. psql&#039;s variables can be used as an argument of the `\bind` command.&lt;br /&gt;
&lt;br /&gt;
psql&#039;s variables are typeless client side variables available only inside `psql` or `pgbench`. &lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:40:18) postgres=# \set myvar ahoj&lt;br /&gt;
    (2025-09-25 09:40:33) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:14) postgres=# select :&#039;myvar&#039;;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:24) postgres=# select $1 \bind :myvar \g&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
psql&#039;s command `\set` allows just simply string concatenation - but allows to execute shell command. When&lt;br /&gt;
query is executed by `\gset` command, then result is stored in psql variable.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:41:40) postgres=# \set ctime `date`&lt;br /&gt;
    (2025-09-25 09:43:55) postgres=# \echo :ctime&lt;br /&gt;
    čt 25. září 2025, 09:43:55 CEST&lt;br /&gt;
&lt;br /&gt;
&#039;\set` command is too simple, and writing some complex operations is unreadable or not portable.&lt;br /&gt;
&lt;br /&gt;
It is good enough for writing tests, but it is not generally available (probably nobody expects it), and it is not&lt;br /&gt;
accessible from server side (from stored procedures). There is not simple way, how to access psql variables from&lt;br /&gt;
anonymous  block (proxy custom GUC variable is needed):&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:58:38) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    (2025-09-25 09:58:43) postgres=# select set_config(&#039;myvars.myvar&#039;, :&#039;myvar&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ ahoj       │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 10:00:27) postgres=# do $$ begin raise notice &#039;%&#039;, current_setting(&#039;myvars.myvar&#039;); end $$;&lt;br /&gt;
    NOTICE:  ahoj&lt;br /&gt;
    DO&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PostgreSQL has configuration GUC (Grand Unified Configuration) variables. These variables are primarily designed for PostgreSQL and PostgreSQL extensions configuration, but can be used as session variables (implicit, explicit, typeless). These variables can be set by command `SET` or by function `set_config`, and can be read by command `SHOW` or by function `current_setting`. GUC variables are designed for holding configuration parameters. When they are created from an internal API, then they can be protected for super users, hidden to common users, and can have assigned types (bool, memory, number, interval, string, enum) and values that can be checked before assign. The type system is independent of the PostgreSQL SQL type system - it is used for configuration and that is processed before postgresql sql type system is initialized.&lt;br /&gt;
&lt;br /&gt;
Variables created from SQL should be only &amp;quot;custom&amp;quot;. The name of these variables have to use a prefix and these variables are string only. These variables are commonly used as a workaround for missing server side session variables in Postgres (typeless unprotected unsecure session variables).&lt;br /&gt;
&lt;br /&gt;
Specific feature of Postgres GUC (build-in or custom) is the possibility to specify scope transaction, session or function execution. Another functionality is to assign a default value of a specific parameter with some event - loading configuration, login to role, login to database, start some function. These features are nice for work with configuration, but can be very problematic when GUC variables are used as session variables.&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE FUNCTION fx() &lt;br /&gt;
    RETURNS void AS $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, current_setting(&#039;my.x&#039;);&lt;br /&gt;
    END $$ LANGUAGE plpgsql SET my.x = 20;&lt;br /&gt;
    CREATE FUNCTION&lt;br /&gt;
    (2025-09-27 06:10:37) postgres=# SET my.x = 0; SELECT fx();&lt;br /&gt;
    SET&lt;br /&gt;
    NOTICE:  20&lt;br /&gt;
    ┌────┐&lt;br /&gt;
    │ fx │&lt;br /&gt;
    ╞════╡&lt;br /&gt;
    │    │&lt;br /&gt;
    └────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    (2025-09-27 06:10:39) postgres=# SHOW my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 0    │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
GUC variables are transactional - without possibility to disable it.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-27 06:17:31) postgres=# set my.x = 10;&lt;br /&gt;
    SET&lt;br /&gt;
    (2025-09-27 06:19:42) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:19:46) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, true);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:19:55) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:01) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:20:11) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:13) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:20:38) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:45) postgres=# rollback;&lt;br /&gt;
    ROLLBACK&lt;br /&gt;
    (2025-09-27 06:20:52) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:55) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:22:36) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:22:39) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:22:42) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
So there is no possibility to store to custom GUC some details about an error.&lt;br /&gt;
&lt;br /&gt;
== Oracle ==&lt;br /&gt;
Oracle PL/SQL allows the use of package variables. PL/SQL is based on ADA&lt;br /&gt;
language - and package variables are &amp;quot;global&amp;quot; variables. They are not&lt;br /&gt;
directly visible from SQL, but Oracle allows reduced syntax for functions&lt;br /&gt;
without arguments, so you need to write a wrapper&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE PACKAGE my_package&lt;br /&gt;
    AS&lt;br /&gt;
      FUNCTION get_a RETURN NUMBER;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    /&lt;br /&gt;
    &lt;br /&gt;
    CREATE OR REPLACE PACKAGE BODY my_package&lt;br /&gt;
    AS&lt;br /&gt;
      a  NUMBER(20);&lt;br /&gt;
     &lt;br /&gt;
      FUNCTION get_a&lt;br /&gt;
      RETURN NUMBER&lt;br /&gt;
      IS&lt;br /&gt;
      BEGIN&lt;br /&gt;
        RETURN a;&lt;br /&gt;
      END get_a;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    &lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
Inside SQL the higher priority has SQL, inside non SQL commands like CALL&lt;br /&gt;
or some PL/SQL command, the higher priority has packages.&lt;br /&gt;
&lt;br /&gt;
The Oracle allows both syntax for calling function with zero arguments so&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a() FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Then there is less risk reduction of collision. Package variables persist&lt;br /&gt;
in session&lt;br /&gt;
&lt;br /&gt;
Another possibility is using variables in SQL*Plus (looks like our psql&lt;br /&gt;
variables, with possibility to define type on server side)&lt;br /&gt;
&lt;br /&gt;
The variable should be declared by command VARIABLE and can be accessed by&lt;br /&gt;
syntax :varname in session before usage (maybe this step is optional)&lt;br /&gt;
&lt;br /&gt;
    VARIABLE bv_variable_name VARCHAR2(30)&lt;br /&gt;
    BEGIN&lt;br /&gt;
     :bv_variable_name := &#039;Some Value&#039;;&lt;br /&gt;
    END;&lt;br /&gt;
    &lt;br /&gt;
    SELECT column_name&lt;br /&gt;
      FROM   table_name&lt;br /&gt;
     WHERE  column_name = :bv_variable_name;&lt;br /&gt;
&lt;br /&gt;
== DB2 ==&lt;br /&gt;
The &amp;quot;user defined global variables&amp;quot; are similar to my proposal. The&lt;br /&gt;
differences are different access rights &amp;quot;READ, WRITE&amp;quot; x &amp;quot;SELECT, UPDATE&amp;quot;.&lt;br /&gt;
Because PostgreSQL has SET command for GUC, I introduced LET command (DB2&lt;br /&gt;
uses SET)&lt;br /&gt;
&lt;br /&gt;
Variables are visible in all sessions, but value is private per session.&lt;br /&gt;
Variables are not transactional. The usage is wider than my proposal. Then&lt;br /&gt;
can be changed by commands SET, SELECT INTO or they can be used like OUT&lt;br /&gt;
parameters of procedures. The search path (or some like that) is used for&lt;br /&gt;
variables too, but the variables has less priority than tables/columns.&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE myCounter INT DEFAULT 01;&lt;br /&gt;
    SELECT EMPNO, LASTNAME, CASE WHEN myCounter = 1 THEN SALARY ELSE NULL END&lt;br /&gt;
    FROM EMPLOYEE WHERE WORKDEPT = ’A00’;&lt;br /&gt;
    SET myCounter = 29;&lt;br /&gt;
&lt;br /&gt;
There are (I think) different kinds of variables - accessed by the function&lt;br /&gt;
GETVARIABLE(&#039;name&#039;, &#039;default) - it looks very similar ro our GUC and&lt;br /&gt;
`current_setting` function. These variables can be set by connection&lt;br /&gt;
string, are of varchar type and 10 values are allowed. Built-in session&lt;br /&gt;
variables (configuration) can be accessed by the function GETVARIABLE too.&lt;br /&gt;
&lt;br /&gt;
== MSSQL (T-SQL) ==&lt;br /&gt;
global variables - the access syntax is @@varname, they are used like GUC&lt;br /&gt;
and little bit more - some state informations are there like @@ERROR,&lt;br /&gt;
@@ROWCOUNT or @@IDENTITY&lt;br /&gt;
&lt;br /&gt;
local variables - the access syntax is @varname, and should be declared&lt;br /&gt;
before usage by DECLARE command. The scope is limited to batch or procedure&lt;br /&gt;
or function, where DECLARE command was executed.&lt;br /&gt;
&lt;br /&gt;
    DECLARE @TestVariable AS VARCHAR(100)&lt;br /&gt;
    SET @TestVariable = &#039;Think Green&#039;&lt;br /&gt;
    GO&lt;br /&gt;
    PRINT @TestVariable&lt;br /&gt;
&lt;br /&gt;
This script fails, because PRINT is executed in another batch. So I think&lt;br /&gt;
so MSSQL doesn&#039;t support session variables&lt;br /&gt;
&lt;br /&gt;
There are similar mechanisms like our custom GUC and usage current_setting&lt;br /&gt;
and set_config functions. Generally, in this area is MSSQL very primitive&lt;br /&gt;
&lt;br /&gt;
    EXEC sp_set_session_context &#039;user_id&#039;, 4;&lt;br /&gt;
    SELECT SESSION_CONTEXT(N&#039;user_id&#039;);&lt;br /&gt;
&lt;br /&gt;
== SQL/PSM ==&lt;br /&gt;
Standard introduces a concept of modules that can be joined with schemas.&lt;br /&gt;
The variables are like PLpgSQL, but only local - the only temp tables can&lt;br /&gt;
be defined on module levels. These tables can be accessed only from&lt;br /&gt;
routines assigned to modules. Modules are declarative versions of our&lt;br /&gt;
extensions (if I understand well, I didn&#039;t find any implementation). It&lt;br /&gt;
allows you to overwrite the search patch for routines assigned in the&lt;br /&gt;
module. Variables are not transactional, the priority - variables/columns&lt;br /&gt;
is not specified.&lt;br /&gt;
&lt;br /&gt;
= Types of different session variables =&lt;br /&gt;
* client side (psql, pgadmin, ...) - mostly similar to shell variables (based on string operations)&lt;br /&gt;
* server side&lt;br /&gt;
** declarative&lt;br /&gt;
*** created only for batch (T-SQL)&lt;br /&gt;
*** created as an database object (or part of database object) (Oracle, DB2)&lt;br /&gt;
** created by usage (MySQL)&lt;br /&gt;
** with special syntax (MySQL, T-SQL)&lt;br /&gt;
** with ANSI SQL syntax for identifiers (Oracle, DB2)&lt;br /&gt;
&lt;br /&gt;
= Implementation of session variables in Postgres =&lt;br /&gt;
&lt;br /&gt;
== Possible use cases ==&lt;br /&gt;
1. usage of session variables as global variables in PL&lt;br /&gt;
2. holding configuration&lt;br /&gt;
3. holding some more used data in interactive session&lt;br /&gt;
4. holding credentials for some RLS and data securing&lt;br /&gt;
5. helping with migration from others systems&lt;br /&gt;
6. allow anonymous block parametrization (only postgres)&lt;br /&gt;
&lt;br /&gt;
It is clean so no one design is perfect for all uses cases. MySQL is good enough and maybe handy for interactive work,&lt;br /&gt;
but cannot be used for storing any sensitive data, and it unsafe. PostgreSQL GUC are perfect for configuration, but&lt;br /&gt;
are not friendly for interactive work, and are very limited for usage in PL as global variables. So no one design is&lt;br /&gt;
perfect, and I can imagine more different designs in one system, that can support some different use cases better.&lt;br /&gt;
&lt;br /&gt;
== Proposal for Postgres - design session variables as global temporary typed objects with ANSI SQL syntax for identifiers ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=Issues that any implementation should to solve =&lt;br /&gt;
* possible collision between session variables and other database objects&lt;br /&gt;
* memory cleaning after dropping session variable (when DDL can be reverted)&lt;br /&gt;
* memory invalidation (variable is dropped by one session, but still used in second session)&lt;br /&gt;
&lt;br /&gt;
= ToDo =&lt;br /&gt;
* SECURITY LABEL support - enhancing dummy_seclabel module, sepgsql extension + pg doc and SecLabelSupportsObjectType&lt;/div&gt;</summary>
		<author><name>Okbobcz</name></author>
	</entry>
	<entry>
		<id>https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41804</id>
		<title>Implementation of declarative catalog session variables</title>
		<link rel="alternate" type="text/html" href="https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41804"/>
		<updated>2025-09-27T05:32:15Z</updated>

		<summary type="html">&lt;p&gt;Okbobcz: /* Oracle */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Session variables =&lt;br /&gt;
Session variables are database objects that can hold a value across multiple queries inside a session. A design of session variables is varied and more times more databases implement more than just one type of session variables. Unfortunately standard SQL/PSM is not commonly accepted, and a part of this standard related to modules and module&#039;s variables was not implemented (in any widely used product). Very often session variables are a part of the stored procedures environment - but there are exceptions (MySQL session variables or any client side session variables). What I know is that session variables don&#039;t support transactions (for values). It is a plus minus just variable like we know from programming languages.&lt;br /&gt;
&lt;br /&gt;
There is no common design of session variables. Currently any database system has its own proprietary design with proprietary syntax and features. Some systems have more than one different design of session variables. It is hard to compare different designs - any design has different advantages and disadvantages.&lt;br /&gt;
&lt;br /&gt;
== MySQL ==&lt;br /&gt;
Session variables in MySQL have the same syntax like more known T-SQL variables (but all other things are proprietary). MySQL knows two kinds of session variables:&lt;br /&gt;
&lt;br /&gt;
* system variables - use the prefix `@@` or `@@xxx.`&lt;br /&gt;
* user defined variables - use the prefix `@`&lt;br /&gt;
&lt;br /&gt;
System variables can be modified by command `SET`. After keyword `SET` keywords `GLOBAL`, `SESSION` or `PERSIST` can be used. &lt;br /&gt;
Some variables can be used just with keyword `GLOBAL` or `SESSION` and in this case, when the name is prefixed by `@@`, is not&lt;br /&gt;
necessary to specify scope. System variables are defined by MySQL or by an mysql&#039;s plugins.&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL some_config = 100;&lt;br /&gt;
    SET @@same_config = 100;&lt;br /&gt;
&lt;br /&gt;
    SET SESSION sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@SESSION.sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
&lt;br /&gt;
Persistent values are stored in `mysql-auto.conf`.&lt;br /&gt;
&lt;br /&gt;
Reset of system variables can be done by setting to default or assigning from global namespace:&lt;br /&gt;
&lt;br /&gt;
    SET @@sql_mode = DEFAULT;&lt;br /&gt;
    SET @@sql_mode = @@GLOBAL.sql_mode&lt;br /&gt;
&lt;br /&gt;
PostgreSQL equivalent:&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL ; -- there is not similar command in Postgres&lt;br /&gt;
    SET SESSION ; -- SET&lt;br /&gt;
    SET PERSIST ; -- ALTER SYSTEM; SELECT pg_reload_conf();&lt;br /&gt;
    SELECT @@var; -- SELECT current_setting(&#039;var);&lt;br /&gt;
&lt;br /&gt;
User defined variables are defined by assignment&lt;br /&gt;
&lt;br /&gt;
    SET @var = expr [, @var2 = expr [ ... ]];&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
User defined variables can be only int, decimal, float, binary, nonbinary string or NULL - casting from/to any types is implicit and hidden.&lt;br /&gt;
UDV cannot be used as table or column names.&lt;br /&gt;
&lt;br /&gt;
An advantage of T-SQL syntax (with special prefixes) is simple solution of possible identifier collisions. MySQL&lt;br /&gt;
variables are very handy too, and user experience can be good for people with knowledge of T-SQL (MSSQL or Sybase).&lt;br /&gt;
On the other hand, prefix based syntax can be disturbing for users who work with different systems than MSSQL. Type system&lt;br /&gt;
is maybe too simple and can be usafe. Generally the performance of MySQL (or MariaDB) stored procedures is not good,&lt;br /&gt;
and stored procedures are not frequently used. There is not any protection against typo errors. Unassigned user variables&lt;br /&gt;
can be used without error (have  NULL value), so static check of stored procedures is not possible (in this area).&lt;br /&gt;
&lt;br /&gt;
There is not any possibility how to limit an access to user defined variables in MySQL. UDV cannot be protected&lt;br /&gt;
against unwanted usage. Some protection can be only naming convention (all UDV share one name space).&lt;br /&gt;
&lt;br /&gt;
==Postgres==&lt;br /&gt;
Postgres has relatively advanced client side session variables in `psql`. `psql`&#039;s variables use prefix &#039;:&#039;. The implementation is simple. psql&#039;s parser reads tokens from input. When it gets a token related to a variable, then this token is replaced by a value of variable. Implemented syntax supports literal and identifier escaping. psql&#039;s variables can be used as an argument of the `\bind` command.&lt;br /&gt;
&lt;br /&gt;
psql&#039;s variables are typeless client side variables available only inside `psql` or `pgbench`. &lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:40:18) postgres=# \set myvar ahoj&lt;br /&gt;
    (2025-09-25 09:40:33) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:14) postgres=# select :&#039;myvar&#039;;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:24) postgres=# select $1 \bind :myvar \g&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
psql&#039;s command `\set` allows just simply string concatenation - but allows to execute shell command. When&lt;br /&gt;
query is executed by `\gset` command, then result is stored in psql variable.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:41:40) postgres=# \set ctime `date`&lt;br /&gt;
    (2025-09-25 09:43:55) postgres=# \echo :ctime&lt;br /&gt;
    čt 25. září 2025, 09:43:55 CEST&lt;br /&gt;
&lt;br /&gt;
&#039;\set` command is too simple, and writing some complex operations is unreadable or not portable.&lt;br /&gt;
&lt;br /&gt;
It is good enough for writing tests, but it is not generally available (probably nobody expects it), and it is not&lt;br /&gt;
accessible from server side (from stored procedures). There is not simple way, how to access psql variables from&lt;br /&gt;
anonymous  block (proxy custom GUC variable is needed):&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:58:38) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    (2025-09-25 09:58:43) postgres=# select set_config(&#039;myvars.myvar&#039;, :&#039;myvar&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ ahoj       │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 10:00:27) postgres=# do $$ begin raise notice &#039;%&#039;, current_setting(&#039;myvars.myvar&#039;); end $$;&lt;br /&gt;
    NOTICE:  ahoj&lt;br /&gt;
    DO&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PostgreSQL has configuration GUC (Grand Unified Configuration) variables. These variables are primarily designed for PostgreSQL and PostgreSQL extensions configuration, but can be used as session variables (implicit, explicit, typeless). These variables can be set by command `SET` or by function `set_config`, and can be read by command `SHOW` or by function `current_setting`. GUC variables are designed for holding configuration parameters. When they are created from an internal API, then they can be protected for super users, hidden to common users, and can have assigned types (bool, memory, number, interval, string, enum) and values that can be checked before assign. The type system is independent of the PostgreSQL SQL type system - it is used for configuration and that is processed before postgresql sql type system is initialized.&lt;br /&gt;
&lt;br /&gt;
Variables created from SQL should be only &amp;quot;custom&amp;quot;. The name of these variables have to use a prefix and these variables are string only. These variables are commonly used as a workaround for missing server side session variables in Postgres (typeless unprotected unsecure session variables).&lt;br /&gt;
&lt;br /&gt;
Specific feature of Postgres GUC (build-in or custom) is the possibility to specify scope transaction, session or function execution. Another functionality is to assign a default value of a specific parameter with some event - loading configuration, login to role, login to database, start some function. These features are nice for work with configuration, but can be very problematic when GUC variables are used as session variables.&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE FUNCTION fx() &lt;br /&gt;
    RETURNS void AS $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, current_setting(&#039;my.x&#039;);&lt;br /&gt;
    END $$ LANGUAGE plpgsql SET my.x = 20;&lt;br /&gt;
    CREATE FUNCTION&lt;br /&gt;
    (2025-09-27 06:10:37) postgres=# SET my.x = 0; SELECT fx();&lt;br /&gt;
    SET&lt;br /&gt;
    NOTICE:  20&lt;br /&gt;
    ┌────┐&lt;br /&gt;
    │ fx │&lt;br /&gt;
    ╞════╡&lt;br /&gt;
    │    │&lt;br /&gt;
    └────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    (2025-09-27 06:10:39) postgres=# SHOW my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 0    │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
GUC variables are transactional - without possibility to disable it.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-27 06:17:31) postgres=# set my.x = 10;&lt;br /&gt;
    SET&lt;br /&gt;
    (2025-09-27 06:19:42) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:19:46) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, true);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:19:55) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:01) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:20:11) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:13) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:20:38) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:45) postgres=# rollback;&lt;br /&gt;
    ROLLBACK&lt;br /&gt;
    (2025-09-27 06:20:52) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:55) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:22:36) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:22:39) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:22:42) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
So there is no possibility to store to custom GUC some details about an error.&lt;br /&gt;
&lt;br /&gt;
== Oracle ==&lt;br /&gt;
Oracle PL/SQL allows the use of package variables. PL/SQL is based on ADA&lt;br /&gt;
language - and package variables are &amp;quot;global&amp;quot; variables. They are not&lt;br /&gt;
directly visible from SQL, but Oracle allows reduced syntax for functions&lt;br /&gt;
without arguments, so you need to write a wrapper&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE PACKAGE my_package&lt;br /&gt;
    AS&lt;br /&gt;
      FUNCTION get_a RETURN NUMBER;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    /&lt;br /&gt;
    &lt;br /&gt;
    CREATE OR REPLACE PACKAGE BODY my_package&lt;br /&gt;
    AS&lt;br /&gt;
      a  NUMBER(20);&lt;br /&gt;
     &lt;br /&gt;
      FUNCTION get_a&lt;br /&gt;
      RETURN NUMBER&lt;br /&gt;
      IS&lt;br /&gt;
      BEGIN&lt;br /&gt;
        RETURN a;&lt;br /&gt;
      END get_a;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    &lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
Inside SQL the higher priority has SQL, inside non SQL commands like CALL&lt;br /&gt;
or some PL/SQL command, the higher priority has packages.&lt;br /&gt;
&lt;br /&gt;
The Oracle allows both syntax for calling function with zero arguments so&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a() FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Then there is less risk reduction of collision. Package variables persist&lt;br /&gt;
in session&lt;br /&gt;
&lt;br /&gt;
Another possibility is using variables in SQL*Plus (looks like our psql&lt;br /&gt;
variables, with possibility to define type on server side)&lt;br /&gt;
&lt;br /&gt;
The variable should be declared by command VARIABLE and can be accessed by&lt;br /&gt;
syntax :varname in session before usage (maybe this step is optional)&lt;br /&gt;
&lt;br /&gt;
    VARIABLE bv_variable_name VARCHAR2(30)&lt;br /&gt;
    BEGIN&lt;br /&gt;
     :bv_variable_name := &#039;Some Value&#039;;&lt;br /&gt;
    END;&lt;br /&gt;
    &lt;br /&gt;
    SELECT column_name&lt;br /&gt;
      FROM   table_name&lt;br /&gt;
     WHERE  column_name = :bv_variable_name;&lt;br /&gt;
&lt;br /&gt;
== DB2 ==&lt;br /&gt;
The &amp;quot;user defined global variables&amp;quot; are similar to my proposal. The&lt;br /&gt;
differences are different access rights &amp;quot;READ, WRITE&amp;quot; x &amp;quot;SELECT, UPDATE&amp;quot;.&lt;br /&gt;
Because PostgreSQL has SET command for GUC, I introduced LET command (DB2&lt;br /&gt;
uses SET)&lt;br /&gt;
&lt;br /&gt;
Variables are visible in all sessions, but value is private per session.&lt;br /&gt;
Variables are not transactional. The usage is wider than my proposal. Then&lt;br /&gt;
can be changed by commands SET, SELECT INTO or they can be used like OUT&lt;br /&gt;
parameters of procedures. The search path (or some like that) is used for&lt;br /&gt;
variables too, but the variables has less priority than tables/columns.&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE myCounter INT DEFAULT 01;&lt;br /&gt;
    SELECT EMPNO, LASTNAME, CASE WHEN myCounter = 1 THEN SALARY ELSE NULL END&lt;br /&gt;
    FROM EMPLOYEE WHERE WORKDEPT = ’A00’;&lt;br /&gt;
    SET myCounter = 29;&lt;br /&gt;
&lt;br /&gt;
There are (I think) different kinds of variables - accessed by the function&lt;br /&gt;
GETVARIABLE(&#039;name&#039;, &#039;default) - it looks very similar ro our GUC and&lt;br /&gt;
`current_setting` function. These variables can be set by connection&lt;br /&gt;
string, are of varchar type and 10 values are allowed. Built-in session&lt;br /&gt;
variables (configuration) can be accessed by the function GETVARIABLE too.&lt;br /&gt;
&lt;br /&gt;
== MSSQL (T-SQL) ==&lt;br /&gt;
global variables - the access syntax is @@varname, they are used like GUC&lt;br /&gt;
and little bit more - some state informations are there like @@ERROR,&lt;br /&gt;
@@ROWCOUNT or @@IDENTITY&lt;br /&gt;
&lt;br /&gt;
local variables - the access syntax is @varname, and should be declared&lt;br /&gt;
before usage by DECLARE command. The scope is limited to batch or procedure&lt;br /&gt;
or function, where DECLARE command was executed.&lt;br /&gt;
&lt;br /&gt;
    DECLARE @TestVariable AS VARCHAR(100)&lt;br /&gt;
    SET @TestVariable = &#039;Think Green&#039;&lt;br /&gt;
    GO&lt;br /&gt;
    PRINT @TestVariable&lt;br /&gt;
&lt;br /&gt;
This script fails, because PRINT is executed in another batch. So I think&lt;br /&gt;
so MSSQL doesn&#039;t support session variables&lt;br /&gt;
&lt;br /&gt;
There are similar mechanisms like our custom GUC and usage current_setting&lt;br /&gt;
and set_config functions. Generally, in this area is MSSQL very primitive&lt;br /&gt;
&lt;br /&gt;
    EXEC sp_set_session_context &#039;user_id&#039;, 4;&lt;br /&gt;
    SELECT SESSION_CONTEXT(N&#039;user_id&#039;);&lt;br /&gt;
&lt;br /&gt;
== SQL/PSM ==&lt;br /&gt;
Standard introduces a concept of modules that can be joined with schemas.&lt;br /&gt;
The variables are like PLpgSQL, but only local - the only temp tables can&lt;br /&gt;
be defined on module levels. These tables can be accessed only from&lt;br /&gt;
routines assigned to modules. Modules are declarative versions of our&lt;br /&gt;
extensions (if I understand well, I didn&#039;t find any implementation). It&lt;br /&gt;
allows you to overwrite the search patch for routines assigned in the&lt;br /&gt;
module. Variables are not transactional, the priority - variables/columns&lt;br /&gt;
is not specified.&lt;br /&gt;
&lt;br /&gt;
= Types of different session variables =&lt;br /&gt;
* client side (psql, pgadmin, ...) - mostly similar to shell variables (based on string operations)&lt;br /&gt;
* server side&lt;br /&gt;
** declarative&lt;br /&gt;
*** created only for batch (T-SQL)&lt;br /&gt;
*** created as an database object (or part of database object) (Oracle, DB2)&lt;br /&gt;
** created by usage (MySQL)&lt;br /&gt;
&lt;br /&gt;
=Issues that any implementation should to solve =&lt;br /&gt;
* possible collision between session variables and other database objects&lt;br /&gt;
* memory cleaning after dropping session variable (when DDL can be reverted)&lt;br /&gt;
* memory invalidation (variable is dropped by one session, but still used in second session)&lt;br /&gt;
&lt;br /&gt;
= ToDo =&lt;br /&gt;
* SECURITY LABEL support - enhancing dummy_seclabel module, sepgsql extension + pg doc and SecLabelSupportsObjectType&lt;/div&gt;</summary>
		<author><name>Okbobcz</name></author>
	</entry>
	<entry>
		<id>https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41803</id>
		<title>Implementation of declarative catalog session variables</title>
		<link rel="alternate" type="text/html" href="https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41803"/>
		<updated>2025-09-27T05:31:44Z</updated>

		<summary type="html">&lt;p&gt;Okbobcz: /* Session variables */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Session variables =&lt;br /&gt;
Session variables are database objects that can hold a value across multiple queries inside a session. A design of session variables is varied and more times more databases implement more than just one type of session variables. Unfortunately standard SQL/PSM is not commonly accepted, and a part of this standard related to modules and module&#039;s variables was not implemented (in any widely used product). Very often session variables are a part of the stored procedures environment - but there are exceptions (MySQL session variables or any client side session variables). What I know is that session variables don&#039;t support transactions (for values). It is a plus minus just variable like we know from programming languages.&lt;br /&gt;
&lt;br /&gt;
There is no common design of session variables. Currently any database system has its own proprietary design with proprietary syntax and features. Some systems have more than one different design of session variables. It is hard to compare different designs - any design has different advantages and disadvantages.&lt;br /&gt;
&lt;br /&gt;
== MySQL ==&lt;br /&gt;
Session variables in MySQL have the same syntax like more known T-SQL variables (but all other things are proprietary). MySQL knows two kinds of session variables:&lt;br /&gt;
&lt;br /&gt;
* system variables - use the prefix `@@` or `@@xxx.`&lt;br /&gt;
* user defined variables - use the prefix `@`&lt;br /&gt;
&lt;br /&gt;
System variables can be modified by command `SET`. After keyword `SET` keywords `GLOBAL`, `SESSION` or `PERSIST` can be used. &lt;br /&gt;
Some variables can be used just with keyword `GLOBAL` or `SESSION` and in this case, when the name is prefixed by `@@`, is not&lt;br /&gt;
necessary to specify scope. System variables are defined by MySQL or by an mysql&#039;s plugins.&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL some_config = 100;&lt;br /&gt;
    SET @@same_config = 100;&lt;br /&gt;
&lt;br /&gt;
    SET SESSION sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@SESSION.sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
&lt;br /&gt;
Persistent values are stored in `mysql-auto.conf`.&lt;br /&gt;
&lt;br /&gt;
Reset of system variables can be done by setting to default or assigning from global namespace:&lt;br /&gt;
&lt;br /&gt;
    SET @@sql_mode = DEFAULT;&lt;br /&gt;
    SET @@sql_mode = @@GLOBAL.sql_mode&lt;br /&gt;
&lt;br /&gt;
PostgreSQL equivalent:&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL ; -- there is not similar command in Postgres&lt;br /&gt;
    SET SESSION ; -- SET&lt;br /&gt;
    SET PERSIST ; -- ALTER SYSTEM; SELECT pg_reload_conf();&lt;br /&gt;
    SELECT @@var; -- SELECT current_setting(&#039;var);&lt;br /&gt;
&lt;br /&gt;
User defined variables are defined by assignment&lt;br /&gt;
&lt;br /&gt;
    SET @var = expr [, @var2 = expr [ ... ]];&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
User defined variables can be only int, decimal, float, binary, nonbinary string or NULL - casting from/to any types is implicit and hidden.&lt;br /&gt;
UDV cannot be used as table or column names.&lt;br /&gt;
&lt;br /&gt;
An advantage of T-SQL syntax (with special prefixes) is simple solution of possible identifier collisions. MySQL&lt;br /&gt;
variables are very handy too, and user experience can be good for people with knowledge of T-SQL (MSSQL or Sybase).&lt;br /&gt;
On the other hand, prefix based syntax can be disturbing for users who work with different systems than MSSQL. Type system&lt;br /&gt;
is maybe too simple and can be usafe. Generally the performance of MySQL (or MariaDB) stored procedures is not good,&lt;br /&gt;
and stored procedures are not frequently used. There is not any protection against typo errors. Unassigned user variables&lt;br /&gt;
can be used without error (have  NULL value), so static check of stored procedures is not possible (in this area).&lt;br /&gt;
&lt;br /&gt;
There is not any possibility how to limit an access to user defined variables in MySQL. UDV cannot be protected&lt;br /&gt;
against unwanted usage. Some protection can be only naming convention (all UDV share one name space).&lt;br /&gt;
&lt;br /&gt;
==Postgres==&lt;br /&gt;
Postgres has relatively advanced client side session variables in `psql`. `psql`&#039;s variables use prefix &#039;:&#039;. The implementation is simple. psql&#039;s parser reads tokens from input. When it gets a token related to a variable, then this token is replaced by a value of variable. Implemented syntax supports literal and identifier escaping. psql&#039;s variables can be used as an argument of the `\bind` command.&lt;br /&gt;
&lt;br /&gt;
psql&#039;s variables are typeless client side variables available only inside `psql` or `pgbench`. &lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:40:18) postgres=# \set myvar ahoj&lt;br /&gt;
    (2025-09-25 09:40:33) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:14) postgres=# select :&#039;myvar&#039;;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:24) postgres=# select $1 \bind :myvar \g&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
psql&#039;s command `\set` allows just simply string concatenation - but allows to execute shell command. When&lt;br /&gt;
query is executed by `\gset` command, then result is stored in psql variable.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:41:40) postgres=# \set ctime `date`&lt;br /&gt;
    (2025-09-25 09:43:55) postgres=# \echo :ctime&lt;br /&gt;
    čt 25. září 2025, 09:43:55 CEST&lt;br /&gt;
&lt;br /&gt;
&#039;\set` command is too simple, and writing some complex operations is unreadable or not portable.&lt;br /&gt;
&lt;br /&gt;
It is good enough for writing tests, but it is not generally available (probably nobody expects it), and it is not&lt;br /&gt;
accessible from server side (from stored procedures). There is not simple way, how to access psql variables from&lt;br /&gt;
anonymous  block (proxy custom GUC variable is needed):&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:58:38) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    (2025-09-25 09:58:43) postgres=# select set_config(&#039;myvars.myvar&#039;, :&#039;myvar&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ ahoj       │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 10:00:27) postgres=# do $$ begin raise notice &#039;%&#039;, current_setting(&#039;myvars.myvar&#039;); end $$;&lt;br /&gt;
    NOTICE:  ahoj&lt;br /&gt;
    DO&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PostgreSQL has configuration GUC (Grand Unified Configuration) variables. These variables are primarily designed for PostgreSQL and PostgreSQL extensions configuration, but can be used as session variables (implicit, explicit, typeless). These variables can be set by command `SET` or by function `set_config`, and can be read by command `SHOW` or by function `current_setting`. GUC variables are designed for holding configuration parameters. When they are created from an internal API, then they can be protected for super users, hidden to common users, and can have assigned types (bool, memory, number, interval, string, enum) and values that can be checked before assign. The type system is independent of the PostgreSQL SQL type system - it is used for configuration and that is processed before postgresql sql type system is initialized.&lt;br /&gt;
&lt;br /&gt;
Variables created from SQL should be only &amp;quot;custom&amp;quot;. The name of these variables have to use a prefix and these variables are string only. These variables are commonly used as a workaround for missing server side session variables in Postgres (typeless unprotected unsecure session variables).&lt;br /&gt;
&lt;br /&gt;
Specific feature of Postgres GUC (build-in or custom) is the possibility to specify scope transaction, session or function execution. Another functionality is to assign a default value of a specific parameter with some event - loading configuration, login to role, login to database, start some function. These features are nice for work with configuration, but can be very problematic when GUC variables are used as session variables.&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE FUNCTION fx() &lt;br /&gt;
    RETURNS void AS $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, current_setting(&#039;my.x&#039;);&lt;br /&gt;
    END $$ LANGUAGE plpgsql SET my.x = 20;&lt;br /&gt;
    CREATE FUNCTION&lt;br /&gt;
    (2025-09-27 06:10:37) postgres=# SET my.x = 0; SELECT fx();&lt;br /&gt;
    SET&lt;br /&gt;
    NOTICE:  20&lt;br /&gt;
    ┌────┐&lt;br /&gt;
    │ fx │&lt;br /&gt;
    ╞════╡&lt;br /&gt;
    │    │&lt;br /&gt;
    └────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    (2025-09-27 06:10:39) postgres=# SHOW my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 0    │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
GUC variables are transactional - without possibility to disable it.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-27 06:17:31) postgres=# set my.x = 10;&lt;br /&gt;
    SET&lt;br /&gt;
    (2025-09-27 06:19:42) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:19:46) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, true);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:19:55) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:01) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:20:11) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:13) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:20:38) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:45) postgres=# rollback;&lt;br /&gt;
    ROLLBACK&lt;br /&gt;
    (2025-09-27 06:20:52) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:55) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:22:36) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:22:39) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:22:42) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
So there is no possibility to store to custom GUC some details about an error.&lt;br /&gt;
&lt;br /&gt;
== Oracle ==&lt;br /&gt;
Oracle PL/SQL allows the use of package variables. PL/SQL is based on ADA&lt;br /&gt;
language - and package variables are &amp;quot;global&amp;quot; variables. They are not&lt;br /&gt;
directly visible from SQL, but Oracle allows reduced syntax for functions&lt;br /&gt;
without arguments, so you need to write a wrapper&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE PACKAGE my_package&lt;br /&gt;
    AS&lt;br /&gt;
      FUNCTION get_a RETURN NUMBER;&lt;br /&gt;
    END my_package;&lt;br /&gt;
    /&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE PACKAGE BODY my_package&lt;br /&gt;
    AS&lt;br /&gt;
      a  NUMBER(20);&lt;br /&gt;
&lt;br /&gt;
      FUNCTION get_a&lt;br /&gt;
      RETURN NUMBER&lt;br /&gt;
      IS&lt;br /&gt;
      BEGIN&lt;br /&gt;
        RETURN a;&lt;br /&gt;
      END get_a;&lt;br /&gt;
    END my_package;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
Inside SQL the higher priority has SQL, inside non SQL commands like CALL&lt;br /&gt;
or some PL/SQL command, the higher priority has packages.&lt;br /&gt;
&lt;br /&gt;
The Oracle allows both syntax for calling function with zero arguments so&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
    SELECT my_package.get_a() FROM DUAL;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Then there is less risk reduction of collision. Package variables persist&lt;br /&gt;
in session&lt;br /&gt;
&lt;br /&gt;
Another possibility is using variables in SQL*Plus (looks like our psql&lt;br /&gt;
variables, with possibility to define type on server side)&lt;br /&gt;
&lt;br /&gt;
The variable should be declared by command VARIABLE and can be accessed by&lt;br /&gt;
syntax :varname in session before usage (maybe this step is optional)&lt;br /&gt;
&lt;br /&gt;
    VARIABLE bv_variable_name VARCHAR2(30)&lt;br /&gt;
    BEGIN&lt;br /&gt;
     :bv_variable_name := &#039;Some Value&#039;;&lt;br /&gt;
    END;&lt;br /&gt;
&lt;br /&gt;
    SELECT column_name&lt;br /&gt;
      FROM   table_name&lt;br /&gt;
     WHERE  column_name = :bv_variable_name;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== DB2 ==&lt;br /&gt;
The &amp;quot;user defined global variables&amp;quot; are similar to my proposal. The&lt;br /&gt;
differences are different access rights &amp;quot;READ, WRITE&amp;quot; x &amp;quot;SELECT, UPDATE&amp;quot;.&lt;br /&gt;
Because PostgreSQL has SET command for GUC, I introduced LET command (DB2&lt;br /&gt;
uses SET)&lt;br /&gt;
&lt;br /&gt;
Variables are visible in all sessions, but value is private per session.&lt;br /&gt;
Variables are not transactional. The usage is wider than my proposal. Then&lt;br /&gt;
can be changed by commands SET, SELECT INTO or they can be used like OUT&lt;br /&gt;
parameters of procedures. The search path (or some like that) is used for&lt;br /&gt;
variables too, but the variables has less priority than tables/columns.&lt;br /&gt;
&lt;br /&gt;
    CREATE VARIABLE myCounter INT DEFAULT 01;&lt;br /&gt;
    SELECT EMPNO, LASTNAME, CASE WHEN myCounter = 1 THEN SALARY ELSE NULL END&lt;br /&gt;
    FROM EMPLOYEE WHERE WORKDEPT = ’A00’;&lt;br /&gt;
    SET myCounter = 29;&lt;br /&gt;
&lt;br /&gt;
There are (I think) different kinds of variables - accessed by the function&lt;br /&gt;
GETVARIABLE(&#039;name&#039;, &#039;default) - it looks very similar ro our GUC and&lt;br /&gt;
`current_setting` function. These variables can be set by connection&lt;br /&gt;
string, are of varchar type and 10 values are allowed. Built-in session&lt;br /&gt;
variables (configuration) can be accessed by the function GETVARIABLE too.&lt;br /&gt;
&lt;br /&gt;
== MSSQL (T-SQL) ==&lt;br /&gt;
global variables - the access syntax is @@varname, they are used like GUC&lt;br /&gt;
and little bit more - some state informations are there like @@ERROR,&lt;br /&gt;
@@ROWCOUNT or @@IDENTITY&lt;br /&gt;
&lt;br /&gt;
local variables - the access syntax is @varname, and should be declared&lt;br /&gt;
before usage by DECLARE command. The scope is limited to batch or procedure&lt;br /&gt;
or function, where DECLARE command was executed.&lt;br /&gt;
&lt;br /&gt;
    DECLARE @TestVariable AS VARCHAR(100)&lt;br /&gt;
    SET @TestVariable = &#039;Think Green&#039;&lt;br /&gt;
    GO&lt;br /&gt;
    PRINT @TestVariable&lt;br /&gt;
&lt;br /&gt;
This script fails, because PRINT is executed in another batch. So I think&lt;br /&gt;
so MSSQL doesn&#039;t support session variables&lt;br /&gt;
&lt;br /&gt;
There are similar mechanisms like our custom GUC and usage current_setting&lt;br /&gt;
and set_config functions. Generally, in this area is MSSQL very primitive&lt;br /&gt;
&lt;br /&gt;
    EXEC sp_set_session_context &#039;user_id&#039;, 4;&lt;br /&gt;
    SELECT SESSION_CONTEXT(N&#039;user_id&#039;);&lt;br /&gt;
&lt;br /&gt;
== SQL/PSM ==&lt;br /&gt;
Standard introduces a concept of modules that can be joined with schemas.&lt;br /&gt;
The variables are like PLpgSQL, but only local - the only temp tables can&lt;br /&gt;
be defined on module levels. These tables can be accessed only from&lt;br /&gt;
routines assigned to modules. Modules are declarative versions of our&lt;br /&gt;
extensions (if I understand well, I didn&#039;t find any implementation). It&lt;br /&gt;
allows you to overwrite the search patch for routines assigned in the&lt;br /&gt;
module. Variables are not transactional, the priority - variables/columns&lt;br /&gt;
is not specified.&lt;br /&gt;
&lt;br /&gt;
= Types of different session variables =&lt;br /&gt;
* client side (psql, pgadmin, ...) - mostly similar to shell variables (based on string operations)&lt;br /&gt;
* server side&lt;br /&gt;
** declarative&lt;br /&gt;
*** created only for batch (T-SQL)&lt;br /&gt;
*** created as an database object (or part of database object) (Oracle, DB2)&lt;br /&gt;
** created by usage (MySQL)&lt;br /&gt;
&lt;br /&gt;
=Issues that any implementation should to solve =&lt;br /&gt;
* possible collision between session variables and other database objects&lt;br /&gt;
* memory cleaning after dropping session variable (when DDL can be reverted)&lt;br /&gt;
* memory invalidation (variable is dropped by one session, but still used in second session)&lt;br /&gt;
&lt;br /&gt;
= ToDo =&lt;br /&gt;
* SECURITY LABEL support - enhancing dummy_seclabel module, sepgsql extension + pg doc and SecLabelSupportsObjectType&lt;/div&gt;</summary>
		<author><name>Okbobcz</name></author>
	</entry>
	<entry>
		<id>https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41800</id>
		<title>Implementation of declarative catalog session variables</title>
		<link rel="alternate" type="text/html" href="https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41800"/>
		<updated>2025-09-27T04:30:32Z</updated>

		<summary type="html">&lt;p&gt;Okbobcz: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Session variables =&lt;br /&gt;
Session variables are database objects that can hold a value across multiple queries inside a session. A design of session variables is varied and more times more databases implement more than just one type of session variables. Unfortunately standard SQL/PSM is not commonly accepted, and a part of this standard related to modules and module&#039;s variables was not implemented (in any widely used product). Very often session variables are a part of the stored procedures environment - but there are exceptions (MySQL session variables or any client side session variables). What I know is that session variables don&#039;t support transactions (for values). It is a plus minus just variable like we know from programming languages.&lt;br /&gt;
&lt;br /&gt;
There is no common design of session variables. Currently any database system has its own proprietary design with proprietary syntax and features. Some systems have more than one different design of session variables. It is hard to compare different designs - any design has different advantages and disadvantages.&lt;br /&gt;
&lt;br /&gt;
== MySQL ==&lt;br /&gt;
Session variables in MySQL have the same syntax like more known T-SQL variables (but all other things are proprietary). MySQL knows two kinds of session variables:&lt;br /&gt;
&lt;br /&gt;
* system variables - use the prefix `@@` or `@@xxx.`&lt;br /&gt;
* user defined variables - use the prefix `@`&lt;br /&gt;
&lt;br /&gt;
System variables can be modified by command `SET`. After keyword `SET` keywords `GLOBAL`, `SESSION` or `PERSIST` can be used. &lt;br /&gt;
Some variables can be used just with keyword `GLOBAL` or `SESSION` and in this case, when the name is prefixed by `@@`, is not&lt;br /&gt;
necessary to specify scope. System variables are defined by MySQL or by an mysql&#039;s plugins.&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL some_config = 100;&lt;br /&gt;
    SET @@same_config = 100;&lt;br /&gt;
&lt;br /&gt;
    SET SESSION sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@SESSION.sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
&lt;br /&gt;
Persistent values are stored in `mysql-auto.conf`.&lt;br /&gt;
&lt;br /&gt;
Reset of system variables can be done by setting to default or assigning from global namespace:&lt;br /&gt;
&lt;br /&gt;
    SET @@sql_mode = DEFAULT;&lt;br /&gt;
    SET @@sql_mode = @@GLOBAL.sql_mode&lt;br /&gt;
&lt;br /&gt;
PostgreSQL equivalent:&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL ; -- there is not similar command in Postgres&lt;br /&gt;
    SET SESSION ; -- SET&lt;br /&gt;
    SET PERSIST ; -- ALTER SYSTEM; SELECT pg_reload_conf();&lt;br /&gt;
    SELECT @@var; -- SELECT current_setting(&#039;var);&lt;br /&gt;
&lt;br /&gt;
User defined variables are defined by assignment&lt;br /&gt;
&lt;br /&gt;
    SET @var = expr [, @var2 = expr [ ... ]];&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
User defined variables can be only int, decimal, float, binary, nonbinary string or NULL - casting from/to any types is implicit and hidden.&lt;br /&gt;
UDV cannot be used as table or column names.&lt;br /&gt;
&lt;br /&gt;
An advantage of T-SQL syntax (with special prefixes) is simple solution of possible identifier collisions. MySQL&lt;br /&gt;
variables are very handy too, and user experience can be good for people with knowledge of T-SQL (MSSQL or Sybase).&lt;br /&gt;
On the other hand, prefix based syntax can be disturbing for users who work with different systems than MSSQL. Type system&lt;br /&gt;
is maybe too simple and can be usafe. Generally the performance of MySQL (or MariaDB) stored procedures is not good,&lt;br /&gt;
and stored procedures are not frequently used. There is not any protection against typo errors. Unassigned user variables&lt;br /&gt;
can be used without error (have  NULL value), so static check of stored procedures is not possible (in this area).&lt;br /&gt;
&lt;br /&gt;
There is not any possibility how to limit an access to user defined variables in MySQL. UDV cannot be protected&lt;br /&gt;
against unwanted usage. Some protection can be only naming convention (all UDV share one name space).&lt;br /&gt;
&lt;br /&gt;
==Postgres==&lt;br /&gt;
Postgres has relatively advanced client side session variables in `psql`. `psql`&#039;s variables use prefix &#039;:&#039;. The implementation is simple. psql&#039;s parser reads tokens from input. When it gets a token related to a variable, then this token is replaced by a value of variable. Implemented syntax supports literal and identifier escaping. psql&#039;s variables can be used as an argument of the `\bind` command.&lt;br /&gt;
&lt;br /&gt;
psql&#039;s variables are typeless client side variables available only inside `psql` or `pgbench`. &lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:40:18) postgres=# \set myvar ahoj&lt;br /&gt;
    (2025-09-25 09:40:33) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:14) postgres=# select :&#039;myvar&#039;;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:24) postgres=# select $1 \bind :myvar \g&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
psql&#039;s command `\set` allows just simply string concatenation - but allows to execute shell command. When&lt;br /&gt;
query is executed by `\gset` command, then result is stored in psql variable.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:41:40) postgres=# \set ctime `date`&lt;br /&gt;
    (2025-09-25 09:43:55) postgres=# \echo :ctime&lt;br /&gt;
    čt 25. září 2025, 09:43:55 CEST&lt;br /&gt;
&lt;br /&gt;
&#039;\set` command is too simple, and writing some complex operations is unreadable or not portable.&lt;br /&gt;
&lt;br /&gt;
It is good enough for writing tests, but it is not generally available (probably nobody expects it), and it is not&lt;br /&gt;
accessible from server side (from stored procedures). There is not simple way, how to access psql variables from&lt;br /&gt;
anonymous  block (proxy custom GUC variable is needed):&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:58:38) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    (2025-09-25 09:58:43) postgres=# select set_config(&#039;myvars.myvar&#039;, :&#039;myvar&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ ahoj       │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 10:00:27) postgres=# do $$ begin raise notice &#039;%&#039;, current_setting(&#039;myvars.myvar&#039;); end $$;&lt;br /&gt;
    NOTICE:  ahoj&lt;br /&gt;
    DO&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PostgreSQL has configuration GUC (Grand Unified Configuration) variables. These variables are primarily designed for PostgreSQL and PostgreSQL extensions configuration, but can be used as session variables (implicit, explicit, typeless). These variables can be set by command `SET` or by function `set_config`, and can be read by command `SHOW` or by function `current_setting`. GUC variables are designed for holding configuration parameters. When they are created from an internal API, then they can be protected for super users, hidden to common users, and can have assigned types (bool, memory, number, interval, string, enum) and values that can be checked before assign. The type system is independent of the PostgreSQL SQL type system - it is used for configuration and that is processed before postgresql sql type system is initialized.&lt;br /&gt;
&lt;br /&gt;
Variables created from SQL should be only &amp;quot;custom&amp;quot;. The name of these variables have to use a prefix and these variables are string only. These variables are commonly used as a workaround for missing server side session variables in Postgres (typeless unprotected unsecure session variables).&lt;br /&gt;
&lt;br /&gt;
Specific feature of Postgres GUC (build-in or custom) is the possibility to specify scope transaction, session or function execution. Another functionality is to assign a default value of a specific parameter with some event - loading configuration, login to role, login to database, start some function. These features are nice for work with configuration, but can be very problematic when GUC variables are used as session variables.&lt;br /&gt;
&lt;br /&gt;
    CREATE OR REPLACE FUNCTION fx() &lt;br /&gt;
    RETURNS void AS $$&lt;br /&gt;
    BEGIN&lt;br /&gt;
      RAISE NOTICE &#039;%&#039;, current_setting(&#039;my.x&#039;);&lt;br /&gt;
    END $$ LANGUAGE plpgsql SET my.x = 20;&lt;br /&gt;
    CREATE FUNCTION&lt;br /&gt;
    (2025-09-27 06:10:37) postgres=# SET my.x = 0; SELECT fx();&lt;br /&gt;
    SET&lt;br /&gt;
    NOTICE:  20&lt;br /&gt;
    ┌────┐&lt;br /&gt;
    │ fx │&lt;br /&gt;
    ╞════╡&lt;br /&gt;
    │    │&lt;br /&gt;
    └────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    (2025-09-27 06:10:39) postgres=# SHOW my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 0    │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
GUC variables are transactional - without possibility to disable it.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-27 06:17:31) postgres=# set my.x = 10;&lt;br /&gt;
    SET&lt;br /&gt;
    (2025-09-27 06:19:42) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:19:46) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, true);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:19:55) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:01) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:20:11) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:13) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:20:38) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:45) postgres=# rollback;&lt;br /&gt;
    ROLLBACK&lt;br /&gt;
    (2025-09-27 06:20:52) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 10   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:20:55) postgres=# begin;&lt;br /&gt;
    BEGIN&lt;br /&gt;
    (2025-09-27 06:22:36) postgres=# select set_config(&#039;my.x&#039;, &#039;20&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ 20         │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-27 06:22:39) postgres=# commit;&lt;br /&gt;
    COMMIT&lt;br /&gt;
    (2025-09-27 06:22:42) postgres=# show my.x;&lt;br /&gt;
    ┌──────┐&lt;br /&gt;
    │ my.x │&lt;br /&gt;
    ╞══════╡&lt;br /&gt;
    │ 20   │&lt;br /&gt;
    └──────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
So there is no possibility to store to custom GUC some details about an error.&lt;br /&gt;
&lt;br /&gt;
== Oracle ==&lt;br /&gt;
== DB2 ==&lt;br /&gt;
== MSSQL (T-SQL) ==&lt;br /&gt;
&lt;br /&gt;
= Types of different session variables =&lt;br /&gt;
* client side (psql, pgadmin, ...) - mostly similar to shell variables (based on string operations)&lt;br /&gt;
* server side&lt;br /&gt;
** declarative&lt;br /&gt;
*** created only for batch (T-SQL)&lt;br /&gt;
*** created as an database object (or part of database object) (Oracle, DB2)&lt;br /&gt;
** created by usage (MySQL)&lt;br /&gt;
&lt;br /&gt;
=Issues that any implementation should to solve =&lt;br /&gt;
* possible collision between session variables and other database objects&lt;br /&gt;
* memory cleaning after dropping session variable (when DDL can be reverted)&lt;br /&gt;
* memory invalidation (variable is dropped by one session, but still used in second session)&lt;br /&gt;
&lt;br /&gt;
= ToDo =&lt;br /&gt;
* SECURITY LABEL support - enhancing dummy_seclabel module, sepgsql extension + pg doc and SecLabelSupportsObjectType&lt;/div&gt;</summary>
		<author><name>Okbobcz</name></author>
	</entry>
	<entry>
		<id>https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41787</id>
		<title>Implementation of declarative catalog session variables</title>
		<link rel="alternate" type="text/html" href="https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41787"/>
		<updated>2025-09-25T08:03:28Z</updated>

		<summary type="html">&lt;p&gt;Okbobcz: /* Postgres */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Session variables =&lt;br /&gt;
Session variables are database objects that can hold a value across multiple queries inside a session. A design of session variables is varied and more times more databases implement more than just one type of session variables. Unfortunately standard SQL/PSM is not commonly accepted, and a part of this standard related to modules and module&#039;s variables was not implemented (in any widely used product). Very often session variables are a part of the stored procedures environment - but there are exceptions (MySQL session variables or any client side session variables). What I know is that session variables don&#039;t support transactions (for values). It is a plus minus just variable like we know from programming languages.&lt;br /&gt;
&lt;br /&gt;
There is no common design of session variables. Currently any database system has its own proprietary design with proprietary syntax and features. Some systems have more than one different design of session variables. It is hard to compare different designs - any design has different advantages and disadvantages.&lt;br /&gt;
&lt;br /&gt;
== MySQL ==&lt;br /&gt;
Session variables in MySQL have the same syntax like more known T-SQL variables (but all other things are proprietary). MySQL knows two kinds of session variables:&lt;br /&gt;
&lt;br /&gt;
* system variables - use the prefix `@@` or `@@xxx.`&lt;br /&gt;
* user defined variables - use the prefix `@`&lt;br /&gt;
&lt;br /&gt;
System variables can be modified by command `SET`. After keyword `SET` keywords `GLOBAL`, `SESSION` or `PERSIST` can be used. &lt;br /&gt;
Some variables can be used just with keyword `GLOBAL` or `SESSION` and in this case, when the name is prefixed by `@@`, is not&lt;br /&gt;
necessary to specify scope. System variables are defined by MySQL or by an mysql&#039;s plugins.&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL some_config = 100;&lt;br /&gt;
    SET @@same_config = 100;&lt;br /&gt;
&lt;br /&gt;
    SET SESSION sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@SESSION.sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
&lt;br /&gt;
Persistent values are stored in `mysql-auto.conf`.&lt;br /&gt;
&lt;br /&gt;
Reset of system variables can be done by setting to default or assigning from global namespace:&lt;br /&gt;
&lt;br /&gt;
    SET @@sql_mode = DEFAULT;&lt;br /&gt;
    SET @@sql_mode = @@GLOBAL.sql_mode&lt;br /&gt;
&lt;br /&gt;
PostgreSQL equivalent:&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL ; -- there is not similar command in Postgres&lt;br /&gt;
    SET SESSION ; -- SET&lt;br /&gt;
    SET PERSIST ; -- ALTER SYSTEM; SELECT pg_reload_conf();&lt;br /&gt;
    SELECT @@var; -- SELECT current_setting(&#039;var);&lt;br /&gt;
&lt;br /&gt;
User defined variables are defined by assignment&lt;br /&gt;
&lt;br /&gt;
    SET @var = expr [, @var2 = expr [ ... ]];&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
User defined variables can be only int, decimal, float, binary, nonbinary string or NULL - casting from/to any types is implicit and hidden.&lt;br /&gt;
UDV cannot be used as table or column names.&lt;br /&gt;
&lt;br /&gt;
An advantage of T-SQL syntax (with special prefixes) is simple solution of possible identifier collisions. MySQL&lt;br /&gt;
variables are very handy too, and user experience can be good for people with knowledge of T-SQL (MSSQL or Sybase).&lt;br /&gt;
On the other hand, prefix based syntax can be disturbing for users who work with different systems than MSSQL. Type system&lt;br /&gt;
is maybe too simple and can be usafe. Generally the performance of MySQL (or MariaDB) stored procedures is not good,&lt;br /&gt;
and stored procedures are not frequently used. There is not any protection against typo errors. Unassigned user variables&lt;br /&gt;
can be used without error (have  NULL value), so static check of stored procedures is not possible (in this area).&lt;br /&gt;
&lt;br /&gt;
There is not any possibility how to limit an access to user defined variables in MySQL. UDV cannot be protected&lt;br /&gt;
against unwanted usage. Some protection can be only naming convention (all UDV share one name space).&lt;br /&gt;
&lt;br /&gt;
==Postgres==&lt;br /&gt;
Postgres has relatively advanced client side session variables in `psql`. `psql`&#039;s variables use prefix &#039;:&#039;. The implementation is simple. psql&#039;s parser reads tokens from input. When it gets a token related to a variable, then this token is replaced by a value of variable. Implemented syntax supports literal and identifier escaping. psql&#039;s variables can be used as an argument of the `\bind` command.&lt;br /&gt;
&lt;br /&gt;
psql&#039;s variables are typeless client side variables available only inside `psql` or `pgbench`. &lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:40:18) postgres=# \set myvar ahoj&lt;br /&gt;
    (2025-09-25 09:40:33) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:14) postgres=# select :&#039;myvar&#039;;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:24) postgres=# select $1 \bind :myvar \g&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
psql&#039;s command `\set` allows just simply string concatenation - but allows to execute shell command. When&lt;br /&gt;
query is executed by `\gset` command, then result is stored in psql variable.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:41:40) postgres=# \set ctime `date`&lt;br /&gt;
    (2025-09-25 09:43:55) postgres=# \echo :ctime&lt;br /&gt;
    čt 25. září 2025, 09:43:55 CEST&lt;br /&gt;
&lt;br /&gt;
&#039;\set` command is too simple, and writing some complex operations is unreadable or not portable.&lt;br /&gt;
&lt;br /&gt;
It is good enough for writing tests, but it is not generally available (probably nobody expects it), and it is not&lt;br /&gt;
accessible from server side (from stored procedures). There is not simple way, how to access psql variables from&lt;br /&gt;
anonymous  block (proxy custom GUC variable is needed):&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:58:38) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    (2025-09-25 09:58:43) postgres=# select set_config(&#039;myvars.myvar&#039;, :&#039;myvar&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ ahoj       │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 10:00:27) postgres=# do $$ begin raise notice &#039;%&#039;, current_setting(&#039;myvars.myvar&#039;); end $$;&lt;br /&gt;
    NOTICE:  ahoj&lt;br /&gt;
    DO&lt;br /&gt;
&lt;br /&gt;
== Oracle ==&lt;br /&gt;
== DB2 ==&lt;br /&gt;
== MSSQL (T-SQL) ==&lt;br /&gt;
&lt;br /&gt;
= Types of different session variables =&lt;br /&gt;
* client side (psql, pgadmin, ...) - mostly similar to shell variables (based on string operations)&lt;br /&gt;
* server side&lt;br /&gt;
** declarative&lt;br /&gt;
*** created only for batch (T-SQL)&lt;br /&gt;
*** created as an database object (or part of database object) (Oracle, DB2)&lt;br /&gt;
** created by usage (MySQL)&lt;br /&gt;
&lt;br /&gt;
=Issues that any implementation should to solve =&lt;br /&gt;
* possible collision between session variables and other database objects&lt;br /&gt;
* memory cleaning after dropping session variable (when DDL can be reverted)&lt;br /&gt;
* memory invalidation (variable is dropped by one session, but still used in second session)&lt;br /&gt;
&lt;br /&gt;
= ToDo =&lt;br /&gt;
* SECURITY LABEL support - enhancing dummy_seclabel module, sepgsql extension + pg doc and SecLabelSupportsObjectType&lt;/div&gt;</summary>
		<author><name>Okbobcz</name></author>
	</entry>
	<entry>
		<id>https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41786</id>
		<title>Implementation of declarative catalog session variables</title>
		<link rel="alternate" type="text/html" href="https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41786"/>
		<updated>2025-09-25T08:02:42Z</updated>

		<summary type="html">&lt;p&gt;Okbobcz: /* Postgres */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Session variables =&lt;br /&gt;
Session variables are database objects that can hold a value across multiple queries inside a session. A design of session variables is varied and more times more databases implement more than just one type of session variables. Unfortunately standard SQL/PSM is not commonly accepted, and a part of this standard related to modules and module&#039;s variables was not implemented (in any widely used product). Very often session variables are a part of the stored procedures environment - but there are exceptions (MySQL session variables or any client side session variables). What I know is that session variables don&#039;t support transactions (for values). It is a plus minus just variable like we know from programming languages.&lt;br /&gt;
&lt;br /&gt;
There is no common design of session variables. Currently any database system has its own proprietary design with proprietary syntax and features. Some systems have more than one different design of session variables. It is hard to compare different designs - any design has different advantages and disadvantages.&lt;br /&gt;
&lt;br /&gt;
== MySQL ==&lt;br /&gt;
Session variables in MySQL have the same syntax like more known T-SQL variables (but all other things are proprietary). MySQL knows two kinds of session variables:&lt;br /&gt;
&lt;br /&gt;
* system variables - use the prefix `@@` or `@@xxx.`&lt;br /&gt;
* user defined variables - use the prefix `@`&lt;br /&gt;
&lt;br /&gt;
System variables can be modified by command `SET`. After keyword `SET` keywords `GLOBAL`, `SESSION` or `PERSIST` can be used. &lt;br /&gt;
Some variables can be used just with keyword `GLOBAL` or `SESSION` and in this case, when the name is prefixed by `@@`, is not&lt;br /&gt;
necessary to specify scope. System variables are defined by MySQL or by an mysql&#039;s plugins.&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL some_config = 100;&lt;br /&gt;
    SET @@same_config = 100;&lt;br /&gt;
&lt;br /&gt;
    SET SESSION sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@SESSION.sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
&lt;br /&gt;
Persistent values are stored in `mysql-auto.conf`.&lt;br /&gt;
&lt;br /&gt;
Reset of system variables can be done by setting to default or assigning from global namespace:&lt;br /&gt;
&lt;br /&gt;
    SET @@sql_mode = DEFAULT;&lt;br /&gt;
    SET @@sql_mode = @@GLOBAL.sql_mode&lt;br /&gt;
&lt;br /&gt;
PostgreSQL equivalent:&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL ; -- there is not similar command in Postgres&lt;br /&gt;
    SET SESSION ; -- SET&lt;br /&gt;
    SET PERSIST ; -- ALTER SYSTEM; SELECT pg_reload_conf();&lt;br /&gt;
    SELECT @@var; -- SELECT current_setting(&#039;var);&lt;br /&gt;
&lt;br /&gt;
User defined variables are defined by assignment&lt;br /&gt;
&lt;br /&gt;
    SET @var = expr [, @var2 = expr [ ... ]];&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
User defined variables can be only int, decimal, float, binary, nonbinary string or NULL - casting from/to any types is implicit and hidden.&lt;br /&gt;
UDV cannot be used as table or column names.&lt;br /&gt;
&lt;br /&gt;
An advantage of T-SQL syntax (with special prefixes) is simple solution of possible identifier collisions. MySQL&lt;br /&gt;
variables are very handy too, and user experience can be good for people with knowledge of T-SQL (MSSQL or Sybase).&lt;br /&gt;
On the other hand, prefix based syntax can be disturbing for users who work with different systems than MSSQL. Type system&lt;br /&gt;
is maybe too simple and can be usafe. Generally the performance of MySQL (or MariaDB) stored procedures is not good,&lt;br /&gt;
and stored procedures are not frequently used. There is not any protection against typo errors. Unassigned user variables&lt;br /&gt;
can be used without error (have  NULL value), so static check of stored procedures is not possible (in this area).&lt;br /&gt;
&lt;br /&gt;
There is not any possibility how to limit an access to user defined variables in MySQL. UDV cannot be protected&lt;br /&gt;
against unwanted usage. Some protection can be only naming convention (all UDV share one name space).&lt;br /&gt;
&lt;br /&gt;
==Postgres==&lt;br /&gt;
Postgres has relatively advanced client side session variables in `psql`. `psql`&#039;s variables use prefix &#039;:&#039;. The implementation is simple. psql&#039;s parser reads tokens from input. When it gets a token related to a variable, then this token is replaced by a value of variable. Implemented syntax supports literal and identifier escaping. psql&#039;s variables can be used as an argument of the `\bind` command.&lt;br /&gt;
&lt;br /&gt;
psql&#039;s variables are typeless client side variables available only inside `psql` or `pgbench`. &lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:40:18) postgres=# \set myvar ahoj&lt;br /&gt;
    (2025-09-25 09:40:33) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:14) postgres=# select :&#039;myvar&#039;;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
    &lt;br /&gt;
    (2025-09-25 09:41:24) postgres=# select $1 \bind :myvar \g&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
psql&#039;s command `\set` allows just simply string concatenation - but allows to execute shell command. When&lt;br /&gt;
query is executed by `\gset` command, then result is stored in psql variable.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:41:40) postgres=# \set ctime `date`&lt;br /&gt;
    (2025-09-25 09:43:55) postgres=# \echo :ctime&lt;br /&gt;
    čt 25. září 2025, 09:43:55 CEST&lt;br /&gt;
&lt;br /&gt;
&#039;\set` command is too simple, and writing some complex operations is unreadable or not portable.&lt;br /&gt;
&lt;br /&gt;
It is good enough for writing tests, but it is not generally available (probably nobody expects it), and it is not&lt;br /&gt;
accessible from server side (from stored procedures). There is not simple way, how to access psql variables from&lt;br /&gt;
anonymous  block (proxy custom GUC variable is needed):&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:58:38) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    (2025-09-25 09:58:43) postgres=# select set_config(&#039;myvars.myvar&#039;, :&#039;myvar&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ ahoj       │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 10:00:27) postgres=# do $$ begin raise notice &#039;%&#039;, current_setting(&#039;myvars.myvar&#039;); end $$;&lt;br /&gt;
    NOTICE:  ahoj&lt;br /&gt;
    DO&lt;br /&gt;
&lt;br /&gt;
== Oracle ==&lt;br /&gt;
== DB2 ==&lt;br /&gt;
== MSSQL (T-SQL) ==&lt;br /&gt;
&lt;br /&gt;
= Types of different session variables =&lt;br /&gt;
* client side (psql, pgadmin, ...) - mostly similar to shell variables (based on string operations)&lt;br /&gt;
* server side&lt;br /&gt;
** declarative&lt;br /&gt;
*** created only for batch (T-SQL)&lt;br /&gt;
*** created as an database object (or part of database object) (Oracle, DB2)&lt;br /&gt;
** created by usage (MySQL)&lt;br /&gt;
&lt;br /&gt;
=Issues that any implementation should to solve =&lt;br /&gt;
* possible collision between session variables and other database objects&lt;br /&gt;
* memory cleaning after dropping session variable (when DDL can be reverted)&lt;br /&gt;
* memory invalidation (variable is dropped by one session, but still used in second session)&lt;br /&gt;
&lt;br /&gt;
= ToDo =&lt;br /&gt;
* SECURITY LABEL support - enhancing dummy_seclabel module, sepgsql extension + pg doc and SecLabelSupportsObjectType&lt;/div&gt;</summary>
		<author><name>Okbobcz</name></author>
	</entry>
	<entry>
		<id>https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41785</id>
		<title>Implementation of declarative catalog session variables</title>
		<link rel="alternate" type="text/html" href="https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41785"/>
		<updated>2025-09-25T08:02:09Z</updated>

		<summary type="html">&lt;p&gt;Okbobcz: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Session variables =&lt;br /&gt;
Session variables are database objects that can hold a value across multiple queries inside a session. A design of session variables is varied and more times more databases implement more than just one type of session variables. Unfortunately standard SQL/PSM is not commonly accepted, and a part of this standard related to modules and module&#039;s variables was not implemented (in any widely used product). Very often session variables are a part of the stored procedures environment - but there are exceptions (MySQL session variables or any client side session variables). What I know is that session variables don&#039;t support transactions (for values). It is a plus minus just variable like we know from programming languages.&lt;br /&gt;
&lt;br /&gt;
There is no common design of session variables. Currently any database system has its own proprietary design with proprietary syntax and features. Some systems have more than one different design of session variables. It is hard to compare different designs - any design has different advantages and disadvantages.&lt;br /&gt;
&lt;br /&gt;
== MySQL ==&lt;br /&gt;
Session variables in MySQL have the same syntax like more known T-SQL variables (but all other things are proprietary). MySQL knows two kinds of session variables:&lt;br /&gt;
&lt;br /&gt;
* system variables - use the prefix `@@` or `@@xxx.`&lt;br /&gt;
* user defined variables - use the prefix `@`&lt;br /&gt;
&lt;br /&gt;
System variables can be modified by command `SET`. After keyword `SET` keywords `GLOBAL`, `SESSION` or `PERSIST` can be used. &lt;br /&gt;
Some variables can be used just with keyword `GLOBAL` or `SESSION` and in this case, when the name is prefixed by `@@`, is not&lt;br /&gt;
necessary to specify scope. System variables are defined by MySQL or by an mysql&#039;s plugins.&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL some_config = 100;&lt;br /&gt;
    SET @@same_config = 100;&lt;br /&gt;
&lt;br /&gt;
    SET SESSION sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@SESSION.sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
&lt;br /&gt;
Persistent values are stored in `mysql-auto.conf`.&lt;br /&gt;
&lt;br /&gt;
Reset of system variables can be done by setting to default or assigning from global namespace:&lt;br /&gt;
&lt;br /&gt;
    SET @@sql_mode = DEFAULT;&lt;br /&gt;
    SET @@sql_mode = @@GLOBAL.sql_mode&lt;br /&gt;
&lt;br /&gt;
PostgreSQL equivalent:&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL ; -- there is not similar command in Postgres&lt;br /&gt;
    SET SESSION ; -- SET&lt;br /&gt;
    SET PERSIST ; -- ALTER SYSTEM; SELECT pg_reload_conf();&lt;br /&gt;
    SELECT @@var; -- SELECT current_setting(&#039;var);&lt;br /&gt;
&lt;br /&gt;
User defined variables are defined by assignment&lt;br /&gt;
&lt;br /&gt;
    SET @var = expr [, @var2 = expr [ ... ]];&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
User defined variables can be only int, decimal, float, binary, nonbinary string or NULL - casting from/to any types is implicit and hidden.&lt;br /&gt;
UDV cannot be used as table or column names.&lt;br /&gt;
&lt;br /&gt;
An advantage of T-SQL syntax (with special prefixes) is simple solution of possible identifier collisions. MySQL&lt;br /&gt;
variables are very handy too, and user experience can be good for people with knowledge of T-SQL (MSSQL or Sybase).&lt;br /&gt;
On the other hand, prefix based syntax can be disturbing for users who work with different systems than MSSQL. Type system&lt;br /&gt;
is maybe too simple and can be usafe. Generally the performance of MySQL (or MariaDB) stored procedures is not good,&lt;br /&gt;
and stored procedures are not frequently used. There is not any protection against typo errors. Unassigned user variables&lt;br /&gt;
can be used without error (have  NULL value), so static check of stored procedures is not possible (in this area).&lt;br /&gt;
&lt;br /&gt;
There is not any possibility how to limit an access to user defined variables in MySQL. UDV cannot be protected&lt;br /&gt;
against unwanted usage. Some protection can be only naming convention (all UDV share one name space).&lt;br /&gt;
&lt;br /&gt;
==Postgres==&lt;br /&gt;
Postgres has relatively advanced client side session variables in `psql`. `psql`&#039;s variables use prefix &#039;:&#039;. The implementation is simple. psql&#039;s parser reads tokens from input. When it gets a token related to a variable, then this token is replaced by a value of variable. Implemented syntax supports literal and identifier escaping. psql&#039;s variables can be used as an argument of the `\bind` command.&lt;br /&gt;
&lt;br /&gt;
psql&#039;s variables are typeless client side variables available only inside `psql` or `pgbench`. &lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:40:18) postgres=# \set myvar ahoj&lt;br /&gt;
    (2025-09-25 09:40:33) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:41:14) postgres=# select :&#039;myvar&#039;;&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:41:24) postgres=# select $1 \bind :myvar \g&lt;br /&gt;
    ┌──────────┐&lt;br /&gt;
    │ ?column? │&lt;br /&gt;
    ╞══════════╡&lt;br /&gt;
    │ ahoj     │&lt;br /&gt;
    └──────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
psql&#039;s command `\set` allows just simply string concatenation - but allows to execute shell command. When&lt;br /&gt;
query is executed by `\gset` command, then result is stored in psql variable.&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:41:40) postgres=# \set ctime `date`&lt;br /&gt;
    (2025-09-25 09:43:55) postgres=# \echo :ctime&lt;br /&gt;
    čt 25. září 2025, 09:43:55 CEST&lt;br /&gt;
&lt;br /&gt;
&#039;\set` command is too simple, and writing some complex operations is unreadable or not portable.&lt;br /&gt;
&lt;br /&gt;
It is good enough for writing tests, but it is not generally available (probably nobody expects it), and it is not&lt;br /&gt;
accessible from server side (from stored procedures). There is not simple way, how to access psql variables from&lt;br /&gt;
anonymous  block (proxy custom GUC variable is needed):&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 09:58:38) postgres=# \echo :myvar&lt;br /&gt;
    ahoj&lt;br /&gt;
    (2025-09-25 09:58:43) postgres=# select set_config(&#039;myvars.myvar&#039;, :&#039;myvar&#039;, false);&lt;br /&gt;
    ┌────────────┐&lt;br /&gt;
    │ set_config │&lt;br /&gt;
    ╞════════════╡&lt;br /&gt;
    │ ahoj       │&lt;br /&gt;
    └────────────┘&lt;br /&gt;
    (1 row)&lt;br /&gt;
&lt;br /&gt;
    (2025-09-25 10:00:27) postgres=# do $$ begin raise notice &#039;%&#039;, current_setting(&#039;myvars.myvar&#039;); end $$;&lt;br /&gt;
    NOTICE:  ahoj&lt;br /&gt;
    DO&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Oracle ==&lt;br /&gt;
== DB2 ==&lt;br /&gt;
== MSSQL (T-SQL) ==&lt;br /&gt;
&lt;br /&gt;
= Types of different session variables =&lt;br /&gt;
* client side (psql, pgadmin, ...) - mostly similar to shell variables (based on string operations)&lt;br /&gt;
* server side&lt;br /&gt;
** declarative&lt;br /&gt;
*** created only for batch (T-SQL)&lt;br /&gt;
*** created as an database object (or part of database object) (Oracle, DB2)&lt;br /&gt;
** created by usage (MySQL)&lt;br /&gt;
&lt;br /&gt;
=Issues that any implementation should to solve =&lt;br /&gt;
* possible collision between session variables and other database objects&lt;br /&gt;
* memory cleaning after dropping session variable (when DDL can be reverted)&lt;br /&gt;
* memory invalidation (variable is dropped by one session, but still used in second session)&lt;br /&gt;
&lt;br /&gt;
= ToDo =&lt;br /&gt;
* SECURITY LABEL support - enhancing dummy_seclabel module, sepgsql extension + pg doc and SecLabelSupportsObjectType&lt;/div&gt;</summary>
		<author><name>Okbobcz</name></author>
	</entry>
	<entry>
		<id>https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41783</id>
		<title>Implementation of declarative catalog session variables</title>
		<link rel="alternate" type="text/html" href="https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41783"/>
		<updated>2025-09-25T05:08:58Z</updated>

		<summary type="html">&lt;p&gt;Okbobcz: /* MySQL */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Session variables =&lt;br /&gt;
Session variables are database objects that can hold a value across multiple queries inside a session. A design of session variables is varied and more times more databases implement more than just one type of session variables. Unfortunately standard SQL/PSM is not commonly accepted, and a part of this standard related to modules and module&#039;s variables was not implemented (in any widely used product). Very often session variables are a part of the stored procedures environment - but there are exceptions (MySQL session variables or any client side session variables). What I know is that session variables don&#039;t support transactions (for values). It is a plus minus just variable like we know from programming languages.&lt;br /&gt;
&lt;br /&gt;
There is no common design of session variables. Currently any database system has its own proprietary design with proprietary syntax and features. Some systems have more than one different design of session variables. It is hard to compare different designs - any design has different advantages and disadvantages.&lt;br /&gt;
&lt;br /&gt;
== MySQL ==&lt;br /&gt;
Session variables in MySQL have the same syntax like more known T-SQL variables (but all other things are proprietary). MySQL knows two kinds of session variables:&lt;br /&gt;
&lt;br /&gt;
* system variables - use the prefix `@@` or `@@xxx.`&lt;br /&gt;
* user defined variables - use the prefix `@`&lt;br /&gt;
&lt;br /&gt;
System variables can be modified by command `SET`. After keyword `SET` keywords `GLOBAL`, `SESSION` or `PERSIST` can be used. &lt;br /&gt;
Some variables can be used just with keyword `GLOBAL` or `SESSION` and in this case, when the name is prefixed by `@@`, is not&lt;br /&gt;
necessary to specify scope. System variables are defined by MySQL or by an mysql&#039;s plugins.&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL some_config = 100;&lt;br /&gt;
    SET @@same_config = 100;&lt;br /&gt;
&lt;br /&gt;
    SET SESSION sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@SESSION.sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
&lt;br /&gt;
Persistent values are stored in `mysql-auto.conf`.&lt;br /&gt;
&lt;br /&gt;
Reset of system variables can be done by setting to default or assigning from global namespace:&lt;br /&gt;
&lt;br /&gt;
    SET @@sql_mode = DEFAULT;&lt;br /&gt;
    SET @@sql_mode = @@GLOBAL.sql_mode&lt;br /&gt;
&lt;br /&gt;
PostgreSQL equivalent:&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL ; -- there is not similar command in Postgres&lt;br /&gt;
    SET SESSION ; -- SET&lt;br /&gt;
    SET PERSIST ; -- ALTER SYSTEM; SELECT pg_reload_conf();&lt;br /&gt;
    SELECT @@var; -- SELECT current_setting(&#039;var);&lt;br /&gt;
&lt;br /&gt;
User defined variables are defined by assignment&lt;br /&gt;
&lt;br /&gt;
    SET @var = expr [, @var2 = expr [ ... ]];&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
User defined variables can be only int, decimal, float, binary, nonbinary string or NULL - casting from/to any types is implicit and hidden.&lt;br /&gt;
UDV cannot be used as table or column names.&lt;br /&gt;
&lt;br /&gt;
An advantage of T-SQL syntax (with special prefixes) is simple solution of possible identifier collisions. MySQL&lt;br /&gt;
variables are very handy too, and user experience can be good for people with knowledge of T-SQL (MSSQL or Sybase).&lt;br /&gt;
On the other hand, prefix based syntax can be disturbing for users who work with different systems than MSSQL. Type system&lt;br /&gt;
is maybe too simple and can be usafe. Generally the performance of MySQL (or MariaDB) stored procedures is not good,&lt;br /&gt;
and stored procedures are not frequently used. There is not any protection against typo errors. Unassigned user variables&lt;br /&gt;
can be used without error (have  NULL value), so static check of stored procedures is not possible (in this area).&lt;br /&gt;
&lt;br /&gt;
There is not any possibility how to limit an access to user defined variables in MySQL. UDV cannot be protected&lt;br /&gt;
against unwanted usage. Some protection can be only naming convention (all UDV share one name space).&lt;br /&gt;
&lt;br /&gt;
== Oracle ==&lt;br /&gt;
== DB2 ==&lt;br /&gt;
== MSSQL (T-SQL) ==&lt;br /&gt;
&lt;br /&gt;
= Types of different session variables =&lt;br /&gt;
* client side (psql, pgadmin, ...) - mostly similar to shell variables (based on string operations)&lt;br /&gt;
* server side&lt;br /&gt;
** declarative&lt;br /&gt;
*** created only for batch (T-SQL)&lt;br /&gt;
*** created as an database object (or part of database object) (Oracle, DB2)&lt;br /&gt;
** created by usage (MySQL)&lt;br /&gt;
&lt;br /&gt;
=Issues that any implementation should to solve =&lt;br /&gt;
* possible collision between session variables and other database objects&lt;br /&gt;
* memory cleaning after dropping session variable (when DDL can be reverted)&lt;br /&gt;
* memory invalidation (variable is dropped by one session, but still used in second session)&lt;br /&gt;
&lt;br /&gt;
= ToDo =&lt;br /&gt;
* SECURITY LABEL support - enhancing dummy_seclabel module, sepgsql extension + pg doc and SecLabelSupportsObjectType&lt;/div&gt;</summary>
		<author><name>Okbobcz</name></author>
	</entry>
	<entry>
		<id>https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41782</id>
		<title>Implementation of declarative catalog session variables</title>
		<link rel="alternate" type="text/html" href="https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41782"/>
		<updated>2025-09-25T04:44:04Z</updated>

		<summary type="html">&lt;p&gt;Okbobcz: /* MySQL */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Session variables =&lt;br /&gt;
Session variables are database objects that can hold a value across multiple queries inside a session. A design of session variables is varied and more times more databases implement more than just one type of session variables. Unfortunately standard SQL/PSM is not commonly accepted, and a part of this standard related to modules and module&#039;s variables was not implemented (in any widely used product). Very often session variables are a part of the stored procedures environment - but there are exceptions (MySQL session variables or any client side session variables). What I know is that session variables don&#039;t support transactions (for values). It is a plus minus just variable like we know from programming languages.&lt;br /&gt;
&lt;br /&gt;
There is no common design of session variables. Currently any database system has its own proprietary design with proprietary syntax and features. Some systems have more than one different design of session variables. It is hard to compare different designs - any design has different advantages and disadvantages.&lt;br /&gt;
&lt;br /&gt;
== MySQL ==&lt;br /&gt;
Session variables in MySQL have the same syntax like more known T-SQL variables (but all other things are proprietary). MySQL knows two kinds of session variables:&lt;br /&gt;
&lt;br /&gt;
* system variables - use the prefix `@@` or `@@xxx.`&lt;br /&gt;
* user defined variables - use the prefix `@`&lt;br /&gt;
&lt;br /&gt;
System variables can be modified by command `SET`. After keyword `SET` keywords `GLOBAL`, `SESSION` or `PERSIST` can be used. &lt;br /&gt;
Some variables can be used just with keyword `GLOBAL` or `SESSION` and in this case, when the name is prefixed by `@@`, is not&lt;br /&gt;
necessary to specify scope. System variables are defined by MySQL or by an mysql&#039;s plugins.&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL some_config = 100;&lt;br /&gt;
    SET @@same_config = 100;&lt;br /&gt;
&lt;br /&gt;
    SET SESSION sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@SESSION.sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
&lt;br /&gt;
Persistent values are stored in `mysql-auto.conf`.&lt;br /&gt;
&lt;br /&gt;
Reset of system variables can be done by setting to default or assigning from global namespace:&lt;br /&gt;
&lt;br /&gt;
    SET @@sql_mode = DEFAULT;&lt;br /&gt;
    SET @@sql_mode = @@GLOBAL.sql_mode&lt;br /&gt;
&lt;br /&gt;
PostgreSQL equivalent:&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL ; -- there is not similar command in Postgres&lt;br /&gt;
    SET SESSION ; -- SET&lt;br /&gt;
    SET PERSIST ; -- ALTER SYSTEM; SELECT pg_reload_conf();&lt;br /&gt;
    SELECT @@var; -- SELECT current_setting(&#039;var);&lt;br /&gt;
&lt;br /&gt;
User defined variables are defined by assignment&lt;br /&gt;
&lt;br /&gt;
    SET @var = expr [, @var2 = expr [ ... ]];&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
User defined variables can be only int, decimal, float, binary, nonbinary string or NULL - casting from/to any types is implicit and hidden.&lt;br /&gt;
UDV cannot be used as table or column names.&lt;br /&gt;
&lt;br /&gt;
== Oracle ==&lt;br /&gt;
== DB2 ==&lt;br /&gt;
== MSSQL (T-SQL) ==&lt;br /&gt;
&lt;br /&gt;
= Types of different session variables =&lt;br /&gt;
* client side (psql, pgadmin, ...) - mostly similar to shell variables (based on string operations)&lt;br /&gt;
* server side&lt;br /&gt;
** declarative&lt;br /&gt;
*** created only for batch (T-SQL)&lt;br /&gt;
*** created as an database object (or part of database object) (Oracle, DB2)&lt;br /&gt;
** created by usage (MySQL)&lt;br /&gt;
&lt;br /&gt;
=Issues that any implementation should to solve =&lt;br /&gt;
* possible collision between session variables and other database objects&lt;br /&gt;
* memory cleaning after dropping session variable (when DDL can be reverted)&lt;br /&gt;
* memory invalidation (variable is dropped by one session, but still used in second session)&lt;br /&gt;
&lt;br /&gt;
= ToDo =&lt;br /&gt;
* SECURITY LABEL support - enhancing dummy_seclabel module, sepgsql extension + pg doc and SecLabelSupportsObjectType&lt;/div&gt;</summary>
		<author><name>Okbobcz</name></author>
	</entry>
	<entry>
		<id>https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41780</id>
		<title>Implementation of declarative catalog session variables</title>
		<link rel="alternate" type="text/html" href="https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41780"/>
		<updated>2025-09-24T19:25:20Z</updated>

		<summary type="html">&lt;p&gt;Okbobcz: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Session variables =&lt;br /&gt;
Session variables are database objects that can hold a value across multiple queries inside a session. A design of session variables is varied and more times more databases implement more than just one type of session variables. Unfortunately standard SQL/PSM is not commonly accepted, and a part of this standard related to modules and module&#039;s variables was not implemented (in any widely used product). Very often session variables are a part of the stored procedures environment - but there are exceptions (MySQL session variables or any client side session variables). What I know is that session variables don&#039;t support transactions (for values). It is a plus minus just variable like we know from programming languages.&lt;br /&gt;
&lt;br /&gt;
There is no common design of session variables. Currently any database system has its own proprietary design with proprietary syntax and features. Some systems have more than one different design of session variables. It is hard to compare different designs - any design has different advantages and disadvantages.&lt;br /&gt;
&lt;br /&gt;
== MySQL ==&lt;br /&gt;
Session variables in MySQL have the same syntax like more known T-SQL variables. MySQL knows two kinds of session variables:&lt;br /&gt;
&lt;br /&gt;
* system variables - use the prefix `@@` or `@@xxx.`&lt;br /&gt;
* user defined variables - use the prefix `@`&lt;br /&gt;
&lt;br /&gt;
System variables can be modified by command `SET`. After keyword `SET` keywords `GLOBAL`, `SESSION` or `PERSIST` can be used. &lt;br /&gt;
Some variables can be used just with keyword `GLOBAL` or `SESSION` and in this case, when the name is prefixed by `@@`, is not&lt;br /&gt;
necessary to specify scope. System variables are defined by MySQL or by an mysql&#039;s plugins.&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL some_config = 100;&lt;br /&gt;
    SET @@same_config = 100;&lt;br /&gt;
&lt;br /&gt;
    SET SESSION sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET @@SESSION.sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
    SET sql_mode = &#039;TRADITIONAL&#039;;&lt;br /&gt;
&lt;br /&gt;
Persistent values are stored in `mysql-auto.conf`.&lt;br /&gt;
&lt;br /&gt;
Reset of system variables can be done by setting to default or assigning from global namespace:&lt;br /&gt;
&lt;br /&gt;
    SET @@sql_mode = DEFAULT;&lt;br /&gt;
    SET @@sql_mode = @@GLOBAL.sql_mode&lt;br /&gt;
&lt;br /&gt;
PostgreSQL equivalent:&lt;br /&gt;
&lt;br /&gt;
    SET GLOBAL ; -- there is not similar command in Postgres&lt;br /&gt;
    SET SESSION ; -- SET&lt;br /&gt;
    SET PERSIST ; -- ALTER SYSTEM; SELECT pg_reload_conf();&lt;br /&gt;
    SELECT @@var; -- SELECT current_setting(&#039;var);&lt;br /&gt;
&lt;br /&gt;
User defined variables are defined by assignment&lt;br /&gt;
&lt;br /&gt;
    SET @var = expr [, @var2 = expr [ ... ]];&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
User defined variables can be only int, decimal, float, binary, nonbinary string or NULL - casting from/to any types is implicit and hidden.&lt;br /&gt;
UDV cannot be used as table or column names.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Oracle ==&lt;br /&gt;
== DB2 ==&lt;br /&gt;
== MSSQL (T-SQL) ==&lt;br /&gt;
&lt;br /&gt;
= Types of different session variables =&lt;br /&gt;
* client side (psql, pgadmin, ...) - mostly similar to shell variables (based on string operations)&lt;br /&gt;
* server side&lt;br /&gt;
** declarative&lt;br /&gt;
*** created only for batch (T-SQL)&lt;br /&gt;
*** created as an database object (or part of database object) (Oracle, DB2)&lt;br /&gt;
** created by usage (MySQL)&lt;br /&gt;
&lt;br /&gt;
=Issues that any implementation should to solve =&lt;br /&gt;
* possible collision between session variables and other database objects&lt;br /&gt;
* memory cleaning after dropping session variable (when DDL can be reverted)&lt;br /&gt;
* memory invalidation (variable is dropped by one session, but still used in second session)&lt;br /&gt;
&lt;br /&gt;
= ToDo =&lt;br /&gt;
* SECURITY LABEL support - enhancing dummy_seclabel module, sepgsql extension + pg doc and SecLabelSupportsObjectType&lt;/div&gt;</summary>
		<author><name>Okbobcz</name></author>
	</entry>
	<entry>
		<id>https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41779</id>
		<title>Implementation of declarative catalog session variables</title>
		<link rel="alternate" type="text/html" href="https://wiki.postgresql.org/index.php?title=Implementation_of_declarative_catalog_session_variables&amp;diff=41779"/>
		<updated>2025-09-24T18:16:11Z</updated>

		<summary type="html">&lt;p&gt;Okbobcz: /* Session variables */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Session variables =&lt;br /&gt;
Session variables are database objects that can hold a value across multiple queries inside a session. A design of session variables is varied and more times more databases implement more than just one type of session variables. Unfortunately standard SQL/PSM is not commonly accepted, and a part of this standard related to modules and module&#039;s variables was not implemented (in any widely used product). Very often session variables are a part of the stored procedures environment - but there are exceptions (MySQL session variables or any client side session variables). What I know is that session variables don&#039;t support transactions (for values). It is a plus minus just variable like we know from programming languages.&lt;br /&gt;
&lt;br /&gt;
There is no common design of session variables. Currently any database system has its own proprietary design with proprietary syntax and features. Some systems have more than one different design of session variables. It is hard to compare different designs - any design has different advantages and disadvantages.&lt;br /&gt;
&lt;br /&gt;
= Types of different session variables =&lt;br /&gt;
* client side (psql, pgadmin, ...) - mostly similar to shell variables (based on string operations)&lt;br /&gt;
* server side&lt;br /&gt;
** declarative&lt;br /&gt;
*** created only for batch (T-SQL)&lt;br /&gt;
*** created as an database object (or part of database object) (Oracle, DB2)&lt;br /&gt;
** created by usage (MySQL)&lt;br /&gt;
&lt;br /&gt;
=Issues that any implementation should to solve =&lt;br /&gt;
* possible collision between session variables and other database objects&lt;br /&gt;
* memory cleaning after dropping session variable (when DDL can be reverted)&lt;br /&gt;
* memory invalidation (variable is dropped by one session, but still used in second session)&lt;br /&gt;
&lt;br /&gt;
= ToDo =&lt;br /&gt;
* SECURITY LABEL support - enhancing dummy_seclabel module, sepgsql extension + pg doc and SecLabelSupportsObjectType&lt;/div&gt;</summary>
		<author><name>Okbobcz</name></author>
	</entry>
</feed>