How can I get select (table_name, table_name.age)?
I need to get values from column 'age' from all tables having this column/
I have this function
CREATE OR REPLACE FUNCTION union_all_tables()
RETURNS TABLE
(
age bigint
) AS
$$
DECLARE
dynamic_query text = '';
r_row record;
BEGIN
FOR r_row IN SELECT table_schema || '.' || table_name qualified_table_name
FROM information_schema.COLUMNS
WHERE column_name = 'age'
LOOP
dynamic_query := dynamic_query || format('UNION SELECT ' ||
'age ' ||
'FROM %s ',r_row.qualified_table_name) || E'\n'; -- adding new line for pretty print, it is not necessary
END LOOP;
dynamic_query := SUBSTRING(dynamic_query, 7) || ';';
RAISE NOTICE 'Union all tables in staging, executing statement: %', dynamic_query;
RETURN QUERY EXECUTE dynamic_query;
END;
$$
LANGUAGE plpgsql;
You don't need to generate a single huge UNION statement. If you use RETURN QUERY the result of that query is appended to the overall result of the function every time you use it.
When dealing with dynamic SQL you should also use format() to properly deal with identifiers.
Your function can be simplified to:
CREATE OR REPLACE FUNCTION union_all_tables()
RETURNS TABLE (table_schema text, table_name text, age bigint)
AS
$$
DECLARE
dynamic_query text = '';
r_row record;
BEGIN
FOR r_row IN SELECT c.table_schema, c.table_name
FROM information_schema.columns c
WHERE c.column_name = 'age'
LOOP
dynamic_query := format(
'select %L as table_schema, %L as table_name, age from %I.%I',
r_row.table_schema, r_row.table_name,
r_row.table_schema, r_row.table_name);
RETURN QUERY EXECUTE dynamic_query;
END LOOP;
END;
$$
LANGUAGE plpgsql;
Note that the whole function will fail if there is (at least) one table where the age column is not a bigint.
Related
I have an working UPDATE statement that must be passed through 220 columns (type text) to apply a nested CASE operator.
UPDATE TabName
SET
ColName = CASE ColName
WHEN 'Strongly disagree' THEN '1'
WHEN 'Disagree' THEN '2'
WHEN 'Indifferent' THEN '3'
WHEN 'Agree' THEN '4'
WHEN 'Strongly agree' THEN '5'
WHEN '#NULL!' THEN NULL
WHEN '' THEN NULL
ELSE ColName
END
WHERE ColName IS NOT NULL;
When TabName and ColName are manually replaced on this code by valid table_name and column_name the update statement work as intended. All matching values are replaced by respective new values and all non matching values are maintained.
So far so good.
The challenge is to parse automatically this statement through all columns of a table.
I was able to list all columns names of the referred table with the code below, but I have tried with no success to write a functional FOR LOOP code with an UPDATE statement.
DO
$$
DECLARE ColName text;
BEGIN
FOR ColName IN
(SELECT column_name
FROM information_schema.columns
WHERE table_schema = 'public' AND table_name = 'TabName')
LOOP
RAISE NOTICE '%', ColName;
END LOOP;
END;
$$;
At first I tried a prepared statement but discovered that PREPARE/EXECUTE statements does not accept schema qualified names (table_name, column_name) as parameters.
Then I tried to create a plpgsql user-defined function but got stucked on parameters and CASE errors.
Any help or direction to a solution is greatly appreciated.
If needed I am available to share the table source code and a CSV file with sample data for tinkering.
Thanks.
To make the dynamic SQL very simple, recommended create additional function for changing texts.
CREATE OR REPLACE FUNCTION change_text(txt text)
RETURNS text
LANGUAGE plpgsql
AS $function$
declare
p_text text;
begin
select
CASE txt
WHEN 'Strongly disagree' THEN '1'
WHEN 'Disagree' THEN '2'
WHEN 'Indifferent' THEN '3'
WHEN 'Agree' THEN '4'
WHEN 'Strongly agree' THEN '5'
WHEN '#NULL!' THEN NULL
WHEN '' THEN NULL
ELSE txt
end into p_text;
return p_text;
end;
$function$
;
After then we can create function for dynamically update table. (Only for string fields)
CREATE OR REPLACE FUNCTION update_table(p_schema text, p_table text)
RETURNS bool
LANGUAGE plpgsql
AS $function$
declare
col_name text;
v_sql text;
v_seperate text;
begin
v_seperate = '';
v_sql = 'update ' || p_schema || '.' || p_table || ' set ';
FOR col_name IN
(SELECT column_name
FROM information_schema.columns
WHERE
table_schema = p_schema
AND table_name = p_table
and data_type in ('text', 'character varying'))
LOOP
v_sql = v_sql || v_seperate || col_name || ' = change_text(' || col_name || ')';
v_seperate = ',';
END LOOP;
if (v_seperate<>'') then
execute v_sql;
end if;
return true;
end;
$function$
;
postgres 12
I am trying to loop through a table which has schema , table_names and columns
I want to do various things like finding nulls ,row count etc. I failed at the first hurdle trying to update the col records.
table i am using
CREATE TABLE test.table_study (
table_schema text,
table_name text,
column_name text,
records int,
No_Nulls int,
No_Blanks int,
per_pop int
);
I populate the table with some schema names ,tables and columns from information_schema.columns
insert into test.table_study select table_schema, table_name, column_name
from information_schema.columns
where table_schema like '%white'
order by table_schema, table_name, ordinal_position;
I want to populate the rest with a function
function :-
CREATE OR REPLACE PROCEDURE test.insert_data_population()
as $$
declare s record;
declare t record;
declare c record;
BEGIN
FOR s IN SELECT distinct table_schema FROM test.table_study
LOOP
FOR t IN SELECT distinct table_name FROM test.table_study where table_schema = s.table_schema
loop
FOR c IN SELECT column_name FROM test.table_study where table_name = t.table_name
LOOP
execute 'update test.table_study set records = (select count(*) from ' || s.table_schema || '.' || t.table_name || ') where table_study.table_name = '|| t.table_name ||';';
END LOOP;
END LOOP;
END LOOP;
END;
$$
LANGUAGE plpgsql;
I get this error SQL Error [42703]: ERROR: column "age" does not exist. the table age does exist.
when I take out the where clause
execute 'update referralunion.testinsert ti set records = (select count(*) from ' || s.table_schema || '.' || t.table_name || ') ;';
it works, I just cant figure out whats wrong?
Your procedure is structured entirely wrong. What it results in is an attempt to get every column name for every table name in every schema. I would guess results in your column does not exist error. Further is shows procedural thinking. SQL requires think in terms of sets. Below I use basically your query to demonstrate then a revised version which uses a single loop.
-- setup (dropping schema references)
create table table_study (
table_schema text,
table_name text,
column_name text,
records int,
no_nulls int,
no_blanks int,
per_pop int
);
insert into table_study(table_schema, table_name, column_name)
values ('s1','t1','age')
, ('s2','t1','xyz');
-- procedure replacing EXECUTE with Raise Notice.
create or replace procedure insert_data_population()
as $$
declare
s record;
t record;
c record;
line int = 0;
begin
for s in select distinct table_schema from table_study
loop
for t in select distinct table_name from table_study where table_schema = s.table_schema
loop
for c in select column_name from table_study where table_name = t.table_name
loop
line = line+1;
raise notice '%: update table_study set records = (select count(*) from %.% where table_study.table_name = %;'
, line, s.table_schema, t.table_name, c.column_name;
end loop;
end loop;
end loop;
end;
$$
language plpgsql;
Run procedure
do $$
begin
call insert_data_population();
end;
$$;
RESULTS
1: update table_study set records = (select count(*) from s2.t1 where table_study.table_name = age; 2: update table_study set records = (select count(*) from s2.t1 where table_study.table_name = xyz; 3: update table_study set records = (select count(*) from s1.t1 where table_study.table_name = age; 4: update table_study set records = (select count(*) from s1.t1 where table_study.table_name = xyz;
Notice lines 2 and 3. Each references a column name that does not exist in the table. This results from the FOR structure with the same table name in different schema.
Revision for Single Select statement with Single For loop.
create or replace
procedure insert_data_population()
language plpgsql
as $$
declare
s record;
line int = 0;
begin
for s in select distinct table_schema, table_name, column_name from table_study
loop
line = line+1;
raise notice '%: update table_study set records = (select count(*) from %.% where table_study.table_name = %;'
, line, s.table_schema, s.table_name, s.column_name;
end loop;
end;
$$;
do $$
begin
call insert_data_population();
end;
$$;
RESULTS
1: update table_study set records = (select count(*) from s2.t1 where table_study.table_name = xyz;
2: update table_study set records = (select count(*) from s1.t1 where table_study.table_name = age;
Note: In Postgres DECLARE begins a block. It is not necessary to declared each variable. I would actually consider it bad practice. In theory it could require an end for each declare as each could be considered a nested block. Fortunately Postgres does not require this.
Good morning, I have the following function in postgresql, which works correctly only that it returns the first result it finds and not all of them as I want
CREATE OR REPLACE FUNCTION public.buscarcadena(
cadena uuid,
esquema character varying)
RETURNS character varying AS
$BODY$
DECLARE
tabla character varying;
columna character varying;
r record;
BEGIN
FOR tabla IN
select table_name from information_schema.tables where table_schema = esquema
LOOP
FOR columna IN
SELECT column_name FROM information_schema.columns WHERE table_schema = 'public'
AND table_name = tabla and data_type = 'uuid'
LOOP
FOR r IN EXECUTE format('SELECT 1 FROM %I where %I = ' || '''' || cadena || '''', tabla,columna)
LOOP
return 'tabla: '|| tabla||' || columna: '||columna;
END LOOP;
END LOOP;
END LOOP;
return 'No encontrada';
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
Where can be the error?
I work wirh oracle Database. I have a plsql code where i run a query in a loop for multiple tables. so, table name is a variable in my code. I would like to have another variable (a single number) that I can call inside the loop and every time it counts the total rows of each table for me
declare
Cursor C_TABLE is
select trim(table_name) as table_name
from all_tables
where table_name in ('T1', 'T2', 'T3');
V_ROWNUM number;
begin
for m in C_TABLE
loop
for i in ( select column_name
from (
select c.column_name
from all_tab_columns c
where c.table_name = m.table_name
and c.owner = 'owner1'
)
)
loop
--I have this:
execute immediate ' insert into MY-table value (select ' || i.column_name || ' from ' || m.table_name || ')';
--I want this but it does not work of course:
V_ROWNUM := execute immediate 'select count(*) from ' || m.table_name;
execute immediate ' insert into MY-table value (select ' || i.column_name || ', ' || V_ROWNUM || ' from ' || m.table_name || ')';
end loop;
end loop;
end;
/
I count not use the "insert into" because I am not selecting from 1 table but the table I want to select from changes every round.
There are three things wrong with your dynamic SQL.
EXECUTE IMMEDIATE is not a function: the proper syntax is execute immediate '<<query>>' into <<variable>>.
An INSERT statement takes a VALUES clause or a SELECT but not both. SELECT would be very wrong in this case. Also note that it's VALUES not VALUE.
COLUMN_NAME is a string literal in the dynamic SQL so it needs to be in quotes. But because the SQL statement is itself a string, quotes in dynamic strings need to be escaped so it should be `'''||column_name||'''.
So the corrected version will look something like this
declare
Cursor C_TABLE is
select trim(table_name) as table_name
from all_tables
where table_name in ('T1', 'T2', 'T3');
V_ROWNUM number;
begin
for m in C_TABLE
loop
for i in ( select column_name
from (
select c.column_name
from all_tab_columns c
where c.table_name = m.table_name
and c.owner = 'owner1'
)
)
loop
execute immediate 'select count(*) from ' || m.table_name into V_ROWNUM;
execute immediate 'insert into MY_table values ( ''' || i.column_name || ''', ' || V_ROWNUM || ')';
end loop;
end loop;
end;
/
Dynamic SQL is hard because it turns compilation errors into runtime errors. It is good practice to write the statements first as static SQL. Once you have got the basic syntax right you can convert it into dynamic SQL.
you can't assign the result of execute immediate to a variable. it is not a function.
but you can do it by using the into_clause e.g.
execute immediate 'select count(*) from ' || m.table_name into V_ROWNUM ;
DECLARE
alltables record;
table_all varchar;
BEGIN
for alltables in select distinct table_name , column_name
from information_schema.colunms
loop
table_all = alltables.table_name;
raise notice 'TAB_Name:% , table_all;
end loop;
return table_all;
In here, I can see all tables in (raise notice 'TAB_Name:% , table_all;) message tab in PgAdmin
but Data output tab (return table_all;) return only one column
How can I show all the tables in the data output tab?
I am not sure, if I understand to your query. You wont to write table function probably.
CREATE OR REPLACE FUNCTION xxx
RETURNS TABLE(table_name text, column_name text)
AS $$
BEGIN
FOR table_name, column_name IN
SELECT c.table_name, c.column_name
FROM information_schema.columns
LOOP
RETURN NEXT;
END LOOP;
RETURN;
END;
$$ LANGUAGE plpgsql;
or little bit simply and little bit faster
CREATE OR REPLACE FUNCTION xxx
RETURNS TABLE(table_name text, column_name text)
AS $$
BEGIN
RETURN QUERY
SELECT c.table_name, c.column_name
FROM information_schema.columns
RETURN;
END;
$$ LANGUAGE plpgsql;
you can call it
SELECT * FROM xxx();