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

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何查找仅包含数字数据的非数字列?相关的知识,希望对你有一定的参考价值。

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

我有一个查询所有varchar2列:

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(而不是数据)。

在伪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:]]+$'));
答案

创建一个函数:

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块的性能会很差。希望这只是一次性的工作。

另一答案

有两种可能的方法:动态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。

另一答案

检查字符串是否为全部数字与包含至少一个非数字字符的更快方法是使用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 - 我运行一个非常小的数据库,只有一个“普通”用户,加上Oracle“标准”用户,如SCOTT,HR等) - 所以,在模式中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是数字的(实际上,也许是为了强调它不应该是一个“数字”的事实,尽管名称,它通常用几个短划线写...)

以上是关于如何查找仅包含数字数据的非数字列?的主要内容,如果未能解决你的问题,请参考以下文章

PySpark:如何删除 DataFrame 中的非数字列?

过滤数字列名称上的非NA

华为OD机试 -非严格递增连续数字序列(Java) | 机试题+算法思路+考点+代码解析 2023

无数据返回 如果选择查询的搜索条件仅包含数字并且查询表的 nVarChar 列

如何在多行/列Excel电子表格表中查找值的位置

验证文本框是不是仅包含数字