Pass parameters to view (set returning function?) - sql

I basically have a pretty complicated view that currently returns what I want from last-week aggregations.
SELECT *
FROM ...
WHERE t1.event_date >= ('now'::text::date - 7)
...
Now I'd like to be able to make the same calculations for any given 2 dates, so I want to be able to pass parameters to my view. Pretty much I want to replace the WHERE clause with something like:
WHERE t1.event_date BETWEEN %first date% AND %second_date%
I think I should be able to do this with a set returning function but can't figure exactly how to do it. Any ideas?

Create a function (sometimes called table-function, when returning a set of rows). There are many (procedural) languages, in particular PL/pgSQL. In your case LANGUAGE sql does the job:
CREATE OR REPLACE FUNCTION get_user_by_username(d1 date, d2 date)
RETURNS TABLE ( ... ) AS
$body$
SELECT ...
WHERE t1.event_date >= $1
AND t1.event_date < $2 -- note how I exclude upper border
$body$ LANGUAGE sql;
One of many examples for LANGUAGE sql:
Return rows from a PL/pgSQL function
One of many examples for LANGUAGE plpgsql:
PostgreSQL: ERROR: 42601: a column definition list is required for functions returning "record"

Related

How to use variable in view definition in Postgres/plpgsql

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);

How to put part of a SELECT statement into a Postgres function

I have part of a SELECT statement that is a pretty lengthy set of conditional statements. I want to put it into a function so I can call it much more efficiently on any table I need to use it on.
So instead of:
SELECT
itemnumber,
itemname,
base,
CASE
WHEN labor < 100 AND overhead < .20 THEN
WHEN .....
WHEN .....
WHEN .....
.....
END AS add_cost,
gpm
FROM items1;
I can just do:
SELECT
itemnumber,
itemname,
base,
calc_add_cost(),
gpm
FROM items1;
Is it possible to add part of a SELECT to a function so that can be injected just by calling the function?
I am sorting through documentation and Google, and it seems like it might be possible if creating the function in the plpgsql language as opposed to sql. However, what I am reading isn't very clear.
You cannot just wrap any part of a SELECT statement into a function. But an expression like your CASE can easily be wrapped:
CREATE OR REPLACE FUNCTION pg_temp.calc_add_cost(_labor integer, _overhead numeric)
RETURNS numeric AS
$func$
SELECT
CASE
WHEN _labor < 100 AND _overhead < .20 THEN numeric '1' -- example value
-- WHEN .....
-- WHEN .....
-- WHEN .....
ELSE numeric '0' -- example value
END;
$func$
LANGUAGE sql IMMUTABLE;
While you could also use PL/pgSQL for this, the demo is a simple SQL function. See:
Difference between language sql and language plpgsql in PostgreSQL functions
Adapt input and output data types to your need. Just guessing integer and numeric for lack of information.
Call:
SELECT calc_add_cost(1, 0.1);
In your statement:
SELECT
itemnumber,
itemname,
base,
calc_add_cost(labor, overhead) AS add_cost, -- pass column values as input
gpm
FROM items1;
You should understand some basics about Postgres functions to make proper use. Might start with the manual page on CREATE FUNCTION.
There are also many related questions & answers here on SO.
Also see the related, more sophisticated case passing whole rows here:
Pass the table name used in FROM to function automatically in PostgreSQL 9.6.3

Pass table name used in FROM to function automatically?

Working with PostgreSQL 9.6.3. I am new to functions in databases.
Let's say there are multiple tables of item numbers. Each one has the item number, the item cost and several other columns which are factored into the "additional cost". I would like to put the calculation into a function so I can call it for any of these tables.
So instead of:
SELECT
itemnumber,
itemname,
base,
CASE
WHEN labor < 100 AND overhead < .20 THEN
WHEN .....
WHEN .....
WHEN .....
.....
END AS add_cost,
gpm
FROM items1;
I can just do:
SELECT
itemnumber,
itemname,
base,
calc_add_cost(),
gpm
FROM items1;
If I want to be able to use it on any of the item tables, I guess I would need to set a table_name parameter that the function takes since adding the table name into the function would be undesirable to say the least.
calc_add_cost(items1)
However, is there a simpler way such that when I call calc_add_cost() it will just use the table name from the FROM clause?
SELECT ....., calc_add_cost(item1) FROM item1
Just seems redundant.
I did come across a few topics with titles that sounded like they addressed what I was hoping to accomplish, but upon reviewing them it looked like they were a different issue.
You can even emulate a "computed field" or "generated column" like you had in mind. Basics here:
Store common query as column?
Simple demo for one table:
CREATE OR REPLACE FUNCTION add_cost(items1) -- function name = default col name
RETURNS numeric AS
$func$
SELECT
CASE
WHEN $1.labor < 100 AND $1.overhead < .20 THEN numeric '1'
-- WHEN .....
-- WHEN .....
-- WHEN .....
ELSE numeric '0' -- ?
END;
$func$
LANGUAGE sql IMMUTABLE;
Call:
SELECT *, t.add_cost FROM items1 t;
Note the table-qualification in t.add_cost. I only demonstrate this syntax variant since you have been asking for it. My advise is to use the less confusing standard syntax:
SELECT *, add_cost(t) AS add_cost FROM items1 t; -- column alias is also optional
However, SQL is a strictly typed language. If you define a particular row type as input parameter, it is bound to this particular row type. Passing various whole table types is more sophisticated, but still possible with polymorphic input type.
CREATE OR REPLACE FUNCTION add_cost(ANYELEMENT) -- function name = default col name
RETURNS numeric AS
$func$
SELECT
CASE
WHEN $1.labor < 100 AND $1.overhead < .20 THEN numeric '1'
-- WHEN .....
-- WHEN .....
-- WHEN .....
ELSE numeric '0' -- ?
END;
$func$
LANGUAGE sql IMMUTABLE;
Same call for any table that has the columns labor and overhead with matching data type.
dbfiddle here
Also see the related simple case passing simple values here:
How to put part of a SELECT statement into a Postgres function
For even more complex requirements - like also returning various row types - see:
Refactor a PL/pgSQL function to return the output of various SELECT queries

Postgres conditional union?

If I have the queries:-
SELECT foo FROM baa where foo='value'
AND
SELECT foo FROM wobble WHERE foo='value'
Can I write an SQL query in postgres such that the second query does not run if the first query returns results without using a common table expression or repeating the first query?
The reason is that both queries are quite heavy, the second query is a fallback in the case that the first query does not return results.
I doubt that it can be done in plain SQL, but in a PL/pgSQL function it is rather straightforward, assuming that both queries return the same set of columns. Given that both queries are "heavy", the function overhead is minimal. A bonus feature is that the query plan gets cached, so successive calls will be faster. Another bonus feature is that you could make foo a parameter so you could query for something other than "value".
CREATE FUNCTION run_heavy_query()
RETURNS TABLE (foo text, ...) AS $$
BEGIN
RETURN QUERY SELECT foo FROM baa WHERE foo='value';
IF NOT FOUND THEN
RETURN QUERY SELECT foo FROM wobble WHERE foo='value';
END IF;
END; $$ LANGUAGE plpgsql STABLE;
The STABLE function volatility modifier lets the query planner optimize successive calls but you should not be using any VOLATILE function calls in your queries.
Now you can simply
SELECT * FROM run_heavy_query();

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.