create type recordObj as object(
rID varchar(5),
rName varchar(5));
create type recordsArr as varray(2) of recordObj;
create table RecordsTable(
ID varchar(3),
records recordsArr);
insert into RecordsTable values ('RE1', recordsArr(recordObj('RE-12','CONF')));
After successfully inserting this row, I want to add an additional record into the 'recordsArr' varray where RecordsTable ID is also 'RE1', since varray is of size 2, but I don't want to do it in the query I have just written. If I run this query again, but with two 'recordObj' inserts, it just duplicates the data and create a new row. I want add a new varray record but in the existing record where ID is 'RE1'. Any help?
You can do it via PL/SQL:
DECLARE
p_records recordsArr;
BEGIN
SELECT records
INTO p_records
FROM recordsTable
WHERE id = 'RE1'
FOR UPDATE;
p_records.EXTEND;
p_records(2) := recordObj( 'RE-13', 'ABC' );
UPDATE RecordsTable
SET records = p_records
WHERE id = 'RE1';
END;
/
Then:
SELECT id,
r.*
FROM recordsTable rt
CROSS JOIN TABLE( rt.records ) r;
Outputs:
ID
RID
RNAME
RE1
RE-12
CONF
RE1
RE-13
ABC
db<>fiddle here
If, instead, you created the data-type as a collection using:
create type recordObj as object(
rID varchar(5),
rName varchar(5)
);
create type recordsArr as table of recordObj; -- TABLE not VARRAY(2)
create table RecordsTable(
ID varchar(3),
records recordsArr
) NESTED TABLE records STORE AS recordstable__records; -- Add storage for the collection
insert into RecordsTable values ('RE1', recordsArr(recordObj('RE-12','CONF')));
Then you could use:
INSERT INTO TABLE( SELECT records FROM RecordsTable WHERE id = 'RE1' )
VALUES ( 'RE-14', 'DEF' );
But, that will not work for a VARRAY.
db<>fiddle here
Related
Table source contains integer column. Its values should be passed to Postgresql 12 function for selecting data from other table.
I tried to use array
CREATE OR REPLACE FUNCTION public.TestAddAssetTransactions(dokumnrs int[])
RETURNS int AS
$BODY$
with i1 as (
INSERT INTO bilkaib (dokumnr)
select dokumnr from dok where dokumnr in (select * from unnest(dokumnrs))
returning *
)
select count(*) from i1;
$BODY$ language sql;
create temp table bilkaib (dokumnr int ) on commit drop;
create temp table dok (dokumnr serial primary key ) on commit drop;
create temp table source (dokumnr int ) on commit drop;
insert into source values (1),(2);
select TestAddAssetTransactions( (select ARRAY[dokumnr] from source)::int[] )
but got error
ERROR: more than one row returned by a subquery used as an expression
How to pass single column values from table rows to function? Should array, table type or temp table used?
Using Postgresql 12+
You need to aggregate the ints in your function call. Otherwise you're just casting each value to a single-element array and they try to cast a column to an array of int.
select TestAddAssetTransactions( (select array_agg(dokumnr)::int[] from source) );
online demo
I have a table
CREATE TABLE items(
id SERIAL PRIMARY KEY,
group_id INT NOT NULL,
item_id INT NOT NULL,
name TEXT,
.....
.....
);
I am creating a function that
takes set of row values for a single group_id, fail if multiple group_ids present in in input rows
compares it with matching values in the table (only for that group_id
updates changed values (only for the input group_id)
inserts new values
deletes table rows that are absent in the row input (compare rows with group_id and item_id)(only for the input group_id)
this is my function definition
CREATE OR REPLACE FUNCTION update_items(rows_input items[]) RETURNS boolean as $$
DECLARE
rows items[];
group_id_input integer;
BEGIN
-- get single group_id from input rows, fail if multiple group_id's present in input
-- read items of that group_id in table
-- compare input rows and table rows (of the same group_id)
-- create transaction
-- delete absent rows
-- upsert
-- return success of transaction (boolean)
END;
$$ LANGUAGE plpgsql;
I am trying to call the function in a query
select update_items(
(38,1,1283,"Name1"),
(39,1,1471,"Name2"),
(40,1,1333,"Name3")
);
I get the following error
Failed to run sql query: column "Name1" does not exist
I tried removing the id column values: that gives me the same error
What is the correct way to pass row values to a function that accepts table type array as arguments?
updates changed values
inserts new values deletes table rows that are
absent in the row input (compare rows with group_id and item_id)
If you want do upsert, you must upsert with unique constraint.
So there is two unique constraints. primary key(id), (group_id, item_id).
insert on conflict need consider these two unique constraint.
Since You want pass items[] type to the functions. So it also means that any id that is not in the input function arguments will also be deleted.
drop table if exists items cascade;
begin;
CREATE TABLE items(
id bigint GENERATED BY DEFAULT as identity PRIMARY KEY,
group_id INT NOT NULL,
item_id INT NOT NULL,
name TEXT
,unique(group_id,item_id)
);
insert into items values
(38,1,1283,'original_38'),
(39,1,1471,'original_39'),
(40,1,1333,'original_40'),
(42,1,1332,'original_42');
end;
main function:
CREATE OR REPLACE FUNCTION update_items (in_items items[])
RETURNS boolean
AS $FUNC$
DECLARE
iter items;
saved_ids bigint[];
BEGIN
saved_ids := (SELECT ARRAY (SELECT (unnest(in_items)).id));
DELETE FROM items
WHERE NOT (id = ANY (saved_ids));
FOREACH iter IN ARRAY in_items LOOP
INSERT INTO items
SELECT
iter.*
ON CONFLICT (id)
DO NOTHING;
INSERT INTO items
SELECT
iter.*
ON CONFLICT (group_id,
item_id)
DO UPDATE SET
name = EXCLUDED.name;
RAISE NOTICE 'rec.groupid: %, rec.items_id:%', iter.group_id, iter.item_id;
END LOOP;
RETURN TRUE;
END
$FUNC$
LANGUAGE plpgsql;
call it:
SELECT
*
FROM
update_items ('{"(38, 1, 1283, Name1) "," (39, 1, 1471, Name2) "," (40, 1, 1333, Name3)"}'::items[]);
references:
Iterating over integer[] in PL/pgSQL
How to match elements in an array of composite type?
IN vs ANY operator in PostgreSQL
Here's how I achieved UPSERT with DELETE missing rows, if anyone is looking to do the same.
CREATE OR REPLACE FUNCTION update_items(in_rows items[]) RETURNS INT AS $$
DECLARE
in_groups INTEGER[];
in_group_id INTEGER;
in_item_ids INTEGER[];
BEGIN
-- get single group id from input rows, fail if multiple group ids present in input
in_groups = (SELECT ARRAY (SELECT distinct(group_id) FROM UNNEST(in_rows)));
IF ARRAY_LENGTH(in_groups,1)>1 THEN
RAISE EXCEPTION 'Multiple group_ids found in input items: %', in_groups;
END IF;
in_group_id = in_groups[1];
-- delete items of this group that are absent in in_rows
in_item_ids := (SELECT ARRAY (SELECT (UNNEST(in_rows)).item_id));
DELETE FROM items
WHERE
master_code <> ANY (in_item_ids)
AND group_id = in_group_id;
-- upsert in_rows
INSERT INTO items
SELECT * FROM UNNEST(in_rows)
ON CONFLICT (group_id,item_d)
DO UPDATE SET
parent_group_id = EXCLUDED.parent_group_id,
mat_centre_id = EXCLUDED.mat_centre_id,
NAME = EXCLUDED.NAME,
opening_date = EXCLUDED.opening_date;
RETURN in_group_id;
-- return success of transaction (boolean)
END;
$$ LANGUAGE plpgsql;
This function removes rows that are missing from your in_rows
I would like to make a trigger that changes the value in one table, after updating the other table. Let's say I have such 2 tables
first table
A(
id serial,
value1 int,
A_date date);
second table
B(
id serial,
value2 int,
B_date date);
Let's say that after inserting a value 50 to table A field value1, I want to increase the value2 by 50 in table B. I did so, I get no errors, but the result in table B does not change for me.Why?
create function Fun()
returns trigger
language plpgsql
as $$
begin
if(TG_OP='INSERT') then
update B
set value2= value2+new.value1
where "id"=1;
end if;
return null;
end ;
$$;
create trigger Fun_trig
after insert
on A
for each row
execute procedure Fun()
I have a large number of rows that i want to insert simultaneously into a PostgreSQL database. I need to track what id is assigned for each row that is inserted. For example say we have the table:
CREATE TABLE example
(
id serial,
name text,
CONSTRAINT example_pkey PRIMARY KEY (id),
);
Now i have some data with ids that i dont want inserted (as the serial id column will assign a new id), but i need to keep track of the mapping between the old id and new id:
old id | name
-------------
-1 | foo
-2 | bar
-3 | baz
So i wrote this query
WITH data(oldid,name) AS ( VALUES
(-1,'foo'),
(-2,'bar'),
(-3,'baz')
)
INSERT INTO example(name)
SELECT name FROM data d
RETURNING id, d.oldid
Expecting to get something back like:
id | oldid
-----------
1 | -1
2 | -2
3 | -3
However this doesn't work, as i don't believe you can return a column that wasn't inserted. Is there any alternative way to do this?
I ended up creating a function that wrapped the inserting of a single row:
CREATE OR REPLACE FUNCTION add_example(
in_name text)
RETURNS integer AS
$BODY$
DECLARE
new_id integer;
BEGIN
INSERT INTO example(name)
VALUES (in_name) RETURNING id INTO new_id;
RETURN new_id;
END;
$BODY$
LANGUAGE plpgsql;
Then i can do:
WITH data(oldid, name) AS (VALUES
(-1,'foo'),
(-2,'bar'),
(-3,'baz')
)
SELECT oldid, add_example(name) AS id
FROM data
Which returns what i expect. I'd like to see if this can be done without the function though.
CREATE SEQUENCE data_id_seq;
CREATE TABLE DATA (
id integer default nextval('data_id_seq') NOT NULL PRIMARY KEY,
oldid integer,
name text,
);
INSERT INTO DATA(oldid,name) values (-1,'foo'),(-2,'bar'),(-3,'baz') returning id,oldid;
The optional RETURNING clause causes INSERT to compute and return
value(s) based on each row actually inserted
from https://www.postgresql.org/docs/current/static/sql-insert.html
so column parasite is unavoidable for such solution:
alter table example add column old bigint;
WITH d(oldid,name) AS ( VALUES
(-1,'foo'),
(-2,'bar'),
(-3,'baz')
)
INSERT INTO example(name,old)
SELECT "name", oldid FROM d
RETURNING id, old
I want create a table 'product' and have a column date, is it possible that current date will be added when I add some info to table?
If yes please example of this table
create table products (
id number not null,
date number not null
);
Assuming that
Your column is not actually named date since that is a reserved word
Your column is actually defined as a date rather than as a number
You want to populate the column when you insert a new row
you can define a default value for the column.
SQL> ed
Wrote file afiedt.buf
1 create table products (
2 id number not null,
3 dt date default sysdate not null
4* )
SQL> /
Table created.
SQL>
SQL> insert into products( id ) values( 1 );
1 row created.
SQL> select * from products;
ID DT
---------- ---------
1 20-NOV-12
If you want to modify the dt column when you UPDATE the row, you would need a trigger
CREATE OR REPLACE TRIGGER trg_products
BEFORE INSERT OR UPDATE ON products
FOR EACH ROW
BEGIN
:new.dt := sysdate;
END;
A trigger will override any value passed in as part of the INSERT or UPDATE statement for the dt column. A default value will not.