Запись файла PL/SQL с общим вводом

Недавно я создал программу PL/SQL, которая создает пять разных файлов с разделителями каналов из связанных данных в базе данных.

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

Я не могу не думать, что должен быть лучший способ. Я искал эталонные курсоры, но я не думаю, что это именно то, что я ищу.

Как я могу добиться этого в PL/SQL?

Я думаю, что я ищу какой-то общий тип, который может брать любые данные из курсора, учитывая любое количество записей и столбцов записей, и иметь возможность запрашивать себя, чтобы найти, какие данные в нем.


person Clint L    schedule 20.10.2015    source источник
comment
Вы хотите экспортировать данные (в .csv, т.е.) или записать их, используя UTL_FILE?   -  person Neria Nachum    schedule 20.10.2015
comment
Процедуры выполнялись как параллельный запрос в пакете oracle e-business. Поэтому я думаю, что мне нужно, чтобы это было написано с использованием UTL_FILE. Однако требования заключаются в том, что после запуска этой программы результатом будет пять новых файлов в исходящем каталоге. Так что, пока он экспортирует данные без использования UTL_FILE и в результате получается пять файлов csv, все готово. благодарю за разъяснение   -  person Clint L    schedule 20.10.2015
comment
Вот хорошая статья о предмете, который также предоставляет общую процедуру экспорта в CSV.   -  person Neria Nachum    schedule 20.10.2015
comment
Спасибо, Нерия, я прочитал статью. Это дало мне много идей, и это, кажется, работает для одной таблицы. Каждый файл, который я создаю, создается из сложных курсоров, которые принимают параметры и объединяют множество таблиц, один из них даже объединяет 12 таблиц. Эти параметры отправляются в курсоры при вызове пакета, и поэтому курсор является динамическим до такой степени, что столбцы остаются согласованными (проблема, которую эта статья, которую вы мне прислали, действительно решает;)) но данные остаются неизменными при изменении суммы. Я обновлю свой ответ более подробно, так как теперь вижу, что некоторые сделанные мной предположения вводят ограничения.   -  person Clint L    schedule 20.10.2015
comment
Чтобы добавить (ограничение количества символов комментария) переменную varchar в статье, содержащей выбор sql, нужно будет взять текст моего курсора, а затем добавить параметр для каждой таблицы, который отличается для каждого курсора. Таким образом, мне по-прежнему потребуется пять отдельных процедур для обработки разного количества таблиц и имен таблиц. Кроме того, я понятия не имею, как бы я представил поля, к которым присоединяются эти таблицы. Это действительно решило мою проблему, когда количество столбцов было фиксированным, и я вижу, как они использовали dbms_describe_columns, чтобы отказаться от этого, поэтому я благодарю вас за то, что вы обратили мое внимание на эту новую часть знаний.   -  person Clint L    schedule 20.10.2015
comment
если вы разбираете ref_cursor с помощью dbms_sql, не имеет значения, сколько объединений было в запросе, создавшем курсор, — только структура результирующей записи. Точно так же, если ваша структура останется прежней, но изменятся соединения, рассмотрите возможность создания запроса в виде строки varchar2, а затем использования немедленного выполнения для его выполнения в предопределенный тип курсора.   -  person Michael Broughton    schedule 20.10.2015
comment
Привет, Майкл, вы снова заинтересовали меня ref_cursors, так как ваше описание, кажется, то, что мне нужно. Я провел немало исследований, но, поскольку я новичок, возможно, я пропустил что-то простое в его концепции. Есть ли у вас какой-либо пример или иной пример, демонстрирующий использование ref_cursor, который используется, а затем повторно используется с другой результирующей структурой?   -  person Clint L    schedule 20.10.2015
comment
Думайте о sys_refcursor как о типе указателя. Он может указывать на любой открытый курсор любой структуры, поэтому вы можете использовать его для одного курсора, закрыть его, а затем снова открыть с помощью совершенно другого оператора select. Оттуда вам нужно иметь возможность анализировать структуру, чтобы иметь доступ к элементам данных. Посмотрите это для начала: morganslibrary.org/hci/hci003.html   -  person Michael Broughton    schedule 20.10.2015
comment
@ClintL, извините, мне пришлось обрезать ваш пост. Не стесняйтесь отредактировать его/откатить изменение, если хотите. Но имейте в виду, что стандарты stackoverflow, как правило, избегают диалогового стиля и отдают предпочтение коду, а не тексту. Также, пожалуйста, покажите свой существующий код, вы значительно увеличите внимание к своему вопросу.   -  person Sebas    schedule 20.10.2015
comment
Вы даже можете передать запрос как параметр VARCHAR2, а затем открыть его в процедуре. Если вы не можете сделать это с помощью курсоров ref, возможно, вы могли бы сделать это с помощью DBMS_SQL.   -  person pablomatico    schedule 21.10.2015
comment
@Sebas Нет проблем, мне нужно обновить мои стандарты StackOverflow.   -  person Clint L    schedule 21.10.2015


Ответы (1)


Передайте курсор в свою процедуру как SYS_REFCURSOR. Затем используйте DBMS_SQL.TO_CURSOR_NUMBER(); для преобразования курсора ref в курсор DBMS_SQL.

Затем используйте DBMS_SQL.DESCRIBE_COLUMNS для определения столбцов в курсоре и DBMS_SQL.DEFINE_COLUMN, DBMS_SQL.FETCH_ROWS и DBMS_SQL.VALUE для получения данных из курсора в переменные PL/SQL. Затем запишите переменные PL/SQL в выходной файл.

Вот некоторый код, который объединяет все это для вас.

DECLARE
  l_rc SYS_REFCURSOR; 

PROCEDURE dump_cursor (p_rc IN OUT SYS_REFCURSOR) IS
  -- Dump the results of p_rc to log

  l_cursor                INTEGER;
  l_column_count          INTEGER;
  l_column_descriptions   SYS.DBMS_SQL.desc_tab;
  l_status                INTEGER;
  l_column_value          VARCHAR2 (4000);
  l_column_width          NUMBER;
  l_rec_count             NUMBER := 0;
  l_line                  VARCHAR2 (4000);


  FUNCTION get_length (l_column_def IN SYS.DBMS_SQL.desc_rec)
    RETURN NUMBER IS
    l_width   NUMBER;
  BEGIN
    l_width   := l_column_def.col_max_len;
    l_width   := CASE l_column_def.col_type WHEN 12 THEN                                                      /* DATE */
                                                        20 WHEN 2 THEN                                      /* NUMBER */
                                                                      10 ELSE l_width END;
    -- Don't display more than 256 characters of any one column (this was my requirement -- your file writer probably doesn't need to do this
    l_width   := LEAST (256, GREATEST (l_width, l_column_def.col_name_len));
    RETURN l_width;
  END get_length;
BEGIN
  -- This is the date format that I want to use for dates in my output
  EXECUTE IMMEDIATE 'alter session set nls_date_format=''DD-MON-YYYY HH24:MI:SS''';

  l_cursor   := sys.DBMS_SQL.to_cursor_number (p_rc);

  -- Describe columns
  sys.DBMS_SQL.describe_columns (c => l_cursor, col_cnt => l_column_count, desc_t => l_column_descriptions);

  l_line     := '';

  FOR i IN 1 .. l_column_count LOOP
    l_column_width   := get_length (l_column_descriptions (i));

    l_line           := l_line || RPAD (l_column_descriptions (i).col_name, l_column_width);
    l_line           := l_line || ' ';
    DBMS_SQL.define_column (l_cursor,
                            i,
                            l_column_value,
                            4000);
  END LOOP;

  DBMS_OUTPUT.put_line (l_line);

  l_line     := '';

  FOR i IN 1 .. l_column_count LOOP
    l_column_width   := get_length (l_column_descriptions (i));

    l_line           := l_line || RPAD ('-', l_column_width, '-');
    l_line           := l_line || ' ';
    DBMS_SQL.define_column (l_cursor,
                            i,
                            l_column_value,
                            4000);
  END LOOP;

  DBMS_OUTPUT.put_line (l_line);

  --   l_status   := sys.DBMS_SQL.execute (l_cursor);

  WHILE (sys.DBMS_SQL.fetch_rows (l_cursor) > 0) LOOP
    l_rec_count   := l_rec_count + 1;

    l_line        := '';

    FOR i IN 1 .. l_column_count LOOP
      DBMS_SQL.COLUMN_VALUE (l_cursor, i, l_column_value);
      l_column_value   := TRANSLATE (l_column_value, CHR (10), CHR (200));
      l_column_width   := get_length (l_column_descriptions (i));

      IF l_column_value IS NULL THEN
        l_line   := l_line || RPAD (' ', l_column_width);
      ELSE
        l_line   := l_line || RPAD (l_column_value, l_column_width);
      END IF;

      l_line           := l_line || ' ';
    END LOOP;

    DBMS_OUTPUT.put_line (l_line);
  END LOOP;

  IF l_rec_count = 0 THEN
    DBMS_OUTPUT.put_line ('No data found.');
  ELSE
    DBMS_OUTPUT.put_line (l_rec_count || ' rows returned.');
  END IF;

  sys.DBMS_SQL.close_cursor (l_cursor);

  -- It would be better to store the current NLS_DATE_FORMAT on entry and restore it here, instead of assuming that it was
  -- set to DD-MON-YYYY.
  EXECUTE IMMEDIATE 'alter session set nls_date_format=''DD-MON-YYYY''';
EXCEPTION
  WHEN OTHERS THEN
    EXECUTE IMMEDIATE 'alter session set nls_date_format=''DD-MON-YYYY''';
-- Add your own handling here.

END dump_cursor;

-- Tester code, make sure server output is on
BEGIN
  OPEN l_rc FOR 'SELECT object_id, object_name, object_type FROM dba_objects WHERE rownum <= 15';
  dump_cursor(l_rc);
END;
person Matthew McPeak    schedule 28.10.2015
comment
Спасибо за этот ответ. Я узнал о курсорах Ref гораздо больше, чем во многих документах нельзя было просто объяснить. Это определенно то, что я искал. - person Clint L; 05.02.2016