在 oracle select 语句中用循环联合所有
Union all with loop in oracle select statement
我在一个 oracle 数据库中工作,它有 20 个 table 具有相同结构但按年划分。所以,它从 ft_expenses_2002 开始,一直持续到 ft_expenses_2021(我写这篇文章的那一年)。在进行一些数学计算之前,我需要将所有这些 table 的列放在一起,我的第一种方法是使用 UNIAN ALL 语句。它有效,但我想知道是否有可能做一些更优雅的事情,比如使用 FOR 循环。它不仅会使查询更加优雅,而且会避免将来的维护,因为每年都会创建一个带有“_new_year”后缀的新 table。
取决于你对“优雅”的定义。
当然可以使用动态 SQL 来查找名称符合特定模式的每个 table。我的猜测是,最简单的做法是创建一个执行 union all
的视图,针对该视图编写处理代码,然后有一些动态 SQL 可以重建基于视图关于 table 的存在。然后,您可以在创建新的 table 时 运行 该过程(如果您可以连接到该过程)或基于 DDL 触发器,或者只是将其安排在清晨 运行每年/每月/每天的小时数取决于新 table 突然出现的可能性。
create or replace procedure build_view
as
l_sql_stmt varchar2(32000);
type typ_table_names is table of varchar2(256);
l_tables typ_table_names;
begin
l_sql_stmt := 'create or replace view my_view as ';
select table_name
bulk collect into l_tables
from user_tables
where table_name like 'ft_expenses%';
for i in 1 .. l_tables.count
loop
l_sql_stmt := l_sql_stmt ||
' select * from ' || l_tables(i);
if( i != 1_tables.count )
then
l_sql_stmt := l_sql_stmt ||
' union all ';
end if;
end loop;
dbms_output.put_line( l_sql_stmt );
execute immediate l_sql_stmt;
end;
为所有 table 创建一个视图:
CREATE VIEW ft_expenses AS
SELECT * FROM ft_expenses_2002
UNION ALL SELECT * FROM ft_expenses_2003
UNION ALL SELECT * FROM ft_expenses_2004
UNION ALL SELECT * FROM ft_expenses_2005
UNION ALL SELECT * FROM ft_expenses_2006
UNION ALL SELECT * FROM ft_expenses_2007
UNION ALL SELECT * FROM ft_expenses_2008
UNION ALL SELECT * FROM ft_expenses_2009
UNION ALL SELECT * FROM ft_expenses_2010
UNION ALL SELECT * FROM ft_expenses_2011
-- ...
UNION ALL SELECT * FROM ft_expenses_2021
然后只需使用视图进行查询即可。
明年添加 2022 table 然后重新创建视图,将额外的 table 添加到视图中。
或者,从原始文件创建一个 table,这样所有内容都在一个 table 中,您可以直接查询:
CREATE TABLE ft_expenses (year, col1, col2, col3) AS
SELECT 2002, col1, col2, col3 FROM ft_expenses_2002
UNION ALL SELECT 2003, col1, col2, col3 FROM ft_expenses_2003
UNION ALL SELECT 2004, col1, col2, col3 FROM ft_expenses_2004
UNION ALL SELECT 2005, col1, col2, col3 FROM ft_expenses_2005
UNION ALL SELECT 2006, col1, col2, col3 FROM ft_expenses_2006
UNION ALL SELECT 2007, col1, col2, col3 FROM ft_expenses_2007
UNION ALL SELECT 2008, col1, col2, col3 FROM ft_expenses_2008
-- ...
UNION ALL SELECT 2021, col1, col2, col3 FROM ft_expenses_2021
然后删除个人 tables(确保您首先备份所有内容)并创建视图,如果您仍然需要访问然后按原始名称:
CREATE VIEW ft_expenses_2002 (col1, col2, col3) AS
SELECT col1, col2, col3 FROM ft_expenses WHERE year = 2002;
CREATE VIEW ft_expenses_2003 (col1, col2, col3) AS
SELECT col1, col2, col3 FROM ft_expenses WHERE year = 2003;
-- ...
CREATE VIEW ft_expenses_2021 (col1, col2, col3) AS
SELECT col1, col2, col3 FROM ft_expenses WHERE year = 2021;
您对 table 的物理设置是 Oracle 6(1980 年代后期)的 最先进。
您有两种可能升级。其他答案中提出的 DIY
union all 视图,或者简单地遵循自上述版本以来 Oracle 实施的开发。
注意,我不推荐按照建议将所有数据放在一个[=104] =] - 为什么?您会发现涵盖所有数据的查询没有差异,但您会发现在一年或几年的查询中性能显着下降。
自第 6 版以来,Oracle 在这个主题上做了什么?
在 Oracle 7(1990 年代)中引入了 partitioned views - 这与 UNION ALL
视图的提议类似。
从 Oracle 8 开始 分区 概念在后续每个版本中都得到了改进。
因此,如果您想利用当前的 Oracle 功能,则应应用 分区:
- 管理accep中的数据table大小
- 提供访问的灵活性
这是一个示例,您如何在 Oracle 19c
中 migrate
我假设您 table 包含一列 trans_dt
,其中包含一个 DATE
的年份与 table 的年份 [=83] =].
从最早的 table 开始,将其更改为分区 table
alter table ft_expenses_2002 modify
partition by range(trans_dt) interval(NUMTOYMINTERVAL(1,'YEAR'))
( partition p_init values less than (DATE'2002-01-01')
) online
重命名 table 消除年份
rename ft_expenses_2002 to ft_expenses;
现在 partitioned 中的 table 包含两个分区,initial 一个和 2002 年的分区。
select PARTITION_NAME from user_tab_partitions where table_name = 'FT_EXPENSES' order by PARTITION_POSITION;
PARTITION_NAME
----------------
P_INIT
SYS_P2340
接下来的每一年都执行以下步骤
添加新分区
alter table ft_expenses
exchange partition for( DATE'2003-01-01' ) with table ft_expenses_2003
请注意,您使用 for
语法来寻址分区,因此无需知道分区名称。
此外,最新版本可以在交换语句中创建分区,因此不再需要lock table
。
最后的笔记
您可以在重组中包含索引。
一如既往地在开始前备份所有 table。
仔细测试以检查可能的限制。
我发现 here 解决我的问题的一个非常好的和快捷的方法,它是:
SELECT
GROUP_CONCAT(
CONCAT(
'SELECT * FROM `',
TABLE_NAME,
'`') SEPARATOR ' UNION ALL ')
FROM
`INFORMATION_SCHEMA`.`TABLES`
WHERE
`TABLE_NAME` LIKE 'ft_expenses_%'
INTO @sql;
PREPARE stmt FROM @sql;
EXECUTE stmt;
我在一个 oracle 数据库中工作,它有 20 个 table 具有相同结构但按年划分。所以,它从 ft_expenses_2002 开始,一直持续到 ft_expenses_2021(我写这篇文章的那一年)。在进行一些数学计算之前,我需要将所有这些 table 的列放在一起,我的第一种方法是使用 UNIAN ALL 语句。它有效,但我想知道是否有可能做一些更优雅的事情,比如使用 FOR 循环。它不仅会使查询更加优雅,而且会避免将来的维护,因为每年都会创建一个带有“_new_year”后缀的新 table。
取决于你对“优雅”的定义。
当然可以使用动态 SQL 来查找名称符合特定模式的每个 table。我的猜测是,最简单的做法是创建一个执行 union all
的视图,针对该视图编写处理代码,然后有一些动态 SQL 可以重建基于视图关于 table 的存在。然后,您可以在创建新的 table 时 运行 该过程(如果您可以连接到该过程)或基于 DDL 触发器,或者只是将其安排在清晨 运行每年/每月/每天的小时数取决于新 table 突然出现的可能性。
create or replace procedure build_view
as
l_sql_stmt varchar2(32000);
type typ_table_names is table of varchar2(256);
l_tables typ_table_names;
begin
l_sql_stmt := 'create or replace view my_view as ';
select table_name
bulk collect into l_tables
from user_tables
where table_name like 'ft_expenses%';
for i in 1 .. l_tables.count
loop
l_sql_stmt := l_sql_stmt ||
' select * from ' || l_tables(i);
if( i != 1_tables.count )
then
l_sql_stmt := l_sql_stmt ||
' union all ';
end if;
end loop;
dbms_output.put_line( l_sql_stmt );
execute immediate l_sql_stmt;
end;
为所有 table 创建一个视图:
CREATE VIEW ft_expenses AS
SELECT * FROM ft_expenses_2002
UNION ALL SELECT * FROM ft_expenses_2003
UNION ALL SELECT * FROM ft_expenses_2004
UNION ALL SELECT * FROM ft_expenses_2005
UNION ALL SELECT * FROM ft_expenses_2006
UNION ALL SELECT * FROM ft_expenses_2007
UNION ALL SELECT * FROM ft_expenses_2008
UNION ALL SELECT * FROM ft_expenses_2009
UNION ALL SELECT * FROM ft_expenses_2010
UNION ALL SELECT * FROM ft_expenses_2011
-- ...
UNION ALL SELECT * FROM ft_expenses_2021
然后只需使用视图进行查询即可。
明年添加 2022 table 然后重新创建视图,将额外的 table 添加到视图中。
或者,从原始文件创建一个 table,这样所有内容都在一个 table 中,您可以直接查询:
CREATE TABLE ft_expenses (year, col1, col2, col3) AS
SELECT 2002, col1, col2, col3 FROM ft_expenses_2002
UNION ALL SELECT 2003, col1, col2, col3 FROM ft_expenses_2003
UNION ALL SELECT 2004, col1, col2, col3 FROM ft_expenses_2004
UNION ALL SELECT 2005, col1, col2, col3 FROM ft_expenses_2005
UNION ALL SELECT 2006, col1, col2, col3 FROM ft_expenses_2006
UNION ALL SELECT 2007, col1, col2, col3 FROM ft_expenses_2007
UNION ALL SELECT 2008, col1, col2, col3 FROM ft_expenses_2008
-- ...
UNION ALL SELECT 2021, col1, col2, col3 FROM ft_expenses_2021
然后删除个人 tables(确保您首先备份所有内容)并创建视图,如果您仍然需要访问然后按原始名称:
CREATE VIEW ft_expenses_2002 (col1, col2, col3) AS
SELECT col1, col2, col3 FROM ft_expenses WHERE year = 2002;
CREATE VIEW ft_expenses_2003 (col1, col2, col3) AS
SELECT col1, col2, col3 FROM ft_expenses WHERE year = 2003;
-- ...
CREATE VIEW ft_expenses_2021 (col1, col2, col3) AS
SELECT col1, col2, col3 FROM ft_expenses WHERE year = 2021;
您对 table 的物理设置是 Oracle 6(1980 年代后期)的 最先进。
您有两种可能升级。其他答案中提出的 DIY
union all 视图,或者简单地遵循自上述版本以来 Oracle 实施的开发。
注意,我不推荐按照建议将所有数据放在一个[=104] =] - 为什么?您会发现涵盖所有数据的查询没有差异,但您会发现在一年或几年的查询中性能显着下降。
自第 6 版以来,Oracle 在这个主题上做了什么?
在 Oracle 7(1990 年代)中引入了 partitioned views - 这与 UNION ALL
视图的提议类似。
从 Oracle 8 开始 分区 概念在后续每个版本中都得到了改进。
因此,如果您想利用当前的 Oracle 功能,则应应用 分区:
- 管理accep中的数据table大小
- 提供访问的灵活性
这是一个示例,您如何在 Oracle 19c
中 migrate我假设您 table 包含一列 trans_dt
,其中包含一个 DATE
的年份与 table 的年份 [=83] =].
从最早的 table 开始,将其更改为分区 table
alter table ft_expenses_2002 modify
partition by range(trans_dt) interval(NUMTOYMINTERVAL(1,'YEAR'))
( partition p_init values less than (DATE'2002-01-01')
) online
重命名 table 消除年份
rename ft_expenses_2002 to ft_expenses;
现在 partitioned 中的 table 包含两个分区,initial 一个和 2002 年的分区。
select PARTITION_NAME from user_tab_partitions where table_name = 'FT_EXPENSES' order by PARTITION_POSITION;
PARTITION_NAME
----------------
P_INIT
SYS_P2340
接下来的每一年都执行以下步骤
添加新分区
alter table ft_expenses
exchange partition for( DATE'2003-01-01' ) with table ft_expenses_2003
请注意,您使用 for
语法来寻址分区,因此无需知道分区名称。
此外,最新版本可以在交换语句中创建分区,因此不再需要lock table
。
最后的笔记
您可以在重组中包含索引。
一如既往地在开始前备份所有 table。
仔细测试以检查可能的限制。
我发现 here 解决我的问题的一个非常好的和快捷的方法,它是:
SELECT
GROUP_CONCAT(
CONCAT(
'SELECT * FROM `',
TABLE_NAME,
'`') SEPARATOR ' UNION ALL ')
FROM
`INFORMATION_SCHEMA`.`TABLES`
WHERE
`TABLE_NAME` LIKE 'ft_expenses_%'
INTO @sql;
PREPARE stmt FROM @sql;
EXECUTE stmt;