如何查找仅包含数字数据的非数字列?

时间:2017-12-18 17:32:15

标签: sql oracle oracle11g dynamic-sql data-dictionary

我喜欢在Oracle数据库模式中查找仅包含数字数据但具有非数字类型的所有列。 (所以基本上是列候选者可能选择了错误的数据类型。)

我查询了所有varchar2-columns:

SELECT TABLE_NAME, COLUMN_NAME, DATA_TYPE
FROM user_tab_cols
WHERE DATA_TYPE = 'VARCHAR2';

此外,我有一个查询来检查表myTable和列myColumn中的任何非数字数据:

SELECT 1
FROM myTable
WHERE NOT REGEXP_LIKE(myColumn, '^[[:digit:]]+$');

我喜欢以这种方式组合两个查询,即第一个查询只返回not exists第二个查询的行。

这里的主要问题是第一个查询位于数据字典的元层上,其中TABLE_NAME和COLUMN_NAME作为数据存在,我需要在第二个查询中将数据作为identifiers(而不是数据)。 / p>

在伪SQL中,我有类似的想法:

SELECT TABLE_NAME, COLUMN_NAME, DATA_TYPE
FROM user_tab_cols
WHERE DATA_TYPE = 'VARCHAR2'
AND NOT EXISTS
(SELECT 1 from asIdentifier(TABLE_NAME) 
WHERE NOT REGEXP_LIKE(asIdentifier(COLUMN_NAME), '^[[:digit:]]+$'));

3 个答案:

答案 0 :(得分:2)

创建一个函数:

create or replace function isNumeric(val in VARCHAR2) return INTEGER AS
res NUMBER;
begin
   res := TO_NUMBER(val);
   RETURN 1;
EXCEPTION
   WHEN OTHERS THEN
      RETURN 0;
END;

然后你可以像这样使用它:

DECLARE
  r integer;
BEGIN
   For aCol in (SELECT TABLE_NAME, COLUMN_NAME FROM user_tab_cols WHERE DATA_TYPE = 'VARCHAR2') LOOP
      -- What about CHAR and CLOB data types?
      execute immediate 'select count(*) from '||aCol.TABLE_NAME||' WHERE isNumeric('||aCol.COLUMN_NAME||') = 0' into r;
      if r = 0 then
         DBMS_OUTPUT.put_line(aCol.TABLE_NAME ||' '||aCol.COLUMN_NAME ||' contains numeric values only');
      end if;
   end loop;
end;

注意,此PL / SQL块的性能会很差。希望这只是一次性工作。

答案 1 :(得分:1)

有两种可能的方法:动态SQL(DSQL)和XML。

第一个已在另一个回复中得到证明,而且速度更快。

XML方法只是为了好玩

create or replace function to_number_udf(p in varchar2) return number
  deterministic is
  pragma udf;
begin
  return p * 0;
  exception when invalid_number or value_error then return 1;
end to_number_udf;
/

create table t_chk(str1, str2) as
select '1', '2' from dual union all
select '0001.1000', 'helloworld' from dual;

SQL> column owner format a20
SQL> column table_name format a20
SQL> column column_name format a20
SQL> with tabs_to_check as
  2  (
  3  select 'collection("oradb:/'||owner||'/'||table_name||'")/ROW/'||column_name||'/text()' x,
  4         atc.*
  5    from all_tab_columns atc
  6   where table_name = 'T_CHK'
  7     and data_type = 'VARCHAR2'
  8     and owner = user
  9  )
 10  select --+ no_query_transformation
 11         owner, table_name, column_name
 12    from tabs_to_check ttc, xmltable(x columns "." varchar2(4000)) x
 13  group by owner, table_name, column_name
 14  having max(to_number_udf(".")) = 0;

OWNER                TABLE_NAME           COLUMN_NAME
-------------------- -------------------- --------------------
TEST                 T_CHK                STR1

PS。在Oracle 12.2上,您可以使用to_number(... default ... on conversion error)而不是UDF。

答案 2 :(得分:1)

检查字符串是否为全部数字与包含至少一个非数字字符的更快方法是使用translate函数。唉,由于Oracle处理空字符串的非SQL标准方式,我们必须使用的函数形式有点复杂:

translate(input_string, 'z0123456789', 'z')

z可以是任何非数字字符;我们需要它,以便第三个参数不为空)。这可以通过将z转换为自身和0等来实现。因此,如果输入字符串是null或全数字,并且仅在这种情况下,函数返回的值为null

此外:为了加快流程,您可以使用EXISTS条件测试每个列。如果列不是数字,那么在大多数情况下EXISTS条件将很快变为真,因此您必须从这些列中检查非常少量的值。

当我试图完成这项工作时,我遇到了很多问题。大概你想要查看所有模式(SYSSYSTEM除外)。因此,您需要从具有SYSDBA权限的帐户运行该过程(匿名阻止)。然后 - 我遇到了非标准表和列名称的问题(名称以下划线等开头);这让人想起用双引号定义的标识符 - 这是一种可怕的做法。

为了说明,我将使用HR模式 - 该方法有效。您可能需要进一步调整;我无法通过更改行

来使其工作
and owner = 'HR'

and owner != 'SYS'

所以 - 通过这个长篇介绍 - 这就是我所做的。

首先,在一个“普通”用户帐户(我自己的,名为INTRO - 我运行一个非常小的数据库,只有一个“普通”用户,加上像SCOTT,HR等Oracle“标准”用户。) - 所以,在模式INTRO中,我创建了一个表来接收数据类型为VARCHAR2的所有列的所有者名称,表名和列名,并且只包含“数字”值或null(数字定义了你的方式。)注意:如果你想真正检查所有数值,你确实需要一个正则表达式,或类似Wernfried所展示的东西;否则,我会在匿名程序中使用EXISTS条件而不是COUNT。

然后我创建了一个匿名块来查找所需的列。注意:您将没有模式INTRO - 因此在我的代码中无处不在(在创建表和匿名块中)。如果该过程成功完成,您应该能够查询该表。我最后也表明了这一点。

以SYS (或具有SYSDBA权限的其他用户)登录时:

create table intro.cols_with_numbers (
  owner_name  varchar2(128),
  table_name  varchar2(128),
  column_name varchar2(128)
);

declare x number;
begin
  execute immediate 'truncate table intro.cols_with_numbers';
  for t in ( select owner, table_name, column_name
             from   dba_tab_columns
             where  data_type like 'VARCHAR2%'
               and  owner = 'HR'
           ) 
  loop
    execute immediate 'select case when exists (
                                select *
                                from ' || t.owner || '.' || t.table_name ||
                              ' where  translate(' || t.column_name || ',
                                         ''z0123456789'', ''z'') is not null
                              ) then 1 end
                       from   dual'    
    into x;
    if x is null then
      insert into intro.cols_with_numbers (owner_name, table_name, column_name)
         values(t.owner, t.table_name, t.column_name);
    end if;
  end loop;
end;
/

运行此过程,然后查询表:

select * from intro.cols_with_numbers;

no rows selected

(这意味着HR模式中的表中没有数字列,错误的数据类型为VARCHAR2 - 或者至少没有这样的列只有非负整数值。)您可以通过有意创建进一步测试一个带有这样一个列的表,并测试它是否被程序“捕获”。

已添加 - 以下是我将所有者从'HR'更改为'SCOTT'后会发生的情况:

PL/SQL procedure successfully completed.


OWNER_NAME           TABLE_NAME           COLUMN_NAME        
-------------------- -------------------- --------------------
SCOTT                BONUS                JOB                 
SCOTT                BONUS                ENAME   

所以它似乎工作正常(虽然在其他架构上我有时遇到错误......我会看看我是否能弄清楚它是什么)。

在这种情况下,表格为空(没有行!) - 这是您可能找到的“误报”的一个示例。 (更一般地说,如果VARCHAR2列中的所有内容都是null,则会在表的所有行中得到误报。)

另请注意,列可能只有数值,而最佳数据类型仍然是VARCHAR2。当值只是标识符而不是“数字”(我们可以相互比较或与固定值进行比较,和/或我们可以进行算术运算)时就是这种情况。示例 - SSN(社会安全号码)或其他国家/地区的同等号码; SSN是每个人与政府做生意的“官方”标识符。 SSN是数字的(实际上,也许是为了强调它不应该是一个“数字”的事实,尽管名称,它通常写有几个破折号...)