oracle PLSQL执行即时动态聚集查询和大容量收集

brvekthn  于 2023-03-01  发布在  Oracle
关注(0)|答案(3)|浏览(137)

我需要获取许多表的列填充(表中每列的计数)。我正在编写一个PLSQL脚本,该脚本将创建一个动态查询并希望打印结果。由于我的选择查询包括聚合列,我不确定如何定义 TYPE。是使用游标还是执行立即批量收集。

DECLARE
   V_COLUMN_NAME ALL_TAB_COLS.COLUMN_NAME%type;
   V_SELECT_POPULATION_QUERY varchar2(2000) := 'SELECT ';
   V_TABLE_NAME varchar2(200) := 'EMPLOYEE'; // Will be list of tables in future
   CURSOR TAB_COLS IS SELECT COLUMN_NAME FROM ALL_TAB_COLS WHERE TABLE_NAME = V_TABLE_NAME;
  
BEGIN
   OPEN TAB_COLS;
   LOOP
      FETCH TAB_COLS INTO V_COLUMN_NAME;
      EXIT WHEN TAB_COLS%notfound;
      V_SELECT_POPULATION_QUERY := V_SELECT_POPULATION_QUERY ||'COUNT('|| V_COLUMN_NAME||'),';
   END LOOP;
  V_SELECT_POPULATION_QUERY:= SUBSTR(V_SELECT_POPULATION_QUERY, '0',LENGTH( V_SELECT_POPULATION_QUERY )-1);
  V_SELECT_POPULATION_QUERY:= V_SELECT_POPULATION_QUERY || ' FROM '|| V_TABLE_NAME; 
  

 //NEED A WAY TO EXECUTE DYNAMIC QUERY (variable V_SELECT_POPULATION_QUERY) and print the results.
 //SAMPLE DYNAMIC QUERY1 : SELECT COUNT(EMPLOYEE_ID),COUNT(NAME),COUNT(BASE_URL),COUNT(INPUT_FORM),COUNT(BASE_URL_ASUCASE),COUNT(IS_CUSTOM) FROM EMPLOYEE 
 //SAMPLE DYNAMIC QUERY 2: SELECT COUNT(DEPT_ID),COUNT(NAME),COUNT(BASE_URL) FROM DEPT
END;
thtygnil

thtygnil1#

这样做的问题是每次返回的列列表都不一样,所以假设你用EXECUTE IMMEDIATE修复了代码中的直接问题:

EXECUTE IMMEDIATE V_SELECT_POPULATION_QUERY INTO var_employee_id,var_name,var_base_url,var_input_form,var_base_url_asucase,var_is_custom;

但下一次,你需要一个不同的电话:

EXECUTE IMMEDIATE V_SELECT_POPULATION_QUERY INTO var_dept_id,var_name,var_base_url;

显然,你不能在一个循环中这样做,因为SQL语句本身是动态的,而你获取的变量不是。重用的动态SQL在所有情况下都应该返回相同的结构,但是你每次都在改变结构。
解决这个问题最直接的方法是分别查询每一列,而不是尝试一次查询表中的所有列,这样,您可以将每一列选择为一个数字变量。

EXECUTE IMMEDIATE V_SELECT_POPULATION_QUERY INTO var_column_count; -- one column only

但是假设这对您来说是一个性能负担,如果您真的必须在一次传递中合并所有列,那么您有两种方法。
1.确定您必须查询的最大列数(可能是数百列?)。创建一个具有该列数的记录变量。在组装COUNT(column_name)列表时,计算您追加了多少列,然后追加虚拟NULL列,无论需要多少次才能填充记录类型中的列数。

  • 或 *

1.使用双嵌套动态SQL方法,其中包含动态SQL的整个PL/SQL块本身就是动态SQL。显然,您需要结果来执行某些操作,因此必须在该块中处理结果。假设您只需要将结果输入到表中。

create table my_results (column_name varchar2(128),count_result integer);
 /
 DECLARE
   var_column_list varchar2(32767);
   var_variable_list varchar2(32767);
   var_plsql_block varchar2(32767);
   TYPE columns_tabtype IS TABLE OF varchar2(128);
   tab_columns columns_tabtype := columns_tabtype();
   var_table_name varchar2(128) := 'EMPLOYEE';
 BEGIN
   FOR rec_column IN (SELECT column_name,ROWNUM seq
                        FROM user_tab_columns
                       WHERE table_Name = var_table_name)
   LOOP
     var_column_list := var_column_list || 'COUNT('||rec_column.column_name||'),';
     var_variable_list := var_variable_list || 'C'||rec_column.seq||',';
     tab_columns.EXTEND;
     tab_columns(tab_columns.LAST) := rec_column.column_name;
   END LOOP;

   var_plsql_block := '
     DECLARE
       '||REPLACE(var_variable_list,',',' number;'||CHR(10))||'
     BEGIN
       EXECUTE IMMEDIATE ''SELECT '||RTRIM(var_column_list,',')||' FROM '||var_table_name||''' INTO '||RTRIM(var_variable_list,',')||';';

       FOR j IN tab_columns.FIRST .. tab_columns.LAST
       LOOP
         var_plsql_block := var_plsql_block ||'
           INSERT INTO my_results (column_name,count_result) VALUES ('''||tab_columns(j)||''',c'||j||');';
       END LOOP;

       var_plsql_block := var_plsql_block ||'
     END;
   ';

   --dbms_output.put_line(var_plsql_block); -- enable for debugging
   EXECUTE IMMEDIATE var_plsql_block;
 END;
 /

 SELECT *
   FROM my_results

在回答了您的问题之后,我想问一下您是否正确地处理了这个问题。您实际上是在计算每列的非NULL行数。如果这就是您所需要的,那么只需收集表的统计信息,您就可以在user_tab_columns(user_tables中的row_count减去user_tab_columns中的num_nulls)中得到答案。这比您自己做要容易得多。
如果你想得到 distinct 值的个数,那么你必须给你的每个COUNT()函数加上DISTINCT,但是同样,从user_tab_columns中的num_distinct得到这个信息比自己收集这个信息要容易得多。我这么多年来从来没有做过你想做的事情,所以也许这是一个你不需要真正解决的问题?

r3i60tvu

r3i60tvu2#

有一个选择。
首先创建类型:

SQL> create or replace type t_row as object (table_name varchar2(30), column_name varchar2(30), cnt number);
  2  /

Type created.

SQL> create or replace type t_tab as table of t_row;
  2  /

Type created.

函数接受表名作为参数;然后在循环中对该表中的每一列进行计数,将结果存储到return值的集合中:

SQL> create or replace function f_test (par_table_name in varchar2)
  2    return t_tab
  3  is
  4    l_str varchar2(1000);
  5    l_cnt number;
  6    l_tab t_tab := t_tab();
  7  begin
  8    for cur_r in (select table_name, column_name, column_id
  9                  from user_tab_columns
 10                  where table_name = dbms_assert.sql_object_name(par_table_name)
 11                 )
 12    loop
 13      l_str := 'select count(' || cur_r.column_name || ') from ' ||cur_r.table_name;
 14      execute immediate l_str into l_cnt;
 15
 16      l_tab.extend;
 17      l_tab(cur_r.column_id) := t_row(cur_r.table_name, cur_r.column_name, l_cnt);
 18    end loop;
 19
 20    return l_tab;
 21  end;
 22  /

Function created.

测试:

SQL> select * from table(f_test('EMP'));

TABLE_NAME                     COLUMN_NAME                           CNT
------------------------------ ------------------------------ ----------
EMP                            EMPNO                                  14
EMP                            ENAME                                  14
EMP                            JOB                                    14
EMP                            MGR                                    13
EMP                            HIREDATE                               14
EMP                            SAL                                    14
EMP                            COMM                                    4
EMP                            DEPTNO                                 14

8 rows selected.

SQL>

你说过(在你发布的代码中的注解)你将在将来使用"表列表"。函数仍然只接受一个参数,但您应该传递所需数量的表。
例如:

SQL> select f.*
  2  from user_tables t cross join table(f_test(t.table_name)) f
  3  where t.table_name in ('DEPT', 'ORDERS');

TABLE_NAME                     COLUMN_NAME                           CNT
------------------------------ ------------------------------ ----------
DEPT                           DEPTNO                                  4
DEPT                           DNAME                                   4
DEPT                           LOC                                     4
ORDERS                         STATE                                   3

SQL>
3pvhb19x

3pvhb19x3#

要 * 计算表中的每列 *,您不需要任何PL/SQL脚本。Oracle已经在 * 对象统计信息 * 中为您计算了该列的数量-通过抽样 * 近似 *-这对于大型表非常合适,或者通过扫描整个表来精确计算。
从技术上讲,您需要表的行计数和每列中要减去的NULL值的数量。

小示例

create table tab as 
select 1 col1, 0 col2, 'X' col3 from dual union all
select 1,null, 'Y' from dual union all
select 3, null,null from dual;

select count(col1), count(col2), count(col3) from tab;
COUNT(COL1) COUNT(COL2) COUNT(COL3)
----------- ----------- -----------
          3           1           2

收集100%百分比的统计信息(精确,无抽样)

BEGIN 
   dbms_stats.gather_table_stats(ownname=>user, tabname=>'TAB',estimate_percent => 100);
END;
/

现在使用字典视图获取表NUM_ROWS和列NUM_NULLS,并将它们相减以获得结果。

select t.TABLE_NAME, t.NUM_ROWS, c.COLUMN_NAME, c.NUM_DISTINCT, c.NUM_NULLS, t.NUM_ROWS -  c.NUM_NULLS CNT_NOT_NULL
from user_tab_columns c
join user_tables t on t.table_name = c.table_name
where t.table_name = 'TAB';

TABLE_NAME   NUM_ROWS COLUMN_NAM NUM_DISTINCT  NUM_NULLS CNT_NOT_NULL
---------- ---------- ---------- ------------ ---------- ------------
TAB                 3 COL1                  2          0            3
TAB                 3 COL2                  1          2            1
TAB                 3 COL3                  2          1            2

注意,作为一个额外的好处,你还可以得到每列的 * 不同值 * 的数量。你可以使用抽样来在更短的时间内得到估计值,或者你可以激活一个 * 并行选项 * 来收集统计信息。

相关问题