Oracle SYNONYMs — отличная особенность. Вы можете реализовать всевозможные настройки обратной совместимости, просто создав SYNONYM в своей базе данных. Рассмотрим следующую схему:
CREATE TABLE my_table (col NUMBER(7)); CREATE SYNONYM my_table_old FOR my_table; CREATE SYNONYM my_table_bak FOR my_table_old;
Теперь вы можете запросить вашу старую таблицу по трем различным именам, и все это приведет к одному и тому же выводу:
SELECT* FROMmy_table; -- Same thing: SELECT* FROMmy_table_old; SELECT* FROMmy_table_bak;
Беда в том, что когда вы видите my_table_bak
в коде (или какое-то еще более запутанное имя), вы сразу знаете, что это такое?
Используйте этот запрос, чтобы узнать
Мы можем использовать таблицу ALL_SYNONYMS, чтобы понять это. Этот запрос уже даст простой обзор:
SELECT* FROM ALL_SYNONYMS WHERE TABLE_OWNER = 'PLAYGROUND'
Выход:
Но, как вы видите, это скучно, потому что у нас есть транзитивные синонимы, и я не хочу переходить к полной таблице, чтобы понять это MY_TABLE_BAK -> MY_TABLE_OLD -> MY_TABLE
.
Так что давайте использовать CONNECT BY!
Oracle (так же как Informix и CUBRID) имеют это замечательное CONNECT BY
предложение для иерархического SQL. Существует также возможность выражать иерархический SQL с использованием более мощных общих табличных выражений , если вы решитесь.
Но давайте посмотрим, как мы можем транзитивно разрешить наши таблицы. Вот как:
SELECT s.OWNER, s.SYNONYM_NAME, -- Get to the root of the hierarchy CONNECT_BY_ROOT s.TABLE_OWNER TABLE_OWNER, CONNECT_BY_ROOT s.TABLE_NAME TABLE_NAME FROM ALL_SYNONYMS s WHERE s.TABLE_OWNER = 'PLAYGROUND' -- The magic CONNECT BY clause! CONNECTBYs.TABLE_OWNER = PRIORs.OWNER AND s.TABLE_NAME = PRIORs.SYNONYM_NAME
Во-первых, есть CONNECT BY
, что позволяет «связывать» иерархии по их иерархическим предшественникам. На каждом уровне иерархии мы будем связывать его TABLE_NAME
с предыдущим («ПРИОР») SYNONYM_NAME
. Это будет повторяться до тех пор, пока цепочка не заканчивается (или если она запускается в цикл).
Также интересно CONNECT_BY_ROOT
ключевое слово, которое для каждого пути в иерархии отображает корень пути. В нашем случае это цель TABLE_NAME
.
Вывод можно увидеть здесь:
Если вас смущают отображаемые записи, просто добавьте LEVEL
псевдостолбец, чтобы отобразить уровень рекурсии:
SELECT -- Add level here LEVEL, s.OWNER, s.SYNONYM_NAME, CONNECT_BY_ROOT s.TABLE_OWNER TABLE_OWNER, CONNECT_BY_ROOT s.TABLE_NAME TABLE_NAME FROM ALL_SYNONYMS s WHERE s.TABLE_OWNER = 'PLAYGROUND' CONNECTBYs.TABLE_OWNER = PRIORs.OWNER AND s.TABLE_NAME = PRIORs.SYNONYM_NAME
Избавляемся от «плохих записей» с помощью START WITH
Как видите, некоторые из результатов теперь являются синонимами, указывающими непосредственно на целевую таблицу, тогда как последняя запись по-прежнему указывает на промежуточный элемент из пути синонима. Это потому, что мы возвращаемся в иерархии путей из каждой записи в таблице, а также из «промежуточных» ссылок на синонимы , чей TABLE_NAME
еще один синоним.
Давайте также избавимся от них, используя опциональное START WITH
предложение, которое позволяет ограничить обходы деревьев теми деревьями, корни которых выполняют данный предикат:
SELECT s.OWNER, s.SYNONYM_NAME, CONNECT_BY_ROOT s.TABLE_OWNER TABLE_OWNER, CONNECT_BY_ROOT s.TABLE_NAME TABLE_NAME FROM ALL_SYNONYMS s WHERE s.TABLE_OWNER = 'PLAYGROUND' CONNECTBYs.TABLE_OWNER = PRIORs.OWNER AND s.TABLE_NAME = PRIORs.SYNONYM_NAME -- Start recursing only from non-synonym objects START WITHEXISTS ( SELECT1 FROM ALL_OBJECTS WHERE s.TABLE_OWNER = ALL_OBJECTS.OWNER AND s.TABLE_NAME = ALL_OBJECTS.OBJECT_NAME AND ALL_OBJECTS.OWNER = 'PLAYGROUND' AND ALL_OBJECTS.OBJECT_TYPE <> 'SYNONYM' )
Итак, по сути, мы требуем, TABLE_NAME
чтобы любой объект был из ALL_OBJECTS
нашей схемы, но не a SYNONYM
. (да, синонимы работают для всех объектов, включая процедуры, пакеты, типы и т. д.)
Выполнение вышеуказанного запроса дает нам желаемый результат:
Как насчет ПУБЛИЧНЫХ синонимов?
Чаще всего вы будете использовать не только локальные синонимы, но и ОБЩЕСТВЕННЫЕ. Oracle имеет эту причудливую PUBLIC
псевдосхему, в которой вы не можете создавать объекты, но в которой вы можете создавать синонимы. Итак, давайте создадим еще несколько синонимов для целей обратной совместимости:
CREATEPUBLICSYNONYM my_table_bak2 FORmy_table_bak; CREATESYNONYM bak_backup_old FORmy_table_bak2;
К сожалению, это нарушит нашу цепочку, потому что по какой-то причине только Oracle и Oracle из Delphi знают, что PUBLIC
это хорошо известно как OWNER
синоним, но не как TABLE_OWNER
. Давайте посмотрим некоторые необработанные данные с:
SELECT* FROM ALL_SYNONYMS WHERE TABLE_OWNER = 'PLAYGROUND'
… и поэтому:
Как вы можете видеть, PUBLIC SYNONYM MY_TABLE_BAK2
сообщается, что в PLAYGROUND
схеме! Это нарушает рекурсию, конечно. Мы пропустили запись:
Чтобы обойти эту проблему, нам нужно настроить исходный набор данных. Любой объект, о котором сообщают как, (TABLE_OWNER, TABLE_NAME)
мог фактически быть названным синонимом ('PUBLIC', TABLE_NAME)
. Хитрость заключается в том, чтобы просто дублировать все входные данные как таковые:
SELECT s.OWNER, s.SYNONYM_NAME, CONNECT_BY_ROOT s.TABLE_OWNER TABLE_OWNER, CONNECT_BY_ROOT s.TABLE_NAME TABLE_NAME -- Tweaked data set FROM( SELECTOWNER, SYNONYM_NAME, TABLE_OWNER, TABLE_NAME FROMALL_SYNONYMS UNIONALL SELECTOWNER, SYNONYM_NAME, 'PUBLIC', TABLE_NAME FROMALL_SYNONYMS ) s -- Add the synthetic PUBLIC TABLE_OWNER as well WHERE s.TABLE_OWNER IN( 'PLAYGROUND', 'PUBLIC' ) CONNECTBYs.TABLE_OWNER = PRIORs.OWNER AND s.TABLE_NAME = PRIORs.SYNONYM_NAME START WITHEXISTS ( SELECT1 FROM ALL_OBJECTS WHERE s.TABLE_OWNER = ALL_OBJECTS.OWNER AND s.TABLE_NAME = ALL_OBJECTS.OBJECT_NAME AND ALL_OBJECTS.OWNER = 'PLAYGROUND' AND ALL_OBJECTS.OBJECT_TYPE <> 'SYNONYM' )
Вот оно, наша пропавшая запись!
Отображение иерархии
Существует также причудливая функция SYS_CONNECT_BY_PATH
, которая может быть использована для отображения всей иерархии в виде строки (VARCHAR2, не более 4000 символов!). Вот как:
SELECT -- Magic function SUBSTR( sys_connect_by_path( s.TABLE_OWNER || '.' || s.TABLE_NAME, ' <- ' ) || ' <- ' || s.OWNER || '.' || s.SYNONYM_NAME, 5 ) FROM( SELECTOWNER, SYNONYM_NAME, TABLE_OWNER, TABLE_NAME FROMALL_SYNONYMS UNIONALL SELECTOWNER, SYNONYM_NAME, 'PUBLIC', TABLE_NAME FROMALL_SYNONYMS ) s WHERE s.TABLE_OWNER IN( 'PLAYGROUND', 'PUBLIC' ) CONNECTBYs.TABLE_OWNER = PRIORs.OWNER AND s.TABLE_NAME = PRIORs.SYNONYM_NAME START WITHEXISTS ( SELECT1 FROM ALL_OBJECTS WHERE s.TABLE_OWNER = ALL_OBJECTS.OWNER AND s.TABLE_NAME = ALL_OBJECTS.OBJECT_NAME AND ALL_OBJECTS.OWNER = 'PLAYGROUND' AND ALL_OBJECTS.OBJECT_TYPE <> 'SYNONYM' )
Приведенный выше запрос теперь выведет следующие записи:
Впечатляет, а?
Примечание: если у вас устаревшие синонимы
Если у вас есть «устаревшие» синонимы, то есть синонимы, указывающие на никуда, Oracle может сообщить, что они указывают на себя. Это неудачно и создает ЦИКЛ в CONNECT BY
. Чтобы этого не случилось, просто добавьте еще один предикат, например:
SELECT SUBSTR( sys_connect_by_path( s.TABLE_OWNER || '.' || s.TABLE_NAME, ' <- ' ) || ' <- ' || s.OWNER || '.' || s.SYNONYM_NAME, 5 ) FROM( SELECT* FROM( SELECTOWNER, SYNONYM_NAME, TABLE_OWNER, TABLE_NAME FROMALL_SYNONYMS UNIONALL SELECTOWNER, SYNONYM_NAME, 'PUBLIC', TABLE_NAME FROMALL_SYNONYMS ) s -- Add this predicate to prevent cycles WHERE(s.OWNER , s.SYNONYM_NAME) != ((s.TABLE_OWNER , s.TABLE_NAME)) ) s CONNECTBYs.TABLE_OWNER = PRIORs.OWNER AND s.TABLE_NAME = PRIORs.SYNONYM_NAME START WITHEXISTS ( SELECT1 FROM ALL_OBJECTS WHERE s.TABLE_OWNER = ALL_OBJECTS.OWNER AND s.TABLE_NAME = ALL_OBJECTS.OBJECT_NAME AND ALL_OBJECTS.OWNER = 'PLAYGROUND' AND ALL_OBJECTS.OBJECT_TYPE <> 'SYNONYM' )
Можно ли написать запрос в jOOQ?
Да, конечно. В jOOQ почти все возможно, если вы можете написать это на SQL. Вот как мы используем запрос, аналогичный приведенному выше, для разрешения Oracle Synonmys в генераторе кода jOOQ:
// Some reusable variables AllObjects o = ALL_OBJECTS; AllSynonyms s1 = ALL_SYNONYMS; AllSynonyms s2 = ALL_SYNONYMS.as("s2"); AllSynonyms s3 = ALL_SYNONYMS.as("s3"); Field<String> dot = inline("."); String arr = " <- "; // The actual qeury DSL .using(configuration) .select( s3.OWNER, s3.SYNONYM_NAME, connectByRoot(s3.TABLE_OWNER).as("TABLE_OWNER"), connectByRoot(s3.TABLE_NAME).as("TABLE_NAME"), substring( sysConnectByPath( s3.TABLE_OWNER.concat(dot) .concat(s3.TABLE_NAME), arr ) .concat(arr) .concat(s3.OWNER) .concat(dot) .concat(s3.SYNONYM_NAME), 5 )) .from( select() .from( select( s1.OWNER, s1.SYNONYM_NAME, s1.TABLE_OWNER, s1.TABLE_NAME) .from(s1) .union( select( s1.OWNER, s1.SYNONYM_NAME, inline("PUBLIC"), s1.TABLE_NAME) .from(s1)) .asTable("s2")) .where(row(s2.OWNER, s2.SYNONYM_NAME) .ne(s2.TABLE_OWNER, s2.TABLE_NAME)) .asTable("s3")) .connectBy(s3.TABLE_OWNER.eq(prior(s3.OWNER))) .and(s3.TABLE_NAME.eq(prior(s3.SYNONYM_NAME))) .startWith(exists( selectOne() .from(o) .where(s3.TABLE_OWNER.eq(o.OWNER)) .and(s3.TABLE_NAME.eq(o.OBJECT_NAME)) .and(o.OBJECT_TYPE.ne("SYNONYM")) .and(o.OWNER.in(getInputSchemata())) )) .fetch();
Скачайте jOOQ сегодня и попробуйте сами!
Вывод
Если у вас есть внутренне иерархический набор данных, то вы будете очень недовольны этими упрощенными иерархическими функциями SQL (также с выражениями таблиц общего доступа). Они не очень хорошо работают, и их очень сложно выразить, если иерархия усложняется. Так что вы можете также рассмотреть возможность использования реальной графовой базы данных, такой как Neo4j .
Но время от времени небольшая иерархия может проникнуть в вашу «стандартную» модель реляционных данных. Когда это произойдет, обязательно подготовьте это полезное CONNECT BY
предложение к действию.
CONNECT BY
поддерживается (как минимум):
- CUBRID
- Informix
- оракул
Рекурсивные общие табличные выражения (аналог стандарта SQL CONNECT BY
поддерживается (как минимум):
- DB2
- жар-птица
- HSQLDB
- оракул
- PostgreSQL
- SQL Server
- Sybase SQL Anywhere
и…
- H2 имеет некоторую экспериментальную поддержку
В следующем посте мы рассмотрим, как сделать то же самое с рекурсивным CTE.