Default value for column postgres function with argument - postgresql

I have a postgres function that takes one argument. I want to make this function the default value for a column, but I'm not sure how to pass the argument into the table definition.
This is what I mean, I have two columns in the table that look like this:
trade_id INTEGER NOT NULL
group_id INTEGER DEFAULT trade_id_f(argument_goes_here);
I want to make the DEFAULT value of group_id to be the return value of trade_id_f(trade_id) where trade_id is the trade_id of the record to be inserted.
I'm new to all things postgres functions, is this possible?

Unfortunately, you cannot do that, because of (for the documentation):
The value is any variable-free expression (subqueries and
cross-references to other columns in the current table are not
allowed).
You can use a trigger, e.g.:
create table the_table (
trade_id int not null,
group_id int);
create or replace function trade_id_trigger ()
returns trigger language plpgsql as $$
begin
new.group_id:= new.trade_id+ 1;
return new;
end $$;
create trigger trade_id_trigger
before insert or update on the_table
for each row execute procedure trade_id_trigger();
insert into the_table values (1,1);
select * from the_table;
trade_id | group_id
----------+----------
1 | 2
(1 row)

Related

Saving a query in a table

I would like to store a table variable as to be accessed by another query within a function. Here is what I have so far.
CREATE OR REPLACE FUNCTION suggest(p_id INTEGER)
RETURNS TABLE (
r_id INT,
i_id INT
) AS $$
DECLARE
p_record INT[];
BEGIN
SELECT ingredient_id INTO p_record FROM shop_ingredients
WHERE item_id IN
(SELECT item_id FROM basket
WHERE user_id = p_id);
...
I am not sure whether the type of p-record should be an array of INTEGER or a RECORD. Within the function I would like to access such list of values, for example:
HAVING SUM(ingredient_id = ANY(p_record)) >= (COUNT(*)*0.6)
How can I achieve this? I have searched endlessly to understand how to manage this but to no avail.
The problem with creating a table, like so:
CREATE TEMP TABLE IF NOT EXISTS p_record
AS SELECT ingredient_id
FROM shop_ingredients
WHERE item_id IN
(SELECT item_id FROM basket
WHERE user_id = p_id);
is that if p_id parameter changes, the p_record variables does not.
Maybe the following SQL function can help:
create or replace function suggest(p_id int)
returns table (i_id int)
language sql
as
$$
select ingredient_id
from shop_ingredient
where item_id in
(select item_id from basket
where user_id = p_id);
$$;
Note that the SELECT statement column list must exactly match the TABLE clause after RETURNS keyword and this function is only SQL so no need of BEGIN/END block or intermediate record variable.

Update hstore values with other hstore values

I have a summary table that is updated with new data on a regulary basis. One of the columns is of type hstore. When I update with new data I want to add the value of a key to the existing value of the key if the key exists, otherwise I want to add the pair to the hstore.
Existing data:
id sum keyvalue
--------------------------------------
1 2 "key1"=>"1","key2"=>"1"
New data:
id sum keyvalue
--------------------------------------------------
1 3 "key1"=>"1","key2"=>"1","key3"=>"1"
Wanted result:
id sum keyvalue
--------------------------------------------------
1 5 "key1"=>"2","key2"=>"2","key3"=>"1"
I want to do this in a on conflict part of an insert.
The sum part was easy. But I have not found how to concatenate the hstore in this way.
There is nothing built int. You have to write a function that accepts to hstore values and merges them in the way you want.
create function merge_and_increment(p_one hstore, p_two hstore)
returns hstore
as
$$
select hstore_agg(hstore(k,v))
from (
select k, sum(v::int)::text as v
from (
select *
from each(p_one) as t1(k,v)
union all
select *
from each(p_two) as t2(k,v)
) x
group by k
) s
$$
language sql;
The hstore_agg() function isn't built-in as well, but it's easy to define it:
create aggregate hstore_agg(hstore)
(
sfunc = hs_concat(hstore, hstore),
stype = hstore
);
So the result of this:
select merge_and_increment(hstore('"key1"=>"1","key2"=>"1"'), hstore('"key1"=>"1","key2"=>"1","key3"=>"1"'))
is:
merge_and_increment
-------------------------------------
"key1"=>"2", "key2"=>"2", "key3"=>"1"
Note that the function will fail miserably if there are values that can't be converted to an integer.
With an insert statement you can use it like this:
insert into the_table (id, sum, data)
values (....)
on conflict (id) do update
set sum = the_table.sum + excluded.sum,
data = merge_and_increment(the_table.data, excluded.data);
demo:db<>fiddle
CREATE OR REPLACE FUNCTION sum_hstore(_old hstore, _new hstore) RETURNS hstore
AS $$
DECLARE
_out hstore;
BEGIN
SELECT
hstore(array_agg(key), array_agg(value::text))
FROM (
SELECT
key,
SUM(value::int) AS value
FROM (
SELECT * FROM each('"key1"=>"1","key2"=>"1"'::hstore)
UNION ALL
SELECT * FROM each('"key1"=>"1","key2"=>"1","key3"=>"1"')
) s
GROUP BY key
) s
INTO _out;
RETURN _out;
END;
$$
LANGUAGE plpgsql;
each() expands the key/value pairs into one row per pair with columns key and value
convert type text into type int and group/sum the values
Aggregate into a new hstore value using the hstore(array, array) function. The array elements are the values of the key column and the values of the value column.
You can do such an update:
UPDATE mytable
SET keyvalue = sum_hstore(keyvalue, '"key1"=>"1","key2"=>"1","key3"=>"1"')
WHERE id = 1;

Set the value of a column to its default value

I have few existing tables in which I have to modify various columns to have a default value.
How can I apply the default value to old records which are NULL, so that the old records will be consistent with the new ones
ALTER TABLE "mytable" ALTER COLUMN "my_column" SET DEFAULT NOW();
After modifying table looks something like this ...
Table "public.mytable"
Column | Type | Modifiers
-------------+-----------------------------+-----------------------------------------------
id | integer | not null default nextval('mytable_id_seq'::regclass)
....
my_column | timestamp(0) with time zone | default now()
Indexes:
"mytable_pkey" PRIMARY KEY, btree (id)
Is there a simple to way to have all columns which are currently null and also which have a default value to be set to the default value ?
Deriving from insert into:
For clarity, you can also request default values explicitly, for individual columns or for the entire row:
INSERT INTO products (product_no, name, price) VALUES (1, 'Cheese', DEFAULT);
INSERT INTO products DEFAULT VALUES;
I just tried this, and it is as simple as
update mytable
set my_column = default
where my_column is null
See sqlfiddle
Edit: olaf answer is easiest and correct way of doing this however the below also is viable solution for most cases.
For a each column it is easy to use the information_schema and get the default value of a column and then use that in a UPDATE statement
UPDATE mytable set my_column = (
SELECT column_default
FROM information_schema.columns
WHERE (table_schema, table_name, column_name) = ('public', 'mytable','my_column')
)::timestamp
WHERE my_column IS NULL;
Note the sub-query must by typecast to the corresponding column data type .
Also this statement will not evaluate expressions as column_default will be of type character varying it will work for NOW() but not for expressions like say (NOW()+ interval ' 7 days')
It is better to get expression and validate it then apply it manually

Avoid putting PostgreSQL function result into one field

The end result of what I am after is a query that calls a function and that function returns a set of records that are in their own separate fields. I can do this but the results of the function are all in one field.
ie: http://i.stack.imgur.com/ETLCL.png and the results I am after are: http://i.stack.imgur.com/wqRQ9.png
Here's the code to create the table
CREATE TABLE tbl_1_hm
(
tbl_1_hm_id bigserial NOT NULL,
tbl_1_hm_f1 VARCHAR (250),
tbl_1_hm_f2 INTEGER,
CONSTRAINT tbl_1_hm PRIMARY KEY (tbl_1_hm_id)
)
-- do that for a few times to get some data
INSERT INTO tbl_1_hm (tbl_1_hm_f1, tbl_1_hm_f2)
VALUES ('hello', 1);
CREATE OR REPLACE FUNCTION proc_1_hm(id BIGINT)
RETURNS TABLE(tbl_1_hm_f1 VARCHAR (250), tbl_1_hm_f2 int AS $$
SELECT tbl_1_hm_f1, tbl_1_hm_f2
FROM tbl_1_hm
WHERE tbl_1_hm_id = id
$$ LANGUAGE SQL;
--And here is the current query I am running for my results:
SELECT t1.tbl_1_hm_id, proc_1_hm(t1.tbl_1_hm_id) AS t3
FROM tbl_1_hm AS t1
Thanks for having a read. Please if you want to haggle about the semantics of what I am doing by hitting the same table twice or my naming convention --> this is a simplified test.
When a function returns a set of records, you should treat it as a table source:
SELECT t1.tbl_1_hm_id, t3.*
FROM tbl_1_hm AS t1, proc_1_hm(t1.tbl_1_hm_id) AS t3;
Note that functions are implicitly using a LATERAL join (scroll down to sub-sections 4 and 5) so you can use fields from tables listed previously without having to specify an explicit JOIN condition.

PostgreSQL: how to return composite type

I'm trying to get a composite value with a stored function in PostreSQL as follows.
I created a type called PersonId, and I used the type in a table called Person.
And I inserted values into the table.
CREATE TYPE PersonId AS
(
id VARCHAR(32),
issuer VARCHAR(32)
);
CREATE TABLE Person
(
key INTEGER,
pid PersonId
);
INSERT INTO Person VALUES (1, ('111','ABC'));
INSERT INTO Person VALUES (2, ('222','DEF'));
CREATE OR REPLACE FUNCTION Person_lookup_id
(
p_key IN Person.key%TYPE
)
RETURNS Person.pid%TYPE
LANGUAGE plpgsql
AS $BODY$
DECLARE
v_pid Person.pid%TYPE;
BEGIN
SELECT pid INTO v_pid
FROM Person
WHERE key = p_key;
RETURN v_pid;
EXCEPTION
WHEN no_data_found THEN
RETURN NULL;
END;
$BODY$;
However, the result was different from what I had expected.
Actually I expected the value 111 would be in id column, and ABC in issuer column. But 111 and ABC were combined in id column.
# select person_lookup_id(1);
person_lookup_id
------------------
("(111,ABC)",)
(1 row)
# select * from person_lookup_id(1);
id | issuer
-----------+--------
(111,ABC) |
(1 row)
Where was I wrong?
Since pid is a composite you must extract its columns otherwise you are inserting the whole composite into the first column of the v_pid variable
select (pid).* into v_pid