withfunction isNumber(p_String in varchar2) return number isn number;beginn := to_number(p_String);return 1;exception when others then return 0;end;select * from (withq_pattern as (select '^'||'([-+]?)'||'('||'([[:digit:]]+[.,]?)(E[-+]?[[:digit:]]+)?|'|| -- целое число с научной нотацией'([[:digit:]]*([.,][[:digit:]]+))(E[-+]?[[:digit:]]+)?'|| -- дробное число с научной нотацией')$' pattern from dual),q_nums as (select '.1,1.,.,1.E1,E2,1,-1,.1E1,.1E-1,-.1E+1,1.,-1.,.E3,.0E3, -.0E3, 1.23847,-1.23847,22E2, 22E-2, 22F2, 22E2.2, 1L,0x12, .1112E-1,-.1E-10, 123.2344E1, 123.2344E11.1' num_list from dual),q_tab as (select regexp_substr(num_list, '\s*([^,]+)\s*',1,level,'i',1) num from q_nums connect by regexp_substr(num_list, '[^,]+',1,level) is not null)selectnum,case when regexp_like(num, pattern) then 'Ok' else 'Fail' end by_regexp_like,case when isNumber(num) = 1 then 'Ok' else 'Fail' end by_to_numberfromq_tabcross join q_pattern) t
Записки жертвы высоких технологий
Вы можете связаться со мной по контактному емейлу, если у вас есть какие-либо вопросы, связанные с разработкой баз данных на основе продуктов ORACLE, буду рад помочь вам, если это будет в моих силах.
10 мар. 2023 г.
Из неопубликованного. Забытые regexp этюды для работы со строковым представлением дробных чисел со степенью вида [+/-]NNN[.MMM[E[+/-]PPP]]
Токенизация строки с помощью xmltable
Поэтому решил добавить еще один удобный вариант разбиения на токены, который используется у нас при работе с фронтендом. При данном подходе, разумеется, ограничена длина списка токенизируемых значений, поэтому применять нужно с оглядкой на возможную максимальную длину входной строки.
selectxt.itemfromxmltable('for $text in tokenize($input, ",") return $text'passing 'Северный Кавказ,Волга-Север,Северо-Запад' as "input"columns item varchar2(4000) path '.') xt
ITEM-----------Северный КавказВолга-СеверСеверо-Запад
7 мар. 2023 г.
PL/SQL парсер для CSV данных, находящихся в CLOB-переменной (в соотв. с RFC 4180)
-- CLOB value for tests, separator is semicolon, has been copied from file saved as Windows-1251 CSV worksheet by Excel 2019
c clob := '"1";"Serge;
George
Galina";3
""2"";Tatiana;3
3;"Nikolas
""Maxim""
Lisa";3
4;;3
5;"Daria;";3
6;""Leo"";3
7;Alexey;""3""
8;Ksenia;"3"';
-- special characters
separator constant char(1) := ';'; -- semicolon separator
quote_char constant char(1) := '"'; -- quoting character
new_row constant char(1) := chr(10); -- new row character
-- output cell value as a comment
procedure cell_out(prow in number, pcol in number, pval in varchar2) is
begin
dbms_output.put_line('Cell ('||prow||', '||pcol || ') = '|| nvl(replace(pval,new_row,'<\n>'), '<null>'));
end;
-- CSV parser
procedure parse_csv_clob (p_clob in out nocopy clob) is
offset number := 1;
amount number := 1;
total number := nvl(dbms_lob.getlength(p_clob),0);
read_buffer varchar2(1);
read_buffer1 varchar2(1);
cell_buffer varchar2(32767);
row_index pls_integer := 1;
col_index pls_integer := 1;
quoting boolean := false;
begin
if ( dbms_lob.isopen(p_clob) != 1 ) then dbms_lob.open(p_clob, 0); end if;
loop
exit when ( offset > total );
dbms_lob.read(p_clob, amount, offset, read_buffer); -- read the symbol
if read_buffer = new_row and not quoting then -- new row
cell_out(row_index, col_index, cell_buffer);
row_index := row_index + 1;
col_index := 1;
cell_buffer := null;
elsif read_buffer = separator and not quoting then -- new cell
cell_out(row_index, col_index, cell_buffer);
cell_buffer := null;
col_index := col_index + 1;
elsif read_buffer = quote_char then -- quoted value or a escaped double quote symbol (if next symbol is the same)
if offset < total then dbms_lob.read(p_clob, amount, offset + 1, read_buffer1); else read_buffer1 := null; end if;
if read_buffer1 is null or read_buffer1 != quote_char then quoting := not quoting;
else
cell_buffer := cell_buffer || quote_char;
offset := offset + 1;
end if;
else -- add any other symbols to a current cell value
cell_buffer := cell_buffer || read_buffer;
end if;
offset := offset + 1;
if (offset > total and not quoting) then
if length(cell_buffer) > 0 then
cell_out(row_index, col_index, cell_buffer);
end if;
end if;
end loop;
if ( dbms_lob.isopen(p_clob) = 1 ) then dbms_lob.close(p_clob); end if;
if quoting then raise_application_error(-20001, 'Closing quote character has not been found.'); end if;
exception
when others then
dbms_output.put_line('CSV-PARSER-ERROR: '||sqlerrm);
end;
begin
parse_csv_clob(c);
end;
30 окт. 2020 г.
Нумерация групп при разбиении результирующего набора на части по значению в одном из столбцов
Полезный простенький запрос для разбиения результирующего набора на группы по значению в одном из столбцов. Для чего применяется - точно сказать не могу, т.к. к написанию этого запроса сподвиг мой коллега, но, очевидно, информация используется где-то на клиенте для удобного отображения таких групп.
На вход попадают следующие данные об адресах ADDRID и уникальных точках привязки к этим адресам IDX. Считаем, что сортировка данных проводится вне системы, но требуется сохранить естественный порядок поступления данных. Некоторые строки не имеют привязок адреса к точкам, такие строки считаем разделителями групп привязок точек к адресу:
ADDRID IDX999 1999 2999999 4999 5999999 7
ADDRID IDX GROUPID999 1 1999 2 1999999 4 2999 5 2999999 7 3
Как вариант результата, NULL значения можно тоже относить к какой-либо группе, но это практически не меняет логику написания самого запроса. Как же написать такой запрос.
q_data: Имитируем источник исходных данных, отдающий данные с учетом собственной сортировки (согласно начальным требованиям задания).
q_data_indexed: Подразумевается, что мы не можем управлять порядком следования данных в источнике, да в нем, очевидно, отсутствует тот столбец, по которому можно упорядочить данные. Поэтому, прежде всего нужно закрепить порядок поступаемых данных, для чего применяем либо ROW_NUMBER() OVER (ORDER BY <...выражение дополнительной сортировки>) RN, либо используем обычный ROWNUM. Также создаем столбец NULLC для выделения значением 1 тех строк, по которым происходит разбиение на группы, в нашем случае это те строки, в которых значение IDX IS NULL
q_data_grouped: В принципе, после выполненных выще действий остается только произвести суммирование единичек в столбце NULLC с помощью аналитической функции SUM() OVER(ORDER BY...) согласно закрепленному порядку по столбцу RN, чтобы получить нарастающий итог в столбце GROUPID. Каждое значение NULLC = 1 будет добавлять 1 в индекс группы GROUPID, а нули в NULLC не будут добавлять в него значение.
select: После формирования значения индекса группы можно убрать лишние значения индексов групп в строках, по которым производилось разбиение, но это безусловно диктуется общей логикой запроса - этого конечно можно не делать. В результате получаем следующий запрос:
with q_data as (select 999 addrid, 1 idx from dual union allselect 999 addrid, 2 idx from dual union allselect 999 addrid, null idx from dual union allselect 999 addrid, 4 idx from dual union allselect 999 addrid, 5 idx from dual union allselect 999 addrid, null idx from dual union allselect 999 addrid, 7 idx from dual),q_data_indexed as (select addrid, idx, rownum rn, case when idx is null then 1 else 0 end nullc from q_data),q_data_grouped as (select addrid, idx, sum(nullc) over(order by rn) + 1 groupid from q_data_indexed)select addrid, idx, case when idx is null then null else groupid end groupid from q_data_grouped
А вот, кстати, альтернативный вариант разбиения на такие же группы от моего коллеги. Пример имеет некоторые ограничения, например в нем все точки собираются в строку, которая имеет в Oracle максимальную длину в 4000 символов, а также в нем активно используются регулярные выражения, что здорово снижает производительность. Тем не менее, такой запрос также имеет место быть. Привожу для образца и самостоятельного разбора логики его работы:
with dat as (select 1 as ord, 99 adrid, 1 as id from dual union allselect 2 as ord, 99 adrid, 2 as id from dual union allselect 3 as ord, 99 adrid, null as id from dual union allselect 4 as ord, 99 adrid, 3 as id from dual union allselect 5 as ord, 99 adrid, 4 as id from dual union allselect 6 as ord, 99 adrid, null as id from dual union allselect 7 as ord, 99 adrid, 5 as id from dual union allselect 8 as ord, 99 adrid, 6 as id from dual),l as (select listagg(nvl(to_char(id),'*'),',') within group(order by ord) line from dat),parts as (select level as gr ,trim(',' from regexp_substr(line, '[^*]+',1,level)) as p from l connect by regexp_substr(line, '[^*]+',1,level) is not null)selectt.*, p.grfromdat tleft join parts p on t.id in (select regexp_substr(p.p, '[^,]+',1,level) from dual connect by regexp_substr(p.p, '[^,]+',1,level) is not null)
12 июл. 2019 г.
Работа со значениями типа LONG длиной до 4000 байт без использования PL/SQL
Тем не менее, существует достаточно известный способ, с помощью которого все же можно воспользоваться значениями типа LONG непосредственно в SQL. Единственное ограничение этого способа - максимальная длина значений типа LONG должна быть не больше максимальной разрешенной длины SQL типа VARCHAR2, т.е. не более чем 4000 байт.
Приводимый мной пример в принципе дает понимание этого метода. Мне нужно было достать из системного словаря информацию о максимально возможных значениях ключа партиционирования таблицы по каждой из имеющихся партиций.
select partition_name, high_value from user_tab_partitions where table_name = '<ИМЯ_ТАБЛИЦЫ>'
Запрос крайне прост, но в SQL невозможно без определенных процедур воспользоваться значением HIGH_VALUE, так как оно имеет тип LONG. По "классике" нужно написать PL/SQL процедуру, которая преобразует тип LONG в VARCHAR2 по ROWID строки, но этот метод достаточно трудоемок и приводит к тому, что запрос при выполнении начинает переключать контекст выполнения с SQL в PL/SQL и обратно, что накладно.
Альтернативный метод таков:
with q_xml as (select dbms_xmlgen.getxmltype('select partition_name, high_value from user_tab_partitions where table_name = ''<ИМЯ_ТАБЛИЦЫ>''') xml from dual)
select
z.*
from
q_xml,
xmltable('/ROWSET/ROW' passing q_xml.xml columns partition_name varchar2(64) path 'PARTITION_NAME', high_value varchar2(1024) path 'HIGH_VALUE' ) z
Запрос, конечно, становится более сложным, чем исходный, но тем не менее отпадает потребность в написании PL/SQL функции.
23 мая 2019 г.
Разбиение CSV-значений в таблице на элементы
SQL> CREATE TABLE MY_TABLE(
2 R_ID int,
3 R_Site varchar(20),
4 R_Name varchar(20),
5 R_Role varchar(20)
6 );
Table created.
SQL>
SQL> INSERT INTO MY_TABLE(R_ID , R_Site , R_Name , R_Role )
2 VALUES ('1' , '123,-456,-789', 'qwer' , 'Owner');
1 row created.
SQL>
SQL> INSERT INTO MY_TABLE(R_ID , R_Site , R_Name , R_Role )
2 VALUES ('2' , '56,-741,-852', 'qaz' , 'Manager');
1 row created.
SQL>
SQL>
SQL> select
2 t.r_id,
3 t.r_name,
4 t.r_role,
5 regexp_substr(t.R_Site, '[^,]+', 1, commas.column_value) as site
6 from
7 MY_TABLE t,
8 table(cast(multiset(select level from dual connect by level <= regexp_count(t.R_Site, '[^,]+')) as sys.OdciNumberList)) commas;
R_ID R_NAME R_ROLE SITE
---------- ------------------------------------------------------------ ------------------------------------------------------------ --------------------
1 qwer Owner -456
1 qwer Owner -789
1 qwer Owner 123
2 qaz Manager -741
2 qaz Manager -852
2 qaz Manager 56
6 rows selected.
Единственное, что я сделал с найденным примером, так это заменил невнятное выражение в CONNECT BY на приведенный в примере выше вызов regexp_count(...) и убрал секцию ORDER BY из-за зависания сессии.
30 окт. 2018 г.
Подсчет места, занятого объектами схем(ы) на дисках
-- список схем через запятую и шаблон поиска
q_schema_names_list as (select '&schema_owners' list, '\s*([^,]+)\s*' pattern from dual),
-- таблица со схемами
q_schema_names as (select upper(regexp_substr(list,pattern,1,level,'i',1)) schema_name from q_schema_names_list connect by regexp_substr(list,pattern,1,level,'i',1) is not null )
-- результат
select
owner$ "Владелец",
tablespace_name "Табличное пространство",
segment_name "Сегмент",
segment_type "Тип сегмента",
master_object "Объект владедец",
sum_size$ "Размер объекта"
from (
-- приводим значения в удобочитаемый вид
select
case when grouping$type = '1111111' then 'ИТОГО по запросу' when grouping$type = '0111111' then 'Итого по схеме '|| owner else owner end owner$,
tablespace_name,
segment_name,
segment_type,
master_object,
case
when (tt.sum_size / 1024 / 1024 / 1024 / 1024) > 1 then to_char(tt.sum_size / 1024 / 1024 / 1024 / 1024 / 1024 ) || ' PB'
when (tt.sum_size / 1024 / 1024 / 1024) > 1 then to_char(tt.sum_size / 1024 / 1024 / 1024) || ' GB'
when (tt.sum_size / 1024 / 1024) > 1 then to_char(tt.sum_size / 1024 / 1024) || ' MB'
when (tt.sum_size / 1024) > 1 then to_char(tt.sum_size / 1024) || ' KB'
else to_char(tt.sum_size) || ' B'
end sum_size$,
case when grouping$type = '1111111' then 10E6 when grouping$type = '0111111' then 10E6-1 else rn end rn$
from (
-- rollup запрос с подсчетом суммарных размеров
select
owner,
tablespace_name,
segment_name,
segment_type,
master_object,
bytes,
rn,
grouping(owner)||
grouping(tablespace_name)||
grouping(segment_name)||
grouping(segment_type)||
grouping(master_object)||
grouping(bytes) ||
grouping(rn)grouping$type,
sum(bytes) sum_size
from (
-- перечень интересующих нас сегментов по списку схем, сортируем список по убыванию занимаемого объектами размера
select
s.owner,
s.tablespace_name,
s.segment_name,
s.segment_type,
case
when s.segment_name like 'BIN$%' then '<Recyclebin> => ' || (select distinct original_name from dba_recyclebin r where r.owner = s.owner and r.object_name = s.segment_name)
when s.segment_type = 'TABLE' then s.segment_name
when s.segment_type in ('INDEX', 'LOBINDEX') then (select t.table_name from dba_indexes t where t.owner = s.owner and t.index_name = s.segment_name)
when s.segment_type = 'LOBSEGMENT' then (select t.table_name from dba_lobs t where t.owner = s.owner and t.segment_name = s.segment_name)
when s.segment_type = 'LOB PARTITION' then (select distinct t.table_name from dba_lob_partitions t where t.table_owner = s.owner and t.lob_name = s.segment_name)
when s.segment_type = 'TABLE PARTITION' then nvl((select distinct t.table_name from dba_tab_partitions t where t.table_owner = s.owner and t.partition_name = s.segment_name),s.segment_name)
when s.segment_type = 'INDEX PARTITION' then nvl((select distinct t.table_name from dba_indexes t where t.table_owner = s.owner and t.index_name = s.segment_name),s.segment_name)
end master_object,
s.bytes,
row_number() over (order by owner, bytes desc) rn
from
dba_segments s
where
s.owner in (select schema_name from q_schema_names)
) t group by rollup (owner, tablespace_name, segment_name, segment_type, master_object,bytes, rn)
) tt where grouping$type in ('0000000', '0111111', '1111111')
order by
tt.owner, rn$
) z
11 июл. 2018 г.
Разбиение строки на токены (JSON версия)
Набившую оскомину задачу разбиения строки на токены можно решить и с помощью JSON, обрамив список элементов через запятую квадратными скобками и передав на вход JSON-парсеру:
-- можно не обрамлять элементы двойными кавычками, например для целых чиселwith q_json as (select '[1, 2, 3, 4, 0.24, .2, 5e4]' arrayList from dual)SELECT arrayItem FROM json_table( (select arrayList from q_json) , '$[*]' COLUMNS (arrayItem PATH '$'))-- со строковыми значениями надо обрамлять элементы списка двойными кавычкамиwith q_json as (select '["кактус", "утром", "на","окне", "вел", "старушку","на","ремне"]' arrayList from dual)SELECT arrayItem FROM json_table( (select arrayList from q_json) , '$[*]' COLUMNS (arrayItem PATH '$'))
19 июн. 2018 г.
Разбиение строки на токены
withq_item_list as (select 'a,b,cv,d,r,e,,df,f,' list, ',' delimiter, '''' quotationfrom dual),q_item_list_prepared as (select quotation||replace(list, delimiter, quotation||delimiter||quotation)||quotation list, delimiter, quotationfrom q_item_list)selectregexp_substr(list, '['||quotation||']([^'||delimiter||']*)['||quotation||']+',1,level,'mi',1)fromq_item_list_preparedconnect byregexp_substr(list, '[^'||delimiter||']+',1,level) is not null
В данном случае строкой, имитирующей список является строка 'a,b,cv,d,r,e,,df,f,' (см q_item_list), разделителем я выбрал запятую, а обрамляющим символом принимается одиночный апостроф.
Первым делом квотируем все элементы списка апострофами с обеих сторон, чтобы не потерять при разбиении пустые элементы (см. q_item_list_prepared). После этого спокойно нарезаем все элементы по разделителю и после этого удаляем обрамляющие символы.
Если перед нами стоит задача получить из этой строки только непустые элементы, то все резко упрощается:
withq_item_list as (select'a,b,cv,d,r,e,,df,f,' list, ',' delimiter, '''' quotationfromdual)selectregexp_substr(list, '[^'||delimiter||']+',1,level)fromq_item_listconnect byregexp_substr(list, '[^'||delimiter||']+',1,level) is not null
В данном запросе мы ничего не подготавливаем к нарезке, а просто режем строку по разделителю.
2 июн. 2017 г.
Таинственная ошибка "ORA-04023: Object could not be validated or authorized" и что с ней можно сделать
Cause: A number of attempts were made to validate or authorize an object but failed.
Action: Please report this error to Oracle Support Services.