Loop postgresql Function through Date Range - postgresql

I have a user defined function. This question shows how to loop through dates. Using this approach, I tried this query:
select myfun(a::date) from generate_series('2015-01-01'::date,'2016-01-27','1 day') s(a)
This doesn't quite work. What it returns is a single column of the form:
(10101, "Sample", "test")
(10102, "Sample2", "test2")
When in reality there should be three columns. It merges them into one.
I noticed that this is the same behavior that you get in a vanilla query such as select mytable when I omit the asterisk. The above query doesn't have an asterisk in it, but adding one causes an error.

Place the function call in the FROM clause:
select f.*
from
generate_series('2015-01-01'::date,'2016-01-27','1 day') s(a),
myfun(a::date) f;
or using more formal syntax:
select f.*
from generate_series('2015-01-01'::date,'2016-01-27','1 day') s(a)
cross join myfun(a::date) f;
This form of the FROM clause is known as lateral join.

Related

Full outer join with different WHERE clauses in Knex.js for PostgreSQL

I try to get a single row with two columns showing aggregation results: one column should show the total sum based on one WHERE-clause while the other column should show the total sum based on a different WHERE clause.
Desired output:
amount_vic amount_qld
100 70
In raw PostgreSQL I could write something like that:
select
sum(a.amount) as amount_vic,
sum(b.amount) as amount_qld
from mytable a
full outer join mytable b on 1=1
where a.state='vic' and b.state= 'qld'
Question: How do I write this or a similar query that returns the desired outcome in knex.js? For example: the 'on 1=1' probably needs knex.raw() and I think the table and column aliases do not work for me and it always returns some errors.
One of my not-working-attempts in knex.js:
knex
.sum({ amount_vic: 'a.amount' })
.sum({ amount_qld: 'b.amount' })
.from('mytable')
.as('a')
.raw('full outer join mytable on 1=1')
.as('b')
.where({
a.state: 'vic',
b.state: 'qld'
})
Thank you for your help.
Disclaimer: this does not answer the Knex part of the question - but it is too long for a comment.
Although your current query does what you want, the way it is phrased seems suboptimal. There is not need to generate a self-cartesian product here - which is what full join ... on 1 = 1 does. You can just use conditional aggregation.
In Postgres, you would phrase this as:
select
sum(amount) filter(where state = 'vic') amount_vic,
sum(amount) filter(where state = 'qld') amount_qld
from mytable
where state in ('vic', 'qld')
I don't know Knex so I cannot tell how to translate the query to it. Maybe this query is easier for you to translate.

PostgreSQL reusing computed result as input to other select computations

Is there any way we can take a computed result inside the select clause and insert it into another computation inside the select clause?
For example this is what I want to have but can't so far:
select trim(leading https://www.amazon.com for url) as trimmedURL,
substring(trimmedURL, from position('/' in trimmedURL) for position ('html' in trimmedURL))....
As you can see I have used trimmedURL 3 times inside the substring function. I know how to naively do that be copy/paste of trim(leading https://www.amazon.com for url) into the substring function.
Is there any way to avoid that and not create really large function calls as the first value computed might be placed many times inside other functions. This will improve code readability and usability.
you could use a lateral join and place the computed fields i the lateral query. the lateral fields are then accessible from the main query.
Postgres documentation for lateral join
i.e.
SELECT
trimmedUrl
, SUBSTRING(trimmedURL,10,20) url_part
FROM mytable
LEFT JOIN LATERAL (SELECT trim(leading https://www.amazon.com for url) as trimmedURL) trmd
ON TRUE
also, note that postgresql ignores casing in the naming of columns / tables etc unless they are quoted.
Here's a self-contained example:
WITH x(col) AS (Values ('abc://cdf/def'), ('abc://xyz/pqr'))
SELECT x.col, SUBSTRING(y.col2 from position('/' in y.col2)) resuing_computation
FROM x
LEFT JOIN LATERAL (SELECT trim(leading 'abc://' from col) col2) y ON TRUE

Issue with generate_series in case sentence

Recently my team have updated the version of our postgres server from 9.3 to 10.1
One of our procedures has a piece of code that right now is giving us some problems. This is just an example base on the original code:
SELECT
CASE
WHEN current_date = '2017-12-14' THEN generate_series(current_date , '2017-12-31'::DATE , '1 day')
WHEN current_date = '2017-12-15' THEN generate_series(current_date , '2017-12-31'::DATE , '1 day')
ELSE generate_series('2017-12-01'::DATE, '2017-12-31'::DATE, '1 day')
END AS workday
If I run this code in our previous server I've got the sequence. However in postgres 10.1 I'am getting this error message:
"set-returning functions are not allowed in CASE. "
Is there any problem with this version of postgres is this a bug, or is there another way to get the expected result
You need to move the set-returning functions out of the CASE statement so that it only returns a single row at a time. I would simplify your statement by making the CASE a subquery that you use the resulting column from:
SELECT generate_series(series_start_date::DATE, '2017-12-31'::DATE, '1 day') FROM
(
SELECT
CASE
WHEN current_date='2017-12-14' THEN current_date
WHEN current_date='2017-12-15' THEN current_date
ELSE '2017-12-01'
END AS series_start_date
) as temp_alias;
That query should give you what you want.
Putting 'temp_alias' in there is required, but isn't used.
Disclaimer: I tested this on Postgres 9.6, which is all I have available to test with at the moment. It should work with Postgres 10.1.
The use of set-returning functions in scalar contexts was a hack. They were completely rewritten by Andres Freund to a much better implementation.
Release note: Change the implementation of set-returning functions appearing in a query's SELECT list (Andres Freund).
As an altenative you can use LATERAL JOIN to join the logic of determining reference date to generate_series:
SELECT workday_series.*
FROM
(SELECT CASE CURRENT_DATE
WHEN '2017-12-14' THEN CURRENT_DATE
WHEN '2017-12-15' THEN CURRENT_DATE
ELSE '2017-12-01'
END) AS REF (date_reference)
JOIN LATERAL generate_series(ref.date_reference, '2017-12-31'::DATE, '1 day') AS workday_series(workday) ON TRUE;
SQL fiddle here.

PostgreSQL CTE records as parameters to function

I have a function that accepts two integers as parameters my_function(input_a, input_b). Is there an easy way to pass the results of a CTE (that returns records of input_a, input_b) into the function?
Should I be looking into writing a custom function with a for loop or is there a better approach?
If the function returns a single record then:
WITH cte AS (SELECT 1 a, 2 b)
SELECT my_function(a, b) FROM cte;
will work. However, if the function is an SRF (Set-Returning-Function), then you need to use LATERAL, to let the database know that you want to feed the results of the prior tables in the JOIN statement to the functions later on in the JOIN. This is accomplished like so:
WITH cte AS (SELECT 1 a, 2 b)
SELECT * FROM cte, LATERAL my_function(a, b);
The LATERAL will cause PostgreSQL to take each row from the CTE and run "my_function" with the values from that row, returning the results of that function to the overall SELECT statement.

Re-use a hardcoded value in multiple function calls in PostgreSQL query

I have some functions in PostgreSQL 9.0 that return table results. The idea behind these is to return the data as it was at a certain time, eg.
CREATE FUNCTION person_asof(effective_time timestamp with time zone)
RETURNS SETOF person
...
CREATE FUNCTION pgroup_asof(effective_time timestamp with time zone)
RETURNS SETOF pgroup
...
I can query them almost as if they were tables, with joins and all:
SELECT *
FROM pgroup_asof('2011-01-01') g
JOIN person_asof('2011-01-01') p
ON g.id = p.group_id
This works fine, but is there any trick I can use to specify the effective time just once?
I tried to do something like this:
SELECT *
FROM (SELECT '2010-04-12'::timestamp ts) effective,
pgroup_asof(effective.ts) g
JOIN person_asof(effective.ts) p
ON g.id = p.group_id
...but that fails with ERROR: function expression in FROM cannot refer to other relations of same query level and putting the main query into a sub-query doesn't help, either.
This is something I have wanted to do in the past as well but does not look like it is possible yet, but there may be hope on the horizon.