How to use variable in view definition in Postgres/plpgsql - sql

I am using plpgsql with Postgres 10.6. I have a function that declares and gives value to a variable. That function also defines a view, and I would like to use the variable within the definition.
create view myview as
select
some_columns
from
mytable
where
id = _id /*_id is declared earlier in function */
;
In this case, the function can be defined, but when it is run it gives an error: UndefinedColumn: column "_id" does not exist
Is such a thing possible in Postgres? Can views include variables as part of their definition?
I do see here that in BigQuery (which I have never used), what I am asking is not possible, which makes me think it may also not be possible in plpgsql.
It is not a big deal, but I am curious. A workaround - and probably the recommended solution - is to pass the _id when I select from the view (e.g. select * from myview where id = 3). Or, if I really want to keep the select call simple (which I do, because my actual implementation is more complicated and has multiple variables), I could define the view as a string and use execute within the function (this is all internal stuff used in building up and creating a db, not in a situation where the various risks inherent to dynamic sql are a concern).

No, you can not pass a variable to a view. But you can do this with a function:
create function my_view_function(p_id integer)
returns table (id int, column_1 int, column2 text)
as
$$
select id, column_1, column_2
from my_table
where id = p_id;
$$
language sql
stable;
Then use it like this
select *
from my_view_function(42);

Related

Is it possibile to write a query that returns multiple fields from an object obtained by calling a function in Oracle?

I would like to know if it is possible to write a query that, given an object obtained by calling a function, lists many of that object's fields.
For example if I have this schema:
CREATE OR REPLACE TYPE T1 as OBJECT (
col1 varchar2(10),
col2 varchar2(10),
col3 varchar2(10),
CONSTRUCTOR FUNCTION T1(input varchar ) RETURN SELF AS RESULT
)
CREATE OR REPLACE TYPE BODY T1 IS
CONSTRUCTOR FUNCTION T1(input varchar ) RETURN SELF AS RESULT IS
random_String varchar(5);
BEGIN
random_String:=dbms_random.string(opt => '',len=>'5');
self.col1:='1'||input||random_String;
self.col2:='2'||input||random_String;
self.col3:='3'||input||random_String;
RETURN;
end;
end;
create or replace function GET_T1(input varchar) return T1 is
object_created T1;
BEGIN
RETURN T1(input);
end;
If I run this query:
select * from (
select
get_t1('1').col1 c1,get_t1('1').col2 c2,get_t1('1').col3
from dual
)
I see that get_t1 is called three times, while I would like to write a query that "intuitively" would work like this one below:
select obj.col1,obj.col2,obj.col3 from (
select get_t1('1') obj from dual
)
which unfortunately doesn't parse, and gives ORA 00904 - invalid identifier (Oracle doesn't recognize obj.colx as meaningful).
Please note that I don't want to find a workaround to this specific situation, nor do I want to create other types or modify the function, I would like to know if what I want is possible at all and, if so, how.
Please note also that I would like the function that returns the object to be executed exactly once.
You need to supply a table alias, and then use that when referring to the object, to satisfy the object name resolution rules:
select t.obj.col1,t.obj.col2,t.obj.col3
from (
select get_t1('1') obj
from dual
where rownum = 1
) t;
OBJ.COL1 OBJ.COL2 OBJ.COL3
---------- ---------- ----------
11IQGCV 21IQGCV 31IQGCV
Without that table alias, it doesn't know what obj is referring to. There is no table or alias called obj, so it looks for other objects/schemas/synonyms etc called obj, rather than recognising it as a column from the inline view.
This is actually covered in the object-relational developer's guide, under name resolution:
To avoid inner capture and similar problems resolving references, Oracle Database requires you to use a table alias to qualify any dot-notational reference to subprograms or attributes of objects.
I've also add where rownum = 1 to the inline view, which seems to stop it calling the function multiple times. I'm not sure quite why; I'd wondered if was something to do with fast dual but you need it with a real single-row table too. Using a CTE and/or the undocumented materialize hint doesn't stop the multiple calls either; not does making the function deterministic (which may not be appropriate anyway). This is only thing I've found so far that does - though you found that the RESULT_CACHE hint also works. That's probably worthy of its own question though.

How to check which function uses a type?

I have a type which I'd like to change but I don't know who else is using it.
How can I check for all functions that return this type?
You can find all dependencies in the system catalog pg_depend.
This returns all functions depending on the type. I.e. not only those with the type in the RETURNS clause, but also those with the type as function parameter:
SELECT objid::regproc AS function_name
, pg_get_functiondef(objid) AS function_definition
, pg_get_function_identity_arguments(objid) AS function_args
, pg_get_function_result(objid) AS function_returns
FROM pg_depend
WHERE refclassid = 'pg_type'::regclass
AND refobjid = 'my_type'::regtype -- insert your type name here
AND classid = 'pg_proc'::regclass; -- only find functions
This also works for table functions:
...
RETURNS TABLE (foo my_type, bar int)
Using system catalog information functions.
There may be other dependencies (not to functions). Remove the last WHERE condition from my query to test (and adapt the SELECT list, obviously).
And there is still the possibility of the type being used explicitly (in a cast for instance) in queries in the function body or in dynamic SQL. You can only identify such use cases by parsing the text of the function body. There are no explicit dependencies registered in the system.
Related:
How to get function parameter lists (so I can drop a function)
DROP FUNCTION without knowing the number/type of parameters?
As mentioned by Erwin Brandstetter, this only works for functions directly returning the data type.
SELECT * FROM information_schema.routines r
WHERE r.type_udt_name = 'YOUR_DATA_TYPE' ORDER BY r.routine_name;

Is it possible to look up a table-valued function's return columns in SAP HANA's dictionary views?

I've created a table-valued function in SAP HANA:
CREATE FUNCTION f_tables
RETURNS TABLE (
column_value INTEGER
)
LANGUAGE SQLSCRIPT
AS
BEGIN
RETURN SELECT 1 column_value FROM SYS.DUMMY;
END
Now I'd like to be able to discover the function's table type using the dictionary views. I can run this query here:
select *
from function_parameters
where schema_name = '[xxxxxxxxxx]'
and function_name = 'F_TABLES'
order by function_name, position;
Which will yield something like:
PARAMETER_NAME TABLE_TYPE_SCHEMA TABLE_TYPE_NAME
---------------------------------------------------------------------
_SYS_SS2_RETURN_VAR_ [xxxxxxxxxx] _SYS_SS_TBL_[yyyyyyy]_RET
Unfortunately, I cannot seem to be able to look up that _SYS_SS_TBL_[yyyyyyy]_RET table in SYS.TABLES (and TABLE_COLUMNS), SYS.VIEWS (and VIEW_COLUMNS), SYS.DATA_TYPES, etc. in order to find the definitions of the individual columns.
Note that explicitly named table types created using CREATE TYPE ... do appear in SYS.TABLES...
Is there any way for me to look formally look up a table-valued function's return columns? I'm not interested in parsing the source, obviously.
These kind of tables are internal row-store tables, therefore you can only find your _SYS_SS_TBL_[yyyyyyy]_RET table in SYS.RS_TABLES_. This will give you some basic information, including a column ID (CID). This value is important to find the column information.
For example, if your CID is 100, you can find column information in the RS_COLUMNS_ table with this query:
SELECT * FROM SYS.RS_COLUMNS_ WHERE CID = 100

Advantage of using a SQL function rather than a SELECT

In a Postgresql 9.0 database, I can create a url for a webpage with a SELECT statement using an integer (profile_id) in the WHERE clause.
In the past I've simply done this SELECT whenever it is convenient, for instance using a subquery as a column/field in a view. But I recently realized I could create a SQL function to do the same thing. (This is a SQL function, NOT plpgsql).
I want to know if there is an advantage, mostly in terms of resources that are being spent, in using a function rather than a SELECT in a case like this? See below and thanks in advance. I could not find anything on this topic elsewhere on this site. (Long-time reader, first-time caller).
The function is below.
CREATE OR REPLACE FUNCTION msurl(integer)
RETURNS text AS
$BODY$
SELECT (('https://www.thenameofmywebsite/'::text ||
CASE
WHEN prof.type = 1 THEN 'm'::text
ELSE 'f'::text
END) || '/handler/'::text) || prof.profile_id AS profile_url
FROM profile prof
WHERE prof.profile_id = $1;
$BODY$
LANGUAGE sql
To get my url I can either use
SELECT prof.name,
SELECT (('https://www.thenameofmywebsite/'::text ||
CASE
WHEN prof.type = 1 THEN 'm'::text
ELSE 'f'::text
END) || '/handler/'::text) || prof.profile_id AS profile_url, prof.start_date
FROM profile prof,
WHERE prof.profile_id = id_number;
OR the tidier version:
SELECT prof.name, msurl(id_number) as profile_url, prof.start_date FROM profile prof;
The way you are using the function will not have any advantage - the opposite is the case: it will slow down your select drastically. Because for each row that is retrieved from the main select statement (the one calling the function) you are running another select on the same table.
Functions do have an advantage when you want to encapsulate the logic of building the url. But you need to write the function differently to be more efficient by passing it the row you want to work with:
CREATE OR REPLACE FUNCTION msurl(profile)
RETURNS text AS
$BODY$
SELECT (('https://www.thenameofmywebsite/' ||
CASE
WHEN $1.type = 1 THEN 'm'
ELSE 'f'
END) || '/handler/' || $1.profile_id:: AS profile_url;
$BODY$
LANGUAGE sql;
Another option would have been to pass all columns that you need separately, but by passing the row (type) the function signature (and thus calls to it) will not need to be changed if the logic changes and you need more or less columns from the table.
You can then call this with the following syntax:
SELECT prof.name,
msurl( (prof) ) as profile_url,
prof.start_date
FROM profile prof;
Note that the alias must be enclosed in parentheses (prof) when passing it to the function. The additional parentheses are not optional here.
That way the function still gets called for each row, but it doesn't run another select on the profile table.
Due to the object oriented way Postgres treats such a function you can even call it as it it was a column of the table:
SELECT prof.name,
prof.msurl as profile_url,
prof.start_date
FROM profile prof;
Sense of functions (sql functions) is encapsulation. With functions some fragments of your SQL statements has name, semantics - you can reuse it, you can build a libraries. There are no any other benefits like performance - it has impact just only on your code readability.

PostgreSQL - Rule to create a copy of the primaryID table

In my schema I want to have a primaryID and a SearchID. For every SearchID it is the primaryID plus some text at the start. I need this to look like this:
PrimaryID = 1
SearchID = Search1
Since the PrimaryID is set to autoincrement, I was hoping I could use a postgresql rule to do the following (pseudo code)
IF PRIMARYID CHANGES
{
SEARCHID = SEARCH(PRIMARYID)
}
This would hopefully occure exactly after the primaryID is updated and happen automatically. So, is this the best way of achieving this and can anyone provide an example of how it is done?
Thank you
Postgres 11 introduced genuine generated columns. See:
Computed / calculated / virtual / derived columns in PostgreSQL
For older (or any) versions, you could emulate a "virtual generated column" with a special function. Say your table is named tbl and the serial primary key is named tbl_id:
CREATE FUNCTION search_id(t tbl)
RETURNS text STABLE LANGUAGE SQL AS
$$
SELECT 'Search' || $1.tbl_id;
$$;
Then you can:
SELECT t.tbl_id, t.search_id FROM tbl t;
Table-qualification in t.search_id is needed in this case. Since search_id is not found as column of table tbl, Postgres looks for a function that takes tbl as argument next.
Effectively, t.search_id is just a syntax variant of search_id(t), but makes usage rather intuitive.