Dynamic query in Postgres over db link - postgresql

I'm trying to create a function which will go over a DB LINK and do a count of object types belonging to a user and then store that value (I will insert this value into a table later so I can compare counts from my local DB with the remote one). I can build the SQL dynamically and it looks right but when it gets called over a DB link using the below I can't get it to accept the single quotes. Is there a way to plug this in for queries over a db link?
DO $$
DECLARE
sqlSmt text;
v_new_count NUMERIC:=0;
item record;
begin
sqlSmt = null;
FOR item IN
(select nsp.nspname schema, cls.relkind obj_type from pg_class cls
join pg_roles rol on rol.oid = cls.relowner
join pg_namespace nsp on nsp.oid = cls.relnamespace
where nsp.nspname like 'dwh%'
group by nsp.nspname, cls.relkind
order by nsp.nspname, cls.relkind limit 10)
LOOP
sqlSmt = 'select * from dblink(''old_live'',''select count(*) from pg_class cls
join pg_roles rol on rol.oid = cls.relowner
join pg_namespace nsp on nsp.oid = cls.relnamespace
where nsp.nspname = '''||item.schema||''' and cls.relkind='''||item.obj_type||''') as total_count(total_count numeric)';
EXECUTE sqlSmt INTO v_new_count;
raise notice '%', sqlSmt;
raise notice '%, %, %', item.schema, item.obj_type, v_new_count;
END LOOP;
END $$;
ERROR:
ERROR: syntax error at or near "dwh_10"
LINE 6: where nsp.nspname = 'dwh_10' and cls.relkind='S') as total_...
^
QUERY: select * from dblink('old_live','select count(*) from pg_class cls
join pg_roles rol
on rol.oid = cls.relowner
join pg_namespace nsp
on nsp.oid = cls.relnamespace
where nsp.nspname = 'dwh_10' and cls.relkind='S') as total_count(total_count numeric)
CONTEXT: PL/pgSQL function inline_code_block line 27 at EXECUTE

String manipulation should solve this problem:
Examples :
SELECT '''';
SELECT E'\'';
SELECT $$'$$;
SELECT $$ SELECT * FROM test WHERE name='test' ;$$ ;
sqlSmt = 'select * from dblink(''old_live'',$$select count(*) from pg_class cls
join pg_roles rol on rol.oid = cls.relowner
join pg_namespace nsp on nsp.oid = cls.relnamespace
where nsp.nspname = '''||item.schema||''' and cls.relkind='''||item.obj_type||''') as total_count(total_count numeric)$$;'
The $$ should solve the problem in string manipulation for the second level.

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.

How to GRANT SELECT PRIVILEGES on table with wild card with psql postgres

I have tables' names that start with underscore '_XXXXXXX' among other tables. I need to create a user that can only do a query on these '_XXXX" tables (nothing else) without the possibility of viewing/finding the other tables (not starting with '_XXXXX').
How can i do that in postgres psql?:
I tried
GRANT SELECT ON TABLE "_*" TO username;
i get the following:
ERROR: relation "_*" does not exist
Any help is appreciated.
Thank you
when I execute this code in PgAdmin4 query editor:
DO
$$
DECLARE
r record;
BEGIN
FOR r IN SELECT c.relname,
n.nspname
FROM pg_class c
INNER JOIN pg_namespace n
ON n.oid = c.relnamespace
WHERE n.nspname = 'Schemas'
AND c.relkind = 'r'
AND c.relname LIKE '$_%'
ESCAPE '$' LOOP
EXECUTE 'GRANT SELECT ON "' || r.nspname || '"."' || r.relname || '" TO kpidata;';
END LOOP;
END;
$$
LANGUAGE plpgsql;
i get the following response and nothing changes (still have the same access rights to all for the created user 'kpidata'). I am sure it is me who is not understanding how things work
my db structure is the following:
You could use a DO block looping over all table with names beginning with underscore, build the statement for it an execute the statement.
DO
$$
DECLARE
r record;
BEGIN
FOR r IN SELECT c.relname,
n.nspname
FROM pg_class c
INNER JOIN pg_namespace n
ON n.oid = c.relnamespace
WHERE n.nspname = 'public'
AND c.relkind = 'r'
AND c.relname LIKE '$_%'
ESCAPE '$' LOOP
EXECUTE 'GRANT SELECT ON "' || r.nspname || '"."' || r.relname || '" TO username;';
END LOOP;
END;
$$
LANGUAGE plpgsql;

PL/pgsql Correct way to cast regclass dynamically in query

I have a function that is going to loop through a table, and then loop through all the tables that inherit from each table from the outerloop. I found a great function to find tables that inherit from a parent table. My question is, how do I dynamically change the table name to find all parent? so 'shopmaster.pb'::regclass would be something like 'shopmaster.'||i.tablename::regClass.
CREATE OR REPLACE FUNCTION shopmaster.cascade_filters()
RETURNS jsonb AS $$
DECLARE
i record;
k shopmaster."catalog_filters";
BEGIN
DELETE FROM shopmaster.catalog_filters WHERE isparent=false;
FOR i IN SELECT shopmaster.catalog.catalogid,columnname,columntype,columnnvalues,
tablename FROM shopmaster.catalog_filters INNER JOIN catalog ON
(catalog_filters.catalogid=catalog.catalogid) WHERE isparent=true LOOP
FOR k IN
WITH RECURSIVE inh AS (
SELECT i.inhrelid FROM pg_catalog.pg_inherits i WHERE inhparent=
'shopmaster.pb'::regclass
UNION
SELECT i.inhrelid FROM inh INNER JOIN pg_catalog.pg_inherits i ON
(inh.inhrelid = i.inhparent)
)
SELECT pg_namespace.nspname, pg_class.relname
FROM inh
INNER JOIN pg_catalog.pg_class ON (inh.inhrelid=pg_class.oid)
INNER JOIN pg_catalog.pg_namespace ON
(pg_class.relnamespace=pg_namespace.oid) LOOP
END LOOP;
END LOOP;
END;
$$ LANGUAGE plpgsql;
Turns out there's an easy way. Perform a sub-expression to concat or whatever to get a table identifier, then cast it to a ::regclass. E.g. ('shopmaster.'||i.tablename)::regclass
CREATE OR REPLACE FUNCTION shopmaster.cascade_filters()
RETURNS jsonb AS $$
DECLARE
i record;
k record;
BEGIN
DELETE FROM shopmaster.catalog_filters WHERE isparent=false;
FOR i IN SELECT shopmaster.catalog.catalogid,columnname,columntype,
columnnvalues,tablename
FROM shopmaster.catalog_filters INNER JOIN shopmaster.catalog ON
(catalog_filters.catalogid=catalog.catalogid) WHERE isparent=true LOOP
FOR k IN
WITH RECURSIVE inh AS (
SELECT ih.inhrelid FROM pg_catalog.pg_inherits ih WHERE inhparent=
('shopmaster.'||i.tablename)::regclass
UNION
SELECT ih.inhrelid FROM inh INNER JOIN pg_catalog.pg_inherits ih ON
(inh.inhrelid = ih.inhparent)
)
SELECT pg_namespace.nspname, pg_class.relname,
shopmaster.catalog.catalogid
FROM inh
INNER JOIN pg_catalog.pg_class ON (inh.inhrelid=pg_class.oid)
INNER JOIN pg_catalog.pg_namespace ON
(pg_class.relnamespace=pg_namespace.oid)
INNER JOIN shopmaster.catalog ON
(pg_class.relname=shopmaster.catalog.tablename) LOOP
EXECUTE 'INSERT INTO shopmaster.catalog_filters (catalogid,'
||'columnname,columntype,columnnvalues,isparent,owner) '
||'VALUES($1,$2,$3,$4,false,$5)' USING k.catalogid,i.columnname,
i.columntype,i.columnnvalues,i.catalogid;
END LOOP;
END LOOP;
RETURN jsonb_build_object('ok',true);
END;
$$ LANGUAGE plpgsql;

PostgreSQL, drop custom functions

In order to restore my data from pgdump I need to delete custom functions from database.
This function identify them well:
SELECT pp.proname
FROM pg_proc pp
INNER JOIN pg_namespace pn on (pp.pronamespace = pn.oid)
INNER JOIN pg_language pl on (pp.prolang = pl.oid)
WHERE pl.lanname NOT IN ('c','internal')
AND pn.nspname NOT LIKE 'pg_%'
AND pn.nspname <> 'information_schema';
So, immediately after I hear for dynamic SQL and ensure that I can use it from .NET I try to apply it for that purpose.
do
$$
declare
func_rec record;
begin
for func_rec in (SELECT pp.proname as funcname
FROM pg_proc pp
INNER JOIN pg_namespace pn on (pp.pronamespace = pn.oid)
INNER JOIN pg_language pl on (pp.prolang = pl.oid)
WHERE pl.lanname NOT IN ('c','internal')
AND pn.nspname NOT LIKE 'pg_%'
AND pn.nspname <> 'information_schema')
loop
execute 'drop function '||func_rec.funcname||' cascade';
end loop;
end;
$$
In meantime I found that for drop a function I have to supply it's parameters to DROP command.
Here is one example subquery on how those parameters can be retrieved:
(SELECT typname FROM pg_type WHERE oid = funcrow.proargtypes[i])
Now remain a problem that I don't know how to put those in functional code which will add needed parameters to func_rec.funcname in order to delete such functions.
So, please help to get query for deleting custom functions from all functions.
Because functions can be overloaded in Postgres, you need to include the function's signature in the drop.
Suppose you have these functions:
get_answer(p1 integer);
get_answer(p1 integer, p2 integer);
then Postgres wouldn't know which one to drop when using drop function get_answer;.
Luckily Postgres has a function to format the arguments so that they can be used for that purpose: pg_get_function_identity_arguments.
So you need to change your select to:
SELECT pp.proname||'('||pg_get_function_identity_arguments(pp.oid)||')' as funcname
FROM pg_proc pp
INNER JOIN pg_namespace pn on (pp.pronamespace = pn.oid)
INNER JOIN pg_language pl on (pp.prolang = pl.oid)
WHERE pl.lanname NOT IN ('c','internal')
AND pn.nspname NOT LIKE 'pg_%'
AND pn.nspname <> 'information_schema';

List all tables in Postgres that contain a boolean type column with NULL values?

Is there a way to list all tables within information schema which contain a boolean type column with NULL values? If I have a names of the tables, I can later on use query:
SELECT * FROM table_name WHERE column_name IS NULL
Of course, if there is a way to list all rows from all the tables with single query, that would be even faster.
Doing this step by step would be:
SELECT * FROM table1 WHERE column_name IS NULL
SELECT * FROM table2 WHERE column_name IS NULL
SELECT * FROM table3 WHERE column_name IS NULL
...
Tables are already populated, for new database, such columns should have NOT NULL constraint.
Try:
SELECT table_catalog, table_schema, table_name, column_name, ordinal_position
FROM information_schema.columns
WHERE table_schema <> 'pg_catalog' AND data_type = 'boolean';
and:
DO $$
DECLARE
r record;
s record;
BEGIN
FOR r IN SELECT table_catalog, table_schema, table_name, column_name, ordinal_position
FROM information_schema.columns
WHERE table_schema <> 'pg_catalog' AND data_type = 'boolean'
LOOP
FOR s IN EXECUTE 'SELECT ' || quote_literal(r.table_schema) || ', ' || quote_literal(r.table_name) || ', ' || quote_literal(r.column_name) || ' WHERE EXISTS (SELECT 1 FROM ' || quote_ident(r.table_schema) || '.' || quote_ident(r.table_name) || ' WHERE ' || quote_ident(r.column_name) || ' IS NULL);'
LOOP
RAISE NOTICE '** % % %', quote_ident(r.table_schema), quote_ident(r.table_name), quote_ident(r.column_name);
END LOOP;
END LOOP;
END
$$;
Osvaldo
If you are running postgresql 9.0+, you can use an anonymous plpgsql block to execute some dynamic SQL.
DO $$
DECLARE
rec RECORD;
v_result INTEGER;
BEGIN
FOR rec IN
SELECT 'select 1 from ' || quote_ident(n.nspname) ||'.'|| quote_ident(c.relname) ||
' where ' || quote_ident(a.attname) || ' IS NULL LIMIT 1' as qry_to_run,
n.nspname||'.'||c.relname as tbl,
a.attname as col
FROM pg_class as c
INNER JOIN pg_attribute as a ON (a.attrelid = c.oid)
INNER JOIN pg_type as t ON (t.oid = a.atttypid)
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE a.attnum >= 1
AND c.relkind = 'r'
AND pg_catalog.format_type(a.atttypid, a.atttypmod) = 'boolean'
AND n.nspname NOT IN ('pg_catalog','information_schema')
AND a.attnotnull IS NOT TRUE
LOOP
EXECUTE rec.qry_to_run INTO v_result;
IF v_result = 1 THEN
RAISE NOTICE 'Table % has NULLs in the BOOLEAN field %', rec.tbl,rec.col;
v_result := 0;
END IF;
END LOOP;
END;
$$;
This advances #bma's excellent answer to make it shorter, faster and a bit smarter:
DO $$
DECLARE
rec record;
_found boolean;
BEGIN
FOR rec IN
SELECT format('SELECT TRUE FROM %s WHERE %I IS NULL LIMIT 1'
, c.oid::regclass, a.attname) AS qry_to_run
,c.oid::regclass AS tbl
,a.attname AS col
FROM pg_namespace n
JOIN pg_class c ON c.relnamespace = n.oid
JOIN pg_attribute a ON a.attrelid = c.oid
WHERE n.nspname <> 'information_schema'
AND n.nspname NOT LIKE 'pg_%' -- exclude system, temp, toast tbls
AND c.relkind = 'r'
AND a.attnum > 0
AND a.atttypid = 'boolean'::regtype
AND a.attnotnull = FALSE
AND a.attisdropped = FALSE
LOOP
EXECUTE rec.qry_to_run INTO _found;
IF _found THEN
RAISE NOTICE 'Table % has NULLs in the BOOLEAN field %'
, rec.tbl,rec.col;
END IF;
END LOOP;
END
$$;