Database functions mapped to ActiveRecord Models - postgresql

Let's suppose I have two database functions like this (pl/pgsql):
CREATE TABLE balances (rents decimal(12,2) DEFAULT 0 NOT NULL,
expenses decimal(12,2) DEFAULT 0 NOT NULL);
CREATE FUNCTION account_balance(id integer)
RETURNS balances AS $$
SELECT
SUM(CASE WHEN t.amount > 0 THEN t.amount ELSE 0 END) as rents,
SUM(CASE WHEN t.amount < 0 THEN t.amount ELSE 0 END) as expenses
FROM transactions t
WHERE t.account_id = $1
$$ language 'sql';
CREATE FUNCTION tenant_balance(id integer)
RETURNS balances AS $$
SELECT
SUM(CASE WHEN t.amount > 0 THEN t.amount ELSE 0 END) as rents,
SUM(CASE WHEN t.amount < 0 THEN t.amount ELSE 0 END) as expenses
FROM transactions t
WHERE t.tenant_id = $1
$$ language 'sql';
How could I relate to this in my ActiveRecord models (in this examples, Account and Tenant) so I can call something like Account.first.balance and it returns a valid instance of Balance?
Speaking of Balance, how should I create this model?

Related

Retutring from a function using case

Here is my postgres function:
create or replace function fail_hold_pay_count_metric()
returns table (
fail_count int,
hold_pay_count int
)
as
$$
declare
total_count int;
fail_count int;
hold_pay_count int;
required_rows_count int;
percent_count int;
begin
select count(1) as total_count,
sum(case when status = 'FAIL' then 1 else 0 end) as fail_count,
sum(case when status = 'HOLD_PAY' then 1 else 0 end) as hold_pay_count
into total_count, fail_count, hold_pay_count
from bundle where updated_at > (now() - interval '1 year');
if total_count > 10
then
required_rows_count := (select fail_count + hold_pay_count);
percent_count := (select (required_rows_count / total_count * 100));
end if;
return query (select case when (percent_count > 50) then (fail_count, hold_pay_count) else (0, 0) end);
end;
$$ language plpgsql;
I need to return (0,0) if percent_count was lower than 50. My function doesn't work. Where did I make a mistake?
You are returning a single column of a row type instead of a row of two int types.
Please try this, instead:
return query select case when percent_count > 50 then fail_count else 0 end,
case when percent_count > 50 then hold_pay_count else 0 end;

Postgres function returns ERROR of no destination

Here is my function for my DB:
create function calculate_metrics()
returns table
(
fail_count int,
hold_pay_count int
)
as
$$
declare
total_count int;
fail_count int;
hold_pay_count int;
begin
select count(1) as total_count,
sum(case when status = 'FAIL' then 1 else 0 end) as fail_count,
sum(case when status = 'HOLD_PAY' then 1 else 0 end) as hold_pay_count
from bundle where updated_at > (now() - interval '1 day');
if total_count > 0
then
return query select fail_count, hold_pay_count;
end if;
end;
$$ language plpgsql;
When I'm trying to get select calculate_metrics(), I'm getting the error:
SQL Error [42601]: ERROR: query has no destination for result data
Suggestion: If you want to discard the results of a SELECT, use PERFORM instead.
Where: PL/pgSQL function calculate_metrics() line 10 at SQL statement
The function is successfully create, and I have already insert data which consists the conditions of the requests. Where is my mistake?
Naming the column is not enough, you need to save the output into your variables
select count(1) as total_count,
sum(case when status = 'FAIL' then 1 else 0 end) as fail_count,
sum(case when status = 'HOLD_PAY' then 1 else 0 end) as hold_pay_count
INTO total_count, fail_count, hold_pay_count
from bundle where updated_at > (now() - interval '1 day');

How to UPDATE table column with call function after INSERT command in PostgreSQL?

I need your help, I have an issue with updating specific column after running insert command.
table:
CREATE SEQUENCE public.llh_type_id_seq
INCREMENT 1
MINVALUE 1
MAXVALUE 9223372036854775807
START 261
CACHE 1;
ALTER TABLE public.llh_type_id_seq
OWNER TO postgres;
CREATE TABLE public.llh_type
(id bigint NOT NULL DEFAULT nextval('llh_type_id_seq'::regclass),
identifier text,
name text)
WITH (
OIDS=FALSE);
ALTER TABLE public.llh_type
OWNER TO postgres;
function:
CREATE OR REPLACE FUNCTION public.generate_identifier(
id bigint,
prefix text)
RETURNS text AS
$BODY$
SELECT
CASE WHEN length($1::text) < 2
THEN UPPER($2 || '00000' || $1)
WHEN length($1::text) >= 2 AND length($1::text) < 3
THEN UPPER($2 || '0000' || $1)
WHEN length($1::text) >= 3 AND length($1::text) < 4
THEN UPPER($2 || '000' || $1)
WHEN length($1::text) >= 4 AND length($1::text) < 5
THEN UPPER($2 || '00' || $1)
WHEN length($1::text) >= 5 AND length($1::text) < 6
THEN UPPER($2 || '0' || $1)
ELSE
UPPER($2 || $1)
END;
$BODY$
LANGUAGE sql IMMUTABLE STRICT
COST 100;
ALTER FUNCTION public.generate_identifier(bigint, text)
OWNER TO postgres;
After this I try to call my function after inserting data:
WITH t AS(
INSERT INTO llh_type (name) values('one')
RETURNING id)
UPDATE llh_type SET identifier = generate_identifier((select id from
t),'TC') WHERE id = (select id from t);
After running this code I have message:"Query returned successfully: 0 rows affected, 12 msec execution time." But table now looks like: http://joxi.ru/nAypMQjCYBggq2
In another case I have a solution, but I am not sure that it is correct:
INSERT INTO llh_type (name) values('one');
UPDATE llh_type SET identifier = generate_identifier((select id from
llh_type order by id desc limit 1),'TC')
WHERE id = (select id from llh_type order by id desc limit 1);
And after running, I have a message: "Query returned successfully: one row affected, 12 msec execution time." And result looks like as expected: http://joxi.ru/5md13oZCkM3el2
function as the same, just add:
CREATE OR REPLACE FUNCTION insert_llh_type_identifier()
RETURNS TRIGGER
LANGUAGE PLPGSQL
AS
$$
BEGIN
UPDATE llh_type SET identifier = (generate_identifier(NEW.id, 'TC')) WHERE id = NEW.id;
RETURN NEW;
END ;
$$;
CREATE TRIGGER llh_type_changes
AFTER INSERT
ON llh_type
FOR EACH ROW
EXECUTE PROCEDURE insert_llh_type_identifier();

if inside for Loop in postgresql

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.

How to call Postgres function returning SETOF record?

I have written the following function:
-- Gets stats for all markets
CREATE OR REPLACE FUNCTION GetMarketStats (
)
RETURNS SETOF record
AS
$$
BEGIN
SELECT 'R approved offer' AS Metric,
SUM(CASE WHEN M.MarketName = 'A+' AND M.Term = 24 THEN LO.Amount ELSE 0 end) AS MarketAPlus24,
SUM(CASE WHEN M.MarketName = 'A+' AND M.Term = 36 THEN LO.Amount ELSE 0 end) AS MarketAPlus36,
SUM(CASE WHEN M.MarketName = 'A' AND M.Term = 24 THEN LO.Amount ELSE 0 end) AS MarketA24,
SUM(CASE WHEN M.MarketName = 'A' AND M.Term = 36 THEN LO.Amount ELSE 0 end) AS MarketA36,
SUM(CASE WHEN M.MarketName = 'B' AND M.Term = 24 THEN LO.Amount ELSE 0 end) AS MarketB24,
SUM(CASE WHEN M.MarketName = 'B' AND M.Term = 36 THEN LO.Amount ELSE 0 end) AS MarketB36
FROM "Market" M
INNER JOIN "Listing" L ON L.MarketID = M.MarketID
INNER JOIN "ListingOffer" LO ON L.ListingID = LO.ListingID;
END
$$
LANGUAGE plpgsql;
And when trying to call it like this...
select * from GetMarketStats() AS (
Metric VARCHAR(50),
MarketAPlus24 INT,
MarketAPlus36 INT,
MarketA24 INT,
MarketA36 INT,
MarketB24 INT,
MarketB36 INT);
I get an error:
ERROR: query has no destination for result data
HINT: If you want to discard the results of a SELECT, use PERFORM instead.
CONTEXT: PL/pgSQL function "getmarketstats" line 2 at SQL statement
I don't understand this output. I've tried using perform too, but I thought one only had to use that if the function doesn't return anything.
Your function doesn't maken sense, it doesn't return anything. It looks like a VIEW, so why don't you create a view?
Edit:
You have use the OUT parameters or RETURN TABLE() with the parameters:
CREATE OR REPLACE FUNCTION my_func(OUT o_id INT, OUT o_bar TEXT)
RETURNS SETOF RECORD AS
$$
BEGIN
RETURN QUERY SELECT id, bar FROM foo;
END;
$$
LANGUAGE plpgsql;
SELECT * FROM my_func();