Oracle Pl/SQL: как реализовать указатели на типы записей в памяти

У меня есть два пакета pkg_company и pkg_employee. pkg_company включает определение типа (запись) typ_company. pkg_employee включает определение типа typ_employee.

У меня есть один экземпляр типа компании и тысячи экземпляров typ_employee. В типе сотрудника я хотел бы иметь указатель на экземпляр компании.

declare
    c pkg_company.typ_company;
    e pkg_employee.typ_employee;
begin
  c := pkg_company.get(12345);
  for e in (select employeeid from employee where companyid=12345)
  loop
    e := pkg_employee.get(12345, c);   -- c passed by reference in out nocopy
    pkg_employee.process(e);  -- I would like to access company info inside here as e.c.companyname
  end loop;  
end;

Как мне сохранить указатель на c внутри e? Я не хочу создавать тысячи экземпляров c. Просто хотите сохранить указатель и получить доступ к значению при необходимости.

Спасибо за помощь!

конец;

2 ответа

Решение

В Oracle SQL можно использовать REF а также DEREF функции для передачи логических указателей на объекты, хранящиеся в таблицах базы данных.
Но нет такой вещи, как указатель или ссылка в PL/SQL так что вам нужен обходной путь.

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

Подход 1 - метод кэшированного доступа

Общая идея состоит в том, чтобы получить доступ к объектам через функцию, которая кеширует результаты:

create or replace package EntitytAccess as

  procedure GetCompany1(
    pCompanyId in         number, 
    pCompany   out nocopy pkg_company.typ_company 
  ); 

  function GetCompany2(
    pCompanyId in number
  ) return pkg_company.typ_company;

  procedure ClearCompanyCache;

end;

Тело пакета доступа:

create or replace package body EntitytAccess as

  type typ_company_cache is table of pkg_company.typ_company index by number;

  var_company_cache typ_company_cache;

  procedure GetCompany1(
    pCompanyId in         number, 
    pCompany   out nocopy pkg_company.typ_company 
  )
  is           
  begin
    if( var_company_cache.exists(pCompanyId) ) 
      pCompany := var_company_cache(pCompanyId);
    else
      pCompany := pkg_company.get(pCompanyId);
      var_company_cache(pCompanyId) := pCompany;
    end if; 
  end;

  function GetCompany2(
    pCompanyId in number
  ) return pkg_company.typ_company
  is       
  begin
    if(not var_company_cache.exists(pCompanyId) ) 
      var_company_cache(pCompanyId) := pkg_company.get(pCompanyId);
    end if;                               
    return  var_company_cache(pCompanyId);
  end;

  procedure ClearCompanyCache
  is
  begin              
    var_company_cache.Delete;
  end;

end;

Использование:

declare
  c pkg_company.typ_company;
  e pkg_employee.typ_employee;
begin
  EntityAccess.GetCompany2(12345,c);
  for e in (select employeeid from employee where companyid=12345)
  loop

    e := pkg_employee.get(12345, c); -- c passed by reference in out nocopy
                                     -- and c.companyid stored inside e.

    pkg_employee.process(e);  -- No need to pass company, but inside process() 
                              -- method you need to call either 
                              -- EntityAccess.GetCompany1(e.companyid, var_c)
                              -- or
                              -- var_c := EntityAccess.GetCompany2(e.companyid)

  end loop;  
end;

Подход 2 - пакет среды

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

create or replace package ProcessEnvironment as

  var_current_company pkg_company.typ_company;

  -- may be more "global" variables here
end;
/

create or replace package body ProcessEnvironment as
end;

Использование:

declare
  e pkg_employee.typ_employee;
begin
  ProcessEnvironmant.var_current_company := pkg_company.get(12345);

  for e in (select employeeid from employee where companyid=12345)
  loop

    e := pkg_employee.get(12345, ProcessEnvironmant.var_current_company); 
                            -- c passed by reference in out nocopy
                            -- and c.companyid stored inside e.

    pkg_employee.process(e);  -- No need to pass company, but inside process() 
                              -- method you references
                              -- ProcessEnvironmant.var_current_company 
                              -- with appropriate checks

  end loop;  
end;

Смешанный подход

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

create or replace package EntitytAccess as

  procedure GetCompany(
    pCompanyId in         number, 
    pCompany   out nocopy pkg_company.typ_company 
  ); 

end;

Тело пакета:

create or replace package body EntitytAccess as

  var_cached_company pkg_company.typ_company;

  procedure GetCompany(
    pCompanyId in         number, 
    pCompany   out nocopy pkg_company.typ_company 
  )
  is           
  begin

    if( (var_cached_company is null) 
        or 
        (var_cached_company.comanyid != pCompanyId)
      )then

      var_company_cache := pkg_company.get(pCompanyId);

    end if; 

    pCompany := var_cached_company;

  end;

end;

Использование:

declare
  c pkg_company.typ_company;
  e pkg_employee.typ_employee;
begin
  EntityAccess.GetCompany(12345,c);

  for e in (select employeeid from employee where companyid= c.companyid)
  loop

    e := pkg_employee.get(c.companyid , c); -- c passed by reference in out nocopy
                                            -- and c.companyid stored inside e.

    pkg_employee.process(e);  -- No need to pass company, but inside process() 
                              -- method you need to call 
                              -- EntityAccess.GetCompany(e.companyid, var_c)
                              -- where var_c is company object variable.

  end loop;  
end;

Как мне сохранить указатель на c внутри e? Я не хочу создавать тысячи экземпляров c. Просто хотите сохранить указатель и получить доступ к значению при необходимости.

"pkg_employee.get" - это функция - удалить параметры OUT.

  1. Неправильно определять параметры OUT для функции.
  2. Почему это выходной параметр, если вы работаете только с результатом функции?

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

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