SQL Views - no variables? - tsql

Is it possible to declare a variable within a View? For example:
Declare #SomeVar varchar(8) = 'something'
gives me the syntax error:
Incorrect syntax near the keyword 'Declare'.

You are correct. Local variables are not allowed in a VIEW.
You can set a local variable in a table valued function, which returns a result set (like a view does.)
http://msdn.microsoft.com/en-us/library/ms191165.aspx
e.g.
CREATE FUNCTION dbo.udf_foo()
RETURNS #ret TABLE (col INT)
AS
BEGIN
DECLARE #myvar INT;
SELECT #myvar = 1;
INSERT INTO #ret SELECT #myvar;
RETURN;
END;
GO
SELECT * FROM dbo.udf_foo();
GO

You could use WITH to define your expressions. Then do a simple Sub-SELECT to access those definitions.
CREATE VIEW MyView
AS
WITH MyVars (SomeVar, Var2)
AS (
SELECT
'something' AS 'SomeVar',
123 AS 'Var2'
)
SELECT *
FROM MyTable
WHERE x = (SELECT SomeVar FROM MyVars)

EDIT: I tried using a CTE on my previous answer which was incorrect, as pointed out by #bummi. This option should work instead:
Here's one option using a CROSS APPLY, to kind of work around this problem:
SELECT st.Value, Constants.CONSTANT_ONE, Constants.CONSTANT_TWO
FROM SomeTable st
CROSS APPLY (
SELECT 'Value1' AS CONSTANT_ONE,
'Value2' AS CONSTANT_TWO
) Constants

#datenstation had the correct concept. Here is a working example that uses CTE to cache variable's names:
CREATE VIEW vwImportant_Users AS
WITH params AS (
SELECT
varType='%Admin%',
varMinStatus=1)
SELECT status, name
FROM sys.sysusers, params
WHERE status > varMinStatus OR name LIKE varType
SELECT * FROM vwImportant_Users
also via JOIN
WITH params AS ( SELECT varType='%Admin%', varMinStatus=1)
SELECT status, name
FROM sys.sysusers INNER JOIN params ON 1=1
WHERE status > varMinStatus OR name LIKE varType
also via CROSS APPLY
WITH params AS ( SELECT varType='%Admin%', varMinStatus=1)
SELECT status, name
FROM sys.sysusers CROSS APPLY params
WHERE status > varMinStatus OR name LIKE varType

Yes this is correct, you can't have variables in views
(there are other restrictions too).
Views can be used for cases where the result can be replaced with a select statement.

Using functions as spencer7593 mentioned is a correct approach for dynamic data. For static data, a more performant approach which is consistent with SQL data design (versus the anti-pattern of writting massive procedural code in sprocs) is to create a separate table with the static values and join to it. This is extremely beneficial from a performace perspective since the SQL Engine can build effective execution plans around a JOIN, and you have the potential to add indexes as well if needed.
The disadvantage of using functions (or any inline calculated values) is the callout happens for every potential row returned, which is costly. Why? Because SQL has to first create a full dataset with the calculated values and then apply the WHERE clause to that dataset.
Nine times out of ten you should not need dynamically calculated cell values in your queries. Its much better to figure out what you will need, then design a data model that supports it, and populate that data model with semi-dynamic data (via batch jobs for instance) and use the SQL Engine to do the heavy lifting via standard SQL.

What I do is create a view that performs the same select as the table variable and link that view into the second view. So a view can select from another view. This achieves the same result

How often do you need to refresh the view? I have a similar case where the new data comes once a month; then I have to load it, and during the loading processes I have to create new tables. At that moment I alter my view to consider the changes.
I used as base the information in this other question:
Create View Dynamically & synonyms
In there, it is proposed to do it 2 ways:
using synonyms.
Using dynamic SQL to create view (this is what helped me achieve my result).

Related

How to declare a variable in select statement in Postgres SQL [duplicate]

How do I declare a variable for use in a PostgreSQL 8.3 query?
In MS SQL Server I can do this:
DECLARE #myvar INT
SET #myvar = 5
SELECT *
FROM somewhere
WHERE something = #myvar
How do I do the same in PostgreSQL? According to the documentation variables are declared simply as "name type;", but this gives me a syntax error:
myvar INTEGER;
Could someone give me an example of the correct syntax?
I accomplished the same goal by using a WITH clause, it's nowhere near as elegant but can do the same thing. Though for this example it's really overkill. I also don't particularly recommend this.
WITH myconstants (var1, var2) as (
values (5, 'foo')
)
SELECT *
FROM somewhere, myconstants
WHERE something = var1
OR something_else = var2;
There is no such feature in PostgreSQL. You can do it only in pl/PgSQL (or other pl/*), but not in plain SQL.
An exception is WITH () query which can work as a variable, or even tuple of variables. It allows you to return a table of temporary values.
WITH master_user AS (
SELECT
login,
registration_date
FROM users
WHERE ...
)
SELECT *
FROM users
WHERE master_login = (SELECT login
FROM master_user)
AND (SELECT registration_date
FROM master_user) > ...;
You could also try this in PLPGSQL:
DO $$
DECLARE myvar integer;
BEGIN
SELECT 5 INTO myvar;
DROP TABLE IF EXISTS tmp_table;
CREATE TABLE tmp_table AS
SELECT * FROM yourtable WHERE id = myvar;
END $$;
SELECT * FROM tmp_table;
The above requires Postgres 9.0 or later.
Dynamic Config Settings
you can "abuse" dynamic config settings for this:
-- choose some prefix that is unlikely to be used by postgres
set session my.vars.id = '1';
select *
from person
where id = current_setting('my.vars.id')::int;
Config settings are always varchar values, so you need to cast them to the correct data type when using them. This works with any SQL client whereas \set only works in psql
The above requires Postgres 9.2 or later.
For previous versions, the variable had to be declared in postgresql.conf prior to being used, so it limited its usability somewhat. Actually not the variable completely, but the config "class" which is essentially the prefix. But once the prefix was defined, any variable could be used without changing postgresql.conf
It depends on your client.
However, if you're using the psql client, then you can use the following:
my_db=> \set myvar 5
my_db=> SELECT :myvar + 1 AS my_var_plus_1;
my_var_plus_1
---------------
6
If you are using text variables you need to quote.
\set myvar 'sometextvalue'
select * from sometable where name = :'myvar';
This solution is based on the one proposed by fei0x but it has the advantages that there is no need to join the value list of constants in the query and constants can be easily listed at the start of the query. It also works in recursive queries.
Basically, every constant is a single-value table declared in a WITH clause which can then be called anywhere in the remaining part of the query.
Basic example with two constants:
WITH
constant_1_str AS (VALUES ('Hello World')),
constant_2_int AS (VALUES (100))
SELECT *
FROM some_table
WHERE table_column = (table constant_1_str)
LIMIT (table constant_2_int)
Alternatively you can use SELECT * FROM constant_name instead of TABLE constant_name which might not be valid for other query languages different to postgresql.
Using a Temp Table outside of pl/PgSQL
Outside of using pl/pgsql or other pl/* language as suggested, this is the only other possibility I could think of.
begin;
select 5::int as var into temp table myvar;
select *
from somewhere s, myvar v
where s.something = v.var;
commit;
I want to propose an improvement to #DarioBarrionuevo's answer, to make it simpler leveraging temporary tables.
DO $$
DECLARE myvar integer = 5;
BEGIN
CREATE TEMP TABLE tmp_table ON COMMIT DROP AS
-- put here your query with variables:
SELECT *
FROM yourtable
WHERE id = myvar;
END $$;
SELECT * FROM tmp_table;
True, there is no vivid and unambiguous way to declare a single-value variable, what you can do is
with myVar as (select "any value really")
then, to get access to the value stored in this construction, you do
(select * from myVar)
for example
with var as (select 123)
... where id = (select * from var)
You may resort to tool special features. Like for DBeaver own proprietary syntax:
#set name = 'me'
SELECT :name;
SELECT ${name};
DELETE FROM book b
WHERE b.author_id IN (SELECT a.id FROM author AS a WHERE a.name = :name);
As you will have gathered from the other answers, PostgreSQL doesn’t have this mechanism in straight SQL, though you can now use an anonymous block. However, you can do something similar with a Common Table Expression (CTE):
WITH vars AS (
SELECT 5 AS myvar
)
SELECT *
FROM somewhere,vars
WHERE something = vars.myvar;
You can, of course, have as many variables as you like, and they can also be derived. For example:
WITH vars AS (
SELECT
'1980-01-01'::date AS start,
'1999-12-31'::date AS end,
(SELECT avg(height) FROM customers) AS avg_height
)
SELECT *
FROM customers,vars
WHERE (dob BETWEEN vars.start AND vars.end) AND height<vars.avg_height;
The process is:
Generate a one-row cte using SELECT without a table (in Oracle you will need to include FROM DUAL).
CROSS JOIN the cte with the other table. Although there is a CROSS JOIN syntax, the older comma syntax is slightly more readable.
Note that I have cast the dates to avoid possible issues in the SELECT clause. I used PostgreSQL’s shorter syntax, but you could have used the more formal CAST('1980-01-01' AS date) for cross-dialect compatibility.
Normally, you want to avoid cross joins, but since you’re only cross joining a single row, this has the effect of simply widening the table with the variable data.
In many cases, you don’t need to include the vars. prefix if the names don’t clash with the names in the other table. I include it here to make the point clear.
Also, you can go on to add more CTEs.
This also works in all current versions of MSSQL and MySQL, which do support variables, as well as SQLite which doesn’t, and Oracle which sort of does and sort of doesn’t.
Here is an example using PREPARE statements. You still can't use ?, but you can use $n notation:
PREPARE foo(integer) AS
SELECT *
FROM somewhere
WHERE something = $1;
EXECUTE foo(5);
DEALLOCATE foo;
In DBeaver you can use parameters in queries just like you can from code, so this will work:
SELECT *
FROM somewhere
WHERE something = :myvar
When you run the query DBeaver will ask you for the value for :myvar and run the query.
Here is a code segment using plain variable in postges terminal. I have used it a few times. But need to figure a better way. Here I am working with string variable. Working with integer variable, you don't need the triple quote. Triple quote becomes single quote at query time; otherwise you got syntax error. There might be a way to eliminate the need of triple quote when working with string variables. Please update if you find a way to improve.
\set strainname '''B.1.1.7'''
select *
from covid19strain
where name = :strainname ;
In psql, you can use these 'variables' as macros. Note that they get "evaluated" every time they are used, rather than at the time that they are "set".
Simple example:
\set my_random '(SELECT random())'
select :my_random; -- gives 0.23330629315990592
select :my_random; -- gives 0.67458399344433542
this gives two different answers each time.
However, you can still use these as a valuable shorthand to avoid repeating lots of subselects.
\set the_id '(SELECT id FROM table_1 WHERE name = ''xxx'' LIMIT 1)'
and then use it in your queries later as
:the_id
e.g.
INSERT INTO table2 (table1_id,x,y,z) VALUES (:the_id, 1,2,3)
Note you have to double-quote the strings in the variables, because the whole thing is then string-interpolated (i.e. macro-expanded) into your query.

Postgres Functions: Getting the Return Table Column Details

I feel the need to get the column names and data types of the table returned by any function that has a 'record' return data type, because...
A key process in an existing SQL Server-based system makes use of a stored procedure that takes a user-defined function as a parameter. An initial step gets the column names and types of the table returned by the function that was passed as a parameter.
In Postgres 13 I can use pg_proc.prorettype and the corresponding pg_type to find functions that return record types...that's a start. I can also use pg_get_function_result() to get the string containing the information I need. But, it's a string, and while I ultimately will have to assemble a very similar string, this is just one application of the info. Is there a tabular equivalent containing (column_name, data_type, ordinal_position), or do I need to do that myself?
Is there access to a composite data type the system may have created when such a function is created?
One option that I think will work for me, but I think it's a little weird, is to:
> create temp table t as select * from function() limit 0;
then look that table up in info_schema.columns, assemble what I need and drop the temp table...putting all of this into a function.
You can query the catalog table pg_proc, which contains all the required information:
SELECT coalesce(p.na, 'column' || p.i),
p.ty::regtype,
p.i
FROM pg_proc AS f
CROSS JOIN LATERAL unnest(
coalesce(f.proallargtypes, ARRAY[f.prorettype]),
f.proargmodes,
f.proargnames
)
WITH ORDINALITY AS p(ty,mo,na,i)
WHERE f.proname = 'interval_ok'
AND coalesce(p.mo, 'o') IN ('o', 't')
ORDER BY p.i;

Postgresql & PgAdmin SIMPLE variable [duplicate]

How do I declare a variable for use in a PostgreSQL 8.3 query?
In MS SQL Server I can do this:
DECLARE #myvar INT
SET #myvar = 5
SELECT *
FROM somewhere
WHERE something = #myvar
How do I do the same in PostgreSQL? According to the documentation variables are declared simply as "name type;", but this gives me a syntax error:
myvar INTEGER;
Could someone give me an example of the correct syntax?
I accomplished the same goal by using a WITH clause, it's nowhere near as elegant but can do the same thing. Though for this example it's really overkill. I also don't particularly recommend this.
WITH myconstants (var1, var2) as (
values (5, 'foo')
)
SELECT *
FROM somewhere, myconstants
WHERE something = var1
OR something_else = var2;
There is no such feature in PostgreSQL. You can do it only in pl/PgSQL (or other pl/*), but not in plain SQL.
An exception is WITH () query which can work as a variable, or even tuple of variables. It allows you to return a table of temporary values.
WITH master_user AS (
SELECT
login,
registration_date
FROM users
WHERE ...
)
SELECT *
FROM users
WHERE master_login = (SELECT login
FROM master_user)
AND (SELECT registration_date
FROM master_user) > ...;
You could also try this in PLPGSQL:
DO $$
DECLARE myvar integer;
BEGIN
SELECT 5 INTO myvar;
DROP TABLE IF EXISTS tmp_table;
CREATE TABLE tmp_table AS
SELECT * FROM yourtable WHERE id = myvar;
END $$;
SELECT * FROM tmp_table;
The above requires Postgres 9.0 or later.
Dynamic Config Settings
you can "abuse" dynamic config settings for this:
-- choose some prefix that is unlikely to be used by postgres
set session my.vars.id = '1';
select *
from person
where id = current_setting('my.vars.id')::int;
Config settings are always varchar values, so you need to cast them to the correct data type when using them. This works with any SQL client whereas \set only works in psql
The above requires Postgres 9.2 or later.
For previous versions, the variable had to be declared in postgresql.conf prior to being used, so it limited its usability somewhat. Actually not the variable completely, but the config "class" which is essentially the prefix. But once the prefix was defined, any variable could be used without changing postgresql.conf
It depends on your client.
However, if you're using the psql client, then you can use the following:
my_db=> \set myvar 5
my_db=> SELECT :myvar + 1 AS my_var_plus_1;
my_var_plus_1
---------------
6
If you are using text variables you need to quote.
\set myvar 'sometextvalue'
select * from sometable where name = :'myvar';
This solution is based on the one proposed by fei0x but it has the advantages that there is no need to join the value list of constants in the query and constants can be easily listed at the start of the query. It also works in recursive queries.
Basically, every constant is a single-value table declared in a WITH clause which can then be called anywhere in the remaining part of the query.
Basic example with two constants:
WITH
constant_1_str AS (VALUES ('Hello World')),
constant_2_int AS (VALUES (100))
SELECT *
FROM some_table
WHERE table_column = (table constant_1_str)
LIMIT (table constant_2_int)
Alternatively you can use SELECT * FROM constant_name instead of TABLE constant_name which might not be valid for other query languages different to postgresql.
Using a Temp Table outside of pl/PgSQL
Outside of using pl/pgsql or other pl/* language as suggested, this is the only other possibility I could think of.
begin;
select 5::int as var into temp table myvar;
select *
from somewhere s, myvar v
where s.something = v.var;
commit;
I want to propose an improvement to #DarioBarrionuevo's answer, to make it simpler leveraging temporary tables.
DO $$
DECLARE myvar integer = 5;
BEGIN
CREATE TEMP TABLE tmp_table ON COMMIT DROP AS
-- put here your query with variables:
SELECT *
FROM yourtable
WHERE id = myvar;
END $$;
SELECT * FROM tmp_table;
True, there is no vivid and unambiguous way to declare a single-value variable, what you can do is
with myVar as (select "any value really")
then, to get access to the value stored in this construction, you do
(select * from myVar)
for example
with var as (select 123)
... where id = (select * from var)
You may resort to tool special features. Like for DBeaver own proprietary syntax:
#set name = 'me'
SELECT :name;
SELECT ${name};
DELETE FROM book b
WHERE b.author_id IN (SELECT a.id FROM author AS a WHERE a.name = :name);
As you will have gathered from the other answers, PostgreSQL doesn’t have this mechanism in straight SQL, though you can now use an anonymous block. However, you can do something similar with a Common Table Expression (CTE):
WITH vars AS (
SELECT 5 AS myvar
)
SELECT *
FROM somewhere,vars
WHERE something = vars.myvar;
You can, of course, have as many variables as you like, and they can also be derived. For example:
WITH vars AS (
SELECT
'1980-01-01'::date AS start,
'1999-12-31'::date AS end,
(SELECT avg(height) FROM customers) AS avg_height
)
SELECT *
FROM customers,vars
WHERE (dob BETWEEN vars.start AND vars.end) AND height<vars.avg_height;
The process is:
Generate a one-row cte using SELECT without a table (in Oracle you will need to include FROM DUAL).
CROSS JOIN the cte with the other table. Although there is a CROSS JOIN syntax, the older comma syntax is slightly more readable.
Note that I have cast the dates to avoid possible issues in the SELECT clause. I used PostgreSQL’s shorter syntax, but you could have used the more formal CAST('1980-01-01' AS date) for cross-dialect compatibility.
Normally, you want to avoid cross joins, but since you’re only cross joining a single row, this has the effect of simply widening the table with the variable data.
In many cases, you don’t need to include the vars. prefix if the names don’t clash with the names in the other table. I include it here to make the point clear.
Also, you can go on to add more CTEs.
This also works in all current versions of MSSQL and MySQL, which do support variables, as well as SQLite which doesn’t, and Oracle which sort of does and sort of doesn’t.
Here is an example using PREPARE statements. You still can't use ?, but you can use $n notation:
PREPARE foo(integer) AS
SELECT *
FROM somewhere
WHERE something = $1;
EXECUTE foo(5);
DEALLOCATE foo;
In DBeaver you can use parameters in queries just like you can from code, so this will work:
SELECT *
FROM somewhere
WHERE something = :myvar
When you run the query DBeaver will ask you for the value for :myvar and run the query.
Here is a code segment using plain variable in postges terminal. I have used it a few times. But need to figure a better way. Here I am working with string variable. Working with integer variable, you don't need the triple quote. Triple quote becomes single quote at query time; otherwise you got syntax error. There might be a way to eliminate the need of triple quote when working with string variables. Please update if you find a way to improve.
\set strainname '''B.1.1.7'''
select *
from covid19strain
where name = :strainname ;
In psql, you can use these 'variables' as macros. Note that they get "evaluated" every time they are used, rather than at the time that they are "set".
Simple example:
\set my_random '(SELECT random())'
select :my_random; -- gives 0.23330629315990592
select :my_random; -- gives 0.67458399344433542
this gives two different answers each time.
However, you can still use these as a valuable shorthand to avoid repeating lots of subselects.
\set the_id '(SELECT id FROM table_1 WHERE name = ''xxx'' LIMIT 1)'
and then use it in your queries later as
:the_id
e.g.
INSERT INTO table2 (table1_id,x,y,z) VALUES (:the_id, 1,2,3)
Note you have to double-quote the strings in the variables, because the whole thing is then string-interpolated (i.e. macro-expanded) into your query.

Postgres Rules Preventing CTE Queries

Using Postgres 9.3:
I am attempting to automatically populate a table when an insert is performed on another table. This seems like a good use for rules, but after adding the rule to the first table, I am no longer able to perform inserts into the second table using the writable CTE. Here is an example:
CREATE TABLE foo (
id INT PRIMARY KEY
);
CREATE TABLE bar (
id INT PRIMARY KEY REFERENCES foo
);
CREATE RULE insertFoo AS ON INSERT TO foo DO INSERT INTO bar VALUES (NEW.id);
WITH a AS (SELECT * FROM (VALUES (1), (2)) b)
INSERT INTO foo SELECT * FROM a
When this is run, I get the error
"ERROR: WITH cannot be used in a query that is rewritten by rules
into multiple queries".
I have searched for that error string, but am only able to find links to the source code. I know that I can perform the above using row-level triggers instead, but it seems like I should be able to do this at the statement level. Why can I not use the writable CTE, when queries like this can (in this case) be easily re-written as:
INSERT INTO foo SELECT * FROM (VALUES (1), (2)) a
Does anyone know of another way that would accomplish what I am attempting to do other than 1) using rules, which prevents the use of "with" queries, or 2) using row-level triggers? Thanks,
        
TL;DR: use triggers, not rules.
Generally speaking, prefer triggers over rules, unless rules are absolutely necessary. (Which, in practice, they never are.)
Using rules introduces heaps of problems which will needlessly complicate your life down the road. You've run into one here. Another (major) one is, for instance, that the number of affected rows will correspond to that of the very last query -- if you're relying on FOUND somewhere and your query is incorrectly reporting that no rows were affected by a query, you'll be in for painful bugs.
Moreover, there's occasional talk of deprecating Postgres rules outright:
http://postgresql.nabble.com/Deprecating-RULES-td5727689.html
As the other answer I definitely recommend using INSTEAD OF triggers before RULEs.
However if for some reason you don't want to change existing VIEW RULEs and still want use WITH you can do so by wrapping the VIEW in a stored procedure:
create function insert_foo(int) returns void as $$
insert into foo values ($1)
$$ language sql;
WITH a AS (SELECT * FROM (VALUES (1), (2)) b)
SELECT insert_foo(a.column1) from a;
This could be useful when using some legacy db through some system that wraps statements with CTEs.

Writing Recursive StoredProcedures

Basically what i want in my stored procedure is to return a list of tables, store this list in a variable; i need to go through every item in my list to recursively call this storedprocedure. In the end i need an overall listOfTables built up of this recursion.
Any help would be most appreciated
You should take a look at Common Table Expressions in case you're on SQL2005 or higher (not sure if they can help in your specific situation but an important alternative to most recursive queries) . Recursive procedures cannot nest more than 32 levels deep and are not very elegant.
You can use CTE's:
WITH q (column1, column2) (
SELECT *
FROM table
UNION ALL
SELECT *
FROM table
JOIN q
ON …
)
SELECT *
FROM q
However, there are different limitations: you cannot use aggregates, analytics functions, TOP clause etc.
Are you after recursion or just a loop through all tables? If you are using Sql Server 2005 and want to loop through all tables you can use a table variable in your SP, try something along thse lines:
declare #TableList as table (
ID int identity (1,1),
TableName varchar(500)
)
insert into #TableList (TableName)
select name
from sys.tables
declare #count int
declare #limit int
declare #TableName varchar(500)
set #count = 1
select #limit = max(ID) from #TableList
while #count <= #limit
begin
select #TableName = TableName from #TableList where ID = #count
print #TableName --replace with call to SP
set #count = #count + 1
end
Replace the print #TableName with the call to the SP, and if you don't want this to run on every table in the DB then change the query select name from sys.tables to only return the tables you are after
Most likely a CTE would answer your requirement.
If you really must use a stored procedure not a query then all you have to do is iterate through the table list then you can use your code of choice to iterate through the table list and call the procedure. And Macros already posted how to do that as I was typing lol. And as Mehrdad already told you, there is limit on the number of nested levels of call SQL Server allows and is rather shallow. I'm not convinced from your explanation that you need a recursive call, it looks more like a simple iteration over a list, but if you do indeed need recursivity then remember CS 101 class: any recursive algorithm can be transformed into a non-recursive one by using a loop iteration and a stack.
Stored procedures are very useful. BUT.
I recently had to work on a system that was heavily dependent on stored procedures. It was a nightmare. Half the business logic was in one language (Java, in this case), and the other half was in the database in stored procedures. Worse yet, half the application was under source code control and the other half was one database crash from being lost forever (bad backup processes). Plus, all those lovely little tools I have for scanning, analyzing and maintaining source code can't work with sources inside the database.
I'm not inherently anti-stored-procedure, but oh, how they can be abused. Stored procedures are excellent for when you need to enforce rules against data coming from a multiplicity of sources, and there's no better way to offload heavy-duty record access off the webservers (and onto the DBMS server). But for the most part, I'd rather use a View than a Stored Procedure and an application programming language for the business logic. I know it makes some things a little more complex. But it can make life a whole lot easier.