Is there a way for postgres to automatically generate child records with set parameters? I'm basically trying to create an employee timesheet and each time a new timesheet for a given date is created, I'd like to create a 7 child records (one record for each day of that given week for the user to fill in)
Something like this:
date (automatically generated on a weekly basis) | hours | timesheet_id(FK) | project_id(FK)
2019-01-01 8 1 2
2019-01-02 10 1 2
2019-01-03 8 1 2
2019-01-04 8 1 2
2019-01-05 0 1 2
2019-01-06 0 1 2
2019-01-07 9 1 2
#Z4-tier is correct but the trigger function does not need a loop as it can be reduced to a single insert statement.
create or replace function create_timesheet_days()
returns trigger
language 'plpgsql'
as $$
begin
insert into timesheet_days(timesheet_id, week_day)
select new.timesheet_id, wk_day
from generate_series (new.timesheet_date, new.timesheet_date+interval '6 day', interval '1 day') wk_day;
return new;
end;
$$ ;
as mentioned in the comments, this is a textbook case for using triggers.
You'll need a procedure that handles creating the new records. This might work:
create or replace function create_timesheet_days()
returns trigger as $BODY$
declare counter INTEGER := 0;
begin
while counter < 6 loop
insert into my_schema.timesheet_week
(timesheet_date, hrs, timesheet_id, project_id)
select
NEW.timesheet_date + counter as timesheet_date,
0 as hrs,
NEW.timesheet_id as timesheet_id,
project.project_id as project_id
from my_schema.project;
counter := counter + 1;
end loop;
return NEW;
END;
$BODY$
language 'plpgsql';
Then you could create a trigger like this:
CREATE TRIGGER create_timsheet_days_trigger
BEFORE INSERT
ON my_schema.timesheet_week
FOR EACH ROW
EXECUTE PROCEDURE
create_timesheet_days();
Related
I have a database of orders and each order has a time_slot (of type TIME).
select id, time_slot from orders limit 5;
10 | 13:00:00
11 | 12:00:00
13 | 11:00:00
14 | 12:30:00
15 | 11:30:00
I want to make sure that only a certain number of orders can be placed in a time slot; for example, let's say I only want 8 orders per time slot.
I am using Supabase, so I would like to implement using RLS policies.
I've been through several iterations, but none of them have worked. Some complain of infinite recursion. My current approach is the following: I have created a view of time slot load.
create or replace view time_slot_load as
select time_slot, count(*)
from orders
group by time_slot;
select * from time_slot_load limit 5;
11:00:00 | 1
12:30:00 | 1
11:30:00 | 1
13:00:00 | 1
12:00:00 | 7
I can then create a policy that checks against this view.
ALTER POLICY "Only 8 orders per time slot"
ON public.orders
WITH CHECK (
(SELECT (load.count <= 8)
FROM time_slot_load load
WHERE (load.time_slot = orders.time_slot))
);
But this is not working.
Is there some way I can do this using constraints or RLS policies?
Any help is appreciated.
demo
table structure:
begin;
create table orders_count(
time_slot tstzrange ,
order_count integer default 0,
constraint order_ct_max1000 check(order_count <=4));
create table
orders(orderid int primary key, realtimestamp timestamptz , order_info text);
with a as(
SELECT x as begin_,x + interval '1 hour' as end_
FROM generate_series(
timestamp '2022-01-01',
timestamp '2022-01-02',
interval '60 min') t(x))
insert into orders_count(time_slot)
select tstzrange(a.begin_, a.end_,'[]') from a;
commit;
two table, one table for all the orders info, another table is track the timeslot's count, also make one constraint, within one timeslot no more than 4 orderid.
Then create trigger for delete/update/insert action on table orders.
for table orders_count, 20 years only 175200 rows (one hour one row).
main trigger function:
begin;
create or replace function f_trigger_orders_in()
returns trigger as
$$
begin
update orders_count set order_count = order_count + 1
where time_slot #> NEW.realtimestamp;
return null;
end;
$$ language plpgsql;
create or replace function f_trigger_orders_up()
returns trigger as
$$
begin
if OLD.realtimestamp <> NEW.realtimestamp THEN
update orders_count
set order_count = order_count -1
where time_slot #> OLD.realtimestamp;
update orders_count
set order_count = order_count + 1
where time_slot #> NEW.realtimestamp;
end if;
return null;
end
$$ language plpgsql;
create or replace function f_trigger_order_delaft()
returns trigger as
$$
BEGIN
update orders_count set order_count = order_count -1 where time_slot #> OLD.realtimestamp;
return null;
end;
$$
language plpgsql;
triggers action:
create trigger trigger_in_orders
AFTER INSERT ON public.orders FOR EACH ROW execute procedure f_trigger_orders_in();
create trigger trigger_up_orders
after update on public.orders for each row execute procedure f_trigger_orders_up();
create trigger trigger_del_orders
AFTER DELETE ON public.orders FOR EACH ROW execute procedure f_trigger_order_delaft();
I want to make sure that only a certain number of orders can be placed
in a time slot; for example, let's say I only want 8 orders per time
slot.
You cannot do that in PostgreSQL.
https://www.postgresql.org/docs/current/sql-createpolicy.html
check_expression:
Any SQL conditional expression (returning boolean). The conditional
expression cannot contain any aggregate or window functions. This
expression will be used in INSERT and UPDATE queries against the table
if row-level security is enabled. Only rows for which the expression
evaluates to true will be allowed. An error will be thrown if the
expression evaluates to false or null for any of the records inserted
or any of the records that result from the update. Note that the
check_expression is evaluated against the proposed new contents of the
row, not the original contents.
Why view won't work:
https://www.postgresql.org/docs/current/sql-createview.html
see Updatable Views section:
A view is automatically updatable if it satisfies all of the following
conditions:
The view's select list must not contain any aggregates, window functions or set-returning functions.
I am fairly new at SQL and have not incorporated a Loop into a SQL statement previously. This SQL query from elan.elig returns data as shown in the grid below.
select (extract(year from age(case when terminationdate is null then
CURRENT_DATE else terminationdate END ,effectivedate ))) *12 +
(extract(month from age(case when terminationdate is null then
CURRENT_DATE else terminationdate END ,effectivedate )) +1)
as "mbrmonths" ,effectivedate
from elan.elig
Mbr Months Effective Date
1 10/1/2018
10 11/1/2018
2 11/1/2018
8 11/1/2018
8 11/1/2018
8 11/1/2018
2 11/1/2018
2 11/1/2018
7 11/1/2018
For each row from the query I need to execute the subsequent LOOP that spreads the memberMonth counts into the Year/Month buckets. The following Do LOOP does exactly this. I have been trying for some time now to determine how to incorporate the Loop into the SQL statement so that for each row read, it will pass the two variables and execute the Loop and then read the next row and continue on..
DO $$
declare
nbr_mem_months integer=5;
effectivedate date ='20190401';
ym char(6) =to_char(effectivedate,'YYYYMM');
begin
for r in 1..nbr_mem_months loop
update elan.pmpm set mbrmonths=mbrmonths+1 where yyyyymm=ym;
effectivedate=effectivedate + interval '1 month';
ym=to_char(effectivedate,'YYYYMM');
end loop;
end;
$$;
PMPM Buckets
yyyymm mbrmonths
201901 0
201902 0
201903 0
201904 1
201905 1
201906 1
201907 1
201908 1
201909 0
201910 0
201911 0
CREATE FUNCTION "UpdatePMPM"() RETURNS boolean
LANGUAGE plpgsql
AS
$$
DECLARE
nbr_mem_months NUMERIC;
effectivedate date;
ym char(6);
BEGIN
LOOP
ym=to_char(effectivedate,'YYYYMM');
nbr_mem_months=5;
UPDATE elan.pmpm set mbrmonths=mbrmonths+1 where yyyyymm=ym;
effectivedate=effectivedate + interval '1 month';
END LOOP;
RETURN TRUE;
END
$$;
*Response from the Select statement:
ERROR: function public.UpdatePMPM(integer, date, text) does not exist
Select public."UpdatePMPM"(5,cast('20190101' as date),cast('...
^
HINT: No function matches the given name and argument types.
The issue is calling the function with arguments but not specifying any when creating the function. So you need something like(not tested):
CREATE FUNCTION "UpdatePMPM"(nbr_mem_months integer, effectivdate date, some_arg varchar) RETURNS void
LANGUAGE plpgsql
AS
$$
DECLARE
ym varchar := to_char(effectivedate,'YYYYMM');
BEGIN
FOR r IN 1..nbr_mem_months LOOP
UPDATE elan.pmpm set mbrmonths = mbrmonths+1 where yyyyymm = ym;
effectivedate = effectivedate + interval '1 month';
ym=to_char(effectivedate,'YYYYMM');
END LOOP;
RETURN;
END
$$;
From the error it is not clear what the third argument is supposed to be, so that will clarification from you.
If I have a table something like:
MyTable
id date value replaced_by_id
1 2020-01-01 10
2 2020-01-02 20 3
3 2020-01-02 21
With a unique constraint on date and replaced_by
e.g.
CREATE UNIQUE INDEX my_table_null_test ON my_table (date, (replaced_by_id is null))
WHERE replaced_by_id IS NULL;
How can I create an insert statement that sets the conflicting row's replaced_by_id to the the new rows id and then inserts the new row afterwards?
Along the lines of
insert into my_table (id, date, value) values (gen_id(), '2020-01-02', 21)
on conflict (date, (replaced_by_id is null) ) where replaced_by_id is null
do update
set replaced_by_id = excluded.id
**now insert the new row (insert the excluded row)**
for say the file the values are coming from a file that had many values for the same date. e.g.
date value
2020-01-01 10
2020-01-02 20
2020-01-02 21
2020-01-02 22
2020-01-02 23
2020-01-02 24
2020-01-02 22
would result in
MyTable
id date value replaced_by_id
1 2020-01-01 10
2 2020-01-02 20 3
3 2020-01-02 21 4
4 2020-01-02 22 5
5 2020-01-02 23 6
6 2020-01-02 24 7
7 2020-01-02 22
I think you can not use UPSERT for this requirement because UPSERT will perform only update operation on same table or DO Nothing.
Reason is:
You have added unique index which will prevent the insertion with combination of existing date and null of replace_by_id field.
You are trying to get the ID to update the existing records before insertion which is not possible.
So you have to use some workaround for it:
Using Triggers - You have to use 2 triggers on your table one is before insert and one is after insert like below:
Before Insert Trigger
Trigger Function
CREATE FUNCTION trig_beforeinsert()
RETURNS trigger
LANGUAGE 'plpgsql'
AS $BODY$
declare
flg int :=0;
begin
flg=(select count(*) from my_table where date_=new.date_ and replaced_by_id is null);
if flg>0 then
new.replaced_by_id=0;
end if;
return new;
end;
$BODY$;
Trigger on Table
CREATE TRIGGER my_table_before_insert
BEFORE INSERT
ON my_table
FOR EACH ROW
EXECUTE PROCEDURE trig_beforeinsert();
After Insert Trigger
Trigger Function
CREATE FUNCTION trig_afterinsert()
RETURNS trigger
LANGUAGE 'plpgsql'
COST 100
VOLATILE NOT LEAKPROOF
AS $BODY$
declare
flg int :=0;
begin
if new.replaced_by_id = 0 then
UPDATE my_table set replaced_by_id=new.id where date_=new.date_ and replaced_by_id is null;
UPDATE my_table set replaced_by_id=null where id=new.id;
end if;
return new;
end;
$BODY$;
Trigger on Table
CREATE TRIGGER my_table_after_insert
AFTER INSERT
ON public.my_table
FOR EACH ROW
EXECUTE PROCEDURE public.trig_afterinsert();
DEMO
I have two tables as below.
TABLE 1 TABLE 2
-------- --------
id id
date table1_id
total subtotal
balance
table 1 table 2
-------- ---------
id total balance id table1_id subtotal paid
1 20 10 1 1 5 5
2 30 30 2 1 15 5
3 2 10 0
4 2 10 0
5 2 10 0
I have to add paid column in table2. so can anyone help me to add values to newly added column for existing data. I tried to wrote procedure as below but as postgres will not allow if in for loop so am unable to do it.
CREATE OR REPLACE FUNCTION public.add_amountreceived_inbillitem() RETURNS void AS
$BODY$
DECLARE
rec RECORD;
inner_rec RECORD;
distributebalance numeric;
tempvar numeric;
BEGIN
FOR rec IN select * from table1
LOOP
distributebalance = rec.balance;
FOR inner_rec IN(select * from table2 where table1_id = rec.id order by id limit 1)
tempvar = distributebalance - inner_rec.subtotal;
if (distributebalance >0 and tempvar>=0) THEN
update table2 set paid = inner_rec.subtotal where id = inner_rec.id ;
distributebalance =distributebalance-inner_rec.subtotal;
else if( distributebalance >0 and tempvar<0 )THEN
update table2 set paid = distributebalance where id = inner_rec.id;
END IF;
END LOOP;
END LOOP;
END; $BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
Thanks In advance :)
Postgres does allow for IF statements in loops.
The issue is that by writing ELSE IF you've started a new IF statement, so you now have 2 opening IFs but only one closing END IF. A "proper" elseif in plpgsql is ELSIF or ELSEIF. So just delete the space between those words and it should work.
I have a schedule table, like so:
ScheduleId::uuid | Start::timestamptz(now()) | SlotSize::int(minutes) | Interval::int(days)
and a slot table like so:
SlotId::uuid | ScheduleId::uuid | Start::timestamptz | End::timestamptz
I want to automatically insert slots, based on a trigger on the schedule table.
So far I have:
create
trigger create_slots after insert
on
schedule for each row execute procedure create_new_slots();
create or replace function create_new_slots()
returns trigger
language plpgsql
as $function$
begin
-- in a loop determine how many slots there are, then insert each one
insert into slot
select
uuid_generate_v4(),
new."ScheduleId",
start, -- need to determine the start time of each instance of slot
end -- need to determine the end time of each instance of slot
-- end loop
end return new;
end $function$
I need to somehow put this into a cursor and calculate the number of slots and the start and end times each slot.
I am using PostgreSQL 10
Any help is appreciated!
OK so what I needed, it seems, was to generate a series based on the [Start] time (rounded to the hour), the end time (which is the [Start] time + the [PlanningHorizon] in days) and the [SlotSize]. Then loop through this series and insert each time slot into my [Slot] table:
CREATE OR REPLACE FUNCTION create_new_slots()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
declare
slot timestamptz;
begin
for slot in select generate_series(
date_trunc('hour', new."Start")::timestamptz,
(new."Start" + interval '1' day * new."PlanningHorizon")::timestamptz,
new."SlotSize" * '1 minutes'::interval)
loop
insert into slot select uuid_generate_v4(), new."ScheduleId", slot, slot + new."SlotSize" * '1 minutes'::interval;
end loop;
return NEW;
end
$function$