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.







