pl / sql - может ли цикл перебирать имена столбцов? - PullRequest
3 голосов
/ 02 января 2012

Вывод из приведенного ниже кода:

         |LAT|MISC|SID|NO
MIN_LENGTH|1|2|1|1
MAX_LENGTH|6|6|4|2

Вывод такой, как я ожидаю, но есть ли в любом случае циклически проходить по столбцам с использованием индекса (т.е. j) вместо выполнения RESULTS (I). MAX_LENGTH , РЕЗУЛЬТАТЫ (I). MAX_LENGTH и т. Д.?Проблема заключается в том, что при добавлении дополнительных столбцов в запись 'R_RESULT_REC' требуется еще один цикл.

set serveroutput on
DECLARE     
  TYPE R_RESULT_REC IS RECORD
    (COL_NAME        VARCHAR2(100),
      MIN_LENGTH         NUMBER,
      MAX_LENGTH         NUMBER
      );       
  TYPE tr_RESULT IS TABLE OF R_RESULT_REC;
  RESULTS   TR_RESULT := TR_RESULT();
  v_counter NUMBER := 1;
BEGIN
  FOR J IN (SELECT DISTINCT COLUMN_NAME FROM ALL_TAB_COLUMNS 
            WHERE OWNER = 'SYSTEM'
            and TABLE_NAME = 'SPECCHAR')
  LOOP
      RESULTS.EXTEND;
      RESULTS(V_COUNTER).COL_NAME := J.COLUMN_NAME;
      EXECUTE IMMEDIATE 'SELECT MIN(LENGTH('||J.COLUMN_NAME||')),
      MAX(LENGTH('||J.COLUMN_NAME||'))
      FROM '||'SYSTEM'||'.'||'SPECCHAR' INTO 
      RESULTS(V_COUNTER).MIN_LENGTH,
      RESULTS(V_COUNTER).MAX_LENGTH; 
      V_COUNTER := V_COUNTER + 1;
  END LOOP;
     FOR I IN RESULTS.FIRST .. RESULTS.LAST LOOP
       IF I = RESULTS.LAST THEN
         DBMS_OUTPUT.PUT_LINE(RESULTS(I).COL_NAME);
       ELSIF I = RESULTS.FIRST THEN
         DBMS_OUTPUT.PUT('         |'||RESULTS(I).COL_NAME||'|');
        ELSE
         DBMS_OUTPUT.PUT(RESULTS(I).COL_NAME||'|');
       END IF ;
    END LOOP;
    FOR I IN RESULTS.FIRST .. RESULTS.LAST LOOP
       IF I = RESULTS.LAST THEN
         DBMS_OUTPUT.PUT_LINE(RESULTS(I).MIN_LENGTH);
        ELSIF I = RESULTS.FIRST THEN
         DBMS_OUTPUT.PUT('MIN_LENGTH|'||RESULTS(I).MIN_LENGTH||'|');
        ELSE
         DBMS_OUTPUT.PUT(RESULTS(I).MIN_LENGTH||'|');
       END IF ;
    END LOOP;
     FOR I IN RESULTS.FIRST .. RESULTS.LAST LOOP
       IF I = RESULTS.LAST THEN
         DBMS_OUTPUT.PUT_LINE(RESULTS(I).MAX_LENGTH);
        ELSIF I = RESULTS.FIRST THEN
         DBMS_OUTPUT.PUT('MAX_LENGTH|'||RESULTS(I).MAX_LENGTH||'|');
        ELSE
         DBMS_OUTPUT.PUT(RESULTS(I).MAX_LENGTH||'|');
       END IF ;
    END LOOP;
end;

Ответы [ 3 ]

4 голосов
/ 05 января 2012

Используется DBMS_SQL, поэтому читать довольно сложно.Основная причина, по которой я видел его использование, заключалась в том, что я мог получить столбчатые описания оператора SQL и выборки на основе буфера, а не объекта.

Вместо того, чтобы делать вызовы DBMS_OUTPUT во время обработки, он создаеттаблица записей для вывода с использованием ассоциативных массивов для простоты.

Можно было бы дополнительно уточнить, чтобы иметь массив или анализируемый список функций для применения к каждой функции, но это кажется избыточным для текущих требований.Природа кода потребует редактирования, если будут добавлены новые функции агрегирования.

Обзор вызовов (2c + a + s):

  • 3 цикла;
    • 2 цикла по списку столбцов (c),
    • 1 цикла по числу аналитических функций (a).
  • 1 SQL-оператор с данными таблицы.

Обзор вызовов OP (c * s + a + 1):

  • 1 цикл, выполнение оператора sql для данных таблицы для столбца (c * s)
  • a + 1 циклов, где a - количество аналитических функций

Данные испытаний:

  1  select  min(length(GP_ID)),  max(length(GP_ID)),
  2          min(length(GGP_ID)),  max(length(GGP_ID)),
  3          min(length(OBJECT_NAME)),  max(length(OBJECT_NAME))
  4*   from AMUSCH.GP
SQL> /

MIN(LENGTH(GP_ID)) MAX(LENGTH(GP_ID)) MIN(LENGTH(GGP_ID)) 
MAX(LENGTH(GGP_ID)) MIN(LENGTH(OBJECT_NAME)) MAX(LENGTH(OBJECT_NAME))
                  1                  7                   1                              
                  4                        9                       41

Код:

declare
  p_owner         varchar2(30);
  p_table_name    varchar2(30);

  TYPE OUTPUT_TAB_TYPE IS TABLE OF VARCHAR2(32767) index by binary_integer;
  OUTPUT_TAB OUTPUT_TAB_TYPE;

  l_columns_tab   dbms_sql.desc_tab;
  l_columns_cur   integer;
  l_columns_sql   varchar2(32767);
  l_columns_cnt   number;

  l_minmax_sql    varchar2(32767);
  l_minmax_cur    integer;
  l_minmax_tab    dbms_sql.desc_tab;
  l_minmax_cnt    number;

  l_fetch_ok      number;
  l_fetch_value   number;
begin

  p_owner := 'AMUSCH';
  p_table_name := 'GP';

  output_tab(1) := lpad(' ', 20, ' ');
  output_tab(2) := lpad('MIN_LENGTH', 20, ' ');
  output_tab(3) := lpad('MAX_LENGTH', 20, ' ');

  l_columns_sql := 'select * from ' || p_owner || '.' || p_table_name || 
     ' where 1 = 0';
  l_columns_cur := dbms_sql.open_cursor;
  dbms_sql.parse (l_columns_cur, l_columns_sql, dbms_sql.native);
  dbms_sql.describe_columns (l_columns_cur, l_columns_cnt, l_columns_tab);

  -- build the min/max sql statement
  l_minmax_sql := 'select ' ;
  for i in 1..l_columns_cnt
  loop
    l_minmax_sql := l_minmax_sql || 
          ' min(length(' || l_columns_tab(i).col_name || ')), ';
    l_minmax_sql := l_minmax_sql || 
          ' max(length(' || l_columns_tab(i).col_name || ')), ';
  end loop;
  l_minmax_sql := substr(l_minmax_sql, 1, 
                         length(l_minmax_sql) - 2); -- trim trailing comma
  l_minmax_sql := l_minmax_sql || ' from ' || p_owner || '.' || p_table_name;

  l_minmax_cur := dbms_sql.open_cursor;
  dbms_sql.parse (l_minmax_cur, l_minmax_sql, dbms_sql.native);
  dbms_sql.describe_columns (l_minmax_cur, l_minmax_cnt, l_minmax_tab);

  for i in 1..l_minmax_cnt
  loop
    dbms_sql.define_column(l_minmax_cur, i, l_fetch_value);
  end loop;

  l_fetch_ok := dbms_sql.execute(l_minmax_cur);

  loop
    l_fetch_ok := dbms_sql.fetch_rows(l_minmax_cur);
    exit when l_fetch_ok = 0;

    -- loop over the columns selected over
    for i in 1..l_columns_cnt
    loop
      output_tab(1) := output_tab(1) || '|' || l_columns_tab(i).col_name;

      dbms_sql.column_value(l_minmax_cur, (2*i-1), l_fetch_value);
      output_tab(2) := output_tab(2) || '|' || 
        lpad(l_fetch_value, length(l_columns_tab(i).col_name), ' ');
      dbms_sql.column_value(l_minmax_cur, (2*i), l_fetch_value);
      output_tab(3) := output_tab(3) || '|' || 
        lpad(l_fetch_value, length(l_columns_tab(i).col_name), ' ');

    end loop;
  end loop;

  if dbms_sql.is_open(l_minmax_cur) then
    dbms_sql.close_cursor (l_minmax_cur);
  end if;

  if dbms_sql.is_open (l_columns_cur) then
    dbms_sql.close_cursor (l_columns_cur);
  end if;

  for i in output_tab.first..output_tab.last
  loop
    dbms_output.put_line(output_tab(i));
  end loop;
end;
/

Результаты:

                |GP_ID|GGP_ID|OBJECT_NAME
      MIN_LENGTH|    1|     1|          9
      MAX_LENGTH|    7|     4|         41
4 голосов
/ 02 января 2012

Если вы хотите использовать пакет DBMS_SQL (который иногда очень сложен), то есть функция DBMS_SQL.COLUMN_VALUE, которая может работать для вас.

update:
Или еще лучше: DBMS_SQL.DESC_REC вы можете сослаться на: http://docs.oracle.com/cd/B19306_01/appdev.102/b14258/d_sql.htm#i996963
пример уведомления 8

Я не проверял это

обновление:
Возможно, что вам действительно нужно, это зациклить атрибуты типа объекта, а не столбец таблицы, поэтому, возможно, вам следует попробовать другой подход:
Сделайте ваш тип R_RESULT_REC типом объекта в БД, а затем вы можете зациклить результаты запроса.:

SELECT attr_name
FROM user_type_attrs
WHERE type_name = 'R_RESULT_REC'

Это не похоже на работу с индексами, но вам все равно не нужно жестко кодировать имена столбцов / атрибуты типа

вот код (основанный на вашем):

CREATE OR REPLACE TYPE R_RESULT_REC AS OBJECT
(
  COL_NAME        VARCHAR2(100),
  MIN_LENGTH         NUMBER,
  MAX_LENGTH         NUMBER
);
/

и затем:

DECLARE

  TYPE tr_RESULT IS TABLE OF R_RESULT_REC;
  RESULTS   TR_RESULT := TR_RESULT();
  v_counter NUMBER := 1;
  v_max     number;
  v_min     number;

BEGIN
  FOR J IN (SELECT DISTINCT COLUMN_NAME
              FROM ALL_TAB_COLUMNS
             WHERE OWNER = 'SYSTEM'
               and TABLE_NAME = 'SPECCHAR') LOOP

    EXECUTE IMMEDIATE 'SELECT MIN(LENGTH(' || J.COLUMN_NAME || ')),
      MAX(LENGTH(' || J.COLUMN_NAME || '))      FROM ' ||
                      'SPECCHAR'
      INTO v_min, v_max;

    RESULTS.EXTEND;
    RESULTS(V_COUNTER) := new R_RESULT_REC(J.COLUMN_NAME, v_min, v_max);
    V_COUNTER := V_COUNTER + 1;
  END LOOP;

  for r in (select attr_name
              from all_type_attrs t
             where t.owner = 'SYSTEM'
               and t.type_name = 'R_RESULT_REC') loop

    FOR I IN RESULTS.FIRST .. RESULTS.LAST LOOP
      IF I = RESULTS.LAST THEN
        execute immediate 'declare rec R_RESULT_REC := :0; begin' ||
                          ' DBMS_OUTPUT.PUT_LINE(rec.' || r.attr_name || ');' ||
                          'end;'
          using RESULTS(I);
      ELSIF I = RESULTS.FIRST THEN
        execute immediate 'declare rec R_RESULT_REC := :0; begin' ||
                          ' DBMS_OUTPUT.PUT(''' || r.attr_name ||
                          '|'' || rec.' || r.attr_name || ' || ''|'');' ||
                          'end;'
          using RESULTS(I);
      ELSE
        execute immediate 'declare rec R_RESULT_REC := :0; begin' ||
                          ' DBMS_OUTPUT.PUT(rec.' || r.attr_name ||
                          ' || ''|''); ' || 'end;'
          using RESULTS(I);
      END IF;
    END LOOP;

  end loop;

end;

Если вы добавите еще один атрибут в запись (и инициируете его со значениями), он автоматически отобразит его.

2 голосов
/ 05 января 2012

Воспользуйтесь статистикой Oracle для этого.

Сначала полностью построите статистику для таблицы, используя dbms_stats.gather_table_stats

Затем создайте следующую функцию, чтобы помочь преобразовать необработанные низкие / высокие значения, которые Oracle хранит в all_tab_columns

create or replace function show_raw(i_raw raw, i_type varchar2)
return varchar2 is
  l_varchar2 varchar2(32);
  l_number number;
  l_date date;
  l_nvarchar2 nvarchar2(32);
  l_rowid rowid;
  l_char char;
begin

  if (i_type = 'VARCHAR2') then
    DBMS_STATS.CONVERT_RAW_VALUE(i_raw, l_varchar2);
    return to_char(l_varchar2);
  elsif(i_type = 'NUMBER') then
    DBMS_STATS.CONVERT_RAW_VALUE(i_raw, l_number);
    return to_char(l_number);
  elsif(i_type = 'DATE') then
    DBMS_STATS.CONVERT_RAW_VALUE(i_raw, l_date);
    return to_char(l_date);
  elsif(i_type = 'NVARCHAR2') then
    DBMS_STATS.CONVERT_RAW_VALUE(i_raw, l_nvarchar2);
    return to_char(l_nvarchar2);
  elsif(i_type = 'ROWID') then
    DBMS_STATS.CONVERT_RAW_VALUE(i_raw, l_rowid);
    return to_char(l_rowid);
  elsif(i_type = 'CHAR') then
    DBMS_STATS.CONVERT_RAW_VALUE(i_raw, l_char);
    return l_char;
  else return 'Unknown type value';
  end if;
end;

Затем просто выберите низкие / высокие значения для каждого столбца:

select column_id, 
  column_name, 
  data_type, 
  show_raw(low_value, data_type) as min_val, 
  show_raw(high_value, data_type) as max_val
from all_tab_columns
where table_name = 'SOME_TABLE'
and owner = 'SOME_OWNER'
;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...