Страницы

Поиск по вопросам

Показаны сообщения с ярлыком plsql. Показать все сообщения
Показаны сообщения с ярлыком plsql. Показать все сообщения

вторник, 7 апреля 2020 г.

выборка из базы данных строк с нарушением последовательности

#sql #база_данных #oracle #plsql

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

Необходимо найти строки операций по которым произошли нарушения арифметической последовательности
операции. 

Т.е., баланс не соответствует нужному числу с учетом суммы операций.

И еще найти такие строки, по которым дата операции с меньшим номером больше даты
операции с большим номером. 
    


Ответы

Ответ 1



Вы не привели структуру таблиц и данные. Применим экстрасенсорные способности. Структура таблицы такая: create table table1( usr_id int, -- Клиент opnum int, -- Порядковый номер операции, начиная с 1 opval int, -- Сумма операции balance int, -- Текущий баланс dt date -- Дата операции ); Баланс в любой строке равен сумме всех операций по данному клиенту с первой по текущую. Номера операций идут подряд +1 к предыдущей записи данного клиента, начиная с 1. Тестовые данные: insert into table1 values(1,1,100,100,sysdate-12); insert into table1 values(1,2,100,200,sysdate-11); insert into table1 values(1,3,-30,170,sysdate-9); -- Ошибка даты insert into table1 values(1,4,300,470,sysdate-10); -- Ошибка номера, ошибка суммы insert into table1 values(2,1,100,100,sysdate-12); insert into table1 values(2,3,100,200,sysdate-11); -- Пропущен номер операции "2" insert into table1 values(2,4,100,300,sysdate-10); insert into table1 values(2,5,50,350, sysdate-9); insert into table1 values(2,6,-150,200,sysdate-8); insert into table1 values(2,7,100,250,sysdate-7); -- Ошибка в сумме Запрос: select * from ( select a.*, (lag(opnum,1,0) over(partition by usr_id order by dt))+1 n_opnum, -- Ожидаемый номер sum(opval) over(partition by usr_id order by dt) n_balance -- Ожидаемая сумма from table1 a order by usr_id,dt -- Для наглядности, на правильность работы не влияет ) where n_opnum!=opnum -- Номер операции не соответствует or n_balance!=balance -- Сумма не соответствует Результат: USR_ID OPNUM OPVAL BALANCE DT N_OPNUM N_BALANCE 1 4 300 470 28.01.2016 18:27:37 3 500 1 3 -30 170 29.01.2016 18:27:37 5 470 2 3 100 200 27.01.2016 18:27:37 2 200 2 7 100 250 31.01.2016 18:27:37 7 300 Вот примерно так. Используются оконные функции, незаменимые в таких случаях. lag(opnum,1,0) дает значение поля opnum из предыдущей записи в окне, 0 - для первой записи в окне. sum(opval) over(order by) - нарастающая сумма opval в окне. Окно в таких функциях можно задавать различными способами. В данном случае мы используем partition by что бы окно было в пределах одного клиента и применяем order by для указания порядка операций в пределах клиента.

вторник, 31 марта 2020 г.

Как перечислить все записи в тегах

#sql #oracle #plsql #plsql_developer


Есть строка  '/home/pc/test' ,
и слова в тегах '[/alseko][/logs][/archive]'
Как получить ожидаемый результат =   

 [/home/pc/test/alseko][/home/pc/test/logs][/home/pc/test/archive]  


Мой код, пытаюсь найти:  

select '[' || '/home/pc/test' ||
       ltrim(substr('[/alseko][/logs][/archive]',
              instr('[/alseko][/logs][/archive]', '['),
              instr('[/alseko][/logs][/archive]', ']')),'[')
from dual

    


Ответы

Ответ 1



Надо распарсить теги в таблицу, и потом заново собрать в строку, например так: with data as ( select '[/alseko][/logs][/archive]' tags, '\[(\/\w+)' pattern, '/home/pc/test' prefix from dual ), tags as ( select '['||prefix||regexp_substr (tags, pattern, 1, level, null, 1)||']' tag, level sort from data connect by level <= regexp_count (tags, pattern) ) select listagg (tag) within group (order by sort) from tags ; Вывод: [/home/pc/test/alseko][/home/pc/test/logs][/home/pc/test/archive]

Ответ 2



Получился сделать через цикл, сперва определил количество вхождений Declare vn_count_paths Integer; vc_paths varchar(1024); vc_FtpDir varchar(1024); begin vn_count_paths:= length('[/alseko][/logs][/archive]')-LENGTH(REPLACE('[/alseko][/logs][/archive]','[')); dbms_output.put_line('Size: ' || vn_count_paths); for i in 1 .. vn_count_paths loop vc_paths:= vc_paths||'['||'/home/pc/test'||ltrim(substr('[/alseko][/logs][/archive]',instr('[/alseko][/logs][/archive]','[',1,i)+1, instr('[/alseko][/logs][/archive]',']',1,i)- instr('[/alseko][/logs][/archive]','[',1,i)-1),'[')||']'; end loop; vc_FtpDir := '['||'/home/pc/test'||']'||vc_paths; dbms_output.put_line('vc_FtpDir: ' || vc_FtpDir); end;

Автодобавление текстовых партиций

#sql #oracle #plsql #oracle11g


Цель: Автоматическое добавление новых партиций при добавлении  нового текстового значения

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

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

Что то типа такого (выдуманный пример):

create table char_part
(
    id integer,
    txt varchar2(200)
)
partition by range (txt) interval (1) -- Проблема в автодобавлении нового текст. значения
(
    partition "abc" values less than ('abc'),
    partition "max" values less than (maxvalue) -- Для null - значений
)

    


Ответы

Ответ 1



TL;DR: В версии 11g это невозможно. Лучшее решение - перейти на 12c, так как в этой версии уже введено автоматическое лист секционирование. Можно решить задачу через секционирование по interval появившиеся в 11g. На основе бизнес требований надо продумать, как вычислqть колонку для секционирования. Как например, спроси у Тома. Предложение для задачи как в вопросе: create table char_part ( id integer, txt varchar2(200), txt# generated always as (coalesce (ora_hash (txt, power (2,20)-1), 0)) virtual ) partition by range (txt#) interval (1) ( partition "empty" values less than (0), partition "undef" values less than (1) ); insert into char_part (id, txt) select 1, 'ABC' from dual union all select 2, 'DEF' from dual union all select 3, 'ZZZ' from dual union all select 9, null from dual ; select table_name, partition_name, high_value from user_tab_partitions where table_name=upper('char_part') ; TABLE_NAME PARTITION_NAME HIGH_VALUE ---------- -------------------- ---------- CHAR_PART SYS_P1141 222410 CHAR_PART SYS_P1142 59915 CHAR_PART SYS_P1143 627928 CHAR_PART empty 0 CHAR_PART undef 1

Ответ 2



В версии 12.2 такое стало возможно. Для уже существующей таблицы ALTER TABLE char_part SET PARTITIONING AUTOMATIC;. При создании таблицы: ... PARTITION BY LIST (txt) AUTOMATIC (PARTITION part_abc VALUES ('ABC'), PARTITION part_def VALUES ('DEF')); Обратите внимание: не less than, а values, т.е. в таблице секционирование по списку, а не по диапазону значений, иначе с текстовыми данными смысл теряется. В примере также указано, что можно создать секции сразу, несмотря на автоматическое секционирование.

суббота, 21 марта 2020 г.

Какая максимальная длина идентификаторов и можно ли ее изменить?

#sql #oracle #plsql #oracle11g #oracle12c


Использую версию 11g и хотел бы давать имена длина которых более чем 30 символов.
Мне известно, что макс. длина в 11g только 30 символов:

create table tab (very_very_very_very_very_very_very_very_LongColumnName number)



  ORA-00972: identifier is too long


Возможно ли как-то изменить макс. длину? 

Какая макс. длина идентификаторов в версии 12c?
    


Ответы

Ответ 1



Имена объектов БД 11g, а также в версии 12cR1, ограничены 30 байтами (в single-byte кодировке это эквивалентно 30 символам). Можно ли это изменить? Нет, не существует способа измененить это, чтобы можно было пользоваться именами обьектов БД длиной более чем 30 байтов. Ограничение в 30 байт было впервые снято во втором выпуске 12c ( 12cR2), и если значение параметра инициаллизации COMPATIBLE установленно в 12.2 и выше, то макс. длина идентификаторов может быть до 128 байт. SQL> show parameters compat NAME TYPE VALUE ------------------------------------ ----------- ------------------------------ compatible string 12.2.0 SQL> create table tab (very_very_very_very_very_very_very_very_LongColumnName number); Table TAB created. SQL> declare very_very_very_very_very_very_very_very_LongVariableName number; begin null; end; / PL/SQL procedure successfully completed. Источник ответа: @Nick Krasnov.

Как пользоватся типом данных BOOLEAN в SELECT запросе?

#sql #oracle #plsql


Имеется PL/SQL функциия с типом данных BOOLEAN как параметр:

function get_something(name in varchar2, ignore_notfound in boolean);


Эта функция часть инструментов от сторонних разработчиков и я не могу её менять.

Хотелось  бы использовать эту функцию в SELECT запросе:

 select get_something('NAME', TRUE) from dual;


Но так не работает, получаю ошибку:


  ORA-00904: "TRUE": invalid identifier


Как понимаю, кючевое слово TRUE не распознаётся.

Как же сделать чтобы оно работало?
    


Ответы

Ответ 1



Можно сделать обёрточную функцию, например: function get_something (name in varchar2, ignore_notfound in varchar2) return varchar2 is begin return get_something (name, (upper(ignore_notfound) = 'TRUE')); end; Затем вызывать её так: select get_something ('Name', 'true') from dual; Решите, какие значения для ignore_notfound больше подходят. Я предположил, что 'TRUE' или 'true' означает TRUE, всё остальное - FALSE. Источник @TonyAndrews

воскресенье, 15 марта 2020 г.

Ошибка мутирующих таблиц в триггере (ORA-04091), когда запросе участвуют таблицы Master-Detail

#sql #oracle #plsql #триггер


Помогите исправить триггер:

CREATE OR REPLACE TRIGGER OPTT.TBDR_PROTOCOL
AFTER DELETE
ON OPTT.PROTOCOL 
REFERENCING NEW AS NEW OLD AS OLD
FOR EACH ROW
begin
   delete from RULE 
   where  RULE.OWNERENTITYGUID = 'tblJBLJOL4TFBAHLBEWZMLSHXYGMM'--this table idx
   and    RULE.OWNERPID = :old.ID
   and    RULE.CLASSGUID = 'acl4ZWIAW7W45H4HKTIPF6W6IQZTA';

   exception when NO_DATA_FOUND then
       null;    
end;
/


Где, RULE - master таблица, а PROTOCOL - любая таблица, не является для RULE detail
таблицей

При выполнении получается ошибка:


  ORA-04091: table OPTT.TESTCASESTEPRULE is mutating, trigger/function
  may not see it


Где OPTT.TESTCASESTEPRULE - является detail таблицей для RULE, в триггере таблица
PTT.TESTCASESTEPRULE не участвует в запросах

Причем delete RULE можно спокойно заменить на select* from RULE ошибка останется
такая же. 

Спасибо заранее!
    


Ответы

Ответ 1



TL;DR: Не надо использовать триггер там, где в этом нет никакой необходимости. Удалите триггер, перенесите логику из триггера в то место, где вызывается DML выражение, которое приводит к срабатыванию триггера. Создайте там процедуру или функцию, если эта логика используется не единожды в приложении. Перед тем как создать триггер, надо убедится в его необходимости ознакомившись с рекомендациями по применению триггеров, например в офф. документации. Само появление ошибки - ORA-04091: table is mutating, говорит, что допущена логическая ошибка в дизайне кода и БД вынуждена прибегнуть к защите от возможной потери целостности данных в мульти-пользовательской среде. Выше изложеное не раз обсуждалось на различных ресурсах, например, цитирую Тома Кайта на спроси Тома: My personal opinion -- when I hit a mutating table error, I've got a serious fatal flaw in my logic. Have you considered the multi-user implications in your logic? Two people inserting at the same time (about the same time). What happens then??? Neither will see eachothers work, neither will block -- both will think "ah hah, I am first"... anyway, you can do too much work in triggers, this may well be that time -- there is nothing wrong with doing things in a more straightforward fashion (eg: using a stored procedure to implement your transaction) PS Кроме того, не возникнет ситуация как в вопросе, где с наибольшей долей вероятности, поиск ошибки идёт не в том триггере, который эту ошибку действительно вызвал.

Ответ 2



Тут сложно ответить, не видя реальных связей между таблицами. Как вариант обхода такой мутации - написать второй триггер типа statement. В первом триггере, который построчный - инициализировать пакетную переменную как :old.ID, во втором, который statement - вызывать процедуру удаления, используя пакетную переменную, установленную на первом шаге. Естественно, ограничение - удалять записи из rule можно только по одной. PS. Exception тут лишний, как мне кажется.

среда, 4 марта 2020 г.

Группировка с зависимой максимизацией двух столбцов (без подзапросов)

#sql #oracle #plsql


Есть задача вывести все строки, группируя таблицу по столбцу t_name, при этом максимизируя
по двум столбцам t_date и t_num, так что 


вначале максимизируя по столбцу t_num
затем оставшаяся выборка максимизируется по t_date


Вопрос: можно ли это организовать без подзапросов (например, оконными функциями или
другими способами), и будет ли это работать быстрее?

Для примера


   t_name   t_date      t_num
1  aaa      20.01.2018  3
2  aaa      03.01.2018  10
3  aaa      01.01.2018  10
4  aaa      (null)      10
5  bbb      19.01.2018  1
6  bbb      (null)      10



в результате максимизации по столбцу t_num останутся значения



   t_name   t_date      t_num
2  aaa      03.01.2018  10
3  aaa      01.01.2018  10
4  aaa      (null)      10
6  bbb      (null)      10



затем выборку максимизируется по столбцу t_date и останутся значения



   t_name   t_date      t_num
2  aaa      03.01.2018  10
6  bbb      (null)      10


Это легко можно сделать через подзапросы

SELECT
  T.T_NAME,
  MAX(T.T_DATE) AS T_DATE,
  G.T_NUM 
FROM 
  TEST_GROUPING T
  INNER JOIN (
               SELECT T_NAME, MAX(T_NUM) AS T_NUM 
               FROM TEST_GROUPING 
               GROUP BY T_NAME
             ) G ON G.T_NAME = T.T_NAME AND G.T_NUM = T.T_NUM
GROUP BY
  T.T_NAME,
  G.T_NUM 

    


Ответы

Ответ 1



Да, оконными функциями делается элементарно: with t (t_name, t_date, t_num) as ( select 'aaa', to_date('20.01.2018', 'dd.mm.yyyy'), 3 from dual union all select 'aaa', to_date('03.01.2018', 'dd.mm.yyyy'), 10 from dual union all select 'aaa', to_date('01.01.2018', 'dd.mm.yyyy'), 10 from dual union all select 'aaa', null, 10 from dual union all select 'bbb', to_date('20.01.2018', 'dd.mm.yyyy'), 1 from dual union all select 'bbb', null, 10 from dual) select t_name, t_date, t_num from (select t_name, t_date, t_num, row_number() over (partition by t_name order by t_num desc, t_date desc nulls last) rn from t) where rn = 1 Работать тоже должно быстро, особенно если у вас есть индекс по t_num, а лучше даже по (t_num, t_date).

среда, 26 февраля 2020 г.

Создание коллекции с типом данных rowtype одной таблицы

#oracle #коллекции #plsql


Каким образом можно создать коллекцию состоящию из rowtype, для использования ее
в дальнейшем? 

С возможностью удаления и добавления ее элементов, причём все элементы типа rowtype
будут из одной таблицы.  Что-то типа: 

my_col(1) := table%rowtype


С PL/SQL знаком мало и не могу найти подходящий пример.
    


Ответы

Ответ 1



Коллекция определяется, и затем объявляется переменная соответствующая этой коллекции, в декларативной части блока, пакета или функции ключевым словом type: create table table1 as select * from dual; declare type myCollType is table of table1%rowtype index by binary_integer; myRow table1%rowtype; myColl myCollType; begin select * into myRow from table1 where rownum = 1 ; myColl(1) := myRow; dbms_output.put_line ('myColl(1).dummy='||myColl(1).dummy); end; / myColl(1).dummy=X Различные типы коллеккций инициализируются и заполнятся по разному, подробнее о выборе коллекции в теме Какой тип коллекции выбрать. Подробнее про объявление, инициализацую и использование PL/SQL коллекций в офф. док. Collection Variable Declaration.

Объявление переменной типа RECORD в объекте

#sql #oracle #plsql #oracle12c


Хочу создать объект с переменной типа record внутри, пишу код:

CREATE OR REPLACE TYPE someType_t AS OBJECT
(
  connection UTL_TCP.connection

) FINAL;


Выдает ошибку: Error: PLS-00201: identifier 'UTL_TCP.CONNECTION' must be declared

Гранты на UTL_TCP есть



Если объявить переменную этого типа в анонимном блоке, то всё ок

declare
  connection UTL_TCP.connection;
begin
  null;
end;


Версия: Oracle Database 12c Enterprise Edition Release 12.2.0.1.0 - 64bit Production
    


Ответы

Ответ 1



Если указать полное имя типа, то сообщение об ошибке будет более понятным: create or replace type tcpconn as object ( conn sys.utl_tcp.connection ); / PLS-00329: schema-level type has illegal reference to SYS.UTL_TCP Нельзя использовать типы данных обьявленные не на уровне схемы. Для переменных с PL/SQL типом данных воспользуйтесь пакетами: create or replace package util_tcp as function getConnection return utl_tcp.connection; procedure setConnection (conn utl_tcp.connection); end; / create or replace package body util_tcp as conn_ utl_tcp.connection; function getConnection return utl_tcp.connection is begin return conn_; end; procedure setConnection (conn utl_tcp.connection) is begin util_tcp.conn_ := conn; end; end; /

Ответ 2



Проблема оказалась банальной - SQL не поддерживает PL/SQL типы данных для аттрибутов объектов.

пятница, 14 февраля 2020 г.

Ошибка при создании пакета - ORA-00955: name is already used by an existing object

#oracle #plsql #sqldeveloper


Вот мой скрипт для создания пакета:

CREATE OR REPLACE PACKAGE PP AS
  TYPE REFCURSOR IS REF CURSOR;
  FUNCTION getData(id IN VARCHAR2) RETURN REFCURSOR;
END PP;


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


  ORA-00955: name is already used by an existing object


Как мне пересоздать пакет?
    


Ответы

Ответ 1



Не хватает завершающего символа после объявления пакета. Надо так: ... END PP; / Отличия и общее для завершающих символов "/" и ";" в SQl Developer следующее: Оба символа не входят в стандарт SQL и никак в SQL запросе не интепритируются. ";" является составной частью языка PL/SQL, он завершает законченое выражение. Оба не посылаются на сервер, а служат только как служебные символы - "здесь конец". В одной вкладке редактора (SQL Worksheet) могут находится многочисленные запросы и анонимные блоки. Поэтому, для выполнения одного запроса или блока (по умолчанию с CTRL-ENTER), необходимо: SQL запросы не содержащих PL/SQL блока, в частности - все DML, некоторые DDL, DCL - необходимо завершить его либо с ";", либо с "/" на новой строке. Анонимные блоки и SQL запросы содержащие PL/SQL блок (create ... package/function/trigger/... ) или потенциально могущие его содержать (create ... type), необходимо завершить его с "/", т.к. ";" завершает последний END в блоке. Если SQl Developer не находит завершающих символов, он ищет их дальше по тексту, и когда их найдёт (если не найдёт, то до конца текста), посылает несколько запросов как один, что приводит к неопределённому результату. Обычно ошибку выполнения, которая не всегда совсем понятна. Например: create type idType as object (id number); show errors / Error(2,1): PLS-00103: Encountered the symbol "SHOW" На заметку: Если запрос или блок выделить визуально, тогда SQl Developer выполнит только выделенное, даже если завершающие символы отсутствуют.

Разница в производительности динамического SQL между bind переменныеми и конкатенированием

#sql #oracle #plsql


Есть ли весомая разница в производительности на примере БД ORACLE при использовании
bind переменных и конкатенировании для динамического SQL?

Например, если мы подставляем в EXECUTE IMMEDIATE переменную с типом дата, ORACLE
все равно приводит её к строке, или уже знает, что это дата и не выполняет неявного
преобразования?

Пример кода:

declare
  sdate date;
  str   varchar2(255);
  table_type;--Табличный пользовательский тип
begin
  sdate := sysdate;
  str   := 'select * from oper o where date > to_date(''' || sdate || ',''dd.mm.yyyy'')';
  execute immediate str bulk collect into table_type;
  str   := 'select * from oper o where date > :sdate';
  execute immediate str bulk collect into table_type using sdate;
end;

    


Ответы

Ответ 1



Во первых эти два запроса в общем случае могут возвращать разные результаты, т.к. во втором случае bind variable будет содержать компоненту времени, а в первом случае только дату (т.е. время: 00:00:00). Во вторых Oracle DB будет обрабатывать эти запросы по-разному: запрос с to_date(''' || sdate || ',''dd.mm.yyyy'')' - будет парсится при каждом выполнении, после этого будет расчитан хеш строки запроса и по этому хешу Oracle попытается найти в Library Cache (принадлежит Shared Pool) не обрабытывал ли он уже точно такой же (с точностью до знака/пробела) запрос раньше. Если такой хеш найден в Library Cache и в запросе отсутствуют bind variables, то Oracle использует план запроса из Library Cache, т.е. повторно план выполнения не строится. В вашем случае (если использовать sysdate) каждый запрос с отличающейся датой будет иметь отличный хеш. Такие запросы могут вытеснять другие полезные запросы из Library Cache, что может привести к более длительной обработке запросов. При использовании bind variables хеш запроса будет всегда одинаковым вне зависимости от значения bind variable. Дальше все немного сложнее, т.к. Oracle будет анализировать значение bind variable для того чтобы принять решение - подходит ли один из существующих в Library Cache план выполнения запроса или же стоит построить новый план выполнения. Подробнее о Adaptive Cursor Sharing можно прочитать здесь и здесь. В общем случае лучше использовать bind variables, но в старых версиях Oracle (до 12.1) иногда Adaptive Cursor Sharing отрабатывал не очень хорошо и на практике в DWH-подобных БД иногда специально делали так, чтобы Adaptive Cursor Sharing не отрабатывал.

Можно ли убрать спулинг фразы “PL/SQL procedure successfully completed.” и только её?

#sql #oracle #plsql #sqlplus


Спулю в файл из PLSQL блока будущий скрипт на исполнение (своим генератором скриптов).
В конце он приписывает эту фразу, что мешает сразу отправить скрипт на исполнение.
Нужно зайти, удалить последние три строки (еще два переноса), и только тогда исполнить.
Как это отключить? В шапке генератора сейчас:

set serveroutput on
set termout off
set serveroutput on size 1000000
set linesize 32767
set trim on
set trims on
spool scripts.sql

    


Ответы

Ответ 1



Надо добавить до PL/SQL блока: set feedback off И опять включить, если для последующих команд скрипта нужен отклик: set feedback on PS почти все set параметры можно объединять и сокращать, например: set lines 32767 pages 0 trim on feed off

четверг, 13 февраля 2020 г.

Курсор выполняется в блоке, который завершается успешно, но на выходе ничего нет

#oracle #plsql #sql


Есть таблица Lease в которой хранятся данные о договорах,
и есть таблица Tenant в которой хранятся данные об арендаторах.

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

Собственно пишет, что команда PL/SQL завершена успешно, но на выходе ничего:

SET SERVEROUTPUT ON;

--accept firstchar prompt 'Введите первую букву фамилии арендатора: ';
DECLARE 
CURSOR lease_curs2 IS SELECT* FROM Lease;
row_lease lease_curs2%ROWTYPE; 
BEGIN
    OPEN lease_curs2;
    WHILE lease_curs2%FOUND 
        LOOP
        BEGIN
            FETCH lease_curs2 INTO row_lease;
            FOR char IN (SELECT* FROM Tenant WHERE Tenant.NTn = row_lease.NTn)
            LOOP
                IF char.Tn LIKE 'S%' THEN
            DBMS_OUTPUT.PUT_LINE ('Lease number: ' || row_lease.NLease);         
        END IF;
            END LOOP;
        END;
        END LOOP;
CLOSE lease_curs2;
END;
/

    


Ответы

Ответ 1



Что-то вроде этого: declare cursor lease_cur is select * from lease where tn like 'S%' ; lease_rec lease_cur%rowtype; begin open lease_cur; loop fetch lease_cur into lease_rec; dbms_output.put_line('Lease number: '||lease_rec.nlease); exit when lease_cur%NOTFOUND; end loop; close lease_cur; end;

Ответ 2



Кроме отсутствующих чтения данных с fetch и закрытия курсора с close, блок в вопросе имеет ряд серьёзных недостатков: Вложенные циклы с открытием нового курсора внутри цикла - плохая практика. Надо по возможности ограничиться одним запросом, и пусть цикл выполняется в SQL движке, он сделает это наиболее эффективно. Не надо держать курсоры открытыми обрабатывая пошагово данные в цикле. Это не клиентское приложение, блок выполняется на сервере. Лучше считать все данные в табличную переменную и потом работать с ней. Рабочий пример учитывая вышесказанное: declare cursor cur (prefix varchar2) is select * from tenant t join lease l on l.ntn = t.ntn where t.tn like prefix||'%' ; type arr is table of cur%rowtype; rows arr; begin open cur ('S'); fetch cur bulk collect into rows; close cur; for ix in 1..rows.count loop dbms_output.put_line ('Lease number: '||rows(ix).nlease||' for '||rows(ix).tn); end loop; end; / Вывод: Lease number: 10 for Smith Lease number: 20 for Sue Тестовые данные для примера: with tenant (ntn, tn) as ( select rownum, trim (column_value) from xmltable ('"Smith", "Sue", "Woo"')), lease (nlease, ntn) as ( select ntn*10, ntn from tenant )

Ответ 3



Ваша ошибка в том, что вы забыли перед WHILE lease_curs2%FOUND добавить FETCH lease_curs2 INTO row_lease; DECLARE CURSOR lease_curs2 IS SELECT* FROM Lease; row_lease lease_curs2%ROWTYPE; BEGIN OPEN lease_curs2; FETCH lease_curs2 INTO row_lease; WHILE lease_curs2%FOUND LOOP BEGIN FETCH lease_curs2 INTO row_lease; FOR char IN (SELECT* FROM Tenant WHERE Tenant.NTn = row_lease.NTn) LOOP IF char.Tn LIKE 'S%' THEN DBMS_OUTPUT.PUT_LINE ('Lease number: ' || row_lease.NLease); END IF; END LOOP; END; END LOOP; CLOSE lease_curs2; END; /

воскресенье, 9 февраля 2020 г.

Возможно ли выполнить изменения записи DML предложениями - INSERT, UPDATE, DELETE - через представление (view)?

#sql #oracle #plsql #view


Возможно ли выполнить изменения записи DML выражениями - INSERT, UPDATE, DELETE через
представление (view)?   
    


Ответы

Ответ 1



Представления в БД могут быть изменяемыми (updateable views) при определённых условиях. Выдержка из Oracle SQL Reference: Замечания для изменяемых представлений Изменяемое представление значит, что оно может быть использовано для изменения записей в его базовых таблицах. Можно создать представление, которое в принципе изменяемо (т.е. само по себе изменяемо), или также возможно создать для любого представления INSTEAD OF триггер, чтобы сделать его изменяемым. Узнать, какие и каким образом колонки в принципе изменяемого (т.е изменяемого без триггера) представления могут быть изменены, можно посмотреть через представлние USER_UPDATABLE_COLUMNS. Для того, чтобы представление могло быть изменено, все следующие условия должны выполнятся: Каждая колонка представления должна представлять колонку одной таблицы. Например, если колонка представляет вывод TABLE оператора, то это условие не выполняется. Представление не должно содержать одну из следующих конструкций: SET оператор DISTINCT оператор Агрегатную или аналитическую функцию GROUP BY, ORDER BY, MODEL, CONNECT BY, или START WITH выражения Выражение для коллекций в листе SELECT Подзапрос в листе SELECT Подзапрос с WITH READ ONLY Соединения (joins), с некоторыми исключениями указаными в документе Oracle Database Administrator's Guide Дополненительно, если в принципе изменяемое представление содержит псевдо колонки или выражения, то нельзя изменить записи таблицы с UPDATE предложением, которое обращается к этим псевдоколонкам или выражениям. Если надо сделать изменяемым представление содержащее соединение таблиц, то все следующие условия должны быть соблюдены: DML предложение должно затрагивавть только одну таблицу соединения Для INSERT предложения, представление не должно быть создано с WITH CHECK OPTION, и все колонки, в которые вставляются значения, должны происходить из таблицы с сохранёнными ключами. Таблица с сохранёнными ключами (key-preserved table), это базовая таблица, в которой каждое значение первичного или уникального ключа сохранит свою уникальность также в представлении после соединения. Для UPDATE предложения, представление не должно быть создано с WITH CHECK OPTION, и все колонки, которые подлежат изменению, должны происходить из таблицы с сохранёнными ключами. Для DELETE предложения, если в результате соединения более чем одна таблица будет с сохранёнными ключами, то удаление будет из первой таблицы указанной в FROM выражении, независимо от того, было ли создано представление с WITH CHECK OPTION или без него. Источник ответа @DCookie. При переводе сверенно с офф. документацией актуального релиза 19c

Ответ 2



Если в созданном представлении существуют ограничения описанные в ранее данном ответе и оно в принципе не изменяемо, то можно сделать его изменяемым через INSTEAD OF триггер. Простейший случай - изменения представления с 1:N (one-to-many) связью: create table items (id number primary key, item varchar2 (64)); create table parts ( id number primary key, itemid number, part varchar2 (64), constraint fk_part foreign key (itemid) references items (id) ); create or replace view itemparts as select i.id itemid, i.item, p.part from items i join parts p on p.itemid = i.id; Попытки изменить таблицу items: insert into itemparts (itemid, item) values (1, 'item 1'); update itemparts set item = item||'*' where itemid = 1; закончатся безуспешно потому, что первичный ключ items.id в преставлении не сохранил свою уникальность: ORA-01779: cannot modify a column which maps to a non key-preserved table Cause: An attempt was made to insert or update columns of a join view which map to a non-key-preserved table. Action: Modify the underlying base tables directly. Сделаем представление изменяемым через триггер: create or replace trigger trig_itemparts instead of insert or update or delete on itemparts declare dummypartid constant number := 1e20; begin --dbms_output.put_line ('trig_mail_address_book: '||:new.pk_serial_no||'|'||:new.address_a||'|'||:new.address_b); if inserting then -- the same for updating, deleting insert into items values (:new.itemid, :new.item); insert into parts values (dummypartid, :new.itemid, 'dummy part'); elsif updating then update items set item = :new.item where id = :new.itemid; end if; end; / Повторим попытки изменения (см. выше) и результат на лицо: select * from itemparts order by itemid; ITEMID ITEM PART ---------- ---------- ---------- 1 item 1* dummy part

среда, 5 февраля 2020 г.

Селект в коллекцию с пользовательским типом данных

#sql #oracle #plsql


Как сделать селект в в коллекцию с пользовательским типом данных?

Например, есть таблица:

create table emp (  
  empno    number(4,0),  
  ename    varchar2(10),  
  job      varchar2(9),  
  mgr      number(4,0),  
  hiredate date,  
  sal      number(7,2),  
  comm     number(7,2),  
  deptno   number(2,0),  
  constraint pk_emp primary key (empno),  
  constraint fk_deptno foreign key (deptno) references dept (deptno)  
)


Cоздаю пользовательский тип:

CREATE OR REPLACE TYPE typ_emp as OBJECT (
  name VARCHAR2(20),
  deptno VARCHAR2(20)
);

CREATE OR REPLACE TYPE emp_tbl AS TABLE OF typ_emp;


Пишу функцию:

CREATE OR REPLACE FUNCTION getEmpl
  RETURN emp_tbl
  AS
  tbl emp_tbl ;
  BEGIN
    SELECT e.ENAME, e.DEPTNO INTO tbl FROM EMP e;
  RETURN tbl;
  END;


Компиляция с ошибками:


  PL/SQL: ORA-00947: not enough values
  PL/SQL: SQL Statement ignored

    


Ответы

Ответ 1



Вам надо обернуть результат запроса в нужный тип и извлекать через bulk collect into. CREATE OR REPLACE FUNCTION getEmpl RETURN emp_tbl AS tbl emp_tbl ; BEGIN SELECT typ_emp(e.ENAME, e.DEPTNO) BULK COLLECT INTO tbl FROM EMP e; END; Чуть больше примеров на en-so. И сейчас ваша функция не возвращает никакого результат, это странно.

Ответ 2



Так можно обойтись только с INTO: create or replace function getEmpl return emp_tbl as ret emp_tbl; begin select cast (multiset (select e.name, e.deptno from emp e) as emp_tbl) into ret from dual ; return ret; end; / Не рекомендуется для больших коллекций, т.к. создаются временные объекты в sql контехте и поэтому уступит по быстродействию bulk collect. И кроме того, fetch ... limit , если потребуется, в данном случае не возможен.

Отключение конкретной сессии / всех сессий

#sql #база_данных #oracle #plsql #plsql_developer


Добрый день!
Есть темповая таблица, которую необходимо альтернуть (добавить столбец).
Понятное дело, при 300 активных сессиях кто-то эту таблицу использует - из за этого
валится ora-14450.

Могу я как то отследить, какая именно сессия обращается к необходимой мне таблице? 
И
 alter system kill ...?

Если же это отследить невозможно - как отключить все активные сессии?
    


Ответы

Ответ 1



Получить информацию о сессиях, использующих временную таблицу можно следующим запросом: select s.* from v$lock l, dba_objects d, v$session s where d.owner='СХЕМА' and d.OBJECT_NAME='ИМЯ-ТАБЛИЦЫ' and l.id1=d.object_id and l.type='TO' and s.sid=l.sid далее сессии можно убить с помощью ALTER SYSTEM KILL SESSION 'sid,serial#, где sid и serial# взяты из предыдущего запроса. Если сессий много, можно убить их все автоматически, следующим PL/SQL блоком (предварительно проверив то ли вы получаете, что надо, первым запросом): begin for c1 in(select distinct s.sid, s.serial# from v$lock l, dba_objects d, v$session s where d.owner='СХЕМА' and d.OBJECT_NAME='ИМЯ-ТАБЛИЦЫ' and l.id1=d.object_id and l.type='TO' and s.sid=l.sid) loop execute immediate 'ALTER SYSTEM KILL SESSION '''||c1.sid||','||c1.serial#||''''; end loop; end; /

Ответ 2



В таких случаях часто бывает достаточным просто установить DDL_LOCK_TIMEOUT перед выполнением DDL: alter session set ddl_lock_timeout=600; Если в течении 10 минут (600 секунд) объект освободится даже на очень короткий промежуток времени, то Oracle сможет выполнить DDL

воскресенье, 2 февраля 2020 г.

Как обрезать регулярным выражением конец строки начиная со знака если он встретился последний раз

#sql #регулярные_выражения #строки #oracle #plsql


Есть запрос: 

SELECT my_scheme.my_package.my_func ('param1', 'param2', int_param3) AS her FROM DUAL 


который возвращает: 

р-н Московский, п. Первомайский, ул. трактористов, д. 10, п. 11, ящ. 32123


Мне нужно обрезать , ящ. 32123 . Кол-во запятых до может быть разным, всегда надо
обрезать начиная с последней запятой.
    


Ответы

Ответ 1



Если задача стоит как отрезать кусок начиная с последней запятой, то регулярка тут не обязательна. Можно сделать например так: substr(s, 1, instr(s, ',', -1) - 1)

Ответ 2



select regexp_replace(str,'(.*),.*','\1') from DUAL

Ответ 3



Так покороче и более понятно: select regexp_replace(str, ',[^,]*$') from ( select 'р-н Московский, п. Первомайский, ул. трактористов, д. 10, п. 11, ящ. 32123' str from dual ); р-н Московский, п. Первомайский, ул. трактористов, д. 10, п. 11

пятница, 31 января 2020 г.

Произвольный порядок сортировки символов из константы в pl/sql

#sql #oracle #plsql


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

DECLARE
  str CONSTANT varchar2(32767) := 'CBEdfa092борДЖЭ';


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

Пример вывода по передаваемой константе: борДЖЭ029adfBCE.

Можно ли сделать сортировку, использую какую-либо готовую функцию, где можно будет
передать шаблон и она по нему отсортирует(что-то наподобие формата для дат)?

Пишу анонимный блок в SQL Developer
    


Ответы

Ответ 1



with TRANS(start_, end_, ord) as( select 'a', 'z',1 from DUAL union all select 'A', 'Z',2 from DUAL union all select '0', '9',3 from DUAL union all select 'А', 'Я',4 from DUAL union all select 'Ё', 'Ё',4 from DUAL union all select 'а', 'я',5 from DUAL union all select 'ё', 'ё',5 from DUAL ), Src(str) as( select 'CBEdfa092борДЖЭёе' from DUAL ) select listagg(ch) within group (order by rn) from ( select ch, row_number() over(order by ord, ch) rn from ( select substr(str,level,1) ch from Src connect by substr(str,level,1) is not null ) left join TRANS on ch between start_ and end_ order by ord, ch ) Остается только в Src подставить переменную с входной строкой, добавить into что бы поместить результат так же в переменную. И в таблице TRANS задать нужные приоритеты для диапазонов символов. P.S. дополнительный уровень с нумерацией и использованию в listagg номера сделан из за того, что listagg не соблюдает нормальный, алфавитный порядок сортировки буквы ё (в отличие от обычного order by). P.P.S. Внимание Выяснено экспериментальным путем (совместно с @Dmitry), что в зависимости от региональных настроек (видимо) правильный порядок буквы ё может быть правильным либо у listagg, либо у order by. Поэтому требуется проверка работы этих букв на целевой системе и применение в listagg сортировки либо в rn, либо по ord, ch в зависимости от того, что даст лучший результат.

Ответ 2



Если я правильно понял вашу мысль, можно сделать как-то так. Допустим, есть источник: with source as ( select 'abc' a from dual union all select 'def' a from dual union all select 'hij' a from dual) select * from source; Надо сделать кастомную сотрировку, чтобы буква d шла первой, а a - второй. Тогда: with source as ( select 'abc' a from dual union all select 'def' a from dual union all select 'hij' a from dual) select a, sort_column from (select a, translate(a, 'adh', 'dah') sort_column from source) order by sort_column; С помощью translate заменяем символы, третий параметр задает необходимую сортировку. Далее сортируем по результату транслейта. Результат: A SORT_COLUMN --- ----------- def aef abc dbc hij hij UPD. Немного о принципе формирования строки для функции translate. У оракла есть какая-то встроенная последовательность. В данном случае сортируется просто по возрастанию ASCII кодов. В функцию translate мы передаем первым параметром исходную строку, вторым - существующий порядок сортировки, третьим - преобразование к желаемому порядку. Тут можно сломать мозг, так что осторожнее. Ваша исходная строка, существующий порядок сортировки, строка преобразования: 0 2 9 B C E a d f Д Ж Э б о р б о р Д Ж Э 0 2 9 a d f B C E a d f б о р Д Ж Э B C E 0 2 9 <-- это пойдет в функцию translate Вам надо, чтобы сначала шла буква б, потом о, потом р и так далее. В существующем порядке сортировки первым идет 0 (вторая строка). Следовательно, под б в первой строке пишем 0 в третьей. Потом о - под о пишем 2, и так далее. Для полного алфавита, соответственно, будет: существующий порядок 0 1 2 3 4 5 6 7 8 9 A B C ... a b c ... А Б В ... a б в ... э ю я строка замены а б в г д е ё ж з и к л м ................................. X Y Z Принцип, надеюсь, понятен. Далее все решается в один запрос: select listagg(letters) within group (order by srt) from (select letters, translate(letters, '029BCEadfДЖЭбор' /*существующий порядок*/, 'adfборДЖЭBCE029' /*строка замены*/) srt from (select substr('CBEdfa092борДЖЭ', level, 1) letters from dual connect by level <= length('CBEdfa092борДЖЭ')) ) RES ---------------- борДЖЭ029adfBCE

Ответ 3



Написал запрос использующий рекурсивный with. Никаких дополнительных таблиц не надо. Можно просто передать строку и шаблон. Шаблон сортировки должен содержать все символы, которые могут встретится в сортируемой строке. У запроса 2 параметра: :str - сортируемая строка, :mask - шаблон сортировки. with src(symbol, tail, lvl) as( -- разобьем строку на символы select substr(:str, 1, 1) as symbol, substr(:str, 2) as tail, 1 as lvl from dual union all select substr(tail, 1, 1), substr(tail, 2) as tail, lvl + 1 as lvl from src where length(tail) > 0 ) ,mask(symbol, tail, lvl) as( -- разобьем маску на символы. select substr(:mask, 1, 1) as symbol, substr(:mask, 2) as tail, 1 as lvl from dual union all select substr(tail, 1, 1), substr(tail, 2) as tail, lvl + 1 as lvl from mask where length(tail) > 0 ) select listagg(symbol) within group (order by lvl) from ( select m.symbol, m.lvl from mask m inner join src s on m.symbol = s.symbol ) На вход для сортировки подал строку -CBEdfa092борДЖЕ, шаблон сортировки - абвгдорАБВГД0123456789abcdefABCDEF результат: борДЖЕ029adfBCE Сначала сортируемая строка и шаблон разбиваются на символы. При этом символы нумеруются и для каждого символа шаблона понятен его порядковый номер. После символы соединяются и сортируются по порядку шедшему в шаблоне с последующим объединением в одну строку.

вторник, 28 января 2020 г.

Как получить разницу во времени в формате hh:mm в Oracle?

#sql #oracle #plsql


Хочу получить разницу во времени в часах и минутах. В формате вида hh:mm. Есть ли
для этого какая то стандартная функция или что-то такое?
Пока наколхозил такое решение, но оно кажется слишком костыльным:

with src as (
  select sysdate as finishmoment,
  to_date('29.02.2016') as activationmoment
  from dual
)
select  
  trunc((finishmoment-activationmoment)*24)||' :'||to_char(trunc(mod((finishmoment-activationmoment)*24,
1)*60), '00') as runtime
from src


Кто знает решение получше?
    


Ответы

Ответ 1



Да, остается только такой вариант (аналогичный с вашим): select round((sysdate - to_date('29.02.2016')) * 24) || ':' || round(mod((sysdate - to_date('29.02.2016'))*24*60, 60)) from dual Вот тут еще указали такой вариант с использованием NUMTODSINTERVAL, то есть для вашего варианта будет так: select EXTRACT(HOUR FROM NUMTODSINTERVAL((sysdate - to_date('29.02.2016'))*24,'HOUR')) + 24*trunc(sysdate - to_date('29.02.2016')) || ' : ' || EXTRACT(MINUTE FROM NUMTODSINTERVAL((sysdate - to_date('29.02.2016'))*1440,'MINUTE')) from dual По коду, в принципе, равноценно (хотя 1-й вариант имхо покороче, можно все вынести в отдельную функцию, например), выбирать вам.

Ответ 2



Если учесть, что разница не может быть более 24 часа, то можно так: with src as ( select to_date('29.02.2016 23:59') as finishmoment, to_date('29.02.2016') as activationmoment from dual ) select regexp_substr( (finishmoment-activationmoment) day(6) to second, '\d{2}:\d{2}') as runtime from src; --23:59 С учётом комментария - если разница в часах может быть более 24 часа, то: with src as ( select (sysdate - to_date('2016-02-26')) day(4) to second as intrv from dual ) select extract(day from intrv) * 24 + extract(hour from intrv) || ':' || lpad(extract(minute from intrv), 2, 0) as runtime from src;

Ответ 3



Предлагаю такой вариант: with src as ( select sysdate as finishmoment, to_date('29.02.2016','dd.mm.yyyy') as activationmoment from dual ) select d*24+h||':'||lpad(mi,2,'0') from ( select EXTRACT(DAY FROM (finishmoment-activationmoment) DAY TO SECOND) d, EXTRACT(HOUR FROM (finishmoment-activationmoment) DAY TO SECOND) h, EXTRACT(MINUTE FROM (finishmoment-activationmoment) DAY TO SECOND) mi from src)

Ответ 4



Как вариант вместо математики использовать функцию NUMTODSINTERVAL Или же попробовать полученный интервал добавить к любой дате с временем 00:00:00 и от полученной даты уже взять только часы и минуты, отформатировав ее как хочется. Решение имеет один минус - может появится трудноуловимая ошибка, когда интервал станет больше суток.

среда, 22 января 2020 г.

dbms_alert Интервалы опроса

#oracle #plsql #события


Объясните, пожалуйста, вот этот абзац из документации


  WAITANY procedure. If you use the WAITANY procedure, and if a signalling session
does a signal but does not commit within one second of the signal, a polling loop is
required so that this uncommitted alert does not camouflage other alerts. The polling
loop begins at a one second interval and exponentially backs off to 30-second intervals.


Я правильно понимаю, что тут говорится о том, что при вызове WAITANY на сервере поток
опрашивает наличие событий, через определенные интервалы? И если я вызвал WAITANY с
достаточно большим таймаутом, то при возникновении события ко мне придет уведомление
только после истечения текущего интервала запроса? Т.е. на сервере выполняется примерно
такой код

function WaitAny(ATimeout) {
  const intervals = [0, 1, ....., 30);
  for (i = 0; i < intervals.length; i++) {
    Sleep(min(intervals[i], ATimeout))
    if (IsExistsEvents())
      return 0;
    ATimeout -= intervals[i];
    if (ATimeout <= 0)
      return 1;
  }
  maxInterval = intervals[intervals.length - 1];
  while (ATimeout > 0) {
    Sleep(min(maxInterval, ATimeout))
    if (IsExistsEvents())
      return 0;
    ATimeout -= maxInterval;
  }
  return 1;
}

    


Ответы

Ответ 1



TL;DR: Нет, при получении события, оповещение о нём будет практически сразу же передано в обработчик, то есть возврат из функции waitany. Но в определённых случаях задержка возможна (см. далее). Передача события (signal) между отправителем и приёмником в dbms_alert транзакциональна и состоит из двух частей: Не транзакциональная передача события посредством database pipe механизма (реализовано на dbms_pipe). Подтверждение события посредством commit в отправителе. В приёмнике это реализовано стандартным lock меанизмом БД. В нормальном случае, приёмник ждёт получение события из pipe. При его получении, оно только регистрируется как принятое, но не подтверждённое. При наличии таких событий в приёмнике, waitany опрашивает статус сессии на предмет завершения транзакции, в которой событие было послано. Если транзакция завершается с commit, то waitany завершается и передаёт текущее событие в приёмник для дальнейшей обработки, в противном случае - rollback - событие удаляется из очереди принятых в waitany. В исключительном, случае, о котором идёт речь в цитате из вопроса, событие ещё не зафиксировано. т.е. не подтверждено в сессии отправителя. Так как бесконечное ожидание на подтверждение будет блокировать получение других событий из pipe, начинается опрос с интервалом начиная с 1-ой с возрастанием до 30-ти секунд. То есть приёмник ожидает подтверждения завершения транзакции с текущим интервалом ожидания (1..30 сек.) и если оно не происходит, возвращается к считыванию из pipe новых событий, после чего цикл повторяется. Почему последний случай исключительный? Если событие отправленно, но не зафиксировано сразу же, то это, как будет видно в примере ниже, не обеспечит гарантированной асинхронной обработки событий. Поэтому, или устранить задержку между signal и commit, или, если это не представляется возможным, отказаться от dbms_alert и посмотреть в сторону Advanced gueuing (dbms_aq). Пример для наглядности. Понадобится три сесси: приёмнк, "ленивый" отправитель (PROCL=lazy SID=26) и "правильный" отправитель (PROCQ=quick SID=270). Запустим приёмник событий: SQL> set serveroutput on size unlimited exec dbms_alert.register ('PROCQ'); - dbms_alert.register ('PROCL'); - dbms_alert.register ('EXIT'); exec receiveSignals В отправителе SID=26 пошлём событие с задержкой подтверждения: SQL> exec sendSignal ('PROCL',delayed=>true) PROCL sent 14:02:10.482 from 26 delayed В отправителе SID=270 пошлём событие и зафиксируем его сразу: SQL> exec sendSignal ('PROCQ',delayed=>false) PROCQ sent 14:03:20.842 from 270 Вернёмся в SID=26 и подтвердим событие: SQL> exec sendSignal (null,delayed=>false) none sent 14:03:34.272 from 26 В отправителе SID=270 пошлём событие EXIT: SQL> exec sendSignal ('EXIT',delayed=>false) EXIT sent 14:03:41.849 from 270 Вывод в приёмнике после приёма события EXIT: stat=0,sig=PROCL,received=14:03:34.274: msg=sent 14:02:10.482 from 26 delayed stat=0,sig=PROCQ,received=14:03:34.275: msg=sent 14:03:20.842 from 270 stat=0,sig=EXIT,received=14:03:41.852: msg=sent 14:03:41.849 from 270 Видно, что задержка в SID=26 вызвана поздним подтверждением и была получена в приёмнике сразу же после commit в отправителе (14:03:34). Но ожидание на подтверждение привело к задержке почти 15 сек на получения события от SID=270. (отпр. 14:03:20, получ. 14:03:34), т.е функция waitany в момент отправки подтверждённого события от SID=270, находилась в ожидании подтверждения от SID=26. PS: Этот ответ не является переводом, ни прямым, ни вольным, ответа сотрудника компании Oracle на аналогичный вопрос ТС на engl. SO, он скорее всего попытка более наглядного пояснения ситуации. Функции, которые использованы в воиспроизводимом примере выше: create or replace procedure receiveSignals is sig varchar2 (32); stat number; msg varchar2 (64); begin loop dbms_alert.waitany (sig, msg, stat); dbms_output.put_line ( 'stat='||stat||',sig='||sig||',received='||to_char (systimestamp, 'hh24:mi:ss.ff3')||': msg='||msg); exit when sig = 'EXIT'; end loop; end; / create or replace procedure sendSignal (sig varchar2, delayed boolean := true) is msg varchar2 (1800) := 'sent '||to_char (systimestamp, 'hh24:mi:ss.ff3')||' from '|| sys_context ('USERENV', 'SID')||case when delayed then ' delayed' end; begin if sig is not null then dbms_alert.signal (sig, msg); end if; if not delayed then commit; end if; dbms_output.put_line (case when sig is null then 'none' else sig end||' '||msg); end; /