Issue while passing arrays to stored Function - postgresql

trying to delete records by passing arrays into the stored function.
testing=# select * from links;
id | url | name | description | last_update
----+------------------------------------+---------------------+-------------+-------------
1 | https://www.postgresqltutorial.com | PostgreSQL Tutorial | |
2 | http://www.oreilly.com | O'Reilly Media | |
7 | http://www.postgresql.org | PostgreSQL | |
8 | https://www.google.com | Google | | 2013-06-01
(4 rows)
My Function
CREATE OR REPLACE FUNCTION testing(TEXT[])
RETURNS INTEGER AS
$BODY$
DECLARE emp_id INTEGER;
BEGIN
SELECT id into emp_id from links e where name = ANY($1);
DELETE FROM links WHERE id = emp_id;
return emp_id;
END
$BODY$
LANGUAGE plpgsql;
Query to call function
SELECT * from testing(ARRAY['PostgreSQL','Google']::TEXT[]);
We pass two records to delete from the links table, But instead of that only one record is deleting.
testing=# select * from links;
id | url | name | description | last_update
----+------------------------------------+---------------------+-------------+-------------
1 | https://www.postgresqltutorial.com | PostgreSQL Tutorial | |
2 | http://www.oreilly.com | O'Reilly Media | |
7 | http://www.postgresql.org | PostgreSQL | |
(3 rows)

SELECT ... INTO only stores the first row in the variable and ignores the rest.
You should declare the function as RETURNS SETOF integer, omit the SELECT and run only the DELETE as detailed in my other answer. Then you can use an SQL function rather than a PL/pgSQL one, which will simplify everything.

Related

Could I make a constraint on inserting 'if the record is not contained in another table'?

I have 3 tables.
Table 'number':
| x |
|---|
| 1 |
| 2 |
| 3 |
Table 'group':
| group_id |
|----------|
| 1 |
| 2 |
Table 'number_in_group':
| group_id | x |
|----------|---|
| 1 | 4 |
| 1 | 5 |
| 2 | 4 |
| 2 | 5 |
| 2 | 7 |
Could I make a constraint on inserting into table number_in_group that x does not exist in table number?
If yes, is it a good approach, or better to put this business logic on the backend?
There is no constraint in Postgres that say make sure something does not exist, but you can create one - a trigger. In this case you select from numbers for the new value of x in number_in_group. If it exists then raise an exception. (see demo)
create or replace function not_if_in_numbers()
returns trigger
language plpgsql
as $$
begin
if exists
(select null
from numbers
where x = new.x
)
then
raise exception 'Invalid value for x (%). Found in numbers table.',new.x::text;
end if;
return new;
end;
$$;
create trigger check_restricted_x_biur
before insert or update of x
on number_in_group
for each row
execute function not_if_in_numbers();
You should create a similar trigger for numbers selecting from number_in_group and throws a corresponding exception. Do not build around the concept of an immutable table. It will not happen.

Postgresql sequencer not found

I'm creating an item record number generator. The goal is to have a table to house all record number/sequencers for a variety of different types. For example, for a "Part" you may want a number like "110-00001-00". The seqItem table would hold the definition of this number generator (SeqName, preFix, postFix, padding).
InventorySys=# SELECT * FROM information_schema.sequences;
sequence_catalog | sequence_schema | sequence_name | data_type | numeric_precision | numeric_precision_radix | numeric_scale | start_value | minimum_value | maximum_value | increment | cycle_option
------------------+-----------------+---------------+-----------+-------------------+-------------------------+---------------+-------------+---------------+---------------+-----------+--------------
(0 rows)
InventorySys=# \d "SeqItem"
Table "public.SeqItem"
Column | Type | Collation | Nullable | Default
---------+---------+-----------+----------+---------
SeqName | text | | not null |
prefix | text | | |
postfix | text | | |
padding | integer | | not null | 5
Indexes:
"SeqItem_pkey" PRIMARY KEY, btree ("SeqName")
"SeqName" UNIQUE CONSTRAINT, btree ("SeqName")
Triggers:
dropsqeitem AFTER DELETE ON "SeqItem" FOR EACH ROW EXECUTE FUNCTION "RemoveSeq"()
inssqeitem AFTER INSERT ON "SeqItem" FOR EACH ROW EXECUTE FUNCTION "CreateSeq"()
InventorySys=#
When a new record is added to this table, I want to create a new Sequence with the "SeqName". So, I've created the following Trigger/Function:
CREATE OR REPLACE FUNCTION public."CreateSeq"() RETURNS TRIGGER as $CreateSeq$
BEGIN
EXECUTE format('CREATE SEQUENCE %I INCREMENT BY 1 MINVALUE 1 NO MAXVALUE START WITH 1 NO CYCLE', NEW."SeqName");
RETURN NEW;
END
$CreateSeq$ LANGUAGE plpgsql;
CREATE TRIGGER insSqeItem AFTER INSERT ON "SeqItem"
FOR EACH ROW EXECUTE FUNCTION "CreateSeq"();
This works perfect, and with each new record, I get a new sequencer created. I've also created a another function/trigger to delete the sequencer if the row is deleted.
CREATE OR REPLACE FUNCTION public."RemoveSeq"() RETURNS TRIGGER as $RemoveSeq$
BEGIN
EXECUTE format('DROP SEQUENCE IF EXISTS %I', OLD."SeqName");
RETURN NEW;
END
$RemoveSeq$ LANGUAGE plpgsql;
CREATE TRIGGER dropSqeItem AFTER DELETE ON "SeqItem"
FOR EACH ROW EXECUTE FUNCTION "RemoveSeq"();
So far so good! So, Let's add a new record and see that the Sequencer was added:
InventorySys=# INSERT into "SeqItem" ("SeqName", prefix, padding) Values ('testItem1', '115-',6);
INSERT 0 1
InventorySys=# SELECT * FROM "SeqItem";
SeqName | prefix | postfix | padding
-----------+--------+---------+---------
testItem1 | 115- | | 6
(1 row)
InventorySys=# SELECT * FROM information_schema.sequences;
sequence_catalog | sequence_schema | sequence_name | data_type | numeric_precision | numeric_precision_radix | numeric_scale | start_value | minimum_value | maximum_value | increment | cycle_option
------------------+-----------------+---------------+-----------+-------------------+-------------------------+---------------+-------------+---------------+---------------------+-----------+--------------
InventorySys | public | testItem1 | bigint | 64 | 2 | 0 | 1 | 1 | 9223372036854775807 | 1 | NO
(1 row)
InventorySys=#
However, when I try to use the newly created sequencer from the trigger I get the following error that the sequencer is not found.
InventorySys=# select CONCAT("prefix", LPAD((select nextval("SeqItem"."SeqName"))::text, "padding", '0') , "postfix") from "SeqItem" where "SeqName" = 'testItem1' ;
ERROR: relation "testitem1" does not exist
InventorySys=#
ERROR: relation "testitem1" does not exist
If I create a new Sequencer without the Trigger, it works fine:
InventorySys=# CREATE SEQUENCE test1;
CREATE SEQUENCE
InventorySys=# SELECT NEXTVAL ('test1');
nextval
---------
1
(1 row)
InventorySys=#
And if I add that sequencer to my query, it works fine:
InventorySys=# select CONCAT("prefix", LPAD((select nextval('test1'))::text, "padding", '0') , "postfix") from "SeqItem" where "SeqName" = 'testItem1' ;
concat
------------
115-000002
(1 row)
InventorySys=#
Both sequencers look fine to me, but the one created by the Trigger I cannot get to work...
InventorySys=# SELECT * FROM information_schema.sequences;
sequence_catalog | sequence_schema | sequence_name | data_type | numeric_precision | numeric_precision_radix | numeric_scale | start_value | minimum_value | maximum_value | increment | cycle_option
------------------+-----------------+---------------+-----------+-------------------+-------------------------+---------------+-------------+---------------+---------------------+-----------+--------------
InventorySys | public | testItem1 | bigint | 64 | 2 | 0 | 1 | 1 | 9223372036854775807 | 1 | NO
InventorySys | public | test1 | bigint | 64 | 2 | 0 | 1 | 1 | 9223372036854775807 | 1 | NO
(2 rows)
InventorySys=#
Any help would be greatly appreciated!
Ok, I think I figured out my problem. It appears that the sequencer name needs to be all lower case? Or, I should say that if I use all lower case it works just fine...
InventorySys=# INSERT into "SeqItem" ("SeqName", prefix, padding) Values ('testitem3', '110-',4);
INSERT 0 1
InventorySys=# select CONCAT("prefix", LPAD((select nextval("SeqItem"."SeqName"))::text, "padding", '0') , "postfix") from "SeqItem" where "SeqName" = 'testitem3' ;
concat
----------
110-0001
(1 row)
InventorySys=# select CONCAT("prefix", LPAD((select nextval("SeqItem"."SeqName"))::text, "padding", '0') , "postfix") from "SeqItem" where "SeqName" = 'testitem3' ;
concat
----------
110-0002
(1 row)
InventorySys=#
I'm not sure why it will not accept upper and lower case characters...

Transactions confirmation received , but table remains empty

In my application, i write transactions to post gres schema prod.
In order to debug, I have using the psql command line client on OSX
My table the only fields I have to fill are the are message field (json blob) and and status field (text).
Here is what the schema looks like
Table "prod.suggestions"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
------------------+--------------------------+-----------+----------+--------------------+----------+--------------+-------------
id | uuid | | not null | uuid_generate_v4() | plain | |
message | jsonb | | not null | | extended | |
status | text | | not null | | extended | |
transaction_hash | text | | | | extended | |
created_at | timestamp with time zone | | | CURRENT_TIMESTAMP | plain | |
updated_at | timestamp with time zone | | | CURRENT_TIMESTAMP | plain | |
Indexes:
"suggestions_pkey" PRIMARY KEY, btree (id)
Triggers:
update_updated_at_on_prod_suggestions BEFORE UPDATE ON prod.suggestions FOR EACH ROW EXECUTE PROCEDURE update_updated_at()
here is the function the trigger executes:
create function update_updated_at()
returns trigger
as
$body$
begin
new.updated_at = current_timestamp;
return new;
end;
$body$
language plpgsql;
Here is query to write the message:
INSERT INTO prod.suggestions (message, status) VALUES ('{"name": "Paint house", "tags": ["Improvements", "Office"], "finished": true}' , 'rcvd');
It returns INSERT 0 1 which I assume is a sucesss.
however when i query the table, it doesnt return anything.
select * from prod.suggestions;
I will appreciate any pointers on this.
This had nothing to do with postgres. I have another workers thread that was deleting all the data from the table.

simple Postgres Trigger function on update not triggering

Probably something dumb, but I just can't make it work.
I have this table:
Table "public.office"
Column | Type | Collation | Nullable | Default
-------------+--------------------------+-----------+----------+-------------------------------
id | integer | | not null | generated by default as identity
name | text | | not null |
url | text | | |
domain | text | | |
...
Triggers:
updated_save_domain_from_url AFTER UPDATE OF url ON office FOR EACH ROW EXECUTE PROCEDURE save_domain_from_url()
And this trigger function:
-- TRIGGER FUNCTION
CREATE OR REPLACE FUNCTION public.save_domain_from_url()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
BEGIN
new.domain := substring(new.url from '(?:.*://)?(?:www\.)?([^/]*)');
RETURN new;
END;
$function$
Example row:
id | name | url | domain
-----+---------------------------------+--------+--------
425 | Van Halewyck & Marco Architects | [NULL] | [NULL]
When I update the URL:
update office set url = 'http://vanhalewyck-marco.com/en' where id = 425;
The domain is still null, trigger did not work:
id | name | url | domain
-----+---------------------------------+---------------------------------+--------
425 | Van Halewyck & Marco Architects | http://vanhalewyck-marco.com/en | [NULL]
Any hints of what might be going on, please help!!
Thanks
You need to have the trigger defined as BEFORE trigger. AFTER triggers (as yours) cannot alter the new record as they are executed after the record is inserted.
Best regards,
Bjarni

Trigger to update a count number based on another table records

I have these two tables Review & Listing
| listing_id | review_id | comment |
|------------|----------------------------|--------------------|
| 5629709 | 123 | Beautiful |
| 4156372 | 231 | Wonderful |
| 4156372 | 432 | Very Good |
| 4156372 | 649 | Excellent |
| listing_id | number_of_reviews |
|------------|----------------------------|
| 5629709 | 1 |
| 4156372 | 2 |
Is there a way to create an trigger function that when the Review table has an update (insert or delete) then the number_of_reviews column in the Listing table updates also(+1 or -1)?
CREATE OR REPLACE FUNCTION function_number_of_reviews() RETURNS TRIGGER AS
$BODY$
BEGIN
if TG_OP='INSERT' then
Update public."Listing" set number_of_reviews = number_of_reviews + 1 where id = new.listing_id;
end if;
if TG_OP='DELETE' then
Update public."Listing" set number_of_reviews = number_of_reviews - 1 where id = old.listing_id;
end if;
RETURN new;
END;
$BODY$
language plpgsql;
CREATE TRIGGER trig_number_of_reviews
AFTER INSERT OR DELETE ON public."Review"
FOR EACH ROW
EXECUTE PROCEDURE function_number_of_reviews();
This is the wright way
Materializing values that can be calculated from other values bears a severe risk for inconsistencies. If possible avoid that.
In your case drop the number_of_reviews column in listing and create a view calculating the numbers for that column instead.
CREATE VIEW listing_with_number_of_reviews
AS
SELECT l.listing_id,
count(r.review_id) number_of_reviews
FROM listing l
LEFT JOIN review r
ON r.listing_id = l.listing_id
GROUP BY l.listing_id;