ERROR: function pg_catalog.extract(unknown, integer) does not exist - sql

I am writing an SQL query for creating the partitions which looks like:
DO
$$
DECLARE
table_name text := 'table_1';
start_date date := (SELECT MIN(create_date)
FROM db.table);
end_date date := (SELECT MAX(create_date)
FROM db.table);
partition_interval interval := '1 day';
partition_column_value text;
BEGIN
FOR partition_column_value IN SELECT start_date +
(generate_series * extract(day from partition_interval)::integer)::date
FROM generate_series(0, extract(day from end_date - start_date::date) /
extract(day from partition_interval))
LOOP
EXECUTE format(
'create table if not exists %1$s_%2$s partition of %1$s for values in (%2$s) partition by list (create_date)',
table_name, partition_column_value::date);
END LOOP;
END
$$;
I get an error:
[42883] ERROR: function pg_catalog.extract(unknown, integer) does not exist
Hint: No function matches the given name and argument types. You might need to add explicit type casts.
Where: PL/pgSQL function inline_code_block line 9 at FOR over SELECT rows

The immediate cause of the error msg is this:
extract(day from end_date - start_date::date)
It's nonsense to cast start_date::date, start_date being type date to begin with. More importantly, date - date yields integer (not interval like you might assume). And extract() does not operate on integer input.
I removed more confusion and noise to arrive at this:
DO
$do$
DECLARE
table_name text := 'table_1';
partition_interval integer := 1; -- given in days!!
start_date date;
end_date date;
partition_column_value text;
BEGIN
SELECT INTO start_date, end_date -- two assignments for the price of one
min(create_date), max(create_date)
FROM db.table;
FOR partition_column_value IN
SELECT start_date + g * partition_interval -- date + int → date
FROM generate_series(0, (end_date - start_date) -- date - date → int
/ partition_interval) g
LOOP
EXECUTE format(
'CREATE TABLE IF NOT EXISTS %1$I PARTITION OF %1$I
FOR VALUES IN (%3$L) PARTITION BY LIST (create_date)'
, table_name || to_char(partition_column_value, '"_"yyyymmdd') -- !
, table_name
, partition_column_value::text -- only covers single day!!
);
END LOOP;
END
$do$;
This should work.
But it only makes sense for the example interval of '1 day'. For longer intervals, concatenate the list of days per partition or switch to range partitioning ...

Related

Common Table Expressions: Relation does not exist

I am using postgresql version 10.3
I am working with a Common Table Expressions inside a function. The function code is following:
CREATE OR REPLACE FUNCTION subscriptions.to_supply_patients(
character varying,
timestamp with time zone)
RETURNS void
LANGUAGE 'plpgsql'
COST 100
VOLATILE
AS $BODY$
declare
aws_sub_pro alias for $1; -- health professional aws_sub
tour_date alias for $2; -- tour date to manage patients covered accounts
number_cover int; -- the number of account to manage
sub_list integer[]; -- array list for subscribers covered
no_sub_list integer[];-- array list for no subscribers covered
date_tour timestamp with time zone;--tour date to manage patients covered accounts converted with time zone converted to 23:59:59
begin
select subscriptions.convert_date_to_datetime((select date(tour_date)),'23:59:59','0 days') into date_tour; -- function to convert time to 23:59:59
select count(*) from subscriptions.cover where aws_sub = aws_sub_pro into number_cover; -- global number of patient to cover
if number_cover > 0 then
begin
if tour_date >= current_timestamp then -- if tour date is later than today date
begin
with cover_list as (
select id_cover from subscriptions.cover where aws_sub = aws_sub_pro and cover_duration >0 and is_covered_auto = true
-- we selectionned here the patients list to cover for health pro with aws_sub = aws_sub_pro
),
sub_cover_list as (
select id_subs from subscriptions.subscribers_cover inner join cover_list on cover_list.id_cover = subscribers_cover.id_cover
-- list of subscribers covered
),
no_sub_cover_list as (
select id_free_trial from subscriptions.no_subscribers_cover inner join cover_list on cover_list.id_cover = no_subscribers_cover.id_cover
-- list of no subscribers covered
)
-----------------------------------------------------------------------------------------------------------------------------
select array( select id_subs from subscriptions.subscribers_cover inner join cover_list on cover_list.id_cover = subscribers_cover.id_cover
) into sub_list; -- convert list of subscribers covered into array list
if array_upper(sub_list,1) <>0 then -- if array list is not empty
begin
for i in 1 .. array_upper (sub_list,1) loop -- for every one in this list
if date_tour = (select sub_deadline_date from subscriptions.subscription where id_subs =sub_list[i] ) then -- if tour date is equals to
-- the deadline date
begin
update subscriptions.subscription
set
sub_expiration_date = sub_expiration_date + interval'30 days', -- add 30 days to the exp date
sub_deadline_date = sub_deadline_date + interval'30 days', -- add 30 date to deadline date
sub_source = aws_sub_pro, -- supply source is no the professional
is_sub_activ = false -- we turn off patients subscription
where id_subs = (select id_subs from subscriptions.subscription where id_subs =sub_list[i] );
end;
end if;
end loop;
end;
end if;
--------------------------------------------------------------------------------------------------------------------------------
select array(select id_free_trial from subscriptions.no_subscribers_cover inner join cover_list on cover_list.id_cover = no_subscribers_cover.id_cover
) into no_sub_list;
if array_upper(no_sub_list,1) <>0 then
begin
for i in 1 .. array_upper (no_sub_list,1) loop
if date_tour = (select expiration_date from subscriptions.free_trial where id_free_trial =no_sub_list[i] and is_free_trial_activ = true ) then
begin
update subscriptions.free_trial
set
expiration_date = expiration_date + interval'30 days'
where id_free_trial = (select id_free_trial from subscriptions.free_trial where id_free_trial =no_sub_list[i] );
end;
end if;
end loop;
end;
end if;
end;
else
raise 'tour date must be later than today''s date' using errcode='71';
end if;
end;
else
raise notice 'your cover list is empty. you don''t have any patient to cover' using errcode='70';
end if;
end;
$BODY$;
ALTER FUNCTION subscriptions.to_supply_patients(character varying, timestamp with time zone)
OWNER TO master_pgsql_hygeexv2;
When I run this function, I get the following error:
ERROR: ERREUR: la relation « cover_list » n'existe pas
LINE 1: ...rom subscriptions.no_subscribers_cover inner join cover_list...
Translated:
(the relation « cover_list » does not exist )
I tried to run only the CTE in a query window and I get the same error message.
Is there something I am missing?
The CTE is part of the SQL statement and not visible anywhere outside of it.
So you can use cover_list only in the SELECT statement with the WITH clause.
Either repeat the WITH clause in the second SELECT statement or refractor the code so you need only a single query.
An alternative would be to use a temporary table.

PostgreSQL function Return table

i want to setup a function on PostgreSQL which returns a table. This is the source code of the function:
CREATE OR REPLACE FUNCTION feiertag(inDate Date)
RETURNS TABLE (eingabeDatum DATE, f_heute INT, f_1 INT, f_2 INT, f_3 INT, f_5 INT)
AS $$
DECLARE
f_heute integer := 0;
f_1 integer := 0;
f_2 integer := 0;
f_3 integer := 0;
f_5 integer := 0;
BEGIN
SELECT 1 INTO f_heute FROM feiertage where datum = inDate;
SELECT 1 INTO f_1 FROM feiertage where datum = (inDate + interval '1' day);
SELECT 1 INTO f_2 FROM feiertage where datum = (inDate + interval '2' day);
SELECT 1 INTO f_3 FROM feiertage where datum = (inDate + interval '3' day);
SELECT 1 INTO f_5 FROM feiertage where datum = (inDate + interval '5' day);
RETURN QUERY SELECT inDate as eingabeDatum, coalesce(f_heute, 0) as f_heute, coalesce(f_1,0) as f_1, coalesce(f_2,0) as f_2, coalesce(f_3,0) as f_3, coalesce(f_5,0) as f_5 ;
END;
$$ LANGUAGE plpgsql;
Calling the function returns only one column with ',' separated values:
psql (9.5.12)
Type "help" for help.
tarec=> select feiertag('2017-01-01');
feiertag
------------------------
(2017-01-01,1,0,0,0,0)
(1 row)
I expected differnt columns (one for each value as the table is specified at the beginning of the function) and not only one with all values. Does anybody know why this is happening and how i could fix this?
Thanks
Timo
Use
SELECT *
FROM feiertag('2017-01-01');
instead of
SELECT feiertag('2017-01-01');
to get the result as a table.
(Treat the function as if it were a table.)

how to get names of partition in oracle while i input a date

I have a table with many partitions range. I need to get the name of all partition when I give a date.
For eg: if I input date 20/09/2014, it should list all partitions before that given date.
create or replace function get_part_name(p_date in date)
return varchar2 is
d date;
retp varchar2(30);
mind date:=to_date('4444-01-01','yyyy-mm-dd');
str varchar2(32000);
cursor c is
select high_value, partition_name p
from user_tab_partitions
where table_name='TEST';
begin
for r in c loop
str := r.high_value;
execute immediate 'select '||str||' from dual' into d;
if p_date<d and d<mind then
retp:=r.p;
mind:=d;
end if;
end loop;
return retp;
end;
This is returing a single date. I need all the dates, is it possible?
WITH DATA AS (
select table_name,
partition_name,
to_date (
trim (
'''' from regexp_substr (
extractvalue (
dbms_xmlgen.getxmltype (
'select high_value from all_tab_partitions where table_name='''
|| table_name
|| ''' and table_owner = '''
|| table_owner
|| ''' and partition_name = '''
|| partition_name
|| ''''),
'//text()'),
'''.*?''')),
'syyyy-mm-dd hh24:mi:ss')
high_value_in_date_format
FROM all_tab_partitions
WHERE table_name = 'SALES' AND table_owner = 'SH'
)
SELECT * FROM DATA
WHERE high_value_in_date_format < SYSDATE
/
TABLE_NAME PARTITION_NAME HIGH_VALU
-------------------- -------------------- ---------
SALES SALES_Q4_2003 01-JAN-04
SALES SALES_Q4_2002 01-JAN-03
SALES SALES_Q4_2001 01-JAN-02
SALES SALES_Q4_2000 01-JAN-01
SALES SALES_Q4_1999 01-JAN-00
SALES SALES_Q4_1998 01-JAN-99
SALES SALES_Q3_2003 01-OCT-03
SALES SALES_Q3_2002 01-OCT-02
SALES SALES_Q3_2001 01-OCT-01
SALES SALES_Q3_2000 01-OCT-00
SALES SALES_Q3_1999 01-OCT-99
SALES SALES_Q3_1998 01-OCT-98
SALES SALES_Q2_2003 01-JUL-03
SALES SALES_Q2_2002 01-JUL-02
SALES SALES_Q2_2001 01-JUL-01
SALES SALES_Q2_2000 01-JUL-00
SALES SALES_Q2_1999 01-JUL-99
SALES SALES_Q2_1998 01-JUL-98
SALES SALES_Q1_2003 01-APR-03
SALES SALES_Q1_2002 01-APR-02
SALES SALES_Q1_2001 01-APR-01
SALES SALES_Q1_2000 01-APR-00
SALES SALES_Q1_1999 01-APR-99
SALES SALES_Q1_1998 01-APR-98
SALES SALES_H2_1997 01-JAN-98
SALES SALES_H1_1997 01-JUL-97
SALES SALES_1996 01-JAN-97
SALES SALES_1995 01-JAN-96
28 rows selected.
SQL>
Use your desired date in place of SYSDATE in above query. Or you can pass it as INPUT through the FUNCTION and RETURN the result set.
Find Partition name using date IF you have a meaningful date column in the table in Oracle DB
WITH table_sample AS (select COLUMN_WITH_DATE from table SAMPLE (5))
SELECT uo.SUBOBJECT_NAME AS "PARTITION_NAME_1"
FROM table_sample sw,
SYS.USER_OBJECTS uo
WHERE sw.COLUMN_WITH_DATE = TRUNC(SYSDATE) -- ENTER DATE HERE AS 'DD-MM-YYYY 00:00:00'
AND OBJECT_ID = dbms_rowid.rowid_object(sw.rowid)
AND ROWNUM < 2;
I know this issue is old, but I ran across it looking for something and thought I'd weigh in to prevent others from going down the road above.The answers provided make it way more difficult than it has to be. You can use the dbms_rowid.rowid_object() to get the data object id of the row and join that with either user_objects, all_objects or dba_objects (whichever fits your needs).
Something like this should work ...
select distinct
o.subobject_name
from user_objects o,
my_table x
where o.object_name = 'MY_TABLE'
and dbms_rowid.rowid_object( x.rowid ) = o.data_object_id
and trunc( x.stamp ) > ( current_timestamp - 31 );
I use this for code that has to determine partition names, for various reasons (queries, DML, etc...). I even used this very recently identify the partitions which have fallen outside of a defined window, as an auto-drop feature for an interval partitioned table.
Single SQL Solution: (high_value has to converted to date with correct format!)
SELECT
partition_name p
from user_tab_partitions
where table_name='TEST'
AND high_value < to_date('4444-01-01','yyyy-mm-dd') AND high_value > SYSDATE;
PL/SQL Solution:
Create a global type;
create type ty_partition_names is table of varchar2(30);
/
Function:
create or replace function get_part_name(p_date in date)
return ty_partition_names is
d date;
retp ty_partition_names := ty_partition_names();
mind date:=to_date('4444-01-01','yyyy-mm-dd');
str varchar2(32000);
idx number := 0;
cursor c is
select high_value, partition_name p
from user_tab_partitions
where table_name='test';
begin
for r in c loop
str := r.high_value;
/*execute immediate 'select '||str||' from dual' into d; */
if p_date<str and str <mind then
retp.extend(1);
idx := idx + 1;
retp(idx):=r.p;
mind:=str;
end if;
end loop;
return retp;
end;
And Finally,
SELECT * FROM TABLE(get_part_name(sysdate));

Find all tables updated on a specific date

I'm using an Oracle DB, and I'm trying to find all tables that were updated on a certain date. All of the tables that track updates have a column called DT_UPDATE. I've been trying this:
SELECT * FROM
(SELECT TABLE_NAME FROM ALL_TAB_COLUMNS WHERE COLUMN_NAME = 'DT_UPDATE')
WHERE DT_UPDATE = <date>
But get this error:
ORA-00904: "DT_UPDATE": invalid identifier
00904. 00000 - "%s: invalid identifier"
*Cause:
*Action:
Error at Line: 3 Column: 7
I've also tried aliasing the nested Select clause.
As #zaratustra said, you have to use dynamic SQL. You can do something like this:
set serveroutput on
declare
counter number;
begin
for r in (
select owner, table_name
from all_tab_columns
where column_name = 'DT_UPDATE'
) loop
execute immediate 'select count(*) from "'
|| r.owner || '"."' || r.table_name
|| '" where dt_update = :dt and rownum = 1'
into counter
using date '2014-07-07';
if counter = 1 then
dbms_output.put_line(r.table_name);
end if;
end loop;
end;
/
For each table_name (and owner, for completeness) identified in all_tab_columns as having a column called dt_update, a new dynamic select is generated, in the form:
select count(*) from "<owner>"."<table_name>"
where dt_update = date '2014-07-07'
and rownum = 1;
The rownum = 1 filter lets the query execution stop as soon as a matching row is found; since you said you want to know which tables were updated, not how many rows or exactly which rows, if one row matches then that is all you really need to know. So for every table the dynamic query gets either 0 or 1.
For any tables that have at least one row matching the date, this printd the table name using dbms_output, so you have to have that enabled - with set serveroutput on, or with the DBMS_OUTPUT panel in SQL Developer, or your favourite client's equivalent.
If I create some tables with that column, but only populate one with the date I'm looking for:
create table tab1 (dt_update date);
create table tab2 (dt_update date);
create table tab3 (dt_update date);
insert into tab1 values (trunc(sysdate) - 1);
insert into tab2 values (trunc(sysdate));
... then running my anonymous block produces:
anonymous block completed
TAB1
Use your own target date, obviously. This assumes your date field doesn't contain a time component. If it does then you'd need to turn that into a range to cover the whole day.
You could also turn this into a pipelined function that takes a date as an argument; this also handles date fields with time elements:
create or replace function get_updated_tables(p_date date)
return sys.odcivarchar2list pipelined as
counter number;
begin
for r in (
select owner, table_name
from all_tab_columns
where column_name = 'DT_UPDATE'
) loop
execute immediate 'select count(*) from "'
|| r.owner || '"."' || r.table_name
|| '" where dt_update >= :dt1 and dt_update < :dt2'
|| ' and rownum = 1'
into counter
using p_date, p_date + interval '1' day;
if counter = 1 then
pipe row (r.table_name);
end if;
end loop;
end;
/
Then you can query it with:
select column_value from table(get_updated_tables(date '2014-07-07'));
COLUMN_VALUE
------------------------------
TAB1
Dynamic SQL is interesting, as you said in a comment, but should only be used when necessary. The generated statement can't be parsed until it's executed, so you might not spot syntax or other errors until run-time. Also make sure you use bind variables for values (but not object names) to avoid SQL injection.
Let's assume we have three tables with the field dt_update, and each of them has one record (doesn't matter if more):
create table tt1 (
dt_update date
);
insert into tt1 values (sysdate);
create table tt2 (
dt_update date
);
insert into tt2 values (sysdate - 1);
create table tt3 (
dt_update date
);
insert into tt3 values (sysdate - 2);
This PL/SQL anonym block prints only tables' names that have record with the value of the column dt_update more than or equals today:
declare
type table_names_tp is table of user_tables.table_name%type index by binary_integer;
table_names table_names_tp;
l_res number(1);
l_deadline date := to_date('2014-07-08', 'YYYY-MM-DD');
begin
select table_name
BULK COLLECT INTO table_names
from user_tab_columns
where lower(column_name) = 'dt_update'
;
for i in table_names.first..table_names.last
loop
execute immediate 'select count(*) from dual where exists (select null from ' || table_names(i) || ' where dt_update >= :dead_line)'
into l_res
using l_deadline;
if l_res = 1
then
DBMS_OUTPUT.put_line('Table ' || table_names(i) || ' was updated after ' || l_deadline);
end if;
end loop;
end;
You can use this code as an example to start writing your code. Pay carefully attention to protect yourself from SQL injections, DO NOT(!) use concatenation of your values, always use bind variables instead. It also helps you to store a cached query plan in SGA, the application will read data from the SGA area and perform soft parsing.

Declaring timestamp in postgres

I have to insert a timestamp value into a table. I am inserting values by writing an stored procedure.
This is the code to my stored procedure.
CREATE OR REPLACE FUNCTION dataInsert_Schedule() RETURNS boolean As
$$
DECLARE
i integer;
j integer;
dur integer;
tup Channel%rowtype;
BEGIN
FOR tup IN SELECT * FROM Channel
LOOP
for i in 0..6 LOOP --days
for j in 0..23 LOOP --hours
dur = round((random() * 2) + 1);
IF i + dur > 24 then
dur = 24 - i;
END IF;
INSERT INTO Schedule VALUES(tup.Channel_ID, round((random() * 999) + 1),( current_date + (integer to_char(i,'9')) )+ (interval to_char(j,'99') || ' hour'), (interval dur ||' hour'));
i = i + dur - 1;
END LOOP;
END LOOP;
END LOOP;
return true;
END
$$ LANGUAGE plpgsql;
When I write the query Select * From dataInsert_Schedule(); I got the following error :
ERROR: syntax error at or near "to_char"
LINE 1: ...d((random() * 999) + 1),( current_date + (integer to_char( $...
^
QUERY: INSERT INTO Schedule VALUES( $1 , round((random() * 999) + 1),( current_date + (integer to_char( $2 ,'9')) )+ (interval to_char( $3 ,'99') || ' hour'), (interval $4 ||' hour'))
CONTEXT: SQL statement in PL/PgSQL function "datainsert_schedule" near line 15
********** Error **********
ERROR: syntax error at or near "to_char"
SQL state: 42601
Context: SQL statement in PL/PgSQL function "datainsert_schedule" near line 15
I First tried this
INSERT INTO Schedule VALUES(tup.Channel_ID, round((random() * 999) + 1),( current_date + (integer ''||i) )+ (interval (j ||' hour')), (interval dur ||' hour'));
way of inserting, but I was getting the same kind of error.
Why I am getting this error?
And the schedule table is defined as following:
CREATE TABLE Schedule(
Channel_ID Integer REFERENCES Channel(Channel_ID),
Program_ID Integer REFERENCES Program(Program_ID),
Start_Time Timestamp NOT NULL,
Duration Interval NOT NULL,
CONSTRAINT Schedule_Key PRIMARY KEY(Channel_ID, Program_ID)
);
It works for me like this:
select (to_char(1,'99') || ' hour')::interval;
You don't need the to_char:
select (1 || ' hour')::interval;
interval
----------
01:00:00
So this would be it:
INSERT INTO Schedule
VALUES (
tup.Channel_ID,
round((random() * 999) + 1),
(current_date + i::integer) + (j || ' hour')::interval,
(dur ||' hour')::interval
)
A type name may be specified before a string constant to cast it into this type, but it applies only to constants. So integer '123' is fine but integer to_char(something) or interval column_name are not permitted, which is why your query fails.
This is explained in the SQL syntax chapter from the manual, specifically this paragraph: Constants of Other Types.
Excerpt:
A constant of an arbitrary type can be entered using any one of the
following notations:
type 'string'
'string'::type
CAST ( 'string' AS type )
and below:
The ::, CAST(), and function-call syntaxes can also be used to specify
run-time type conversions of arbitrary expressions
The point relevant to the question being that type 'string' notation is not included in the syntaxes that can accept arbitrary expressions, contrary to :: and cast().