oracle 数据库版本的 switch case 表达式

Switch case expression for oracle database versions

我需要查询 Oracle 数据库的补丁状态。从 Oracle 版本 12c 开始,视图 sys.REGISTRY$HISTORY 被视图 DBA_REGISTRY_SQLPATCH 取代。在像 11g 这样的旧版本上,视图 dba_registry_sqlpatch 不存在。以下查询在 oracle 版本 < 12c 上创建错误,因为视图 ​​dba_registry_sqlpatch 不存在。我需要构建一个在所有 oracle 数据库版本上运行的查询。我不能使用 PL/SQL。我觉得应该用case表达式来解决

/* Query for version < 11g: */
SELECT MIN (diff) diff, MIN (zeile) zeile
  FROM (SELECT TO_CHAR (TRUNC (SYSDATE - TRUNC (action_time)), '9999') DIFF,
                  'DIFF : '
               || TO_CHAR (TRUNC (SYSDATE - TRUNC (action_time)), '9999')
               || ' DAYS '
               || 'ACTION='
               || action
               || ' VERSION='
               || version
               || ' DATE='
               || TO_CHAR (action_time, 'yyyymmdd')
               || ' ID='
               || TO_CHAR (id, '09')
               || ' COMMENTS='
               || comments
               || ' PORT='
               || (SELECT DBMS_UTILITY.port_string
                     FROM DUAL)
                  ZEILE
          FROM sys.REGISTRY$HISTORY
         WHERE action_time = (SELECT MAX (action_time)
                                FROM sys.REGISTRY$HISTORY
                               WHERE action IN ('APPLY', 'ROLLBACK'))
        UNION ALL
        /*Query for version 12c: */
        (SELECT TO_CHAR (TRUNC (SYSDATE - TRUNC (action_time)), '9999') DIFF,
                   'DIFF : '
                || TO_CHAR (TRUNC (SYSDATE - TRUNC (action_time)), '9999')
                || ' DAYS '
                || 'ACTION='
                || action
                || ' VERSION='
                || version
                || ' DATE='
                || TO_CHAR (action_time, 'yyyymmdd')
                || ' ID='
                || TO_CHAR (patch_id)
                || ' COMMENTS='
                || description
                || ' PORT='
                || (SELECT DBMS_UTILITY.port_string
                      FROM DUAL)
                   ZEILE
           FROM dba_registry_sqlpatch
          WHERE action_time = (SELECT MAX (action_time)
                                 FROM dba_registry_sqlpatch
                                WHERE action IN ('APPLY', 'ROLLBACK')))
        UNION ALL
        /* Query for no patch installed: */
        SELECT (SELECT TO_CHAR (TRUNC (SYSDATE - TRUNC (created)), '9999')
                  FROM v$database)
                  DIFF,
                  'DIFF : '
               || (SELECT TO_CHAR (TRUNC (SYSDATE - TRUNC (created)), '9999')
                     FROM v$database)
               || ' DAYS ACTION=N./A. VERSION='
               || (SELECT SUBSTR (version, 1, 8)
                     FROM v$instance)
               || ' DATE='
               || (SELECT TO_CHAR (created, 'yyyymmdd')
                     FROM v$database)
               || ' ID= 99 COMMENTS='
               || (SELECT SUBSTR (version, 1, 8)
                     FROM v$instance)
               || ' PORT='
               || (SELECT DBMS_UTILITY.port_string
                     FROM DUAL)
                  ZEILE
          FROM DUAL)
 WHERE ROWNUM = 1;

11 天前修补的 Oracle 12c 数据库的示例输出: 差异:11 天操作=应用版本=12.1.0.2 日期=20160429 ID=22809813 评论=WINDOWS 数据库捆绑补丁 12.1.0.2.160419(64 位):22809813 端口=IBMPC/WIN_NT64-9.1.0

case 表达式无法解决您的问题。被查询的 tables 必须在解析时知道 - 你不能在执行查询时动态选择 table 名称,并且案例在案例被执行之前仍然会得到 ORA-00942评价。

假设您只需要旧视图 table 和新视图中都存在的列,您可以使用一些 XML 转换来从任何一个存在的视图中获取数据:

select x.*
from (
  select dbms_xmlgen.getxml(q'[select to_char(action_time, 'YYYY-MM-DD"T"HH24:MI:SS.FF9')
      as action_time, action, version, id as patch_id, comments as description
    from sys.REGISTRY$HISTORY]') as data
  from dba_tables
  where table_name = 'REGISTRY$HISTORY'
  and not exists (select null from dba_views where view_name = 'DBA_REGISTRY_SQLPATCH')
  union all
  select dbms_xmlgen.getxml(q'[select to_char(action_time, 'YYYY-MM-DD"T"HH24:MI:SS.FF9')
      as action_time, action, version, patch_id, description
    from DBA_REGISTRY_SQLPATCH]') as data
  from dba_views
  where view_name = 'DBA_REGISTRY_SQLPATCH'
) t
cross join xmltable('/ROWSET/ROW' passing xmltype(t.data)
  columns action_time timestamp path 'ACTION_TIME',
    action varchar2(30) path 'ACTION',
    version varchar2(30) path 'VERSION',
    patch_id number path 'PATCH_ID',
    comments varchar2(100) path 'DESCRIPTION'
) x;

然后将 select x.* 替换为您想对数据执行的任何操作,本质上是将其插入现有查询,添加联合以获取未修补的版本信息:

...
union all
select vd.created as action_time, 'N/A' as action, substr(vi.version, 1, 8) as version,
  99 as patch_id, substr(vi.version, 1, 8) as description
from v$database vd
cross join v$instance vi;

to_char()是将时间戳值转化为XML中期望的ISO格式。 dbms_xmlgen() 调用将数据从 table/view 转换为 XML 表示; XMLTable() 将其转换回来。这看起来有点无意义,但它让你直到运行时才知道对象名称。

由于列略有不同(ID, COMMENTSPATCH_ID, DESCRIPTION),这与 table 或通过 [=19= 的视图分开 XML ],但不能同时来自两者,因为那样会给出无效的 XML 文档。在 12c 中,REGISTRY$HISTORY 看起来是空的,但万一它不是空的,如果 DBA_REGISTRY_SQLPATCH 存在,它不会从中获取任何数据。 (我有点懒惰,没有检查所有权,所以其他人使用该名称创建 table 将是一个问题,但很容易解决)。它为列名称设置了别名,因此它们看起来与它最终使用的 table/view 相同,从而允许 XML 被解包。

将其与您的字符串格式结合起来,消除子查询,并使用 the last analytic function 仅保留最近的行,您最终可以得到类似的结果:

select to_char (trunc (sysdate - trunc (max(action_time))), '9999') diff,
  'DIFF : ' || to_char (trunc (sysdate - trunc (max(action_time))), '9999') || ' DAYS'
    || ' ACTION=' || max(action) keep (dense_rank last order by action_time)
    || ' VERSION=' || max(version) keep (dense_rank last order by action_time)
    || ' DATE=' || to_char (max(action_time), 'yyyymmdd')
    || ' ID=' || to_char (max(patch_id) keep (dense_rank last order by action_time), '09')
    || ' COMMENTS=' || max(comments) keep (dense_rank last order by action_time)
    || ' PORT=' || dbms_utility.port_string zeile
from (
  select x.* from (
    select dbms_xmlgen.getxml(q'[select to_char(action_time, 'YYYY-MM-DD"T"HH24:MI:SS.FF9')
        as action_time, action, version, id as patch_id, comments as description
      from sys.REGISTRY$HISTORY]') as data
    from dba_tables
    where table_name = 'REGISTRY$HISTORY'
    and not exists (select null from dba_views where view_name = 'DBA_REGISTRY_SQLPATCH')
    union all
    select dbms_xmlgen.getxml(q'[select to_char(action_time, 'YYYY-MM-DD"T"HH24:MI:SS.FF9')
        as action_time, action, version, patch_id, description
      from DBA_REGISTRY_SQLPATCH]') as data
    from dba_views
    where view_name = 'DBA_REGISTRY_SQLPATCH'
  ) t
  cross join xmltable('/ROWSET/ROW' passing xmltype(t.data)
    columns action_time timestamp path 'ACTION_TIME',
      action varchar2(30) path 'ACTION',
      version varchar2(30) path 'VERSION',
      patch_id number path 'PATCH_ID',
      comments varchar2(100) path 'DESCRIPTION'
  ) x
  union all
  select vd.created as action_time, 'N./.A' as action, substr(vi.version, 1, 8) as version,
    99 as patch_id, substr(vi.version, 1, 8) as comments
  from v$database vd
  cross join v$instance vi
);

已在 11.2.0.4 和 10.2.0.5 上测试,但我没有未修补的实例或 12c 实例来验证它的行为是否符合您的预期。

编辑:正如 Alex Poole 在评论(针对他的回答而非我的回答)中显示的那样,我在下面描述的内容将不起作用。它实际上很好地说明了在这种情况下什么是行不通的。

我把它留在这里只是为了让可能已经看到它的人有机会看到它不好。过段时间我会删除答案

谢谢 Alex 指出!

-

很明显,您可以编写自己的查询,因此我将在此处仅展示一种执行您询问的 "switch" 表达式的方法。我只有第 11 版(免费版),所以我无法完全测试,但这应该可以。要查找您的会话所在的 Oracle DB 的版本,您可以查询视图 V$VERSION。在我的机器上,我看到 Oracle 版本显示为:

SQL> select * from v$version where banner like 'Oracle%';

BANNER
--------------------------------------------------------------------------------
Oracle Database 11g Express Edition Release 11.2.0.2.0 - 64bit Production

假设 v$version 在 Oracle 12c 中没有变化(即:仍然有一个视图 v$version,该列仍然称为 banner,并且 Oracle DB 版本显示为 Oracle Database 12c ... .),为了得到 action_time 你可以这样做:

select case 
    regexp_substr((select banner from v$version where banner like 'Oracle%'), '\d{1,2}') 
           when '11' then (select action_time from sys.REGISTRY$HISTORY)
           when '12' then (select action_time from dba_registry_sqlpatch)
           end   as action_time ...

您不需要为来自 "registry" table 的每一位数据编写 Oracle 版本的 case 表达式 - 您可以在 case 的两个分支中构建完整的字符串表达。您也可以调整它以适应 "no patch installed" 分支。

祝你好运!