SQL Oracle - SUBQUERY для выборочного извлечения данных из *разного* столбца, в зависимости от условий таблицы

Сразу приступим. У нас есть таблицы (TABLE) и таблицы аудита (AUDITTABLE), в которых хранятся изменения в основной таблице.

В таблице у нас есть такие записи, как:

ИДЕНТИФИКАТОР Имя Созданный Идентификатор местоположения PersonID 1 Юта 01.01.94 35 1 2 Огайо 01.02.95 42 5

Изменение LocationID строки № 1 с 35 на 50 и PersonID с 1 на 3 будет записано в таблице аудита следующим образом (опуская некоторые столбцы для краткости, есть также такие элементы, как «создано» и «создано», чтобы отслеживать, когда и кем было установлено значение). измененный):

ИДЕНТИФИКАТОР Идентификатор источника Ключ Олдвалуе 1 1 ИДЕНТ. ЛОКАЦИОНА 35 2 1 ЛИЦОИД 1

Я пишу запрос, в котором извлекаю данные из таблицы аудита:

SELECT
    sourceid AS ID,
    id AS EVENT_ID
    key AS COLUMN,
    oldvalue AS PREVIOUS_ENTRY
FROM
    AUDITTABLE

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

РЕДАКТИРОВАТЬ

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

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

SELECT
    sourceid AS ID,
    id AS EVENT_ID
    key AS COLUMN,
    oldvalue AS PREVIOUS_ENTRY,
    (SELECT TABLE. || (SELECT TEMPAUDITTABLE.KEY 
                       FROM AUDITTABLE AS TEMPAUDITTABLE 
                       WHERE TEMPAUDITTABLE.SOURCEID = AUDITTABLE.SOURCEID) 
     FROM Table 
     WHERE Table.ID = Audittable.sourceid) AS Current_Value
FROM
    AUDITTABLE

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

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

Всем, кто хочет помочь мне указать правильное направление, спасибо!

Я пробовал Google, второй пилот, технологию в сети и искал вопросы о переполнении стека. Я также ссылался на документацию по SQL Oracle.

ReactJs | Supabase | Добавление данных в базу данных
ReactJs | Supabase | Добавление данных в базу данных
Это и есть ваш редактор таблиц в supabase.👇
Понимание Python и переход к SQL
Понимание Python и переход к SQL
Перед нами лабораторная работа по BloodOath:
0
0
55
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

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

create or replace function current_value(in_table_name in varchar2,
                                         in_row_column in varchar2, 
                                         in_row_id in integer,
                                         in_select_column in varchar2)
  return varchar2
as
  var_result varchar2(4000);
begin
  execute immediate 'select max("'||in_select_column||'") 
                       from "'||in_table_name||'" 
                      where "'||in_row_column||'" = :id' 
               into var_result 
           using in in_row_id;

  return var_result;
end;

Это, конечно, преобразует любой столбец в строку, поэтому, если вам нужна дата или число, либо повторно преобразуйте их при выводе, либо создайте другие функции или даже упакованные перегрузки для обработки других типов данных. Если все столбцы, которые вы отслеживаете, являются числовыми, замените varchar2 на number, в противном случае использование varchar2 безопаснее всего, поскольку к нему можно привести большинство типов данных. Он также явно не будет обрабатывать LOB и ожидает, что идентификатор строки будет числовым. Какая бы сложность вам ни потребовалась, она должна быть встроена, но это дает вам базовую основу.

Кроме того, чтобы иметь хоть какую-то надежду на достойную производительность, вам необходимо убедиться, что столбец, идентифицирующий строку (ID), проиндексирован в каждой таблице. Если это ПК, то так и должно быть. Затем вы просто запрашиваете таблицу аудита и вызываете функцию для каждой строки:

SELECT
    sourceid AS ID,
    id AS EVENT_ID
    key AS COLUMN,
    oldvalue AS PREVIOUS_ENTRY,
    current_value('MYTABLE','ID',id,key) current_value
FROM AUDITTABLE  

Спасибо, Пол! Это отличная информация, которая дает мне отличную отправную точку.

Joshua 29.05.2024 18:46
Ответ принят как подходящий

Вы можете использовать прием XML для генерации и запуска динамического SQL как части простого запроса без собственной функции PL/SQL.

Если вы добавите вызов

DBMS_XMLGEN.GETXMLTYPE ('select "' || key || '" from table1 where id = ' || sourceid)

на ваш запрос он даст вам текущее значение; но как часть XML-документа. Таким образом, вы можете использовать XMLQuery или XMLTable для его извлечения; это использует этот расширенный запрос как CTE:

WITH cte AS (
    SELECT
        sourceid AS ID,
        id AS EVENT_ID,
        key AS COLUMN_NAME,
        oldvalue AS PREVIOUS_ENTRY,
        DBMS_XMLGEN.GETXMLTYPE (
            'select "' || key || '" from table1 where id = ' || sourceid) AS XML
    FROM
        AUDITTABLE
)
SELECT
    ID,
    EVENT_ID,
    COLUMN_NAME,
    PREVIOUS_ENTRY,
    CURRENT_VALUE
FROM
    cte
CROSS APPLY XMLTABLE (
    '/ROWSET/ROW'
    PASSING cte.XML
    COLUMNS CURRENT_VALUE number PATH '.'
)
ИДЕНТИФИКАТОР ИДЕНТИФИКАТОР_СОБЫТИЯ COLUMN_NAME PREVIOUS_ENTRY ТЕКУЩАЯ СТОИМОСТЬ 1 1 ИДЕНТ. ЛОКАЦИОНА 35 50 1 2 ЛИЦОИД 1 3

с результатом в виде числа, что приемлемо для этих двух значений, но не в целом; вы можете изменить тип данных с числа на vachar2 при вызове XMLTable. А если текущего значения нет, вы можете использовать OUTER APPLY вместо CROSS APPLY.

Или, более непосредственно, с помощью XMLQuery:

SELECT
    sourceid AS ID,
    id AS EVENT_ID,
    key AS COLUMN_NAME,
    oldvalue AS PREVIOUS_ENTRY,
    XMLQUERY ('/ROWSET/ROW/*/text()'
        PASSING DBMS_XMLGEN.GETXMLTYPE (
            'select "' || key || '" from table1 where id = ' || sourceid)
        RETURNING CONTENT) AS CURRENT_VALUE
FROM
    AUDITTABLE
ИДЕНТИФИКАТОР ИДЕНТИФИКАТОР_СОБЫТИЯ COLUMN_NAME PREVIOUS_ENTRY ТЕКУЩАЯ СТОИМОСТЬ 1 1 ИДЕНТ. ЛОКАЦИОНА 35 50 1 2 ЛИЦОИД 1 3

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

рабочий пример

Функциональный подход PaulW может масштабироваться и работать лучше - не в последнюю очередь потому, что он позволяет предоставлять идентификатор (надеюсь, PK) в качестве переменной привязки, что сокращает объем жесткого анализа (вероятно, значительного).

Однако выражение Case может быть более практичным, чем любой из подходов, после его создания; и вы можете сгенерировать его из словаря данных, если не хотите вводить все это. Предположительно, таблица аудита заполняется из триггера, что может привести к обратной проблеме, поэтому вы сможете увидеть, как она там решается, включая то, как генерируются и поддерживаются триггеры.

Привет, Алекс! На самом деле это было именно то, что я искал! С базой данных, которую я использую, я не могу выполнять DML или DDL и в основном ограничен правами только на чтение. Решение PaulW подошло бы, но мне нужно будет подключить одного из наших разработчиков, чтобы внести изменения. Ваше решение позволило мне напрямую получить данные, поэтому спасибо! В итоге я добавил: .getStringVal() в конец XMLQUERY с подзапросом внутри для извлечения ключа, хранящегося в таблице аудита, и это точно сработало.

Joshua 29.05.2024 21:06

Вот что я сделал: <code> SELECT AUDITTABLE.sourceid AS ID, AUDITTABLE.id AS EVENT_ID, AUDITTABLE.key AS COLUMN_NAME, AUDITTABLE.oldvalue AS PREVIOUS_ENTRY, XMLQUERY ('/ROWSET/ROW/*/text()' PASSING DBMS_XMLGEN.GETXMLTYPE ( 'выберите "' || (выберите AT1.key из AUDITTABLE как AT1, где AT1.id = Audittable.id) || '" из TABLE, где table.id = ' || Audittable.sourceid) ВОЗВРАЩАЕТСЯ СОДЕРЖИМОЕ). getStringVal() КАК CURRENT_VALUE ИЗ AUDITTABLE </code>

Joshua 29.05.2024 21:09

Очевидно, я понятия не имею, как здесь форматировать блоки кода...

Joshua 29.05.2024 21:11

Я не уверен, зачем вам нужен подзапрос - похоже, он находит ту же строку в той же таблице, что и основной запрос, и у вас уже есть значение ключа из этого? (Но да, забыл упомянуть getstringval()...)

Alex Poole 29.05.2024 23:46

Не все столбцы таблицы имеют один и тот же тип данных, тип данных столбцов может быть изменен в течение срока службы приложения и т. д. Поэтому, если вы хотите (предыдущая_запись, текущее_значение), они могут быть только строковым представлением данных. Другим способом хранения этих данных аудита может быть полезная нагрузка JSON или XML, поэтому у вас будет только 1 таблица аудита (audit_pk, Meta_pk, полезная нагрузка) для всей схемы и 1 таблица метаданных аудита (meta_pk, имя таблицы, версия, мета_деск). , первый из которых ссылается на второй, Meta_desc содержит полезную нагрузку JSON или XML, описывающую метаданные таблицы для конкретной версии ее определения, метаданные, которые вы можете генерировать из user_tab_columns каждый раз, когда вы вносите изменения в метаданные таблицы. И у вас будет только 1 строка на модификацию исходной строки вместо 1 строки на каждый измененный столбец, и в конечном итоге можно будет иметь полиморфную табличную функцию, возвращающую изменения (таблицы, версии) в виде набора строк (исходный pk, date_modif , col1_old_value, col1_new_value, ..., colN_old_value, colN_new_value), при этом каждый столбец возвращается в исходном типе данных, PTF выполняет обратное преобразование из строки в исходный тип данных благодаря метаданным... (именно здесь все CASE для разных типов данных будут скрыты...) и по-прежнему иметь возможность возвращать тройки (имя_столбца, старое, новое), используя JSON_TABLE или XMLTABLE в сохраненной полезной нагрузке. Или иметь 1 VIEW для каждой (таблицы, версии) для выполнения работы PTF, поэтому вместо 1 AUDITTABLE для каждой ТАБЛИЦЫ у вас будет 1 AUDITVIEW для каждой ТАБЛИЦЫ... (должна быть возможность генерировать код VIEW на основе user_tab_columns слишком).

Другие вопросы по теме