creating an 'insert into' function using variables for schema names - postgresql

I'm trying to create a function to insert data into a table. The query I'm using won't be changing except for the schema names need to be variables. For instance, one of my schema names is bscu.members and then there's 35 others which are very similar (wea.members, pcu.members.. etc etc..). I couldn't find any help on how to create a function in Postgresql using variables.
this is what i came up with so far but it isn't working
create or replace function attsummary(varchar)
RETURNS void
LANGUAGE plpgsql
AS $function$
BEGIN
insert into dwh.attribution_summary
select
m.source,
m.name,
m.partner_id,
d.ucic,
b.acct_type_desc as acct_desc,
a.begin_mo_balance as opening_balance,
c.date,
h.campaignname,
g.description as banner_desc,
f.create_time::timestamp as time_served,
'd' as dep_or_loan,
'h' as home_or_nonhome
from
$1.fact_deposits a
join $1.dim_acct_type b on a.acct_type_id = b.acct_type_id
join $1.dim_date c on a.date_id = c.date_id
join $1.dim_members d on a.ucic = d.ucic
join ad_delivery.sgmt_adic e on d.adic::varchar = e.adic
join ad_delivery.sgmt_user_tracker f on e.cookie_id = f.id
join ad_delivery.ox_banners g on g.bannerid = f.banner_id
join ad_delivery.ox_campaigns h on h.campaignid = f.campaign_id
join ad_delivery.sgmt_kli_adic i on e.adic = i.adic
join dwh.sgmt_clients m on m.partner_id = i.sgmt_partner_id
where
i.kli=8616208
and m.partner_id::integer != 909909
and then my select statement comes after.. I am using $1 for my variable where the schema name usually goes.

It must be dynamically generated
create or replace function attsummary
(schema text)
returns void as
$body$
begin
execute format('
insert into dwh.attribution_summary
select
m.source,
m.name,
m.partner_id,
d.ucic,
b.acct_type_desc as acct_desc,
a.begin_mo_balance as opening_balance,
c.date,
h.campaignname,
g.description as banner_desc,
f.create_time::timestamp as time_served,
''d'' as dep_or_loan,
''h'' as home_or_nonhome
from
%1$s.fact_deposits a
join %1$s.dim_acct_type b on a.acct_type_id = b.acct_type_id
join %1$s.dim_date c on a.date_id = c.date_id
join %1$s.dim_members d on a.ucic = d.ucic
join ad_delivery.sgmt_adic e on d.adic::varchar = e.adic
join ad_delivery.sgmt_user_tracker f on e.cookie_id = f.id
join ad_delivery.ox_banners g on g.bannerid = f.banner_id
join ad_delivery.ox_campaigns h on h.campaignid = f.campaign_id
join ad_delivery.sgmt_kli_adic i on e.adic = i.adic
join dwh.sgmt_clients m on m.partner_id = i.sgmt_partner_id
where
i.kli=8616208
and m.partner_id::integer != 909909
', $1);
end;
$body$
language plpgsql volatile
;

#Clodoaldo gave you a working answer, but note that the format() function he used isn't available prior to Pg 9.1. If you need this to work on older Pg instances you can use string expressions to build your dynamic query. Example:
CREATE SCHEMA a;
CREATE SCHEMA b;
CREATE TABLE a.foo ( data text );
CREATE TABLE b.foo ( data text );
CREATE OR REPLACE FUNCTION insert_with_schema(schema_name text, data text)
RETURNS void
AS $$
BEGIN
EXECUTE 'INSERT INTO ' || quote_ident(schema_name) || '.foo (data) VALUES ($1)'
USING data;
END
$$
LANGUAGE plpgsql;

Related

How to use SELECT INTO multi variables with FOR LOOP in POSTGRESQL PROCEDURE

I'm trying to write a procedure in PostgreSQL to update number of products sold from Order_items table to Stock table.
Here's my query
CREATE OR REPLACE PROCEDURE smallerp.sp_calculate_stock ()
LANGUAGE plpgsql
AS $$
DECLARE f record;
v_pid integer;
v_sid integer;
v_sold integer;
BEGIN
FOR f IN
(SELECT i.product_id, o.store_id, SUM(i.quantity)
INTO v_pid, v_sid, v_sold)
FROM smallerp.s_order_items i
INNER JOIN smallerp.s_orders o
ON i.order_id = o.order_id
INNER JOIN smallerp.p_products r
ON i.product_id = r.product_id
INNER JOIN smallerp.s_stores s
ON o.store_id = s.store_id
GROUP BY o.store_id, i.product_id, s.store_name, r.product_name
ORDER BY s.store_name
LOOP
UPDATE smallerp.p_stocks kk
SET (kk.product_id,
kk.store_id,
kk.sold)
= (v_pid, v_sid, v_sold)
WHERE kk.store_id = _sid AND kk.product_id = _pid;
END LOOP;
END;
$$;
It keeps saying that i have syntax error at INTO v_pid,
ERROR: syntax error at or near ","
LINE 16: INTO v_pid, v_sid, v_sold)
How could i fix it? Thank you guys.
Use a CTE and no loop is needed:
CREATE OR REPLACE PROCEDURE smallerp.sp_calculate_stock ()
LANGUAGE sql
AS $$
WITH cte AS (
SELECT i.product_id, o.store_id, SUM(i.quantity) as sold
FROM smallerp.s_order_items i
INNER JOIN smallerp.s_orders o
ON i.order_id = o.order_id
INNER JOIN smallerp.p_products r
ON i.product_id = r.product_id
INNER JOIN smallerp.s_stores s
ON o.store_id = s.store_id
GROUP BY o.store_id, i.product_id, s.store_name, r.product_name
)
UPDATE smallerp.p_stocks kk
SET (kk.product_id, kk.store_id, kk.sold) = (cte.product_id, cte.store_id, cte.sold)
FROM cte
WHERE kk.store_id = cte.store_id AND kk.product_id = cte.product_id;
$$;
You don't need plpgsql either, sql is good enough.
Frank's answer is the correct solution for the underlying problem. To answer the syntax question: if you use a FOR loop the all columns are available through the record (loop) variable. So get rid of the INTO in the SELECT of the FOR loop, then reference the columns from the record variable:
DECLARE
f record;
BEGIN
FOR f IN
SELECT i.product_id, o.store_id, SUM(i.quantity) as sold
FROM smallerp.s_order_items i
INNER JOIN smallerp.s_orders o
ON i.order_id = o.order_id
INNER JOIN smallerp.p_products r
ON i.product_id = r.product_id
INNER JOIN smallerp.s_stores s
ON o.store_id = s.store_id
GROUP BY o.store_id, i.product_id, s.store_name, r.product_name
ORDER BY s.store_name
LOOP
UPDATE smallerp.p_stocks kk
SET (kk.product_id,
kk.store_id,
kk.sold) = (f.product_id, f.store_id, f.sold)
WHERE kk.store_id = _sid AND kk.product_id = _pid;
END LOOP;
END;
But again: doing an UPDATE in a LOOP is typically not a good idea.

Using PERFORM in recursive query function in PostgreSQL

I have a recursive query which I want to use in PostgreSQL function, and it should return a Boolean value.
CREATE OR REPLACE FUNCTION store.is_item(object1 VARCHAR(40), object2 VARCHAR(40))
RETURNS BOOLEAN AS $$
BEGIN
WITH RECURSIVE externals AS (
SELECT object_id, used_id
FROM store.obj_depend
WHERE external = true
), history AS (
SELECT content_id AS id
FROM store.minfos
WHERE id= $2
UNION
SELECT externals.used_id
FROM externals
INNER JOIN history ON history.id = externals.object_id
),
PERFORM (SELECT c.id FROM store.cinfo AS c WHERE c.id = $1 INNER JOIN history
ON c.id = history.id);
RETURN FOUND;
END;
$$ LANGUAGE plpgsql;
When I try this it gives asyntax error at or near SELECT error.
The PERFORM is plpgsql statement and cannot be used inside any SQL command.
You can use PERFORM like proposed #klin, but then CTE is used inside a subquery, and subquery returns one row every time. Then a FOUND variable should be every time true.
In this case is better to use auxiliary variable as targer:
CREATE OR REPLACE FUNCTION store.is_item(object1 VARCHAR(40), object2 VARCHAR(40))
RETURNS BOOLEAN AS $$
DECLARE r record;
BEGIN
WITH RECURSIVE
externals AS (SELECT object_id, used_id
FROM store.obj_depend
WHERE external = true)
history AS (SELECT content_id AS id
FROM store.minfos
WHERE id = object2
UNION
SELECT externals.used_id
FROM externals
INNER JOIN history ON history.id = externals.object_id)
SELECT c.id
FROM store.cinfo AS c
INNER JOIN history ON c.id = history.id
WHERE c.id = $1
INTO r;
RETURN FOUND;
END;
$$ LANGUAGE plpgsql;

PostgreSQL - Error column reference "id" is ambiguous

I've aliased everything, but can't seem to get this query working without the
error column reference \"id\" is ambiguous"
It seems to work if i remove one of the joins, but i'm just confused as to why it won't work with two?
create function influence.person_info(user_id integer)
returns setof influence.person_object as
$$
declare
obj influence.person_object;
begin
select t1.email as a_email, t2.organisation_url as a_org, t3.first_name as a_first, t3.last_name as a_last into obj
from influence_private.person_account as t1
inner join influence_private.organisation_account as t2 on (t1.organisation_id = t2.id)
inner join influence.person as t3 on (t1.person_id = t3.id)
where id = $1;
return next obj;
end;
$$ LANGUAGE plpgsql stable;
Any pointers?
In your where clause you will need to explicitly state which id you are referring to.
If you check your query:
select t1.email as a_email, ...
from influence_private.person_account as t1
join influence_private.organisation_account as t2
on (t1.organisation_id = t2.id) -- here
join influence.person as t3
on (t1.person_id = t3.id) -- here
Both your tables (influence_private and influence) have column named id. Postgresql doesn't know which one you want to use so you have to use full name like t2.id.
The problem of your query is t2 and t3 both tables have a column called id. In the Where clause id is ambigous because it doesn't know what id you are refering (t2 or t3) specify it and it will work properly.
Example fix:
create function influence.person_info(user_id integer)
returns setof influence.person_object as
$$
declare
obj influence.person_object;
begin
select t1.email as a_email, t2.organisation_url as a_org, t3.first_name as a_first, t3.last_name as a_last into obj
from influence_private.person_account as t1
inner join influence_private.organisation_account as t2 on (t1.organisation_id = t2.id)
inner join influence.person as t3 on (t1.person_id = t3.id)
where t1.id = $1;
return next obj;
end;
$$ LANGUAGE plpgsql stable;

LOOP for table function

I'm a newbie in Posgresql
I have a table function:
CREATE OR REPLACE FUNCTION stage.get_primary_key_info(
schemaName text,
tableName text
) RETURNS TABLE(constraint_name text, column_name text) AS
$BODY$
SELECT c.constraint_name, c.column_name
FROM information_schema.key_column_usage AS c
LEFT JOIN information_schema.table_constraints AS t
ON t.constraint_name = c.constraint_name
WHERE t.table_schema = schemaName
AND t.table_name = tableName
AND t.constraint_type = 'PRIMARY KEY'
;
$BODY$ LANGUAGE sql;
And I'm trying to use this function like:
FOR c IN (SELECT * FROM stage.get_primary_key_info(target_schema, stmt.tablename))
LOOP
joinFields = joinFields || FORMAT('t.%s = s.%s AND', c.column_name);
END LOOP;
But I have this error:
The loop variable of the tuples must be a variable of the type record
or tuple or a list of scalar variables
Try declaring c as a record. You are also missing a second parameter in the FORMAT string.
Also, take a look at string_agg and you might be able to skip that loop entirely. Here is an example (substituting y for the second parameter):
SELECT string_agg(FORMAT('t.%s = s.%s', column_name, 'y'), ' AND ')
FROM get_primary_key_info(target_schema, stmt.tablename)
;

POSTGRESQL- Query has no destination for result data

I'm new to postgres and to programming and I already searched for solution for this but I couldn't quite get it. I'm trying to make a function that will return information about all the customers on that particular country whenever I call the country. This is the error that pops up. I'm really sorry for asking this but I've been stuck here since yesterday.
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 country(text) line 5 at SQL statement
Here is the function:
create or replace function country(text) returns text as $$
begin
select customer_id, customer.first_name, customer.last_name
from customer
inner join address on customer.address_id = address.address_id
inner join city on address.city_id = city.city_id
inner join country on city.country_id = country.country_id
where country = '$1';
end;
$$
language plpgsql;
If you are executing a select statement in a PL/pgSQL function, then you should place the result of the query in some variable(s) (= the destination). Then you work with the variable(s) in the function. You should also have a RETURN statement.
create or replace function country(text) returns text as $$
declare -- declare some variables
id integer;
fname text;
lname text;
begin
select customer_id, customer.first_name, customer.last_name
into id, fname, lname -- store query results in variables
from customer
inner join address on customer.address_id = address.address_id
inner join city on address.city_id = city.city_id
inner join country on city.country_id = country.country_id
where country = $1; -- don't quote parameter references
-- do something with the variables and return a value from the function
return format('%s: %s %s', id, upper(lname), fname);
end;
$$ language plpgsql;
Do note that the above only works if the query returns a single row. If the query returns multiple rows you can use a loop in the function. Even simpler, you can just return the results from the query like so:
create or replace function country(text)
returns table (id integer, first_name varchar, last_name varchar) as $$
begin
return query
select customer_id, customer.first_name, customer.last_name
from customer
inner join address on customer.address_id = address.address_id
inner join city on address.city_id = city.city_id
inner join country on city.country_id = country.country_id
where country = $1;
end;
$$ language plpgsql;
But like Evan Carroll said, unless you need a PL/pgSQL function to modify the data before returning it, you are better off with a simple view.
CREATE OR REPLACE FUNCTIOn func_process_client_contact(p_client_version_id bigint, p_publish_id bigint)
RETURNS TABLE(status_message character varying)
LANGUAGE plpgsql
AS $function$
declare
p_rowcount int;
BEGIN
-- validating input parameters : start
IF ( p_client_version_id IS NULL OR p_publish_id IS NULL )
THEN
RETURN;
END IF;
-- validating input parameters : end
WITH cte_target AS
(
SELECT
g.name AS clientname,
gcr.group_id AS clientid,
cr.id AS clientrelationid,
crc.id AS clientrelationcontactid,
crd.id AS clientrelationdesignation_id
FROM GROUPS g
JOIN group_client_relation gcr ON gcr.group_id =g.id
JOIN client_relation cr ON cr.id = gcr.client_relation_id
JOIN client_relation_contact crc ON crc.client_relation_id =cr.id
JOIN client_relation_designation crd ON cr.client_relation_designation_id =crd.id
)
SELECT * FROM cte_target ct WHERE ct.clientname='ABC';
GET DIAGNOSTICS p_rowcount = ROW_COUNT;
RAISE NOTICE 'returned % rows', p_rowcount;
IF ( p_rowcount=0 )
THEN
RETURN query SELECT 'hello' AS status_message;
ELSE
RETURN query SELECT 'Success' AS status_message;
END IF;
END
$function$
Please use below to get result of given function..
create or replace function country(in_parameter text,out out_res refcursor) as $$
begin
open out_res for
select customer_id, customer.first_name, customer.last_name
from customer
inner join address on customer.address_id = address.address_id
inner join city on address.city_id = city.city_id
inner join country on city.country_id = country.country_id
where country = '$1';
end;
$$
language plpgsql;
This is not normal for SQL. Normally, this would be a VIEW.
CREATE VIEW myView AS
SELECT customer_id, customer.first_name, customer.last_name
FROM customer
INNER JOIN address USING (address_id)
INNER JOIN city USING (city_id)
INNER JOIN country USING (country_id);
Then you do
SELECT * FROM myView WHERE country = ?
All of that said, if you insist on making this a function, and you shouldn't, you should make it a LANAGUAGE SQL and not LANGUAGE plppsql.
It worked for my colleague when she used OPEN MYCURS before the select query and RETURN MYCURS after the select query.
There might be some situations where we want to call another psql function from a function. If we just want to invoke the function and don't assign the return value to anything, then using select inner_function_call(); inside the main function will throw this error. As the hint suggests, use perform inner_function_call() instead.