PostgreSQL unable to INSERT INTO - sql

I want to reflect info from one table into another one. Ideally, I would need to use a function (will be upgrading afterwards). I have a first table called tb_user, with data from users (simple user names such as U0001, U0002....).
I was doing the following:
CREATE TABLE tb_user_statshot (
user_code CHARACTER(5) NOT NULL
);
CREATE OR REPLACE FUNCTION sp_calculatet() RETURNS void AS
$$
DECLARE
BEGIN
INSERT INTO tb_user_statshot (user_code)
SELECT user_code FROM tb_user;
END;
$$ language 'plpgsql';
I expect the users code to reflect in my new statshot table, but this is no happening. Does anyone know why?
Thank you!

Related

I'm trying ro make this function and trigger work:

I have a table with users where there is a colum with phone numbers and another colum with an ID and another table with calls where there are phones and the ID of the user who called (if users call will have the same id for the two tables). Now I need to do a function and a trigger that updates the phone number from the table users when the ID of the user who called is the same ID from a user who is in the table users.
The problem is that when I insert values into the table calls (with the same IDs from users) the phone number doesnt update.
My function and my trigger:
--Function:
CREATE OR REPLACE FUNCTION validate_phones() RETURNS trigger AS $$
BEGIN
UPDATE matchdotcom.user
SET matchdotcom.user.phonenumber =
(SELECT NEW.v.phone_number
FROM matchdotcom.calls v, matchdotcom.user u )
WHERE NEW.calls.user_id = matchdotcom.user.iduser
;
RETURN update_phone;
END;
$$ LANGUAGE plpgsql;
--Trigger:
CREATE TRIGGER update_phone
BEFORE INSERT OR UPDATE ON matchdotcom.calls
FOR EACH ROW EXECUTE PROCEDURE validate_phones();
Thank you for helping me with this :)

Reverse a column in postgres

So I have a table in SQL server that is defined as such
create table test(value varchar(200), Reverse(value) as valueReverse);
now when I insert something in this table lets say I insert the string hello, it will store the value in the table as such.
value | valueReverse
--------------------
hello | olleh
I am trying to convert the table into PostgreSQL however the reverse() function is not working and it's giving me errors. What is the correct way to create this table in postgres?
For PostgreSQL 12 and above
If you are using Postgres 12 or higher then you can use GENERATED ALWAYS AS for the column valueReverse like below: Manual
create table test(value varchar(200),
valueReverse varchar(200) generated always as (reverse(value)) STORED );
DEMO
For PostgreSQL 11 or below
For earlier version you can use Triggers like below.
Creating Trigger Function
create or replace function trig_reverse() returns trigger as
$$
begin
new.valueReverse=reverse(new.value);
return new;
end;
$$
language plpgsql
Creating Trigger
create trigger trig_rev
before insert or update on test
for each row
execute procedure trig_reverse();
DEMO
Do not store string twice (redundantly). It will be much cleaner and cheaper overall to store it once and produce the reverted copy on the fly. You can use a VIEW if you need a drop-in replacement for your table:
CREATE TABLE base_test(value varchar(200));
INSERT INTO base_test VALUES ('hello');
CREATE VIEW test AS
SELECT *, reverse(value) AS value_reverse
FROM base_test;
db<>fiddle here
Related:
Computed / calculated / virtual / derived columns in PostgreSQL

Setting local config in SQL before INSERT

newbie in SQL coming from a JS world needing some advice on triggers.
My user has an id column and my GraphQL API always calls INSERT INTO .... RETURNING * and then doing the transforms on the GraphQL layer to return what I want.
The goal is to allow a query like INSERT INTO .... RETURNING * work with RLS in place.
The policies are:
CREATE POLICY USER_SELECT_RESTRICTIONS
ON "user"
FOR SELECT
USING ("id" = current_user_id() OR current_user_role() = 'admin' OR current_user_role() = 'partner');
CREATE POLICY USER_INSERT_RESTRICTIONS
ON "user"
FOR INSERT
WITH CHECK (true);
This breaks for guest users (more context at the bottom) because they are allowed to INSERT but cannot SELECT (because of the restriction that only authorized users can select their own rows).
So I had the idea to somehow set the user setting manually in a trigger before/after insert (don't know which because I couldn't move forward due to the syntax error).
I've tried this but I get a syntax error at the NEW.id? (it just says "Syntax Error" to me)
CREATE OR REPLACE FUNCTION after_user_insert()
RETURNS TRIGGER AS $$
BEGIN
SET LOCAL jwt.claims.userId NEW.id;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER set_user_id_on_insert
AFTER INSERT ON "user"
FOR EACH ROW
EXECUTE PROCEDURE after_user_insert();
Searched around a lot and to be honest I think I may just not find anything because I am missing the correct terminology to find what I need to find.
Would appreciate not only help on what on this specific problem but also any related advice on policies for guest users that need priviliges.
Context:
These are the relevant tables and functions
CREATE TYPE "user_role" AS ENUM (
'customer',
'partner',
'admin'
);
​
CREATE TABLE "user" (
"id" uuid UNIQUE PRIMARY KEY NOT NULL DEFAULT gen_random_uuid(),
"first_name" varchar NOT NULL,
"last_name" varchar NOT NULL,
"email" text UNIQUE NOT NULL,
"role" user_role NOT NULL DEFAULT 'customer',
"created_at" timestamp NOT NULL DEFAULT NOW(),
"updated_at" timestamp NOT NULL DEFAULT NOW()
);
​
CREATE FUNCTION current_user_id() RETURNS uuid AS $$
SELECT nullif(current_setting('jwt.claims.userId', true), '')::uuid;
$$ LANGUAGE SQL stable;
​
CREATE FUNCTION current_user_role() RETURNS user_role AS $$
SELECT nullif(current_setting('jwt.claims.role', true), '')::user_role;
$$ LANGUAGE SQL stable
The RLS restricts SELECT to rows where the id column of the user table matches current_setting('jwt.claims.userId').
This was set previously by Postgraphile as seen here (https://www.graphile.org/postgraphile/security/).
One possible workaround I thought of would be this but I don't know if this would lead to some kind of vulnerabilities because of the obvious role elevation:
CREATE OR REPLACE FUNCTION after_user_insert()
RETURNS TRIGGER AS $$
BEGIN
SET LOCAL jwt.claims.role TO "customer";
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION before_user_insert()
RETURNS TRIGGER AS $$
BEGIN
SET LOCAL jwt.claims.role TO "admin";
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER before_insert
BEFORE INSERT ON "user"
FOR EACH ROW
EXECUTE PROCEDURE before_user_insert();
CREATE TRIGGER after_insert
AFTER INSERT ON "user"
FOR EACH ROW
EXECUTE PROCEDURE after_user_insert();
Your don't say what a guest is, but your approach seems wrong.
Rather than disabling your checks on a low level, which gives you a bad feeling for good reasons, you should choose one of the following approaches:
fix your policies to allow the necessary operation (perhaps by adding a permissive policy)
have certain operations performed by a SECURITY DEFINER function that belongs to a user not subject to the restrictions.
If you insist on a trigger based solution, you have to use a BEFORE trigger. Also, consider that you cannot use parameterss with a SET statement. You'd either have to use dynamic SQL or (better) use a function:
CREATE OR REPLACE FUNCTION before_user_insert() RETURNS TRIGGER AS
$$BEGIN
SELECT set_config('jwt.claims.userId', NEW.id::text, TRUE);
RETURN NEW;
END;$$ LANGUAGE plpgsql;
CREATE TRIGGER set_user_id_on_insert BEFORE INSERT ON "user"
FOR EACH ROW EXECUTE PROCEDURE before_user_insert();

PostgreSQL Create Trigger which runs a function on every insert or update of a table

I have defined two tables, scores and analyzed_avg_score, in my postgres database. I also have a function which i declaired like that:
CREATE FUNCTION updateAvgScore() RETURNS void AS $$
INSERT into analyzed_avg_score
(SELECT
user,
avg(score_value)
FROM
scores
group by user) on conflict do nothing;
$$ LANGUAGE SQL;
Now, I want to have a trigger or something similar that runs this function every time I insert or update something in score. I don't have a lot of experience with SQL, yet. So, does anyone have an idea how the trigger should look like?
CREATE TRIGGER SCORE_INSERT AFTER INSERT ON SCORE
FOR EACH ROW EXECUTE PROCEDURE updateAvgScore();
/*Have it return a trigger like this */
CREATE OR REPLACE FUNCTION updateAvgScore() RETURNS TRIGGER AS $example_table$
BEGIN
/*YOUR lOGIC HERE*/
END;
$example_table$ LANGUAGE plpgsql;

PLPGSQL Cascading Triggers?

I am trying to create a trigger, so that when ever I add a new record it adds another record in the same table. The session field will only take values between 1 and 4. So when I add a 1 in session I want it to add another record but with session 3 blocked. But the problem is that it leads to cascading triggers and it inserts itself again and again because the trigger is triggered when inserted.
I have for example a simple table:
CREATE TABLE example
(
id SERIAL PRIMARY KEY
,name VARCHAR(100) NOT NULL
,session INTEGER
,status VARCHAR(100)
);
My trigger function is:
CREATE OR REPLACE FUNCTION add_block() RETURNS TRIGGER AS $$
BEGIN
INSERT INTO example VALUES (NEW.id + 1, NEW.name, NEW.session+2, 'blocked');
RETURN NULL;
END;
$$ LANGUAGE 'plpgsql';
Trigger is:
CREATE TRIGGER add_block
AFTER INSERT OR UPDATE
ON example
FOR EACH ROW
EXECUTE PROCEDURE add_block();
I get error:
SQL statement "INSERT INTO example VALUES ( $1 +1, $2 , $3 + 2, $4)"
PL/pgSQL function "add_block" line 37 at SQL statement
This error repeats itself so many times that I can't see the top.
How would I solve this?
EDIT:
CREATE TABLE block_rules
(
id SERIAL PRIMARY KEY
,session INTEGER
,block_session INTEGER
);
This table holds the block rules. So if a new record is inserted into the EXAMPLE table with session 1 then it blocks session 3 accordingly by inserting a new record with blocked status in the same (EXAMPLE) table above (not block_rules). Same for session 2 but it blocks session 4.
The block_rules table holds the rules (or pattern) to block a session by. It holds
id | session | block_session
------------------------------
1 | 1 | 3
2 | 2 | 4
3 | 3 | 2
How would I put that in the WHEN statement of the trigger going with Erwin Branstetter's answer below?
Thanks
New answer to edited question
This trigger function adds blocked sessions according to the information in table block_rules.
I assume that the tables are linked by id - information is missing in the question.
I now assume that the block rules are general rules for all sessions alike and link by session. The trigger is only called for non-blocked sessions and inserts a matching blocked session.
Trigger function:
CREATE OR REPLACE FUNCTION add_block()
RETURNS TRIGGER AS
$BODY$
BEGIN
INSERT INTO example (name, session, status)
VALUES (NEW.name
,(SELECT block_session
FROM block_rules
WHERE session = NEW.session)
,'blocked');
RETURN NULL;
END;
$BODY$ LANGUAGE plpgsql;
Trigger:
CREATE TRIGGER add_block
AFTER INSERT -- OR UPDATE
ON example
FOR EACH ROW
WHEN (NEW.status IS DISTINCT FROM 'blocked')
EXECUTE PROCEDURE add_block();
Answer to original question
There is still room for improvement. Consider this setup:
CREATE OR REPLACE FUNCTION add_block()
RETURNS TRIGGER AS
$BODY$
BEGIN
INSERT INTO example (name, session, status)
VALUES (NEW.name, NEW.session + 2, 'blocked');
RETURN NULL;
END;
$BODY$ LANGUAGE plpgsql;
CREATE TRIGGER add_block
AFTER INSERT -- OR UPDATE
ON example
FOR EACH ROW
WHEN (NEW.session < 3)
-- WHEN (status IS DISTINCT FROM 'blocked') -- alternative guess at filter
EXECUTE PROCEDURE add_block();
Major points:
For PostgreSQL 9.0 or later you can use a WHEN condition in the trigger definition. This would be most efficient. For older versions you use the same condition inside the trigger function.
There is no need to add a column, if you can define criteria to discern auto-inserted rows. You did not tell, so I assume that only auto-inserted rows have session > 2 in my example. I added an alternative WHEN condition for status = 'blocked' as comment.
You should always provide a column list for INSERTs. If you don't, later changes to the table may have unexpected side effects!
Do not insert NEW.id + 1 in the trigger manually. This won't increment the sequence and the next INSERT will fail with a duplicate key violation.
id is a serial column, so don't do anything. The default nextval() from the sequence is inserted automatically.
Your description only mentions INSERT, yet you have a trigger AFTER INSERT OR UPDATE. I cut out the UPDATE part.
The keyword plpgsql doesn't have to be quoted.
OK so can't you just add another column, something like this:
ALTER TABLE example ADD COLUMN trig INTEGER DEFAULT 0;
CREATE OR REPLACE FUNCTION add_block() RETURNS TRIGGER AS $$
BEGIN
IF NEW.trig = 0 THEN
INSERT INTO example VALUES (NEXTVAL('example_id_seq'::regclass), NEW.name, NEW.session+2, 'blocked', 1);
END IF;
RETURN NULL;
END;
$$ LANGUAGE 'plpgsql';
it's not great, but it works :-)
CREATE OR REPLACE FUNCTION add_block() RETURNS TRIGGER AS $$
BEGIN
SET SESSION session_replication_role = replica;
INSERT INTO example VALUES (NEXTVAL('example_id_seq'::regclass), NEW.name, NEW.session+2, 'blocked');
SET SESSION session_replication_role = origin;
RETURN NULL;
END;
$$ LANGUAGE 'plpgsql';