Месячный Архив: Апрель 2016

SQL and PL/SQL

Отчеты по самодокументированной базе данных

Хорошей практикой в разработке баз данных Oracle являются комментарии к объектам физического и логического слоя (таблицам, представлениям, полям) – такая БД является самодокументированной. Вместе со стандартом именования объектов это значительно облегчает жизнь команды разработчиков, облегчает введение в должность новых сотрудников.

Однако приходится иногда делать отчет с группировкой по тем или иным объектам так, чтобы он хорошо выглядел в технической документации по модулю. Приводим пример запроса, который извлекает из БД документацию по интерфейсным представлениям бизнес-модуля “Форма 302”. Использованы аналитические функции LAG (предыдущее значение согласно сортировке) и DENSE_RANK (порядковый номер согласно сортировке).

SELECT CASE
          WHEN NVL(LAG(uv1.view_name) 
                   OVER (ORDER BY uv1.view_name, tc1.column_name), '#') <> uv1.view_name
          THEN DENSE_RANK() 
                   OVER (ORDER BY uv1.view_name)
          ELSE NULL
       END AS r_num,
       CASE
          WHEN NVL(LAG(uv1.view_name) 
                   OVER (ORDER BY uv1.view_name, tc1.column_name), '#') <> uv1.view_name
          THEN uv1.view_name
          ELSE NULL
       END AS first_view_name,
       tc1.column_name,
       cc1.comments
  FROM USER_VIEWS uv1
       LEFT JOIN USER_TAB_COLUMNS tc1 
         ON tc1.table_name = uv1.view_name
       LEFT JOIN USER_COL_COMMENTS cc1 
         ON cc1.table_name = uv1.view_name
        AND cc1.column_name = tc1.column_name
 WHERE LOWER(uv1.view_name) LIKE '%302%gui%v%' /* используйте свои критерии */
 ORDER BY uv1.view_name, tc1.column_name

Часть результатов, включая недокументированные представления, пустая – есть где поработать техническим писателям!

1CBR_302_GUI_DICT_BS_VBUSINESS_SIZE_CD 
  BUSINESS_SIZE_NAME 
  SORT_ID 
2CBR_302_GUI_DICT_OKATO_VOKATO_CD 
  OKATO_CODE 
  SORT_ID 
3CBR_302_GUI_DICT_OKVED_VOKVED_CD 
  OKVED_CODE 
  SORT_ID 
4CBR_302_GUI_OKATO_VAS_OF_DATEОтчетная дата
  FDDR_IDID ФДП-отчета
  OKATOКод Окато
  SECT_IDКод раздела
Новости

Мужской квест… год спустя

Квест Диверсант
Спустя год наши коллеги-женщины опять подарили нам участие в квесте. За год из команды ушел Дима Грачев, а пришли новые сотрудники Виталий и Евгений. И если мы с Сергеем знали, с чем столкнемся, то для новичков квест был в диковинку. На самом деле час (58 минут 40 секунд) в двух комнатах с участием актера и холостой стрельбы из боевых пистолетов вышли очень напряженными, и если бы не некоторые подсказки, нас бы точно застрелили нам не удалось бы выбраться из запертых комнат и не пришлось бы сломя голову бежать куда глаза глядят. Было офигенно и с атмосферой Великой Отечественной Войны.

SQL and PL/SQL

Super Merge

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

CREATE TABLE test_clients
(
   id         NUMBER,
   name       VARCHAR2(30),
   phone      VARCHAR2(30),
   phone_type VARCHAR2(20)
);

INSERT INTO test_clients
VALUES (1, 'Вася', NULL, NULL);

INSERT INTO test_clients
VALUES (2, 'Петя', NULL, NULL);

CREATE TABLE test_phones
(
   client_id   NUMBER,
   phone       VARCHAR2 (30),
   phone_type  VARCHAR2 (20)
);

INSERT INTO test_phones
VALUES (1, '8(905)133-30-10', 'Мобильный');

INSERT INTO test_phones
VALUES (2, '8(905)247-89-17', 'Мобильный');

Решение в виде одного оператора Merge предложил Максим Медведев:

MERGE INTO test_clients dest
  USING 
    (WITH f AS 
      (SELECT c.rowid AS rid,
              NVL(p.client_id, c.id) AS id,
              p.phone,
              p.phone_type,
              CASE
                WHEN c.id IS NULL THEN 'I'
                WHEN p.client_id IS NULL THEN 'D'
              ELSE
                CASE ROW_NUMBER() 
                     OVER (PARTITION BY p.client_id 
                     ORDER BY p.phone)
                WHEN 1 THEN 'U' 
                ELSE 'I'
                END
              END AS r,
              ROW_NUMBER() 
              OVER (PARTITION BY NVL(c.id, p.client_id)
                    ORDER BY 1) AS rn
         FROM test_phones p
              FULL OUTER JOIN
              test_clients c
              ON c.id = p.client_id AND NVL(c.phone, p.phone) = p.phone
        WHERE (p.client_id IS NOT NULL AND NVL(c.phone_type, '-') != p.phone_type)
           OR (p.client_id IS NULL))
     SELECT f.*, s.name
       FROM f JOIN (SELECT DISTINCT id, name FROM test_clients) s ON s.ID = f.ID) src
    ON (dest.rowid = src.rid AND src.r IN ('U', 'D'))
WHEN MATCHED THEN
   UPDATE SET phone = src.phone,
              phone_type = src.phone_type
   DELETE WHERE src.r = 'D' AND src.rn > 1
WHEN NOT MATCHED THEN
   INSERT VALUES (src.id,
                  src.name,
                  src.phone,
                  src.phone_type);

Теперь можно изучить результат, попробовать добавлять, менять и удалять данные из таблицы с номерами телефонов

INSERT INTO test_phones 
VALUES (2, '8(496)245-55-86', 'Второй домашний');

DELETE FROM test_phones
WHERE client_id = 2 AND ROWNUM >= 1;