Страницы

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

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

четверг, 9 апреля 2020 г.

Создание почты в MS Exchange через Oracle или Java

#java #oracle #exchange

                    
Как создать mailbox (почтовый ящик) в MS Exchange через Oracle PL/SQL или java (прикрепить
в oracle)?
Искал везде, есть апи для создания письма и тд.. Но как почтовый ящик создать не
могу найти.
    


Ответы

Ответ 1



С проблемой разобрался. На стороне Exchange добавили веб-сервис, который запускает небольшой powershell-скрипт для создания почтового ящика. На стороне Oracle я вызываю веб-сервис.

вторник, 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


Перед запуском сценария хочу проверить есть ли в БД секвенция и если есть - дропнуть
её. Но DROP SEQUENCE не работает в PLSQL, а IF не работает в SQL. Как быть? Сейчас
в начало сценария приписал:

DECLARE
    V_TEMP_NUM NUMBER(9) := 0;
BEGIN
SELECT COUNT(*) INTO V_TEMP_NUM FROM USER_SEQUENCES WHERE SEQUENCE_NAME = 'TMM_TEMP10_SEQ';
IF V_TEMP_NUM > 0 THEN
  DROP SEQUENCE TMM_TEMP10_SEQ;
END IF;
CREATE SEQUENCE TMM_TEMP10_SEQ
    MINVALUE 0
    START WITH 10
    INCREMENT BY 10
    CACHE 20;
END;


Как я и сказал, ругается на DROP.
    


Ответы

Ответ 1



Много лет назад я написал небольшую процедуру, которая реализует логику: CREATE/ALTER/DROP IF EXISTS... Можно воспользоваться ей в вашем случае: create or replace procedure admin.re_run_ddl (p_sql in varchar2) AUTHID CURRENT_USER as l_line varchar2(500) default rpad('-',20,'-'); l_cr varchar2(2) default chr(10); l_footer varchar2(500) default l_cr||rpad('*',20,'*'); l_ignore_txt varchar2(200) default 'IGNORING --> '; ORA_00955 EXCEPTION; ORA_01430 EXCEPTION; ORA_02260 EXCEPTION; ORA_01408 EXCEPTION; ORA_00942 EXCEPTION; ORA_02275 EXCEPTION; ORA_01418 EXCEPTION; ORA_02443 EXCEPTION; ORA_01442 EXCEPTION; ORA_01434 EXCEPTION; ORA_01543 EXCEPTION; ORA_00904 EXCEPTION; ORA_02261 EXCEPTION; ORA_04043 EXCEPTION; ORA_02289 EXCEPTION; PRAGMA EXCEPTION_INIT(ORA_00955, -00955); --ORA-00955: name is already used by an existing object PRAGMA EXCEPTION_INIT(ORA_01430, -01430); --ORA-01430: column being added already exists in table PRAGMA EXCEPTION_INIT(ORA_02260, -02260); --ORA-02260: table can have only one primary key PRAGMA EXCEPTION_INIT(ORA_01408, -01408); --ORA-01408: such column list already indexed PRAGMA EXCEPTION_INIT(ORA_00942, -00942); --ORA-00942: table or view does not exist PRAGMA EXCEPTION_INIT(ORA_02275, -02275); --ORA-02275: such a referential constraint already exists in the table PRAGMA EXCEPTION_INIT(ORA_01418, -01418); --ORA-01418: specified index does not exist PRAGMA EXCEPTION_INIT(ORA_02443, -02443); --ORA-02443: Cannot drop constraint - nonexistent constraint PRAGMA EXCEPTION_INIT(ORA_01442, -01442); --ORA-01442: column to be modified to NOT NULL is already NOT NULL PRAGMA EXCEPTION_INIT(ORA_01434, -01434); --ORA-01434: private synonym to be dropped does not exist PRAGMA EXCEPTION_INIT(ORA_01543, -01543); --ORA-01543: tablespace '' already exists PRAGMA EXCEPTION_INIT(ORA_00904, -00904); --ORA-00904: "%s: invalid identifier" PRAGMA EXCEPTION_INIT(ORA_02261, -02261); --ORA-02261: "such unique or primary key already exists in the table" PRAGMA EXCEPTION_INIT(ORA_04043, -04043); --ORA-04043: object %s does not exist PRAGMA EXCEPTION_INIT(ORA_02289, -02289); --ORA-02289: sequence does not exist procedure p( p_str in varchar2 ,p_maxlength in int default 120 ) is i int := 1; begin dbms_output.enable( NULL ); while ( (length(substr(p_str,i,p_maxlength))) = p_maxlength ) loop dbms_output.put_line(substr(p_str,i,p_maxlength)); i := i + p_maxlength; end loop; dbms_output.put_line(substr(p_str,i,p_maxlength)); end p; begin p( 'EXEC:'||l_cr||l_line||l_cr||p_sql||l_cr||l_line ); execute immediate p_sql; p( 'done.' ); exception when ORA_00955 or ORA_01430 or ORA_02260 or ORA_01408 or ORA_00942 or ORA_02275 or ORA_01418 or ORA_02443 or ORA_01442 or ORA_01434 or ORA_01543 or ORA_00904 or ORA_02261 or ORA_04043 or ORA_02289 then p( l_ignore_txt || SQLERRM || l_footer ); when OTHERS then p( SQLERRM ); p( DBMS_UTILITY.FORMAT_ERROR_BACKTRACE ); p( l_footer ); RAISE; end; / show err Пример использования: prompt clean-up ... begin admin.re_run_ddl('drop sequence BLA_BLA_BLA'); admin.re_run_ddl('drop procedure BLA_BLA_BLA'); admin.re_run_ddl('drop table BLA_BLA_BLA'); end; /

Ответ 2



А на дроп ругается из за этого (execute immediate), о чём нам напоминает Коннор на AskTOM: DDL - считается редким событием в Oracle (в отличие от других СУБД) DECLARE V_TEMP_NUM NUMBER(9) := 0; BEGIN SELECT COUNT(*) INTO V_TEMP_NUM FROM USER_SEQUENCES WHERE SEQUENCE_NAME = 'TMM_TEMP10_SEQ'; IF V_TEMP_NUM > 0 THEN execute immediate 'DROP SEQUENCE TMM_TEMP10_SEQ'; END IF; execute immediate ' CREATE SEQUENCE TMM_TEMP10_SEQ MINVALUE 0 START WITH 10 INCREMENT BY 10 CACHE 20'; END;

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

#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, т.е. в таблице секционирование по списку, а не по диапазону значений, иначе с текстовыми данными смысл теряется. В примере также указано, что можно создать секции сразу, несмотря на автоматическое секционирование.

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

Где эффективнее ставить фильтрующее условие при соединении таблиц?

#sql #oracle


Предположим, есть таблицы TABLE_1 и TABLE_2. Мы хотим их сджойнить. Но при этом,
мы мы знаем, что нет нужды джойнить всю таблицу TABLE_1, отсекая по дате нужные строки
(может даже индекс есть на столбце с датой). Так вот. У нас есть два варианта джойна:

SELECT * FROM TABLE_1 T1
  JOIN TABLE_2 T2 ON T1.COL_1 = T2.COL_1
WHERE T1.COL_DATE between to_date(...) and to_date(...);


И другой вариант

SELECT * FROM TABLE_1 T1
  JOIN TABLE_2 T2 ON T1.COL_DATE between to_date(...) and to_date(...)
    AND T1.COL_1 = T2.COL_1;


Какой вариант эффективнее и по какой логике? Или вообще без разницы и запрос будет
оптимизирован автоматически?
    


Ответы

Ответ 1



Если речь идет о INNER JOIN, что эквивалентно просто JOIN, то запросы вернут одинаковый результат, но первый вариант лично мне кажется более понятным. Исходные таблицы: SQL> select * from a; ID COL1 ---------- ---------- 1 11 2 12 3 13 3 rows selected. SQL> select * from b; ID COL2 ---------- ---------- 1 111 3 333 5 555 3 rows selected. Пример для INNER JOIN: SELECT * FROM a t1 JOIN b t2 ON t1.id = t2.id 4 WHERE t1.col1 between 12 and 13; ID COL1 ID COL2 ---------- ---------- ---------- ---------- 3 13 3 333 1 row selected. SELECT * FROM a t1 3 JOIN b t2 ON t1.id = t2.id AND t1.col1 between 12 and 13; ID COL1 ID COL2 ---------- ---------- ---------- ---------- 3 13 3 333 1 row selected. Для LEFT OUTER JOIN логика меняется потому что предикат WHERE будет исполнен уже после объединения, а в объединение (до фильтрования - применения предиката WHERE) попадут все строки из левой таблицы. Пример для LEFT OUTER JOIN (синоним LEFT JOIN): SELECT * FROM a t1 LEFT JOIN b t2 ON t1.id = t2.id WHERE t1.col1 between 12 and 13; ID COL1 ID COL2 ---------- ---------- ---------- ---------- 3 13 3 333 2 12 2 rows selected. SELECT * FROM a t1 3 LEFT JOIN b t2 ON t1.id = t2.id AND t1.col1 between 12 and 13; ID COL1 ID COL2 ---------- ---------- ---------- ---------- 3 13 3 333 1 11 2 12 3 rows selected.

Использование INDEX FAST FULL SCAN

#sql #oracle #oracle12c #оптимизация_запросов


Почему в этом запросе используется INDEX RANGE SCAN, а не INDEX FAST FULL SCAN ведь
все значения есть в самом к индексе и их можно выбрать оттуда, не обращаясь к таблице?

-- Создание таблицы и построение функционального индекса
create table del_nvl_ind as
select 
    id,
    case when val < 1 then null else round(val)*12 end val
from
(
    select 
        level id, 
        level * dbms_random.value val
    from dual 
    connect by level < 1000000
);

create index del_nvl_idx_nvl on del_nvl_ind (nvl(val,0));

-- Получение плана запроса
explain plan for
select val from del_nvl_ind where nvl(val,0) = 100;    
select * from table(dbms_xplan.display);

| Id  | Operation                           | Name            | Rows  | Bytes | Cost
(%CPU)| Time     |
-------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |                 | 10000 |   107K| 
 600   (1)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID BATCHED| DEL_NVL_IND     | 10000 |   107K| 
 600   (1)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN                  | DEL_NVL_IDX_NVL |  4000 |       | 
   3   (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access(NVL("VAL",0)=100)


И еще вопрос - как можно изменить запрос, чтобы в плане появился INDEX FAST FULL SCAN?
    


Ответы

Ответ 1



Обращение к таблице и использование INDEX RANGE SCAN присутствовало ввиду следующих причин: Индекс построенный на nvl(val,0) не был покрывающим для поля val. Так как в индексе содержались уже преобразованные значения, а в самой таблице исходные (без преобразования функцией). После изменения в разделе SELECT запроса выражения VAL на NVL(VAL,0) индекс был использован уже без обращения к таблице с исходными данными. INDEX RANGE SCAN - это метод доступ а к диапазону данных индекса, а INDEX (FAST) FULL SCAN - метод доступа к полному набору данных. А так как в предикате запроса (WHERE) содержалось условие, ограничивающее набор данных, то полная выборка не имела смысла, поэтому и был использован именно этот метод доступа (RANGE SCAN).

Логика индексов

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


Есть такая таблица

CREATE TABLE my_table (
  .......
  f1 NUMBER NOT NULL,
  f2 NUMBER NOT NULL,
  ........
);


поле f1 почти уникально. В таблице может порядка 50 записей с одинаковым f1 при общем
числе записей ~100 000

поле f2 повторяется очень часто. В таблице не более 10 различных значений f2

К этой таблице бывают запросы двух типов

SELECT * FROM my_table WHERE f1 = :val1 AND f2 = :val2


и

SELECT * FROM my_table WHERE f2 <> 0
SELECT * FROM my_table WHERE f2 = 0


(сравнение идет только с нулем и только на равно/не равно)

Вопрос какой лучше индекс создать


один составной: (f2, f1)
два простых: (f1), (f2)
составной и простой: (f1, f2), (f2)

    


Ответы

Ответ 1



Один индекс, только по f1. Индекс, по которому надо читать существенную часть таблицы - бесполезен. Сильно дешевле обойти всю таблицу целиком последовательным чтением. Планировщик запросов это знает. Как следствие вы можете сделать индекс по f2, вы будете тратить ресурсы на его хранение и поддержание актуальности, но он просто не будет использоваться. Или, что тоже гипотетически возможно и что ещё хуже: будет использоваться, но вместо ускорения работы превратит простое последовательное чтение в очень много операций случайного чтения сначала прыгая по индексу, потом по таблице за данными. Дополнительно отфильтровать некоторые из 50 строк обычно не стоят того, чтобы раздувать размер индекса. Как по хранению на диске, так и в кэше в памяти. Для маленькой таблицы в 100 тыс. записей, впрочем, не принципиально. Если ваша СУБД умеет частичные (partial) индексы - то может иметь смысл сделать индекс по f2 с исключением наиболее частых значений. Вероятно, у вас большинство строк подходят либо под f2 <> 0 либо под f2 = 0.

Ответ 2



Кроме индекса по f1, я бы еще попробовал построить bitmap index по f2 и посмотреть какие планы строит оптимизатор. Есть неплохой шанс, что запросы вида: SELECT * FROM my_table WHERE f2 <> 0 SELECT * FROM my_table WHERE f2 = 0 будут работать быстрее с использованием bitmap индекса по f2.

JOIN с временной таблицей

#sql #oracle #оптимизация_запросов


Есть временная таблица

CREATE GLOBAL TEMPORARY TABLE "TMP_CONTROL_POINT_DETECT" (
    "POINT_ID" NUMBER NOT NULL ENABLE, 
     CONSTRAINT "TMP_CONTROL_POINT_DETECT_PK" PRIMARY KEY ("POINT_ID") ENABLE
) ON COMMIT PRESERVE ROWS ;


И есть persistent таблица

CREATE TABLE "CONTROL_POINTS_" (
    "ID" NUMBER, 
    "STATUS" NUMBER(1,0), 
     CONSTRAINT "PK_CONTROL_POINTS_" PRIMARY KEY ("ID")
  USING INDEX PCTFREE 10 INITRANS 2 MAXTRANS 255 COMPUTE STATISTICS 
  TABLESPACE "USERS"  ENABLE 
);


Во временной таблице 200 записей, в персистентной 29 000

Делаю запрос

SELECT
  cp.status
FROM
  TMP_CONTROL_POINT_DETECT det
  JOIN CONTROL_POINTS_ cp ON (
    det.POINT_ID = cp.ID
  )


и ужасаюсь плану

-----------------------------------------------------------------------------------------------
| Id  | Operation          | Name                     | Rows  | Bytes | Cost (%CPU)|
Time     |
-----------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |                          |   200 |  3800 |    18   (6)|
00:00:01 |
|*  1 |  HASH JOIN         |                          |   200 |  3800 |    18   (6)|
00:00:01 |
|   2 |   TABLE ACCESS FULL| TMP_CONTROL_POINT_DETECT |   200 |  2600 |     2   (0)|
00:00:01 |
|   3 |   TABLE ACCESS FULL| CONTROL_POINTS_          | 29303 |   171K|    16   (7)|
00:00:01 |
-----------------------------------------------------------------------------------------------


PLAN_TABLE_OUTPUT
---------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------

   1 - access("DET"."POINT_ID"="CP"."ID")

Note
-----
   - dynamic statistics used: dynamic sampling (level=2)
   - this is an adaptive plan


Потом меняю в запросе выбираемое поле

SELECT
  cp.id
FROM
  TMP_CONTROL_POINT_DETECT det
  JOIN CONTROL_POINTS_ cp ON (
    det.POINT_ID = cp.ID
  )


И получаю ожидаемый план

-----------------------------------------------------------------------------------------------
| Id  | Operation          | Name                     | Rows  | Bytes | Cost (%CPU)|
Time     |
-----------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |                          |   200 |  3600 |     2   (0)|
00:00:01 |
|   1 |  NESTED LOOPS      |                          |   200 |  3600 |     2   (0)|
00:00:01 |
|   2 |   TABLE ACCESS FULL| TMP_CONTROL_POINT_DETECT |   200 |  2600 |     2   (0)|
00:00:01 |
|*  3 |   INDEX UNIQUE SCAN| PK_CONTROL_POINTS_       |     1 |     5 |     0   (0)|
00:00:01 |
-----------------------------------------------------------------------------------------------


PLAN_TABLE_OUTPUT
---------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------

   3 - access("DET"."POINT_ID"="CP"."ID")

Note
-----
   - dynamic statistics used: dynamic sampling (level=2)


Кто нибудь может объяснить, что происходит? Откуда берется FULL SCAN? Сам запрос
выбирает честные 200 записей

Манипуляции типа

SELECT
  cp.STATUS
FROM
  CONTROL_POINTS_ cp
WHERE
  cp.ID IN (SELECT ID FROM TMP_CONTROL_POINT_DETECT det)


приводят к еще более удручающим последствиям

-------------------------------------------------------------------------------------------------
| Id  | Operation            | Name                     | Rows  | Bytes | Cost (%CPU)|
Time     |
-------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |                          |  5860K|    33M|  2820 
 (4)| 00:00:01 |
|   1 |  MERGE JOIN CARTESIAN|                          |  5860K|    33M|  2820 
 (4)| 00:00:01 |
|   2 |   TABLE ACCESS FULL  | TMP_CONTROL_POINT_DETECT |   200 |       |     2 
 (0)| 00:00:01 |
|   3 |   BUFFER SORT        |                          | 29303 |   171K|  2818 
 (4)| 00:00:01 |
|   4 |    TABLE ACCESS FULL | CONTROL_POINTS_          | 29303 |   171K|    14 
 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------


Update

Временная таблица ни при чем. С персистентной такой же структуры и такими же данными
та же картина
    


Ответы

Ответ 1



Оба плана абсолютно предсказуемы. во втором случае вам нужно только значение из индекса, поэтому берется индекс. А в первом случае нужно получать значение из области данных. При этом данных у вас 29000 записей, при средней длине записи около 4х байт. На диске вся таблица занимает 171 Кбайт (что видно в плане). При размере блока на диске в 4к это 43 блока. Получать 43 блока за 200 отдельных обращений по указателям из индекса мягко говоря накладно (Гарантировано будет прочитана вся таблица, причем каждый блок придется разбирать 4 раза). Full scan да еще и с hash join более чем оправдан. Использование индекса эффективно, когда с его помощью нужно обратиться не более чем к 10% всех блоков таблицы.

Ответ 2



Использование хинта index привел запрос в чувство SELECT /*+ index(cp PK_CONTROL_POINTS) */ cp.STATUS FROM TMP_CONTROL_POINT_DETECT det JOIN CONTROL_POINTS PARTITION (lic) cp ON ( det.POINT_ID = cp.ID ) -------------------------------------------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop | -------------------------------------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 200 | 4000 | 202 (0)| 00:00:01 | | | | 1 | NESTED LOOPS | | 200 | 4000 | 202 (0)| 00:00:01 | | | | 2 | NESTED LOOPS | | 200 | 4000 | 202 (0)| 00:00:01 | | | | 3 | TABLE ACCESS FULL | TMP_CONTROL_POINT_DETECT | 200 | 2600 | 2 (0)| 00:00:01 | | | |* 4 | INDEX UNIQUE SCAN | PK_CONTROL_POINTS | 1 | | 0 (0)| 00:00:01 | | | | 5 | TABLE ACCESS BY GLOBAL INDEX ROWID| CONTROL_POINTS | 1 | 7 | 1 (0)| 00:00:01 | 2 | 2 | PLAN_TABLE_OUTPUT --------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 4 - access("DET"."POINT_ID"="CP"."ID") filter(TBL$OR$IDX$PART$NUM("CONTROL_POINTS",0,1,0,ROWID)=2)

Порядок выполнения условий в блоке WHERE запроса SQL на БД ORACLE

#sql #oracle


Пример SQL запроса

select 1 
  from dual
 where condition1 
    or condition2


В ходе прочтения книги по oracle от Прибыл/Фейрштейн встречал, что ORACLE при отборах
использующих OR действует следующим образом


Если condition1 - TRUE, то осуществляем выборку без проверки condition2
Иначе condition1 - FALSE, то переходим к проверке condtition2


Верно ли это? 

Попытка найти это в книге не дала положительного результата 
    


Ответы

Ответ 1



SQL в общем случае - это декларативный язык. Вы говорите что хотите получить, но не говорите как. СУБД сама решает, как именно получать ваши данные. Например Oracle может(и почти всегда будет) переписывать ваши запросы для построения наиболее оптимального плана. По этому в общем случае вы вообще не можете быть уверены, что ваш запрос будет иметь такой же вид. И практическое применение ваших знаний не зависимо от результат особо не имеет смысл. Однако можно попробовать понять, что происходит с планом запроса для вашего примера. Запрос: select 1 from dual where 1=1 or 1 != 1 План: Plan Hash Value : 1388734953 ----------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost | Time | ----------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | | 2 | 00:00:01 | | 1 | FAST DUAL | | 1 | | 2 | 00:00:01 | ----------------------------------------------------------------- Видим, что Oracle знал заранее что первый аргумент ВСЕГДА True и просто вообще исключил ее из плана запроса. Попробуем чуть усложнить пример: create table tst(id number, text varchar2(10)); insert into tst values(1, null); insert into tst values(2, 'text'); select 1 from tst where id in (1, 2) or text != 'Дядушка Шу' План: Plan Hash Value : 4148258400 --------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost | Time | --------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 2 | 40 | 3 | 00:00:01 | | * 1 | TABLE ACCESS FULL | TST | 2 | 40 | 3 | 00:00:01 | --------------------------------------------------------------------- Predicate Information (identified by operation id): ------------------------------------------ * 1 - filter("TEXT"<>'Дядушка Шу' OR "ID"=1 OR "ID"=2) Note ----- - dynamic sampling used for this statement Видим в плане запрос был переписан и условие where теперь больше похоже на where "TEXT"<>'Дядушка Шу' OR "ID"=1 OR "ID"=2 Вывод: Логично предположить, что оптимизация описанная вами используется в Oracle как и во многих ЯП. Однако, не зависимо от того, в каком порядке и как обрабатываются условия в логическом выражении вы не можете на это полагаться для решения практических задач при написании sql, так как вы не управляете планом запроса и итоговый запрос может отличаться от вашего.

суббота, 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

Неизвестный идентификатор в запросе из таблицы DUAL

#sql #oracle


select x from dual


этот запрос выдает ошибку:


  ORA-00904: "X": invalid identifier


Хорошо, понятно, идентификатор X у меня нигде не объявлен.

Но почему этот запрос выполняется нормально:

select n from dual


Что это за неизвестный но валидный идентификатор? Откуда он берется?

Также работает и запрос:

select q, y, s, d, n, m from dual




Воспроизводится в SQL*Plus:



PS Oracle Database 12c Enterprise Edition Release 12.2.0.1.0 - 64bit Production
    


Ответы

Ответ 1



DUAL это физическая таблица содержащая только одну колонку dummy и только одну запись со значением 'X'. Ни каких других колонок в ней не существует: SQL> select * from dual; DUMMY ----- X SQL> select n from dual; select n from dual * ERROR at line 1: ORA-00904: "N": invalid identifier Но в качестве столбца может быть указано любое действительное для листа запроса выражение. Например, для вызова функции без параметров скобоки не обязательны, и такое выражение ничем не отличается от колонки таблицы. Проверить наличие таковых по имени можно запросом: select owner, object_name, object_type from all_objects where object_name = 'N';

четверг, 19 марта 2020 г.

Описание “дерева” Oracle в Django models.py

#oracle #python #дерево #django


Доброго времени суток. Помогите, пожалуйста, со следующим вопросом.
Необходимо описать модель дерева в Django. Используемая БД - Oracle.
Описываю так:
class TelDivisions(models.Model):
    parent = models.ForeignKey('self')
    name = models.CharField(max_length=50, blank=True)
    class Meta:
        db_table = u'tel_divisions'

но таким образом parent ссылается сам на себя. Каким образом сделать так, чтобы он
ссылался на столбец id данной таблицы?
Думал может получиться вот так:
class TelDivisions(models.Model):
    parent = models.ForeignKey(TelDivisions, db_column='id', blank=True)
    name = models.CharField(max_length=50, blank=True)
    class Meta:
        db_table = u'tel_divisions'

но команда python manage.py validate выявила:
parent = models.ForeignKey(TelDivisions, db_column='id', blank=True)
NameError: name 'TelDivisions' is not defined

Что, собственно, логично. Как же быть?
Спасибо.    


Ответы

Ответ 1



Рекомендую Вам использовать django-mtpp. Она была создана как раз для отображения древовидных структур в реляционной модели. А что касается вашего вопроса: parent = models.ForeignKey('self', db_column='id', blank=True) И всё должно заработать. Ссылаться надо на самого себя через self. Данный пример описан в django docs. И ещё одно. Если не укажите null = True, то не сможете создать ни одного корновего экземпляра (т.е. такого, у которого нет родителя). Итого: parent = models.ForeignKey('self', db_column='id', blank=True, null = True) db_column='id' можно не указывать, т.к. он и так будет ссылаться по умолчанию на ключевое поле таблицы, т.е. id.

Ответ 2



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

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

Как по другому выполнить запрос в Oracle?

#oracle #sql


Есть две таблицы - А и В.
create table a (
    KEY1 number
);

create table b (
    KEY2 number,
    KEY3 number
);

Нужно проверить, что для каждой записи таблицы А нет записей в таблице B по условию
A.KEY1 = B.KEY2. Если же записи есть, то для каждой такой записи таблицы В проверить
что нет записей в таблице А по условию В.KEY3 = A.KEY1.
Реализовал так:
SELECT *
FROM A
WHERE NOT EXISTS
(SELECT *
 FROM   B 
 WHERE  A.KEY1 = B.KEY2
 AND    B.KEY3 NOT IN (SELECT KEY1 FROM   A))

Есть другие варианты выполнить этот же запрос?    


Ответы

Ответ 1



select * from A where key1 not in(select key2 from B) union all ( select * from A where key1 in(select key2 from B) intersect select * from a where key1 not in(select key3 from b) )

Ответ 2



Для такой комбинации данных: A | B key1 | key2 key3 1 | 3 2 2 | 4 7 3 | 5 8 ожидается a.Key1 1 и 2, т.е две строчки. Правильно надо так: select * from a where not exists (select 1 from b where key1=key2) union all select * from a where exists ( select 1 from b where a.key1=b.key2 and not exists (select 1 from a aa where aa.key1=b.key3) );

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

Возврат множества из условия (аналог CASE)

#sql #oracle


Пытаюсь написать запрос в Oracle, который использует разные множества для IN() в
условии. Общий вид запроса примерно такой:

-- что-то забирает
WHERE id IN
  (CASE 
    WHEN name LIKE '%max%'
    THEN (1, 2, 5)
    ELSE (SELECT id FROM anotherTable)
  END);


Понятно, что такой вариант не работает и выдаёт ошибку ora-01427. Судя по тому, что
я прочитал в сети, CASE не может вернуть множество.

Что можно использовать, чтобы вернуть именно множество?
    


Ответы

Ответ 1



Попробуй это where ( NAME like '%max%' and ID in (1, 2, 5) ) or ( NAME not like '%max%' and ID in (select ID from anotherTable) )

Ответ 2



Можно сделать еще так: where 1 = case when name like '%max%' and id in (1,2,5) then 1 when name like '%min%' and id in (select id from anotherTable) then 1 end

Быстродействие загрузки данных из DB2 в Oracle

#oracle #производительность #odbc #db2


Вопрос сложный и неоднозначный, но надеюсь на любые советы (а может кому и мой опыт
поможет):


Есть сервер с Oracle 11.2.0.3 x32 на Windows Server 2008R2 x64 (4ядра/8 потоков,
8гб оперативы, один HDD без рейда).
В источниках ODBCx32 настроено подключение к DB2 через CLI DRIVER с именем db2
Целевой сервер DB2 находится на мейнфрейме, настроек которого я не знаю, версия 9fix15
Поднят дополнительный listener:   

SID_LIST_LISTENERdb2 =
  (SID_LIST =
    (SID_DESC=
         (SID_NAME=db2)
         (ORACLE_HOME=C:\app\product\11.2.0\dbhome_1)
         (PROGRAM=dg4odbc))
  )
LISTENERdb2 =
 (DESCRIPTION_LIST =
    (DESCRIPTION =
      (ADDRESS = (PROTOCOL = TCP)(HOST = localhost)(PORT = 1523))
    )
  )


В tnsnames.ora:   

db2 =
 (DESCRIPTION=
   (ADDRESS=(PROTOCOL=tcp)(HOST=localhost)(PORT=1523))
   (CONNECT_DATA=(SID=db2))
   (HS=OK)
) 

Создан public dblink: 

CREATE PUBLIC DATABASE LINK db2 CONNECT TO
"login" IDENTIFIED BY "pwd" using 'db2';`

Создана процедура:  

CREATE OR REPLACE PROCEDURE P_LOAD_DATA IS 
BEGIN 
insert into data_table select * from data_table@db2 d where d.date_op between TRUNC(SYSDATE
- 4/24, 'HH24') + 1/24/60 AND TRUNC(SYSDATE - 2/24, 'HH24'));
END;/

Создан job на запуск процедуры раз в 2 часа (или 12 джобов на каждый запуск).


Целевая таблица размером примерно 13млн записей по 50 столбцов, моя таблица собирает
архив из целевой и имеет размер примерно 150млн записей тех же 50 столбцов.

Перекачка ~600к записей (150-200мб) занимает в лучшем случае час, периодически переваливает
за 4-5 часов. Плюс хорошо бы держать индексы на таблицу архива, чтобы из нее можно
было потом что-то вытащить в приемлемый срок, но это опционально. Корректный ли выбран
способ наполнения архива или есть альтернативы лучше? Проблема в том, что периодически
джобы прерываются с причиной "Job slave process was terminated", а в логах 

ORA-00603: ORACLE server session terminated by fatal error
ORA-28500: connection from ORACLE to a non-Oracle system returned this message:
[IBM][CLI Driver] CLI0106E  Connection is closed. SQLSTATE=08003 {08003,NativeErr
= -99999}
ORA-02063: preceding 2 line


Это вероятно мейнфрейм закрывает подключение по таймауту.
Причина разрядности Оракла - ни один найденный вариант драйвера DB2 не работал на х64.
    


Ответы

Ответ 1



Ну, очевидно, стоит начать ковырять с анализа того, куда же уходит и час и 4 часа - т.е. включаем трассировку на сессию джоба и анализируем полученный трейс. Дальше, в порядке отработки версии что тормозит DB2 на мейнфрейме, я бы просто отселектил необходимый диапазон, но без записи в таблицу - т.е., как говорят в unix-мире, в /dev/null. Но при этом чтобы IBM отдал все записи и они прошли по сети и db-link'у - т.е. просто SELECT, но с COUNT'ом или группировкой + поиграть с хинтом +driving_site - чтоб точно все записи сначала пришли к нам в Оракл. Ну и потом уже - классические варианты записи большого объема в Oracle - NOLOGGING, +APPEND, варианты с временной таблицей (партицией) и DBMS_REDEFINITION / EXCHANGE PARTITION, удаление индексов до / пересоздание после, BULK INSERT и тд и тп. Главное начать и выяснить, что оптимизировать или где затык. А дальше уже дело техники. Если есть такая физическая/юридическая возможность + необходимость в предлагаемом - если Вы можете предоставить удаленный доступ - с удовольствием помогу решить (или хотя бы "осмотреть наружно" / применить озвученные выше рекомендации) текущую задачку "физически", бо интересный случай + имею предыдущий опыт в решении похожих ситуаций, когда перекачка данных в гетерогенных средах ведет себя не так, как ожидается. Это предложение, кстати - подключиться и помочь удаленно - относится и ко всем остальным, кто рано или поздно попадет в этот тред/обсуждение (через поиск или еще как).

Ошибка мутирующих таблиц в триггере (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 тут лишний, как мне кажется.

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

Oracle. Загрузка при помощи sqlldr (direct path load). Работает ли откат при ошибке?

#oracle


Грузим большие объемы (порядка 500 тыс. записей) каждые 5 минут с помощью sqlldr
в секционированную таблицу. Секции по часам, плюс есть еще подсекции (12 штук).
Контрол файл:

OPTIONS (DIRECT=TRUE, ERRORS=500000)
UNRECOVERABLE
load data
infile '{FILENAME}'
append
into table TBL_NAME
fields terminated by '|'
TRAILING NULLCOLS
(
)


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

Как такое можно объяснить? По окончании загрузки в лог файле видим:

SQL*Loader-925: Error while uldlgs: OCIStmtExecute (ptc_hp)
ORA-00060: deadlock detected while waiting for resource

SQL*Loader-2026: the load was aborted because SQL Loader cannot continue.

Table TBL_NAME:
   0 Rows successfully loaded.
   0 Rows not loaded due to data errors.
   0 Rows not loaded because all WHEN clauses were failed.
   0 Rows not loaded because all fields were null.

   Date conversion cache disabled due to overflow (default size: 1000)

    


Ответы

Ответ 1



Нет, отката (rollback), в привычном понимании этого термина, с установленой опцией direct=true SQL*Loader не производит. Direct path load не пишет, ни UNDO, ни (по умолчанию) redo logs. Загружаемые данные подготавливаются в буфере и пишутся напрямую в datafile, используя не отформатированные блоки поверх HWM (high-water-mark). При возникновении ошибки (errors=0) или, если порог допостимых ошибок превышен (errors>0), SQL*Loader прерывает загрузку, но все уже загруженые страницы сохранятся. Подробнее о прерывании загрузки в секции Interrupted Loads. Воиспроводимый пример для наглядности. Попробуем записать null в колонку с ограничением not null: create table stage (id number, item varchar (32), created date) partition by range (created) interval (numToDsInterval (1, 'day')) ( partition stage_p1 values less than (date'2018-06-21') ) ; Генеририруем 100 строчек данных с null в 66 строке: insert into stage select rownum, case when rownum<>66 then 'item '||rownum end, date'2018-06-20'+rownum/24 from xmltable ('1 to 100'); $ sqlplus -l -s user/pass@localhost/db1 <

четверг, 5 марта 2020 г.

Преобразование TIMESTAMP WITH TIME ZONE в Oracle 12c

#sql #oracle #timezone


По каким-то причинам не получается правильно преобразовать TIMESTAMP WITH TIMEZONE
из одной временной зоны в другую, пример:

SELECT SYSTIMESTAMP,
       DBTIMEZONE,
       CURRENT_TIMESTAMP
       SESSIONTIMEZONE
  FROM DUAL;




=============================================================================================================================================================================================================
|                   SYSTIMESTAMP                   |                    DBTIMEZONE
                   |                CURRENT_TIMESTAMP                 |           
     SESSIONTIMEZONE                  |
=============================================================================================================================================================================================================
|            20.07.2017 7:15:33 -04:00             |                  Europe/Moscow
                  |            20.07.2017 14:15:33 +03:00            |            
         +03:00                      |
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------




Второй пример:

SELECT 
  SYSTIMESTAMP,
  SYSTIMESTAMP AT TIME ZONE 'Europe/Moscow' Moscow,
  SYSTIMESTAMP AT TIME ZONE 'America/New_York' New_York
  FROM DUAL




==========================================================================================================================================================
|                   SYSTIMESTAMP                   |                      MOSCOW
                     |                     NEW_YORK                     |
==========================================================================================================================================================
|            20.07.2017 7:09:25 -04:00             |            20.07.2017 11:09:25
+00:00            |            20.07.2017 11:09:25 +00:00            |
----------------------------------------------------------------------------------------------------------------------------------------------------------


В данном случае в результате преобразования часовой пояс вообще сбросился, не смотря
на успешное выполнение преобразования.


Почему в первом примере результат CURRENT_TIMESTAMP отстает на час от реального,
которое должно быть равно 15:15:33, а не 14:15:33?
Почему во втором примере, не смотря на успешное выполнение команды, конвертация не
происходит? Может какие-то параметры базы данных не настроены, или я не понимаю как
AT TIME ZONE работает? Наименования временных зон я брал из V$TIMEZONE_NAMES.


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

UPD:
Данные из nls_session_parameters

SQL Developer

NLS_LANGUAGE    RUSSIAN
NLS_TERRITORY   RUSSIA
NLS_CURRENCY    ₽
NLS_ISO_CURRENCY    RUSSIA
NLS_NUMERIC_CHARACTERS  , 
NLS_CALENDAR    GREGORIAN
NLS_DATE_FORMAT DD.MM.RR
NLS_DATE_LANGUAGE   RUSSIAN
NLS_SORT    RUSSIAN
NLS_TIME_FORMAT HH24:MI:SSXFF
NLS_TIMESTAMP_FORMAT    DD.MM.RR HH24:MI:SSXFF
NLS_TIME_TZ_FORMAT  HH24:MI:SSXFF TZR
NLS_TIMESTAMP_TZ_FORMAT DD.MM.RR HH24:MI:SSXFF TZR
NLS_DUAL_CURRENCY   ₽
NLS_COMP    BINARY
NLS_LENGTH_SEMANTICS    BYTE
NLS_NCHAR_CONV_EXCP FALSE


dbForge

NLS_LANGUAGE    AMERICAN
NLS_TERRITORY   AMERICA
NLS_CURRENCY    $
NLS_ISO_CURRENCY    AMERICA
NLS_NUMERIC_CHARACTERS  .,
NLS_CALENDAR    GREGORIAN
NLS_DATE_FORMAT DD-MON-RR
NLS_DATE_LANGUAGE   AMERICAN
NLS_SORT    BINARY
NLS_TIME_FORMAT HH.MI.SSXFF AM
NLS_TIMESTAMP_FORMAT    DD-MON-RR HH.MI.SSXFF AM
NLS_TIME_TZ_FORMAT  HH.MI.SSXFF AM TZR
NLS_TIMESTAMP_TZ_FORMAT DD-MON-RR HH.MI.SSXFF AM TZR
NLS_DUAL_CURRENCY   $
NLS_COMP    BINARY
NLS_LENGTH_SEMANTICS    CHAR
NLS_NCHAR_CONV_EXCP FALSE

    


Ответы

Ответ 1



К 1-ой части вопроса SYSTIMESTAMP возвращает текущее время и часовой пояс операционной системы машины на которой установлен сервер БД. CURRENT_TIMESTAMP возвращает эти же данные учитывая часовой пояс сессии на клиенте, который определяется клиентом из окружения (установки региона на ОС, переменные) в котором он запущен и может быть изменён непосредственно: setenv ORA_SDTZ="Europe/Moscow" или alter session set time_zone='+03:00'. Посмотреть часовой пояс сессии можно так: select sessiontimezone from dual; В примере разница между Москвой и Нью-Йорком составляет -4 и +3 = 7 часов, что соответствует действительности. Значит время, которое возвращает SYSTIMESTAMP тоже с отставанием, т.е. системное время установленно не верно. Ко 2-ой части вопроса Преобразование даты и времени производится на стороне клиента и зависит от его NLS настроек, часового пояса (см. выше), которые можно посмотреть: select * from nls_session_parameters; Некоторые клиенты, как в данном примере с dbForge\IDEA "потерялся" часовой пояс, или sqlplus не учитывает изменения часового пояса в России с октября 2014 года, выполняют преобразование не всегда верно. Поэтому при сомнении имеет смысл выполнить запрос на различных клиентах. На заметку не в рамках вопроса Oracle рекомендует устанавливать DBTIMEZONE на UTC, если не используются по каким-то специфическим соображениям тип данных TIMESTAMP WITH LOCAL TIME ZONE.