Conditional function not working as intended postgresql - sql

So I got this table on postgresql that people regularly update and the below function.
table
id integer
status integer
date date
on_hold boolean
What this function is supposed to do is to fill out the date column automatically whenever the status becomes 50 and also if it was null
Problem is that i do not want the date column to be filled when the on_hold boolean column is true.
I've tried setting up the function just by typing on_hold = true but then it somehow says it doesn't exist. When i use old. or new. it doesn't pass any error but it still updates the date.
How to get to the intended result which is to not update the date when on_hold is true
CREATE OR REPLACE FUNCTION table_update()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
BEGIN
if new.status = 50
and new.date is null
and (new.on_hold = false or old.on_hold = false)
then new.date = now() + interval '90' day ;
end if;
RETURN NEW;
END;
$function$
;
~~~

Just change the condition to
and old.on_hold != true -- != true include false *AND NULL*
and maybe (if you do not want to change anything if status is 50 and becomes 50 again) add:
(new.status = 50 and old.status != 50)
Full function:
demo:db<>fiddle
CREATE OR REPLACE FUNCTION table_update()
RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
if (new.status = 50 and old.status != 50)
and new.the_date is null
and old.on_hold != true
then
new.the_date = now() + interval '90' day;
end if;
RETURN NEW;
END;
$$;

Related

Postgresql column reference is ambiguous

I want to call my function but I get this error:
ERROR: column reference "list" is ambiguous LINE 3: SET
list = ARRAY_APPEND(list, input_list2),
the error is on the second list inside array_append function.
My function:
CREATE OR REPLACE FUNCTION update_order(input_id uuid,input_sku text,input_store_id uuid,input_order_date bigint,input_asin text,input_amount int,input_list text[],input_price real,input_list2 text) RETURNS void LANGUAGE plpgsql AS
$body$
#variable_conflict use_column
BEGIN
INSERT INTO orders_summary(id,sku,store_id,order_date,asin,amount,list,price)
VALUES(input_id,input_sku,input_store_id,to_timestamp(input_order_date / 1000.0),input_asin,input_amount,input_list,input_price) ON CONFLICT(sku,order_date) DO UPDATE
SET list = ARRAY_APPEND(list, input_list2),
amount = amount + input_amount,
price = input_price
WHERE NOT list #> input_list;
END
$body$;
You have to use the alias name in the insert query because list has two references, one reference in EXCLUDED.list and another reference to the column for an update statement.
Please check the below query (I append the alias with name os in query):
CREATE OR REPLACE FUNCTION update_order(input_id uuid,input_sku text,input_store_id uuid,input_order_date bigint,input_asin text,input_amount int,input_list text[],input_price real,input_list2 text) RETURNS void LANGUAGE plpgsql AS
$body$
#variable_conflict use_column
BEGIN
INSERT INTO orders_summary as os (id,sku,store_id,order_date,asin,amount,list,price)
VALUES(input_id,input_sku,input_store_id,to_timestamp(input_order_date / 1000.0),input_asin,input_amount,input_list,input_price) ON CONFLICT(sku,order_date) DO UPDATE
SET list = ARRAY_APPEND(os.list, input_list2),
amount = os.amount + input_amount,
price = input_price
WHERE NOT os.list #> input_list;
END
$body$;
Or you can use table name:
CREATE OR REPLACE FUNCTION update_order(input_id uuid,input_sku text,input_store_id uuid,input_order_date bigint,input_asin text,input_amount int,input_list text[],input_price real,input_list2 text) RETURNS void LANGUAGE plpgsql AS
$body$
#variable_conflict use_column
BEGIN
INSERT INTO orders_summary (id,sku,store_id,order_date,asin,amount,list,price)
VALUES(input_id,input_sku,input_store_id,to_timestamp(input_order_date / 1000.0),input_asin,input_amount,input_list,input_price) ON CONFLICT(sku,order_date) DO UPDATE
SET list = ARRAY_APPEND(orders_summary.list, input_list2),
amount = orders_summary.amount + input_amount,
price = input_price
WHERE NOT orders_summary.list #> input_list;
END
$body$;

In PostgreSQL what does the CREATE AGGREGATE option SORTOP do?

From the Postgres documentation (https://www.postgresql.org/docs/9.6/sql-createaggregate.html) I find it hard to deduce what the parameter SORTOP does.
Is this option only applicable to an ordered-set aggregate?
Concretely I'm trying to create an aggregate function that finds the most frequent number in a column of numbers. I thought specifying the SORTOP option would sort the data before executing my self defined aggregate function, but this doesn't seem to be the case.
Here is my current implementation that only works when the input data is sorted.
It loops over the rows and keeps track of the largest sequence of previous numbers (largfreq variables in state) and the amount of repetitions seen so far of the number that it's currently on (currfreq variables in state).
CREATE TYPE largfreq_state AS (
largfreq_val INT,
largfreq INT,
currfreq_val INT,
currfreq INT
);
CREATE FUNCTION slargfreq(state largfreq_state, x INT) RETURNS largfreq_state AS $$
BEGIN
if state.currfreq_val <> x then
if state.currfreq >= state.largfreq then
state.largfreq = state.currfreq;
state.largfreq_val = state.currfreq_val;
end if;
state.currfreq = 1;
state.currfreq_val = x;
else
state.currfreq = state.currfreq + 1;
end if;
return state;
END;
$$ language plpgsql;
CREATE FUNCTION flargfreq(state largfreq_state) RETURNS INT AS $$
BEGIN
if state.currfreq >= state.largfreq then
return state.currfreq_val;
else
return state.largfreq_val;
end if;
END;
$$ language plpgsql;
CREATE AGGREGATE largfreq(INT) (
SFUNC = slargfreq,
STYPE = largfreq_state,
FINALFUNC = flargfreq,
INITCOND = '(0, 0, 0, 0)',
SORTOP = <
);
This is well explained in the documentation:
Aggregates that behave like MIN or MAX can sometimes be optimized by looking into an index instead of scanning every input row. If this aggregate can be so optimized, indicate it by specifying a sort operator. The basic requirement is that the aggregate must yield the first element in the sort ordering induced by the operator; in other words:
SELECT agg(col) FROM tab;
must be equivalent to:
SELECT col FROM tab ORDER BY col USING sortop LIMIT 1;
So you need that for aggregates that can be calculated using an index scan.

Problem in calling postgres function from .net

I have a function of day closing in postgresql
CREATE OR REPLACE FUNCTION transactions.start_eod_operation(
value_date_ date,
user_id_ integer)
RETURNS void
LANGUAGE 'plpgsql'
COST 100
VOLATILE
AS $BODY$
BEGIN
IF NOT EXISTS(SELECT * FROM core.day_operation WHERE is_eod_completed = false AND value_date = value_date_) THEN
RAISE EXCEPTION 'BOD for this date was never started.';
END IF;
IF EXISTS(SELECT * FROM transactions.transactions WHERE verification_status_id = 0) THEN
RAISE EXCEPTION 'Voucher(s) pending for verification, cannot perform EOD!';
END IF;
DELETE FROM history.balance_holder WHERE value_date::date = value_date_;
INSERT INTO history.balance_holder(office_id, account_number_id, value_date, balance, minimum_balance, audit_user_id)
SELECT 1, t.account_number_id, value_date_, SUM(COALESCE(t.credit,'0') - COALESCE(t.debit, '0')), p.minimum_balance, user_id_
FROM deposit.transaction_view t
INNER JOIN deposit.account_holders a ON a.account_number_id = t.account_number_id
INNER JOIN core.deposit_products p ON a.deposit_product_id = p.deposit_product_id
WHERE t.value_date <= value_date_
GROUP BY t.account_number_id, p.minimum_balance;
PERFORM deposit.post_interest_transit(value_date_, user_id_);
PERFORM deposit.auto_transfer_interest(value_date_, user_id_);
PERFORM deposit.auto_transfer_fd($1, $2);
PERFORM loan.post_interest_transit(value_date_);
UPDATE core.day_operation SET is_eod_completed = true, eod_started_on=NOW(), eod_user_id = user_id_ WHERE value_date = value_date_;
END
$BODY$;
When user begin day close process from software
PERFORM deposit.post_interest_transit(value_date_, user_id_); this function or command in this function run twice a day not every day when there is problem in connection or server computer performance or in normal condition too. Inside this
PERFORM deposit.post_interest_transit(value_date_, user_id_); function there is delete command if there is data exist in that day and only begin insert command but it inserting 2 times in table. There is not any problem if I ran this function directly from database twice or more. What is the solution is this postgres bug or web browser bug ?

postgres: using ANY on array of timestamps

I have a Postgres function where I need to check whether or not a particular value is in an array of timestamps. Here's the function:
CREATE OR REPLACE FUNCTION public.get_appointments(
for_business_id INTEGER,
range_start DATE,
range_end DATE,
for_staff_id INTEGER
)
RETURNS SETOF appointment
LANGUAGE plpgsql STABLE
AS $function$
DECLARE
appointment appointment;
recurrence TIMESTAMP;
appointment_length INTERVAL;
parent_id UUID;
BEGIN
FOR appointment IN
SELECT *
FROM appointment
WHERE business_id = for_business_id
AND (
recurrence_pattern IS NOT NULL
OR (
recurrence_pattern IS NULL
AND starts_at BETWEEN range_start AND range_end
)
)
LOOP
IF appointment.recurrence_pattern IS NULL THEN
RETURN NEXT appointment;
CONTINUE;
END IF;
appointment_length := appointment.ends_at - appointment.starts_at;
parent_id := appointment.id;
FOR recurrence IN
SELECT *
FROM generate_recurrences(
appointment.recurrence_pattern,
appointment.starts_at,
range_start,
range_end
)
LOOP
EXIT WHEN recurrence::date > range_end;
-- THIS IS THE LINE IN QUESTION
CONTINUE WHEN recurrence::date < range_start OR recurrence = ANY(appointment.recurrence_exceptions);
appointment.id := uuid_generate_v5(uuid_nil(), parent_id::varchar || recurrence::varchar);
appointment.parent_id := parent_id;
appointment.starts_at := recurrence;
appointment.ends_at := recurrence + appointment_length;
appointment.recurrence_pattern := appointment.recurrence_pattern;
appointment.recurrence_exceptions := NULL;
appointment.is_recurrence := true;
RETURN NEXT appointment;
END LOOP;
END LOOP;
RETURN;
END;
$function$;
You'll see that just after the second LOOP statement, there's a CONTINUE statement. I want to skip that iteration of the loop if the recurrence (this is a timestamp, but in text format) variable is either out of range OR if it's listed as part of the appointment's recurrence_exceptions array. The recurrence_exceptions array contains timestamps.
The idea is that if the appointment is listed as an exception, it is not returned. Unfortunately, no matter what I do, it seems that the ANY operator isn't working as expected. To test this, I took one of the values from the recurrence_exception array and changed the CONTINUE statement to:
CONTINUE WHEN recurrence::date < range_start OR recurrence = '2016-09-20 18:07:26';
This did not return that recurrence as expected.
Am I using this properly?
Thank you!

Calling postgres function with multiple params

This is my sql function in postgresql:
FUNCTION test(year integer)
RETURNS SETOF json AS
$BODY$
SELECT ARRAY_TO_JSON(ARRAY_AGG(T))
FROM table t
WHERE year = $1;
$BODY$
This works quite good. But now I want specify more parameters and
I'd like to get a return with the condition if parameters are set. For example following function call:
test(year := 2014, location := 'Belo Horizonte')
How should the function look like and where to set conditions? Here is my (wrong) suggestion:
FUNCTION test(year integer, location text)
RETURNS SETOF json AS
$BODY$
SELECT ARRAY_TO_JSON(ARRAY_AGG(T))
FROM table t
IF $1 IS SET THEN
WHERE year = $1
ELSIF $2 THEN
UNION
WHERE location = $2
END IF;
$BODY$
A further challenge is a return of the function for this statements:
test(year := 1584)
-- should return all entries with year 1584
test(location := 'Cambridge')
-- should return all entries with location Cambridge
test(year := 1584, location := 'Cambridge')
-- should return all entries with year 2014 AND location Belo Horizonte
Thanks in advance!
You may try to do something like that, adding default values, and working with OR clauses
FUNCTION test(year integer DEFAULT -1, location text DEFAULT 'noLocation')
RETURNS SETOF json AS
$BODY$
SELECT ARRAY_TO_JSON(ARRAY_AGG(T))
FROM table t
WHERE ($1 = -1 OR year = $1)
AND ($2 = 'noLocation' OR location = $2);
$BODY$