I am trying to active something below in AmazonRedshift:
IF
(SELECT
count(*)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_name = 'ABC'
AND column_name = 'COLUMN'
and table_schema ='SCHEMA'
) > 0
THEN
RAISE INFO 'table present';
ELSE
RAISE INFO 'Table not present';
END IF;
I am getting error below:
Invalid operation: syntax error at or near "IF"
What am I doing wrong?
You can try this. I think it will work.
DECLARE #counter bigint=0;
SELECT #counter = count(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = 'ABC' AND column_name = 'COLUMN' and table_schema ='SCHEMA'
IF (#counter) > 0
BEGIN
print('Table exists');
END
ELSE
BEGIN
print('Table does not exists');
END
Related
I'm changing column lengths for all necessary tables but I got some errors.
I am using PostgreSQL 10 and pgAdmin4 but I couldn't see error messages.
I guess, because of the pgAdmin version. Firstly, I couldn't declare CURSOR, I don't know why? I had succeeded on Oracle.
Can you help me about this situation? My code as shown below;
do $$
DECLARE
modify_column_cursor CURSOR FOR
SELECT 'ALTER TABLE "schema_name"."' || C.TABLE_NAME || '" ALTER COLUMN'|| C.COLUMN_NAME||' varchar(128)' as alter_sql, TABLE_NAME t_name, COLUMN_NAME c_name, 128 c_length FROM information_schema.columns c WHERE column_name LIKE '%PROD_NUM' and TABLE_NAME not like '%STAGING%' UNION
SELECT 'ALTER TABLE "schema_name"."' || C.TABLE_NAME || '" ALTER COLUMN'|| C.COLUMN_NAME||' varchar(128)' as alter_sql, TABLE_NAME t_name, COLUMN_NAME c_name, 128 c_length FROM information_schema.columns c WHERE column_name LIKE '%PREV_PROD_NUM' and TABLE_NAME not like '%STAGING%';
--.
--.
--.
sql_stmt VARCHAR(800);
c_length numeric;
c_length_db numeric;
flag numeric := 0;
BEGIN
--OPEN modify_column_cursor;
for modify_column in modify_column_cursor LOOP
raise notice 'asd : %', modify_column.ex_name;
sql_stmt := 'SELECT character_maximum_length FROM information_schema.columns WHERE column_name = ''' || modify_column.c_name || ''' and table_name = ''' || modify_column.t_name || ''' and table_schema = ''schema_name''';
EXECUTE sql_stmt INTO c_length_db;
IF c_length_db > modify_column.c_length THEN
sql_stmt := 'select max(length(' || modify_column.c_name || ')) from "schema_name".' || modify_column.t_name;
EXECUTE sql_stmt INTO c_length;
IF c_length > modify_column.c_length THEN
flag := 1;
raise notice '--------------INCONSISTENED FIELD FOUND---------------';
raise notice '% - % - % Not Ok! Default field size in db: %', modify_column.t_name, modify_column.c_name, modify_column.c_length, c_length_db;
raise notice '% - % - % Not Ok! Field has a data with length: %', modify_column.t_name, modify_column.c_name, modify_column.c_length, c_length;
raise notice '-------------------------------------------------------';
raise notice ' ';
ELSE
NULL;
END IF;
ELSE
NULL;
END IF;
END LOOP;
IF flag = 0 THEN
FOR modify_column IN modify_column_cursor
LOOP
EXECUTE modify_column.alter_sql;
END LOOP;
raise notice ' ';
raise notice '-----FIELDS ARE SUCCESSFULLY MODIFIED-----';
ELSE
raise notice ' ';
raise notice '-----ERROR: SOME FIELDS ARE NOT SUITABLE TO ALTER-----';
END IF;
end$$;
I am on PostgreSQL 11 but if I remember well its pretty much the same.
If you want to absolutely use a loop to do that I corrected a little your code and injected a debug table.
You had a missing blank space and a wrong declaration of the cursor. I simply got ride of it.
You can read this excellent article on cursor on postgresql if you want : http://www.postgresqltutorial.com/plpgsql-cursor/
do $$
DECLARE
sql_stmt VARCHAR(800);
c_length numeric;
c_length_db numeric;
flag numeric := 0;
modify_column record;
begin
create table if not exists [your_schema_name].test (query varchar);
for modify_column in
SELECT 'ALTER TABLE "'||[your_schema_name]||'"."' || C.TABLE_NAME || '" ALTER COLUMN '|| C.COLUMN_NAME||' varchar(128)' as alter_sql
, TABLE_NAME t_name
, COLUMN_NAME c_name
, 128 c_length
FROM information_schema.columns c
where table_schema = ''||[your_schema_name]||''
LOOP
--raise notice 'asd : %', modify_column.ex_name;
sql_stmt := 'SELECT character_maximum_length FROM information_schema.columns WHERE column_name = ''' || modify_column.c_name || ''' and table_name = ''' || modify_column.t_name || ''' and table_schema = '''||[your_schema_name]||'''';
insert into [your_schema_name].test values (sql_stmt);
EXECUTE sql_stmt INTO c_length_db;
IF c_length_db > modify_column.c_length THEN
sql_stmt := 'select max(length(' || modify_column.c_name || ')) from "'||[your_schema_name]||'".' || modify_column.t_name;
--EXECUTE sql_stmt INTO c_length;
insert into [your_schema_name].test values (sql_stmt);
IF c_length > modify_column.c_length THEN
flag := 1;
raise notice '--------------INCONSISTENED FIELD FOUND---------------';
raise notice '% - % - % Not Ok! Default field size in db: %', modify_column.t_name, modify_column.c_name, modify_column.c_length, c_length_db;
raise notice '% - % - % Not Ok! Field has a data with length: %', modify_column.t_name, modify_column.c_name, modify_column.c_length, c_length;
raise notice '-------------------------------------------------------';
raise notice ' ';
ELSE
NULL;
END IF;
ELSE
NULL;
END IF;
END LOOP;
IF flag = 0 THEN
--FOR modify_column IN modify_column_cursor
-- LOOP
-- EXECUTE modify_column.alter_sql;
--END LOOP;
raise notice ' ';
raise notice '-----FIELDS ARE SUCCESSFULLY MODIFIED-----';
ELSE
raise notice ' ';
raise notice '-----ERROR: SOME FIELDS ARE NOT SUITABLE TO ALTER-----';
END IF;
end;
$$;
Created this Postgres Function which is working fine, but the actual requirement is to pass the input parameter in the function to the Cursor which uses the dynamic SQL as follows,
The below is the Function
CREATE OR REPLACE FUNCTION ssp2_pcat.find_shift_dates (date_to_find date)
RETURNS void
LANGUAGE 'plpgsql'
COST 100
VOLATILE
AS $BODY$
DECLARE
C1 CURSOR FOR
SELECT TABLE_NAME, 'SELECT COUNT(*) FROM ' || TABLE_NAME || ' WHERE ' ||
COLUMN_NAME || ' = '||
'CASE WHEN ' || COLUMN_NAME || ' LIKE ' || '''%START%'''||' THEN
date_to_find ELSE date_to_find-1 END;' SQL_TEXT
FROM (
SELECT TABLE_NAME, COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME IN (SELECT TABLE_NAME FROM RESET_DATES WHERE RESET_IT =
'Y') AND
UPPER(DATA_TYPE) = 'DATE'
AND (COLUMN_NAME LIKE '%START%' OR COLUMN_NAME LIKE '%END%')
AND (COLUMN_NAME NOT LIKE '%TEST%'
AND COLUMN_NAME NOT LIKE '%PCAT%'
AND COLUMN_NAME NOT LIKE '%ORDER%'
AND COLUMN_NAME NOT LIKE '%SEASON%'
AND COLUMN_NAME NOT LIKE '%_AT')
ORDER BY 1, 2) A;
END_COUNT INTEGER := 0;
START_COUNT INTEGER := 0;
TABLENAME VARCHAR(32) := 'ALFU';
l_start TIMESTAMP;
l_end TIMESTAMP;
Time_Taken VARCHAR(20);
BEGIN
l_start := clock_timestamp();
DELETE FROM SHIFT_DATES_COUNT;
FOR I IN C1 LOOP
IF I.TABLE_NAME <> TABLENAME THEN
INSERT INTO SHIFT_DATES_COUNT VALUES (TABLENAME, START_COUNT,
END_COUNT, current_timestamp::timestamp(0));
TABLENAME := I.TABLE_NAME;
END_COUNT := 0;
START_COUNT := 0;
END IF;
IF STRPOS(I.SQL_TEXT, 'END') > 0 THEN
EXECUTE I.SQL_TEXT INTO END_COUNT;
RAISE NOTICE '% ', ('END: ' || I.SQL_TEXT);
ELSE
EXECUTE I.SQL_TEXT INTO START_COUNT;
RAISE NOTICE '% ', ('START: ' || I.SQL_TEXT);
END IF;
END LOOP;
INSERT INTO SHIFT_DATES_COUNT VALUES (TABLENAME, START_COUNT, END_COUNT,
current_timestamp::timestamp(0));
RAISE NOTICE '% ', ('INSERT INTO SHIFT_DATES_COUNT Done...');
l_end := clock_timestamp();
Time_Taken := (l_end-l_start);
RAISE NOTICE '% ', ('FIND_SHIFT_DATES Took: ' || Time_Taken );
END;
$BODY$;
Please let me know how can I use the date_to_find input parameter in the Dynamic SQL in the Cursor in the above Function.
You can use unbound cursor, clause fetch to get data from cursor, and exit when not found to finish, like:
CREATE OR REPLACE FUNCTION example (p_name text) RETURNS void LANGUAGE 'plpgsql' AS $$
DECLARE
C1 refcursor;
res record;
BEGIN
OPEN c1 FOR EXECUTE 'SELECT * FROM pg_database WHERE datname like ''%'||p_name||'%''';
LOOP
FETCH c1 INTO res;
EXIT WHEN not found;
raise notice 'value datname: %',res.datname;
END LOOP;
CLOSE c1;
RETURN;
END; $$;
--in my case
select example ('test')
NOTICE: value datname: test
NOTICE: value datname: test_msmov
NOTICE: value datname: test_resources
NOTICE: value datname: test_load_table
NOTICE: value datname: test_resources2
Total query runtime: 63 msec
1 row retrieved.
You can use EXECUTE clause for open cursor, see the documentation of PostgreSQL
https://www.postgresql.org/docs/10/plpgsql-cursors.html#PLPGSQL-CURSOR-OPENING
Example:
OPEN curs1 FOR EXECUTE format('SELECT * FROM %I WHERE col1 = $1',tabname) USING keyvalue;
I'm trying to get the column size for each row in a table. That's basically the combination of these two queries:
SELECT pg_size_pretty(sum(pg_column_size(COLUMN_NAME))) FROM TABLE_NAME;
And
SELECT column_name FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'TABLE_NAME';
My first attempt was to do these two queries:
=> SELECT column_name, (SELECT pg_size_pretty(sum(pg_column_size(column_name))) FROM TABLE_NAME) FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'TABLE_NAME';
ERROR: column "columns.column_name" must appear in the GROUP BY clause or be used in an aggregate function
LINE 1: SELECT column_name, (SELECT pg_size_pretty(sum(pg_column_siz...
^
=> SELECT column_name, (SELECT pg_size_pretty(sum(pg_column_size(column_name))) FROM TABLE_NAME) FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'TABLE_NAME' GROUP BY column_name;
ERROR: more than one row returned by a subquery used as an expression
Tried the following too:
SELECT column_name, (SELECT pg_size_pretty(sum(pg_column_size(column_name))) FROM TABLE_NAME) FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'TABLE_NAME' GROUP BY 1;
Which returned:
ERROR: more than one row returned by a subquery used as an expression
When I add a LIMIT 1, the result is incorrect:
SELECT column_name,
(SELECT pg_size_pretty(sum(pg_column_size(column_name))) FROM main_apirequest LIMIT 1)
FROM information_schema.columns
WHERE table_schema = 'public' AND table_name = 'main_apirequest'
GROUP BY 1;
It looks something like this:
column_name | pg_size_pretty
------------------+----------------
api_key_id | 11 bytes
id | 3 bytes
...
When it should be something like this (which doesn't happen because of the limit 1)
=> SELECT pg_size_pretty(sum(pg_column_size(id))) FROM main_apirequest
;
pg_size_pretty
----------------
19 MB
Since you don't know the columns names in advance, but want to use the column name in the query, you'll have to use dynamic sql. Here's a quick example:
CREATE TABLE t1 (id INTEGER, txt TEXT);
INSERT INTO t1
SELECT g, random()::TEXT
FROM generate_series(1, 10) g;
Then the SQL to generate the query is:
DO $$
DECLARE
query TEXT;
BEGIN
SELECT 'SELECT ' || STRING_AGG(FORMAT('sum(pg_column_size(%1$I)) AS %1$s', column_name), ', ') || ' FROM t1'
INTO query
FROM information_schema.columns
WHERE table_schema = 'public'
AND table_name = 't1';
RAISE NOTICE '%', query;
END $$
The query created is SELECT pg_size_pretty(sum(pg_column_size(id))) AS id, pg_size_pretty(sum(pg_column_size(txt))) AS txt FROM t1
Would work the same if you had hundreds of columns.
Now to get it to generate and run the query and return you results, it really depends on what you want. If you're happy just having it print to the screen, then maybe you can format it like this instead:
DO $$
DECLARE
query TEXT;
result TEXT;
BEGIN
SELECT 'SELECT CONCAT_WS(E''\n'', ' || STRING_AGG(FORMAT('''%1$s: '' || pg_size_pretty(sum(pg_column_size(%1$I)))', column_name), ', ') || ') FROM t1'
INTO query
FROM information_schema.columns
WHERE table_schema = 'public'
AND table_name = 't1';
EXECUTE query
INTO result;
RAISE NOTICE '%', result;
END $$
That prints:
id: 40 bytes
txt: 181 bytes
If instead you want a record returned with multiple columns, I'm not too sure how you'd go about it because the number of columns and their names would be unknown. Best hack I can think around it would be to return it as JSON, then you return just one thing and there will be a variable number of fields in there with whatever column names:
CREATE OR REPLACE FUNCTION test1(_schema_name TEXT, _table_name TEXT)
RETURNS JSON AS
$$
DECLARE
query TEXT;
result JSON;
BEGIN
SELECT 'SELECT ROW_TO_JSON(cols) FROM (SELECT ' || STRING_AGG(FORMAT('pg_size_pretty(sum(pg_column_size(%1$I))) AS %1$s', column_name), ', ') || ' FROM t1) AS cols'
INTO query
FROM information_schema.columns
WHERE table_schema = _schema_name
AND table_name = _table_name;
EXECUTE query
INTO result;
RETURN result;
END
$$
LANGUAGE plpgsql;
Running it: SELECT test1('public', 't1')
Returns: {"id":"40 bytes","txt":"181 bytes"}
Can any one of you tell me how to approach this:
CREATE OR REPLACE FUNCTION name()
RETURNS ????? AS
$func$
BEGIN
SELECT * FROM tbl_a a;
SELECT * FROM tbl_b b;
END
$func$ LANGUAGE plpgsql;
Both tables have different structures.
You can use cursors but I can hardly imagine why you need such a function.
CREATE OR REPLACE FUNCTION my_multiselect(refcursor, refcursor) RETURNS VOID AS
$func$
BEGIN
OPEN $1 FOR SELECT * FROM information_schema.routines;
OPEN $2 FOR SELECT * FROM information_schema.sequences;
END
$func$ LANGUAGE plpgsql;
BEGIN;
SELECT my_multiselect('first_cursor_to_routines', 'second_cursor_to_sequences');
FETCH ALL IN first_cursor_to_routines;
FETCH ALL IN second_cursor_to_sequences;
COMMIT;
I'm not really sure what you're doing with this, but it sounds like you just want to return a union of these distinct result sets. You can do this with a dynamic query. I'm using Postgres 9.4.
CREATE OR REPLACE FUNCTION make_query(IN p_tables text[])
RETURNS void AS
$BODY$
DECLARE
v_qry text;
v_cols text;
v_types text;
v_as text;
BEGIN
EXECUTE format('
WITH sub AS (
SELECT
table_name,
column_name,
data_type
FROM
information_schema.columns
WHERE
table_name = ANY(%L)
ORDER BY
table_name,
ordinal_position)
,sub2 AS(
SELECT
DISTINCT ON (column_name, data_type)
column_name || '' '' || data_type AS def
FROM
sub
)
SELECT
string_agg(def, '','')
FROM
sub2;
',
p_tables
) INTO v_types;
v_qry := '
CREATE OR REPLACE FUNCTION name()
RETURNS TABLE(' || v_types || ') AS
$func$';
FOR i IN 1..array_upper(p_tables, 1)
LOOP
v_as := 'tbl' || i;
EXECUTE format('
WITH sub AS (
SELECT
table_name,
column_name,
data_type
FROM
information_schema.columns
WHERE
table_name = ANY(%L)
ORDER BY
table_name,
ordinal_position)
,sub2 AS(
SELECT
DISTINCT ON (column_name, data_type)
CASE WHEN table_name = ''%I''
THEN %L || ''.'' || column_name
ELSE ''NULL::'' || data_type
END AS cols
FROM
sub
)
SELECT
string_agg(cols, '','')
FROM
sub2;
',
p_tables,
p_tables[i],
v_as
) INTO v_cols;
IF i > 1 THEN
v_qry := v_qry || '
UNION ALL';
END IF;
v_qry := v_qry || '
SELECT ' || v_cols || ' FROM ' || p_tables[i] || ' AS ' || v_as;
IF i = array_upper(p_tables, 1) THEN
v_qry := v_qry || ';';
END IF;
END LOOP;
v_qry := v_qry || '
$func$ LANGUAGE sql;
';
EXECUTE v_qry;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
Sorry it looks ugly here, but this formatting helps the final product look nicer. If you're shy about executing a dynamic query like this off the bat, just replace EXECUTE v_qry; with RAISE INFO 'v_qry: %', v_qry; and it will simply print the dynamic query out in a message without executing it, so you can review what it will do once executed.
Then execute make_query() with a list of tables you want to display like this:
SELECT make_query(ARRAY['tbl_a', 'tbl_b']);
The result is that you will now have a function called name() which you can call in order to see the results of both tables at the same time, with all the union details already sorted out:
SELECT * FROM name();
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
$$;