部门使用 Oracle 已经有一些时日,最近在工作中遇到了这么一个需求:
我们希望拿到某些数据表的全部索引信息,对索引信息进行检查,检查是否有漏掉没有创建的索引
这个需求,核心的点在于,我需要编写一条 sql 语句,来获取到 Oracle 数据库中的数据表的全部索引信息,并且输出的结果还要能够方便我后续的检查工作。
因此对这条 sql 语句我们有以下的要求:
再把需求翻译成技术语言,那就是:
由于我对数据库的陌生,导致我完成这个工作耗费了不少时间。尽管我花了很长的时间,还是摸索出来了一个解决方案,缺仍然不知道自己的解决方案是不是最佳的。
因此,写下这篇博客,一方面希望给有同样需求的人一条解决问题的思路,另一方面也是希望能够有大佬帮忙参考下有没有更佳的解决方案。
我解决这个问题的过程,是一步步开始的:
先上我的最终解决方案代码:
select index_name, max(to_char(index_keys)) from
(
select index_name, wm_concat(column_name)
over(partition by index_name order by column_name) index_keys
from
(
select a.index_name, b.column_name
from all_indexes a, all_ind_columns b
where a.table_name = b.table_name
and a.index_name = b.index_name
and a.tab_owner = upper('WANGYING')
and a.table_name = 'TAB_TEST'
)
) group by index_name;
我写出这条 sql 的过程,也是按照我上面的思路完成的。在按照我的思路编写的过程中,遇到了不少阻碍。
这里假设在 Oracle 数据库下模式名为 WANGYING 的环境下,有个数据库 TAB_TEST,它有 (A,B,C) 和 (D,E) 两个索引,索引名分别为 index_name_1 和 index_name_2。
这里伴随着上面的假设,介绍下这条 sql 的含义:
从 all_indexes、all_ind_columns 两个表中,拿到属于 WANGYING 模式名下的,表名为 TAB_TEST 的全部索引字段信息。这时候的 sql 输出就是一系列的字段信息,此时的输出类似:
a.index_name | b.column_name |
---|---|
index_name_1 | A |
index_name_2 | D |
index_name_1 | B |
index_name_2 | E |
index_name_1 | C |
我们拿到了索引字段,接下来看看我们还需要什么:
按照上面的思路的话,我们写出来的 sql 应该长下面这样:
select index_name, wm_concat(column_name)
from
(
select a.index_name, b.column_name
from all_indexes a, all_ind_columns b
where a.table_name = b.table_name
and a.index_name = b.index_name
and a.tab_owner = upper('WANGYING')
and a.table_name = 'TAB_TEST'
order by a.index_name, b.column_name
)
group by index_name;
看似很简单的需求,先在内部按照索引名 index_name,再索引字段 column_name 排序,最后再 group by 分组,再 wm_concat 组合成字符串不就好了?
当然没有这么简单,你如果执行上面的语句,会发现内层 sql 的排序在 wm_concat 之后,顺序丢失了 T_T
那么怎么办呢,我搜了好久的资料,查找到了 over(partition by order by) 的用法,它可以:
顾名思义,PARTITION 中文是分割的意思,ORDER 是排序的意思,所以翻译一下就是先把一组数据按照制定的字段进行分割成各种组,然后组内按照某个字段排序。
参考自博客 【Orcale】分析函数 OVER(PARTITION BY… ORDER BY…)的讲解
按照这个说法,我只需要让它按照索引名 index_name 分割成多个组,然后组内按照索引字段 column_name 进行排序就可以了,这样就能保有索引字段的排序了。
因此写出 sql 如下:
select index_name, wm_concat(column_name)
over(partition by index_name order by column_name) index_keys
from
(
select a.index_name, b.column_name
from all_indexes a, all_ind_columns b
where a.table_name = b.table_name
and a.index_name = b.index_name
and a.tab_owner = upper('WANGYING')
and a.table_name = 'TAB_TEST'
)
有人可能会说,诶,你这样写不就好了吗,为什么最终方案还多了一层呢?
别急,你先看看执行结果:
index_name | wm_concat(column_name) |
---|---|
index_name_1 | A |
index_name_1 | A,B |
index_name_1 | A,B,C |
index_name_2 | D |
index_name_2 | D,E |
正因为 partition by 是分析函数而不是聚合函数,它会在一个分组中返回多条数据,因此你就看到了上述的执行结果:
partition by关键字是分析性函数的一部分,它和聚合函数(如group by)不同的地方在于它能返回一个分组中的多条记录,而聚合函数一般只有一条反映统计值的记录,
参考自博客 分组函数 partition by 的详解,与order by 区别
那么怎么解决这个问题呢?
其实也简单,我们再加一层,按照索引名分组,取最大的一条记录即可,所以最终的解决方案如下:
select index_name, max(to_char(index_keys)) from
(
select index_name, wm_concat(column_name)
over(partition by index_name order by column_name) index_keys
from
(
select a.index_name, b.column_name
from all_indexes a, all_ind_columns b
where a.table_name = b.table_name
and a.index_name = b.index_name
and a.tab_owner = upper('WANGYING')
and a.table_name = 'TAB_TEST'
)
) group by index_name;
这样,我们就写出了最终方案的代码,执行结果:
index_name | index_keys |
---|---|
index_name_1 | A,B,C |
index_name_2 | D,E |
上述所说的 wm_concat 函数只有在 Oracle 11g 支持,如果你的 Oracle 版本是 12c 甚至 19c,那么 wm_concat 函数就不能再使用了。
那么这下怎么办呢?最好能够找到一种方法,能够在 Oracle 11g/12c/19c 都兼容的情况下成功运行。
通过我的探索,发现使用 listagg() within group() 函数可以完美替代掉 wm_concat 函数使用。listagg 函数中可以指定特定的分隔符,并且还能对组合的数据进行排序:
-- 组合 column_name 列的数据,并且按照 , 分割,组合出来的数据按照 column_name 排序
listagg(column_name, ',') within group (order by column_name)
同样的,除了把多列变成一行的需求之外,我们还希望能够按照 index_name 分组多列转行,也就是达到上一节中的效果,index_name_1 的索引字段 ABC 放在一起,index_name_2 的索引字段 DE 放在一起。这时候,我们可以继续使用 over (partition by) 的方式进行分析处理,使其输出多行数据。而此时,我们只需要使用 distinct 即可取到每个 index_name 唯一的数据。
select distinct index_name, listagg(column_name, ',')
within group (order by column_name)
over (partition by index_name) index_keys from (
select a.index_name, b.column_name
from all_indexes a, all_ind_columns b
where a.table_name = b.table_name
and a.index_name = b.index_name
and a.tab_owner = upper('WANGYING')
and a.table_name = 'TAB_TEST'
)
输出结果是一样的:
index_name | index_keys |
---|---|
index_name_1 | A,B,C |
index_name_2 | D,E |
使用这种方式的好处就是,代码嵌套层数少,listagg 函数的功能比之 wm_concat 函数更加强大。亲测该代码在 Oracle 11g 以后的版本(Oracle 12c、Oracle 19c)都能正常使用。
我相信我写出来的解决方案一定不是最佳的解决方案,这块希望大佬们能够不吝赐教;另外,伴随着我的日后工作学习,应该也会有更多的不一样的体会。
希望本篇博客能够给你们带来一些启发或者帮助~~~
To be Stronger:)