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

среда, 4 февраля 2015 г.

ORACLE: Ускорение pl/sql циклов и пользовательских функций

Продолжаю тему оптимизации запросов ORACLE, хотелось бы коснуться циклов по SQL запросам в PLSQL.
Возможно, многим это уже известно, но все же.
Чаще всего запросы в plsql пишутся следующим образом:
PROCEDURE increase_salary (
   department_id_in   IN employees.department_id%TYPE,
   increase_pct_in    IN NUMBER)
IS
BEGIN
   FOR employee_rec
      IN (SELECT employee_id
            FROM employees
           WHERE department_id =
                    increase_salary.department_id_in)
   LOOP
      UPDATE employees emp
         SET emp.salary = emp.salary + 
             emp.salary * increase_salary.increase_pct_in
       WHERE emp.employee_id = employee_rec.employee_id;
   END LOOP;
END increase_salary;
У такого подхода есть существенный минус - для каждой записи из SELECT, ORACLE приходится менять контекст выполнения с SQL на PLSQL.
Такого рода операцию можно выполнить без смены контекста 2 способами:
* UPDATE без цикла - в простом варианте ДА, но не все циклы так просты.
* Использовать BULK COLLECT и FORALL
С BULK COLLECT и FORALL запрос получится следующий:
CREATE OR REPLACE PROCEDURE increase_salary (
  department_id_in   IN employees.department_id%TYPE,
  increase_pct_in    IN NUMBER)
IS
  TYPE employee_ids_t IS TABLE OF employees.employee_id%TYPE
          INDEX BY PLS_INTEGER; 
  l_employee_ids   employee_ids_t;
BEGIN
  SELECT employee_id
     BULK COLLECT INTO l_employee_ids
    FROM employees
   WHERE department_id = increase_salary.department_id_in;

  FORALL indx IN 1 .. l_employee_ids.COUNT
     UPDATE employees emp
        SET emp.salary =
                 emp.salary
               + emp.salary * increase_salary.increase_pct_in
      WHERE emp.employee_id = l_employee_ids (indx);
END increase_salary;
Разберем код:
* Создаем Тип "employee_ids_t" - это ассоциативный массив, где ключ PLS_INTEGER, а значение = employees.employee_id%TYPE
* l_employee_ids - это переменная типа employee_ids_t
* BULK COLLECT INTO - поместить отобранные select идентификаторы в коллекцию l_employee_ids. Помещаются все записи разом, в отличии от обычного INTO (одна запись)
* Конструкция FORALL - выполняет UPDATE столько раз, сколько записей в коллекции l_employee_ids и используя данные из нее.

Стоит заметить, что FORALL это не цикл, а конструкция языка, так что он имеет ряд особенностей:
* Внутри FORALL может быть только 1 DML запрос. Если нужно несколько запросов, то нужно использовать несколько FORALL
* При выполнении FORALL не происходит переключения контекста. Весь UPDATE выполняется за 1 раз, что дает значительное преимущество в скорости.

Стоит заметить, что время на смену контекста также расходуется при вызове пользовательских функций в DML запросах.
К примеру:
FUNCTION betwnstr (
   string_in      IN   VARCHAR2
 , start_in       IN   INTEGER
 , end_in         IN   INTEGER
)
   RETURN VARCHAR2 
 
....

SELECT betwnstr (last_name, 2, 6) 
  FROM employees
 WHERE department_id = 10
* Контекст будет сменен столько раз, сколько записей отберется в SELECT
Чтобы избежать смены контекста, можно :
* Попробовать объявить функцию как INLINE "PRAGMA INLINE "
* Или как используемую только в DML "PRAGMA UDF"
* Но лучший вариант в данном случае - отказаться от функции и сознательно денормализировать запрос до чистого SQL.

На основе http://www.oracle.com/technetwork/issue-archive/2012/12-sep/o52plsql-1709862.html

воскресенье, 29 июня 2014 г.

Неочевидные вопросы сертификации 1Z0-047 Oracle Database SQL Expert

Продолжение. Новые вопросы с сентября 2014
  1. Нельзя писать алиас у колонки в select по которой происходит join в using (или * с алиасом таблицы). Это же относится к natural соединениям.
    SELECT key
    FROM A FULL JOIN B
    USING (key)
    
  2. Вставка в несколько таблиц
    * INSERT ALL - может быть без WHEN
    * INSERT ALL - поумолчанию
    * INSERT FIRST - при совпадении WHEN, остальные не просматриваются
    * INSERT ALL - просматриваются все ветвления WHEN
    * Может быть несколько INTO в одном WHEN
    * Нельзя использовать sequence в SELECT запросе множественном INSERT (но можно в VALUES)
    INSERT [ALL|FIRST]
     WHEN <..> THEN
      INTO A (a1,a2) VALUES(b1,b2)
     WHEN <..> THEN
      INTO C (c1,c2) VALUES(b2,b1)
     ELSE
      INTO D (d1,d2) VALUES(b1,b2)
    SELECT
     b1, b2, b3 FROM B
    
  3. MERGE dml операция:
    * Преимущество - один проход
    * D/I/U - возможные операции
    * WHERE можно писать только по таблице из USING
    MERGE INTO <..>
    USING A
      <..>
    WHEN MATHED THEN UPDATE SET <..>
    [WHERE <..>]
    
  4. WHERE выполняется до SET.
    Это значит, что в SET можно писать невалидные значения, если WHERE их отсечет.
  5. AND имеет больший приоритет, чем OR
  6. Арифметические операции имеют больший приоритет, чем SQL операторы сравнения
  7. FROM необязателен в DELETE
  8. CLOB, BLOB, TIMESTAMP WITH TIMEZONE (далее TS WITH TZ) - нельзя использовать в PK
    , а TS WITH LOCAL TZ - можно
  9. Размеры полей:
    * CHAR -> CHAR(1)
    * INTERVAL DAY -> INTERVAL DAY(2)
    * NUMBER(2,-3) - округлит! (не обрежет) до 3 знака перед запятой
    * VARCHAR - обязательно указывать размерность
    * VARCHAR(1 CHAR), если база в cp1251 - это 1 байт, если UTF8 , то от 2 байт
  10. UNION, MINUS, INTERSECT - set операторы
    Сортировка в них возможна только по алиасу или позиции в самом конце.
  11. Допускается только двойное вложение агрегирующих функций (3ий нельзя)
    MAX(SUM(<..>))
  12. Типы соединений:
    * cartesian -> cross
    * nonequijoin -> < , >
    * full -> outer
    * inner -> natural
  13. FK можно создавать по полям:
    * Одного типа, но разных разных размерностей
    * FK может указывать на PK и на UNIQUE index поля
  14. UNUSED поле - поле аналогично дропнутому, но физически из таблицы не вычищено:
    SET UNUSED COUMN <..>; --помечаем неиспользуемым
    ALTER TABLE T DROP UNUSED COUMNS; --физическое удаление неиспользуемых колонок
    
  15. По V$ представлениям нельзя делать запросы, т.к. их структура может меняться со временем.
    Если же в этом есть необходимость, то нужно сперва создать копию.
  16. Нет отдельных прав на создание FK, constraints, index
    Такие права даются вместа с правами на таблицу.
  17. Non-schema objects - это users, roles, public synonims
  18. Допускается сравнение systimestamp и date типов
  19. Если в CREATE SEQUENCE задана опция CYCLE, то MAXVALUE может быть отрицательным (т.к. START тоже может быть отрицательным)
  20. Нельзя создавать NOT NULL constraint отдельно от создания таблицы (alter)
  21. UNIQUE в SELECT аналогичен DISTINCT
  22. ROWNUM нельзя использовать с * без алиса
    SELECT ROWNUM, * --нельзя
    
  23. * NULL поля при ASC сортировке идут последними
    * при DESC первыми
    Можно указать принудительно:
    ORDER BY <..> nulls last[first]
  24. При выборке из TS WITH LOCAL TZ к дате вставки добавляется разность между зоной вставки и зоной выборки.
  25. MONTHS_BETWEEN(большая дата, меньшая дата) = Дробное чило
    Если наоборот, то отрицательное число.
  26. MEDIAN (среднее по порядку) и AVG не приминают в параметре строку
  27. Все групповые (агрегирующие) функции принимают только NOT NULL значения.
  28. RANK( expression1, ... expression_n ) WITHIN GROUP ( ORDER BY expression1, ... expression_n )
    
    Ранк группы, если записей с одним ранком несколько, то им проставляется один номер.
  29. HAVING может использоваться только в SELECT после WHERE (можно даже без GROUP BY)
  30. * > ALL (<..>) - TRUE, если все строки подзапроса больше, или подзапрос ничего не! вернул
    * > SOME (<..>) - TRUE, если хотябы одна строка подзапроса больше, FALSE - подрапрос ничего не вернул
  31. Нельзя делать сложные (составные) столбцы в VIEW и Create table as select без алиасов (общие правила именования столбцов таблиц)
  32. Обычные synonim (не public) также распространяется на всю бд, но права даны только текущему пользователю.
    В случае public synonim права автоматически даны всем пользовалям.
  33. Перекомпиляция VIEW:
    ALTER <..> compile;
  34. ALTER TABLE T ENABLE NOVALIDATE CONSTRAINT <..>;
    Не проверять constraint у уже сущесвующих записей (будет проверяться только у новых)
  35. SET CONSTRAINT <..> DEFERRED;
    * Не проверяет constraint до первого commit;
    * После прохождения commit, constraint автоматом сменяется на immdiate - проверять все данные сразу.
  36. Создание constraint с INDEX
    CREATE TABLE T (
     <..>,
     constraint <..> UNIQUE (<...>)
      USING INDEX (CREATE INDEX <..> ON <..> (<..>) )
    )
    
    * Такой индекс можно создать только по UNIQUE и PK constraint
    * NOT NULL ограничение нельзя использовать в USING INDEX и CHECK (только в inline)
  37. Перманентно дропнуть таблицу без возможности восстановить из корзины.
    PURGE TABLE <..>;
    
  38. Восстановление таблицы на состояние в прошлом (из корзины).
    * SCN - Номер коммита. Можно получить используя пакет FLASHBACK.get_system_change_number
    FLASHBACK TABLE <..> TO SCN <..>
    
    * TIMESTAMP - на определенное время
    FLASHBACK TABLE <..> TO TIMESTAMP <..>
    
    * RESTORE POINT - точка восстановления ( CREATE RESTORE POINT <..> )
    FLASHBACK TABLE <..> TO RESTORE POINT <..>
    
    * BEFORE DROPS - восстановит таблицу, индексы, гранты, constraints ( все, кроме FK )
    FLASHBACK TABLE <..> TO BEFORE DROPS;
    
  39. * Получение данных на определенное время (нельзя использовать подзапросы)
    SELECT * FROM <..> 
      * as of timestamp('дата','формат'); --на точное время
      * as of timestamp systimestamp - intreval '0 00:01:30' DAY TO SEC; --полторы минуты назад
      * VERSION BETWEEN [TIMESTAMP 'TS1' AND 'TS2'] --между датами
                        [SCN '1' AND '2'] --между коммитами
    
    * Показ версии ( в историю попадают записи перед commit)
    SELECT t.*, version_operation, RAWTOHEX(version_xid)
    FROM T
    VERSIONS BETWEEN TIMESTAMP minvalue AND maxvalue;
    
  40. Включение FlashBack
    * DDL (может указана в настройках бд)
    ALTER SESSION SET RECYCLEBIN=ON;
    
    * DML (может быть настроено при создании таблицы)
    ALTER TABLE <..> ENABLE ROW MOVEMENT;
    
  41. EXTERNAL - Внешние метаданные
    * Можно делать select
    * Нельзя: index, constraint, update, delete
    * Хранится в папке на сервере
    * Создание (синтаксис схож с синтаксисом SQL Loader )
    CREATE TABLE <..> (<cols>)
    ORGANIZATION EXTERNAL
    ( ... LOCATION ('file.csv') );
    
  42. Советы по оптимизации составных индексов:
    * Самые часто используемые столбцы должны идти первыми
    ** Поиск по первым столбцам - range scan
    ** Поиск по другим - scip scan
    Поиск по части индекса, для каждого уникального значения префикса индекса.
  43. INTERSECT - убираем дубликаты строк.
    Пересечение 2ух одинаковых строк с 2умя другими даст одну.
  44. CUBE/ROLLUP по одному столбцу добавляет лишь 1 строку - общий итог.
  45. * Системные представления хранятся в схеме SYS
    * DICTIONARY - описание всех таблиц
    * USER_<..> таблицы не имеют OWNER столбца
  46. Иерархические запросы:
    * Сортировка по полю внутри уровня.
    ORDER BY sibling by <..>;
    
    * Полный путь до уровня
    SYS_COMMENT_BY_PATH(col, '/')
    
    * Вывод данных в зависимости от уровня
    CONNECT_BY_ROOT COL -- данные из верха иерархии
    CONNECT_BY_LEAF COL -- данные из самого низа (листка)
    CONNECT_BY_CYCLE COL -- данные, где начался цикл
    
    * WHERE идет и выполняется перед START WITH
  47. * Выдача прав с возможностью дальнейшей передачи (ADMIN OPTION)
    GRANT CREATE ANY TABLE TO <..> WITH ADMIN OPTION;
    
    * REVOKE права - отбирает не только сами права, но и возможность передачи (если были даны через ADMIN OPTION )
  48. UNION с любым другими SET операторами аналогичен DISTINCT
    SELECT 1 FROM DUAL
    UNION ALL
    SELECT 1 FROM DUAL
    UNION
    SELECT 2 FROM DUAL
    
    Запрос даст 2 строки, не смотря что первый оператор UNION ALL.

четверг, 22 ноября 2012 г.

ORACLE: логирование всех DDL операций.

Вот такой небольшой триггер позволит сохранить все операции DDL, происходящие во всех схемах БД. DDL (Data Definition Language) - описание структур объектов базы.
Триггер выполняется после DDL операции и сохраняет: тип объекта (ora_dict_obj_type), схему (ora_dict_obj_owner), имя объекта (ora_dict_obj_name), имя пользователя , дата операции, тип DDL операции (ora_sysevent), имя компьютера и CLOB с текстом DDL операции (ora_sql_txt).
create or replace TRIGGER bi_sa_psk.trg_ddl_trig_hist
 AFTER DDL
 ON DATABASE
declare
  PRAGMA AUTONOMOUS_TRANSACTION;

  v_ddl CLOB := EMPTY_CLOB;
  li ora_name_list_t;
  l_n number;
BEGIN
  /* sys создает системные временные таблицы для sql запросов */
  IF ora_dict_obj_owner = 'SYS' THEN
    return;
  END IF;

  /* Получим текст DDL */
  l_n := ora_sql_txt(li);
  for i in 1 .. l_n loop
    v_ddl := v_ddl || TO_CLOB(TO_CHAR(li(i)));
  end loop;

  /* запишем историю */
  INSERT INTO bi_sa_psk.ddl_hist(object_type, owner, object_name, USER_NAME, DDL_DATE, DDL_TYPE, COMP_NAME, DDL_TXT, stack)
  VALUES(UPPER(ora_dict_obj_type), UPPER(ora_dict_obj_owner), UPPER(ora_dict_obj_name), ora_login_user, SYSDATE, ora_sysevent, SYS_CONTEXT ('USERENV', 'OS_USER'), v_ddl, dbms_utility.format_call_stack);
  commit;

  /* любые ошибки игнорируем, что бы не завалить БД целиком */
  EXCEPTION WHEN OTHERS THEN NULL;
END trg_ddl_trig_hist;
/
Такая штука будет очень полезна, чтобы вернуть утерянные изменения или отыскать виновного в баге :) .

среда, 14 апреля 2010 г.

Генерация штрихкода собственными силами

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

Здесь я приведу рабочий код, с минимальным описанием. Функция преобразует уже готовые штрихкоды, с рассчитанными контрольными суммами (Как считать контрольную сумму, можно прочитать тамже). Код на Oracle PL/Sql.

Ean13 и UPC-A
Отличие штрихкода UPC-A от EAN13 в одном первом символе. В UPC-A он всегда = 0
CREATE OR REPLACE FUNCTION FNC_GET_BARCODE_EAN13 ( sFBarCode IN VARCHAR2)
  RETURN  VARCHAR2 IS retBarCode VARCHAR2(15);
  lcnt INTEGER := 3;
  tableA BOOLEAN := FALSE;
  first CHAR(1);
  sBarCode VARCHAR2(15);
BEGIN
   sBarCode := sFBarCode;--внутренний буфер
   IF(length(sBarCode) = 12) THEN
    sBarCode := '0' || sBarCode;
   ELSIF(length(sBarCode) <> 13) THEN
    raise_application_error(-20010, 'Штрих код EAN13 должен содержать 13 символов');
    return '';
   END IF;
   
   retBarCode := SUBSTR(sBarCode, 1, 1);
   first := retBarCode;
   retBarCode := retBarCode || chr(65+SUBSTR(sBarCode, 2, 1));
   
   -- первая половина штрихкода
   FOR lcnt IN 3..7 LOOP
    tableA := FALSE;
    
    if (lcnt=3) and ((first=0) or (first=1) or (first=2) or (first=3)) THEN tableA:=TRUE; END IF;
    if (lcnt=4) and ((first=0) or (first=4) or (first=7) or (first=8)) THEN tableA:=TRUE; END IF;
    if (lcnt=5) and ((first=0) or (first=1) or (first=4) or (first=5) or (first=9)) THEN tableA:=TRUE; END IF;
    if (lcnt=6) and ((first=0) or (first=2) or (first=5) or (first=6) or (first=7)) THEN tableA:=TRUE; END IF;
    if (lcnt=7) and ((first=0) or (first=3) or (first=6) or (first=8) or (first=9)) THEN tableA:=TRUE; END IF;
    
    If tableA = TRUE THEN 
        retBarCode := retBarCode || chr(65+SUBSTR(sBarCode, lcnt, 1));
    ELSE 
        retBarCode := retBarCode || Chr(75+SUBSTR(sBarCode, lcnt, 1));
    END IF;
    
    tableA := FALSE;
  END LOOP;
  
  --разделитель середины штрихкода
  retBarCode := retBarCode || '*';
  
  --вторая половина штрихкода
  lcnt:=8;
  FOR lcnt IN 8..13 LOOP
    retBarCode := retBarCode || Chr(97+SUBSTR(sBarCode, lcnt, 1));
  END LOOP;
  
  --конец
  retBarCode := retBarCode || '+';
   
  RETURN retBarCode;
EXCEPTION
   WHEN OTHERS THEN
      RETURN '';
END;


EAN8
EAN8 имеет более простой механизм реализации, без смещений по таблице.
CREATE OR REPLACE FUNCTION FNC_GET_BARCODE_EAN8 ( sBarCode IN VARCHAR2)
  RETURN  VARCHAR2 IS retBarCode VARCHAR2(11);
  lcnt INTEGER := 3;
BEGIN
  if(length(sBarCode) <> 8) THEN
   raise_application_error(-20010, 'Штрих код EAN8 должен содержать 8 символов');
   return '';
  END IF;
   
  retBarCode := ':';
   
  -- первая половина штрихкода
  FOR lcnt IN 1..4 LOOP
    retBarCode := retBarCode || chr(65+SUBSTR(sBarCode, lcnt, 1));
  END LOOP;
  
  --разделитель середины штрихкода
  retBarCode := retBarCode || '*';
  
  --вторая половина штрихкода
  FOR lcnt IN 5..8 LOOP
    retBarCode := retBarCode || Chr(97+SUBSTR(sBarCode, lcnt, 1));
  END LOOP;
  
  --конец
  retBarCode := retBarCode || '+';
   
  RETURN retBarCode;
EXCEPTION
   WHEN OTHERS THEN
      RETURN '';
END;


Объединение предыдущих двух функций (В зависимости от переданной строки)
CREATE OR REPLACE FUNCTION FNC_GET_BARCODE_EAN ( sBarCode IN VARCHAR2)
  RETURN  VARCHAR2 IS retBarCode VARCHAR2(15);
BEGIN
  --Объединение EAN8 и EAN13
  IF(length(sBarCode) = 8) THEN
    retBarCode := FNC_GET_BARCODE_EAN8(sBarCode);
  ELSE
    retBarCode := FNC_GET_BARCODE_EAN13(sBarCode);
  END IF;
  RETURN retBarCode;
  
EXCEPTION
   WHEN OTHERS THEN
      RETURN '';
END;


Пример использования
select
FNC_GET_BARCODE_EAN('35967101') AS ean8,
FNC_GET_BARCODE_EAN('4607024381199') AS ean13,
FNC_GET_BARCODE_EAN('607024381199') AS upc
 from DUAL;


Полученную строку (после работы функции FNC_GET_BARCODE_EAN) можно выводить шрифотом ean13 и получить работающий штрихкод.