在 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;