用于为具有 7000 个分区的分区 Table 的日期范围生成语法的脚本
Script to Generate Syntax for Date Range Partitioned Table with 7000 partitions
我必须创建一个日期范围为 20 年的分区 table 范围。每个分区将包含一天的数据。
因此 table 将有大约 7,000 个分区 ( 20*365 )。因此手动创建将非常容易出错。
有没有可以生成所有分区语法的脚本?
这样的事情怎么样?
按范围划分 (date_col)
间隔 (numtoyminterval(1, 'DAY'))
(分区P_OLDDATA
值小于
(to_date('2021 年 1 月 1 日','DD-MON-YYYY'))
);
该语句创建一个 table 具有:
< 2021 年 1 月 1 日的锚定分区:这意味着任何早于 2021 年 1 月 1 日的数据都将进入分区 P_OLDDATA。
1 天间隔:这意味着日期为 2021 年 1 月 1 日或之后的任何数据都将进入其自己的日分区。如果这样的分区不存在,Oracle 将自动创建它。
总而言之,您的历史数据将在 1 个分区中,您的新数据将每天分区在其自己的分区中。
依赖名称是糟糕的编码,您的应用程序应该得到修复。
您可以使用简单的查询,而不是使用分区名称来查找每天的行数。
用正确的变量名替换 create_date。
select trunc(created_date), count(*) as num_created
from the_table
group by trunc(created_date);
trunc() 是必需的,因为在 Oracle 中 DATE 列也包含时间部分(或者如果您的 created_date 实际上是时间戳而不是日期)。
这是我不久前写的分区维护例程中的一些代码...应该可以帮助您入门。我过去常常安排它每晚寻找间隔并清理它们
declare
l_high_value varchar2(4000);
l_partition_boundary date;
l_parname varchar2(40);
l_ddl varchar2(4000);
begin
for i in (
select a.owner, a.table_name, a.partitioning_type, t.partition_name, t.high_value, a.interval
from all_part_tables a, all_tab_partitions t
where a.owner = ...
and a.partitioning_type = 'RANGE'
and a.table_name not like 'BIN$%'
and a.interval is not null
and t.table_owner = a.owner
and t.table_name = a.table_name
and regexp_like(t.partition_name,'^SYS_P[[:digit:]]{1,10}') )
loop
begin
l_high_value := i.high_value;
execute immediate 'select '||l_high_value||'-1 from dual' into l_partition_boundary;
if upper(i.interval) like 'NUMTODSINTERVAL%' then
l_parname := substr(i.table_name,1,21)||'_'||to_char(l_partition_boundary,'YYYYMMDD');
elsif upper(i.interval) like 'NUMTOYMINTERVAL%MONTH%' then
l_parname := substr(i.table_name,1,23)||'_'||to_char(l_partition_boundary,'YYYYMM');
elsif upper(i.interval) like 'NUMTOYMINTERVAL%YEAR%' then
l_parname := substr(i.table_name,1,25)||'_'||to_char(l_partition_boundary,'YYYY');
elsif upper(i.interval) like 'INTERVAL''%''DAY%' then
l_parname := substr(i.table_name,1,21)||'_'||to_char(l_partition_boundary,'YYYYMMDD');
elsif upper(i.interval) like 'INTERVAL''%''MONTH%' then
l_parname := substr(i.table_name,1,23)||'_'||to_char(l_partition_boundary,'YYYYMM');
elsif upper(i.interval) like 'INTERVAL''%''YEAR%' then
l_parname := substr(i.table_name,1,25)||'_'||to_char(l_partition_boundary,'YYYY');
else
l_parname := null;
dbms_output.put_line ('Cannot determine correct partition name for table ' || i.table_name || ' partition ' || i.partition_name);
end if;
exception
when others then
l_parname := null;
dbms_output.put_line ('Cannot determine correct partition name for table ' || i.table_name || ' partition ' || i.partition_name||' hi='||l_high_value);
end;
if (l_parname is not null) then
l_ddl := 'alter table '||i.owner||'.'||i.table_name||' rename partition '||i.partition_name||' to '||l_parname;
dbms_output.put_line(l_ddl);
begin
execute immediate l_ddl;
exception
when others then
dbms_output.put_line('Failed with '||sqlerrm);
end;
end if;
end loop;
--
-- then we can use those new partition names to do the same for indexes
--
for i in (
with tabpar as (
select /*+ materialize */
a.owner, a.table_name, a.partitioning_type, t.partition_name, t.partition_position
from all_part_tables a, all_tab_partitions t
where a.owner = ...
and a.partitioning_type = 'RANGE'
and a.table_name not like 'BIN$%'
and a.interval is not null
and t.table_owner = a.owner
and t.table_name = a.table_name
), indpar as (
select /*+ materialize */
ai.owner, ai.table_name, ai.index_name, i.partition_name, i.partition_position
from all_part_indexes ai, all_ind_partitions i
where ai.owner = ...
and ai.partitioning_type = 'RANGE'
and ai.table_name not like 'BIN$%'
and ai.interval is not null
and i.index_owner = ai.owner
and ai.index_name = i.index_name
and ai.locality = 'LOCAL'
)
select t.owner, t.table_name, t.partition_name new_par_name, i.index_name, i.partition_name
from tabpar t, indpar i
where i.owner = t.owner
and i.table_name = t.table_name
and i.partition_position = t.partition_position
and not regexp_like(t.partition_name,'^SYS_P[[:digit:]]{1,10}')
and regexp_like(i.partition_name,'^SYS_P[[:digit:]]{1,10}')
)
loop
l_ddl := 'alter index '||i.owner||'.'||i.index_name||' rename partition '||i.partition_name||' to '||i.new_par_name;
dbms_output.put_line(l_ddl);
begin
execute immediate l_ddl;
exception
when others then
dbms_output.put_line('Failed with '||sqlerrm);
end;
end loop;
end;
/
我公司有人对自动分区名称有同样的担忧,所以我们现在使用这个程序。
您可以手动 运行 它或设置计划任务以自动 运行 它。我会建议后者。
如果您喜欢这个答案,请点赞并采纳。
CREATE OR REPLACE PROCEDURE MaintainPartitions IS EXPRESSION_IS_OF_WRONG_TYPE EXCEPTION;
PRAGMA EXCEPTION_INIT(EXPRESSION_IS_OF_WRONG_TYPE, -6550);
CURSOR PartTables IS
SELECT TABLE_NAME, INTERVAL
FROM USER_PART_TABLES
WHERE PARTITIONING_TYPE = 'RANGE'
ORDER BY TABLE_NAME;
CURSOR TabParts(aTableName VARCHAR2) IS
SELECT PARTITION_NAME, HIGH_VALUE
FROM USER_TAB_PARTITIONS
WHERE regexp_like(partition_name,'^SYS_P[[:digit:]]{1,10}') AND
TABLE_NAME = aTableName AND
table_name not like 'BIN$%'
and interval is not null
ORDER BY PARTITION_POSITION;
ym INTERVAL YEAR TO MONTH;
ds INTERVAL DAY TO SECOND;
newPartName VARCHAR2(30);
PERIOD TIMESTAMP;
BEGIN
FOR aTab IN PartTables LOOP
BEGIN
EXECUTE IMMEDIATE 'BEGIN :ret := '||aTab.INTERVAL||'; END;' USING OUT ds;
ym := NULL;
EXCEPTION
WHEN EXPRESSION_IS_OF_WRONG_TYPE THEN
EXECUTE IMMEDIATE 'BEGIN :ret := '||aTab.INTERVAL||'; END;' USING OUT ym;
ds := NULL;
END;
FOR aPart IN TabParts(aTab.TABLE_NAME) LOOP
EXECUTE IMMEDIATE 'BEGIN :ret := '||aPart.HIGH_VALUE||'; END;' USING OUT PERIOD;
IF ds IS NOT NULL THEN
IF ds >= INTERVAL '7' DAY THEN
-- Weekly partition
EXECUTE IMMEDIATE 'BEGIN :ret := TO_CHAR('||aPart.HIGH_VALUE||' - :int, :fmt); END;' USING OUT newPartName, INTERVAL '1' DAY, '"P_"IYYY"W"IW';
ELSE
-- Daily partition
EXECUTE IMMEDIATE 'BEGIN :ret := TO_CHAR('||aPart.HIGH_VALUE||' - :int, :fmt); END;' USING OUT newPartName, INTERVAL '1' DAY, '"P_"YYYYMMDD';
END IF;
ELSE
IF ym = INTERVAL '3' MONTH THEN
-- Quarterly partition
EXECUTE IMMEDIATE 'BEGIN :ret := TO_CHAR('||aPart.HIGH_VALUE||' - :int, :fmt); END;' USING OUT newPartName, INTERVAL '1' DAY, '"P_"YYYY"Q"Q';
ELSE
-- Monthly partition
EXECUTE IMMEDIATE 'BEGIN :ret := TO_CHAR('||aPart.HIGH_VALUE||' - :int, :fmt); END;' USING OUT newPartName, INTERVAL '1' DAY, '"P_"YYYYMM';
END IF;
END IF;
IF newPartName <> aPart.PARTITION_NAME THEN
EXECUTE IMMEDIATE 'ALTER TABLE '||aTab.TABLE_NAME||' RENAME PARTITION '||aPart.PARTITION_NAME||' TO '||newPartName;
END IF;
END LOOP;
END LOOP;
END MaintainPartitions;
EXEC MaintainPartitions
我必须创建一个日期范围为 20 年的分区 table 范围。每个分区将包含一天的数据。
因此 table 将有大约 7,000 个分区 ( 20*365 )。因此手动创建将非常容易出错。
有没有可以生成所有分区语法的脚本?
这样的事情怎么样?
按范围划分 (date_col) 间隔 (numtoyminterval(1, 'DAY')) (分区P_OLDDATA 值小于 (to_date('2021 年 1 月 1 日','DD-MON-YYYY')) ); 该语句创建一个 table 具有:
< 2021 年 1 月 1 日的锚定分区:这意味着任何早于 2021 年 1 月 1 日的数据都将进入分区 P_OLDDATA。 1 天间隔:这意味着日期为 2021 年 1 月 1 日或之后的任何数据都将进入其自己的日分区。如果这样的分区不存在,Oracle 将自动创建它。
总而言之,您的历史数据将在 1 个分区中,您的新数据将每天分区在其自己的分区中。
依赖名称是糟糕的编码,您的应用程序应该得到修复。
您可以使用简单的查询,而不是使用分区名称来查找每天的行数。
用正确的变量名替换 create_date。
select trunc(created_date), count(*) as num_created
from the_table
group by trunc(created_date);
trunc() 是必需的,因为在 Oracle 中 DATE 列也包含时间部分(或者如果您的 created_date 实际上是时间戳而不是日期)。
这是我不久前写的分区维护例程中的一些代码...应该可以帮助您入门。我过去常常安排它每晚寻找间隔并清理它们
declare
l_high_value varchar2(4000);
l_partition_boundary date;
l_parname varchar2(40);
l_ddl varchar2(4000);
begin
for i in (
select a.owner, a.table_name, a.partitioning_type, t.partition_name, t.high_value, a.interval
from all_part_tables a, all_tab_partitions t
where a.owner = ...
and a.partitioning_type = 'RANGE'
and a.table_name not like 'BIN$%'
and a.interval is not null
and t.table_owner = a.owner
and t.table_name = a.table_name
and regexp_like(t.partition_name,'^SYS_P[[:digit:]]{1,10}') )
loop
begin
l_high_value := i.high_value;
execute immediate 'select '||l_high_value||'-1 from dual' into l_partition_boundary;
if upper(i.interval) like 'NUMTODSINTERVAL%' then
l_parname := substr(i.table_name,1,21)||'_'||to_char(l_partition_boundary,'YYYYMMDD');
elsif upper(i.interval) like 'NUMTOYMINTERVAL%MONTH%' then
l_parname := substr(i.table_name,1,23)||'_'||to_char(l_partition_boundary,'YYYYMM');
elsif upper(i.interval) like 'NUMTOYMINTERVAL%YEAR%' then
l_parname := substr(i.table_name,1,25)||'_'||to_char(l_partition_boundary,'YYYY');
elsif upper(i.interval) like 'INTERVAL''%''DAY%' then
l_parname := substr(i.table_name,1,21)||'_'||to_char(l_partition_boundary,'YYYYMMDD');
elsif upper(i.interval) like 'INTERVAL''%''MONTH%' then
l_parname := substr(i.table_name,1,23)||'_'||to_char(l_partition_boundary,'YYYYMM');
elsif upper(i.interval) like 'INTERVAL''%''YEAR%' then
l_parname := substr(i.table_name,1,25)||'_'||to_char(l_partition_boundary,'YYYY');
else
l_parname := null;
dbms_output.put_line ('Cannot determine correct partition name for table ' || i.table_name || ' partition ' || i.partition_name);
end if;
exception
when others then
l_parname := null;
dbms_output.put_line ('Cannot determine correct partition name for table ' || i.table_name || ' partition ' || i.partition_name||' hi='||l_high_value);
end;
if (l_parname is not null) then
l_ddl := 'alter table '||i.owner||'.'||i.table_name||' rename partition '||i.partition_name||' to '||l_parname;
dbms_output.put_line(l_ddl);
begin
execute immediate l_ddl;
exception
when others then
dbms_output.put_line('Failed with '||sqlerrm);
end;
end if;
end loop;
--
-- then we can use those new partition names to do the same for indexes
--
for i in (
with tabpar as (
select /*+ materialize */
a.owner, a.table_name, a.partitioning_type, t.partition_name, t.partition_position
from all_part_tables a, all_tab_partitions t
where a.owner = ...
and a.partitioning_type = 'RANGE'
and a.table_name not like 'BIN$%'
and a.interval is not null
and t.table_owner = a.owner
and t.table_name = a.table_name
), indpar as (
select /*+ materialize */
ai.owner, ai.table_name, ai.index_name, i.partition_name, i.partition_position
from all_part_indexes ai, all_ind_partitions i
where ai.owner = ...
and ai.partitioning_type = 'RANGE'
and ai.table_name not like 'BIN$%'
and ai.interval is not null
and i.index_owner = ai.owner
and ai.index_name = i.index_name
and ai.locality = 'LOCAL'
)
select t.owner, t.table_name, t.partition_name new_par_name, i.index_name, i.partition_name
from tabpar t, indpar i
where i.owner = t.owner
and i.table_name = t.table_name
and i.partition_position = t.partition_position
and not regexp_like(t.partition_name,'^SYS_P[[:digit:]]{1,10}')
and regexp_like(i.partition_name,'^SYS_P[[:digit:]]{1,10}')
)
loop
l_ddl := 'alter index '||i.owner||'.'||i.index_name||' rename partition '||i.partition_name||' to '||i.new_par_name;
dbms_output.put_line(l_ddl);
begin
execute immediate l_ddl;
exception
when others then
dbms_output.put_line('Failed with '||sqlerrm);
end;
end loop;
end;
/
我公司有人对自动分区名称有同样的担忧,所以我们现在使用这个程序。
您可以手动 运行 它或设置计划任务以自动 运行 它。我会建议后者。
如果您喜欢这个答案,请点赞并采纳。
CREATE OR REPLACE PROCEDURE MaintainPartitions IS EXPRESSION_IS_OF_WRONG_TYPE EXCEPTION;
PRAGMA EXCEPTION_INIT(EXPRESSION_IS_OF_WRONG_TYPE, -6550);
CURSOR PartTables IS
SELECT TABLE_NAME, INTERVAL
FROM USER_PART_TABLES
WHERE PARTITIONING_TYPE = 'RANGE'
ORDER BY TABLE_NAME;
CURSOR TabParts(aTableName VARCHAR2) IS
SELECT PARTITION_NAME, HIGH_VALUE
FROM USER_TAB_PARTITIONS
WHERE regexp_like(partition_name,'^SYS_P[[:digit:]]{1,10}') AND
TABLE_NAME = aTableName AND
table_name not like 'BIN$%'
and interval is not null
ORDER BY PARTITION_POSITION;
ym INTERVAL YEAR TO MONTH;
ds INTERVAL DAY TO SECOND;
newPartName VARCHAR2(30);
PERIOD TIMESTAMP;
BEGIN
FOR aTab IN PartTables LOOP
BEGIN
EXECUTE IMMEDIATE 'BEGIN :ret := '||aTab.INTERVAL||'; END;' USING OUT ds;
ym := NULL;
EXCEPTION
WHEN EXPRESSION_IS_OF_WRONG_TYPE THEN
EXECUTE IMMEDIATE 'BEGIN :ret := '||aTab.INTERVAL||'; END;' USING OUT ym;
ds := NULL;
END;
FOR aPart IN TabParts(aTab.TABLE_NAME) LOOP
EXECUTE IMMEDIATE 'BEGIN :ret := '||aPart.HIGH_VALUE||'; END;' USING OUT PERIOD;
IF ds IS NOT NULL THEN
IF ds >= INTERVAL '7' DAY THEN
-- Weekly partition
EXECUTE IMMEDIATE 'BEGIN :ret := TO_CHAR('||aPart.HIGH_VALUE||' - :int, :fmt); END;' USING OUT newPartName, INTERVAL '1' DAY, '"P_"IYYY"W"IW';
ELSE
-- Daily partition
EXECUTE IMMEDIATE 'BEGIN :ret := TO_CHAR('||aPart.HIGH_VALUE||' - :int, :fmt); END;' USING OUT newPartName, INTERVAL '1' DAY, '"P_"YYYYMMDD';
END IF;
ELSE
IF ym = INTERVAL '3' MONTH THEN
-- Quarterly partition
EXECUTE IMMEDIATE 'BEGIN :ret := TO_CHAR('||aPart.HIGH_VALUE||' - :int, :fmt); END;' USING OUT newPartName, INTERVAL '1' DAY, '"P_"YYYY"Q"Q';
ELSE
-- Monthly partition
EXECUTE IMMEDIATE 'BEGIN :ret := TO_CHAR('||aPart.HIGH_VALUE||' - :int, :fmt); END;' USING OUT newPartName, INTERVAL '1' DAY, '"P_"YYYYMM';
END IF;
END IF;
IF newPartName <> aPart.PARTITION_NAME THEN
EXECUTE IMMEDIATE 'ALTER TABLE '||aTab.TABLE_NAME||' RENAME PARTITION '||aPart.PARTITION_NAME||' TO '||newPartName;
END IF;
END LOOP;
END LOOP;
END MaintainPartitions;
EXEC MaintainPartitions