Oracle SYNONYMs — отличная особенность. Вы можете реализовать всевозможные настройки обратной совместимости, просто создав SYNONYM в своей базе данных. Рассмотрим следующую схему:
|
1
2
3
4
|
CREATE TABLE my_table (col NUMBER(7));CREATE SYNONYM my_table_old FOR my_table;CREATE SYNONYM my_table_bak FOR my_table_old; |
Теперь вы можете запросить вашу старую таблицу по трем различным именам, все это приведет к одному и тому же выводу
|
1
2
3
4
5
|
SELECT * FROM my_table;-- Same thing:SELECT * FROM my_table_old;SELECT * FROM my_table_bak; |
Проблема в том, что когда вы видите my_table_bak в коде (или какое-то еще более запутанное имя), вы сразу знаете, что это такое?
Используйте этот запрос, чтобы узнать
Мы можем использовать таблицу ALL_SYNONYMS, чтобы понять это. Этот запрос уже даст простой обзор:
|
1
2
3
|
SELECT *FROM ALL_SYNONYMSWHERE TABLE_OWNER = 'PLAYGROUND' |
Выход:
|
1
2
3
4
|
OWNER SYNONYM_NAME TABLE_OWNER TABLE_NAME---------------------------------------------------PLAYGROUND MY_TABLE_BAK PLAYGROUND MY_TABLE_OLDPLAYGROUND MY_TABLE_OLD PLAYGROUND MY_TABLE |
Но, как вы можете видеть, это скучно, потому что у нас есть транзитивные синонимы, и я не хочу просматривать всю таблицу, чтобы выяснить, что MY_TABLE_BAK -> MY_TABLE_OLD -> MY_TABLE .
Так что давайте использовать CONNECT BY!
Oracle (а также Informix и CUBRID) имеют это замечательное предложение CONNECT BY для иерархического SQL. Существует также возможность выражать иерархический SQL с использованием более мощных общих табличных выражений , если вы решитесь.
Но давайте посмотрим, как мы можем транзитивно разрешить наши таблицы. Вот как:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
|
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_NAMEFROM ALL_SYNONYMS sWHERE s.TABLE_OWNER = 'PLAYGROUND'-- The magic CONNECT BY clause!CONNECT BY s.TABLE_OWNER = PRIOR s.OWNERAND s.TABLE_NAME = PRIOR s.SYNONYM_NAME |
Во-первых, есть CONNECT BY , который позволяет «связывать» иерархии по их иерархическим предшественникам. На каждом уровне иерархии мы будем связывать TABLE_NAME с его предыдущим («PRIOR») SYNONYM_NAME . Это будет повторяться до тех пор, пока цепочка не заканчивается (или если она запускается в цикл).
Также интересно ключевое слово CONNECT_BY_ROOT , которое для каждого пути в иерархии отображает корень пути. В нашем случае это цель TABLE_NAME .
Вывод можно увидеть здесь:
|
1
2
3
4
5
|
OWNER SYNONYM_NAME TABLE_OWNER TABLE_NAME---------------------------------------------------PLAYGROUND MY_TABLE_OLD PLAYGROUND MY_TABLEPLAYGROUND MY_TABLE_BAK PLAYGROUND MY_TABLEPLAYGROUND MY_TABLE_BAK PLAYGROUND MY_TABLE_OLD <-- Useless |
Если вас смущают отображаемые записи, просто добавьте псевдостолбец LEVEL для отображения уровня рекурсии:
|
01
02
03
04
05
06
07
08
09
10
11
12
|
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_NAMEFROM ALL_SYNONYMS sWHERE s.TABLE_OWNER = 'PLAYGROUND'CONNECT BY s.TABLE_OWNER = PRIOR s.OWNERAND s.TABLE_NAME = PRIOR s.SYNONYM_NAME |
|
1
2
3
4
5
6
7
|
LEVEL OWNER SYNONYM_NAME TABLE_OWNER TABLE_NAME----------------------------------------------------------1 PLAYGROUND MY_TABLE_OLD PLAYGROUND MY_TABLE2 PLAYGROUND MY_TABLE_BAK PLAYGROUND MY_TABLE1 PLAYGROUND MY_TABLE_BAK PLAYGROUND MY_TABLE_OLD^^^^^^ Awesome! |
Избавляемся от «плохих записей» с помощью START WITH
Как видите, некоторые из результатов теперь являются синонимами, указывающими непосредственно на целевую таблицу, тогда как последняя запись по-прежнему указывает на промежуточный элемент из пути синонима. Это потому, что мы возвращаемся к иерархии путей из каждой записи в таблице, а также из «промежуточных» ссылок на TABLE_NAME , у которых TABLE_NAME является еще одним синонимом.
Давайте также избавимся от них, используя опциональное предложение START WITH , которое позволяет ограничить обходы деревьев теми деревьями, корни которых выполняют данный предикат:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
SELECT s.OWNER, s.SYNONYM_NAME, CONNECT_BY_ROOT s.TABLE_OWNER TABLE_OWNER, CONNECT_BY_ROOT s.TABLE_NAME TABLE_NAMEFROM ALL_SYNONYMS sWHERE s.TABLE_OWNER = 'PLAYGROUND'CONNECT BY s.TABLE_OWNER = PRIOR s.OWNERAND s.TABLE_NAME = PRIOR s.SYNONYM_NAME-- Start recursing only from non-synonym objectsSTART WITH EXISTS ( SELECT 1 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 чтобы TABLE_NAME был любым объектом из ALL_OBJECTS который есть в нашей схеме, но не SYNONYM . (да, синонимы работают для всех объектов, включая процедуры, пакеты, типы и т. д.)
Выполнение вышеуказанного запроса дает нам желаемый результат:
|
1
2
3
4
|
OWNER SYNONYM_NAME TABLE_OWNER TABLE_NAME---------------------------------------------------PLAYGROUND MY_TABLE_OLD PLAYGROUND MY_TABLEPLAYGROUND MY_TABLE_BAK PLAYGROUND MY_TABLE |
Как насчет ПУБЛИЧНЫХ синонимов?
Чаще всего вы будете использовать не только локальные синонимы, но и ОБЩЕСТВЕННЫЕ. Oracle имеет эту причудливую псевдошуму PUBLIC , в которой вы не можете создавать объекты, но в которой вы можете создавать синонимы. Итак, давайте создадим еще несколько синонимов для целей обратной совместимости:
|
1
2
|
CREATE PUBLIC SYNONYM my_table_bak2 FOR my_table_bak;CREATE SYNONYM bak_backup_old FOR my_table_bak2; |
К сожалению, это нарушит нашу цепочку, потому что по какой-то причине только Oracle и Oracle Delphi знают, что PUBLIC хорошо известен как OWNER синонима, но не как TABLE_OWNER . Давайте посмотрим некоторые необработанные данные с:
|
1
2
3
|
SELECT *FROM ALL_SYNONYMSWHERE TABLE_OWNER = 'PLAYGROUND' |
… и поэтому:
|
1
2
3
4
5
6
|
OWNER SYNONYM_NAME TABLE_OWNER TABLE_NAME------------------------------------------------------PLAYGROUND MY_TABLE_OLD PLAYGROUND MY_TABLEPLAYGROUND MY_TABLE_BAK PLAYGROUND MY_TABLE_OLDPUBLIC MY_TABLE_BAK2 PLAYGROUND MY_TABLE_BAKPLAYGROUND BAK_BACKUP_OLD PLAYGROUND MY_TABLE_BAK2 <-- Not PUBLIC |
Как вы можете видеть, PUBLIC SYNONYM MY_TABLE_BAK2 как сообщается, находится в схеме PUBLIC SYNONYM MY_TABLE_BAK2 ! Это нарушает рекурсию, конечно. Мы пропустили запись:
|
1
2
3
4
5
|
OWNER SYNONYM_NAME TABLE_OWNER TABLE_NAME------------------------------------------------------PLAYGROUND MY_TABLE_OLD PLAYGROUND MY_TABLEPLAYGROUND MY_TABLE_BAK PLAYGROUND MY_TABLEPUBLIC MY_TABLE_BAK2 PLAYGROUND MY_TABLE <-- Hmm? |
Чтобы обойти эту проблему, нам нужно настроить исходный набор данных. Любой объект, указанный как (TABLE_OWNER, TABLE_NAME) на самом деле может быть синонимом ('PUBLIC', TABLE_NAME) . Хитрость заключается в том, чтобы просто дублировать все входные данные как таковые:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
SELECT s.OWNER, s.SYNONYM_NAME, CONNECT_BY_ROOT s.TABLE_OWNER TABLE_OWNER, CONNECT_BY_ROOT s.TABLE_NAME TABLE_NAME-- Tweaked data setFROM ( SELECT OWNER, SYNONYM_NAME, TABLE_OWNER, TABLE_NAME FROM ALL_SYNONYMS UNION ALL SELECT OWNER, SYNONYM_NAME, 'PUBLIC', TABLE_NAME FROM ALL_SYNONYMS) s-- Add the synthetic PUBLIC TABLE_OWNER as wellWHERE s.TABLE_OWNER IN ( 'PLAYGROUND', 'PUBLIC')CONNECT BY s.TABLE_OWNER = PRIOR s.OWNERAND s.TABLE_NAME = PRIOR s.SYNONYM_NAMESTART WITH EXISTS ( SELECT 1 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') |
Вот оно, наша пропавшая запись!
|
1
2
3
4
5
6
|
OWNER SYNONYM_NAME TABLE_OWNER TABLE_NAME---------------------------------------------------PLAYGROUND MY_TABLE_OLD PLAYGROUND MY_TABLEPLAYGROUND MY_TABLE_BAK PLAYGROUND MY_TABLEPUBLIC MY_TABLE_BAK2 PLAYGROUND MY_TABLEPLAYGROUND BAK_BACKUP_OLD PLAYGROUND MY_TABLE <-- Yep! |
Отображение иерархии
Существует также причудливая функция SYS_CONNECT_BY_PATH , которая может быть использована для отображения всей иерархии в виде строки (VARCHAR2, максимум 4000 символов!). Вот как:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
SELECT-- Magic function SUBSTR( sys_connect_by_path( s.TABLE_OWNER || '.' || s.TABLE_NAME, ' <- ' ) || ' <- ' || s.OWNER || '.' || s.SYNONYM_NAME, 5 )FROM ( SELECT OWNER, SYNONYM_NAME, TABLE_OWNER, TABLE_NAME FROM ALL_SYNONYMS UNION ALL SELECT OWNER, SYNONYM_NAME, 'PUBLIC', TABLE_NAME FROM ALL_SYNONYMS) sWHERE s.TABLE_OWNER IN ( 'PLAYGROUND', 'PUBLIC')CONNECT BY s.TABLE_OWNER = PRIOR s.OWNERAND s.TABLE_NAME = PRIOR s.SYNONYM_NAMESTART WITH EXISTS ( SELECT 1 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') |
Приведенный выше запрос теперь выведет следующие записи:
|
1
2
3
4
|
PLAYGROUND.MY_TABLE <- PLAYGROUND.MY_TABLE_OLDPLAYGROUND.MY_TABLE <- PLAYGROUND.MY_TABLE_OLD <- PLAYGROUND.MY_TABLE_BAKPLAYGROUND.MY_TABLE <- PLAYGROUND.MY_TABLE_OLD <- PLAYGROUND.MY_TABLE_BAK <- PUBLIC.MY_TABLE_BAK2PLAYGROUND.MY_TABLE <- PLAYGROUND.MY_TABLE_OLD <- PLAYGROUND.MY_TABLE_BAK <- PUBLIC.MY_TABLE_BAK2 <- PLAYGROUND.BAK_BACKUP_OLD |
Впечатляет, а?
Примечание: если у вас устаревшие синонимы
Если у вас есть «устаревшие» синонимы, то есть синонимы, указывающие на никуда, Oracle может сообщить, что они указывают на себя. Это печально и создает ЦИКЛ в CONNECT BY . Чтобы этого не случилось, просто добавьте еще один предикат, например:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
SELECT SUBSTR( sys_connect_by_path( s.TABLE_OWNER || '.' || s.TABLE_NAME, ' <- ' ) || ' <- ' || s.OWNER || '.' || s.SYNONYM_NAME, 5 )FROM ( SELECT * FROM ( SELECT OWNER, SYNONYM_NAME, TABLE_OWNER, TABLE_NAME FROM ALL_SYNONYMS UNION ALL SELECT OWNER, SYNONYM_NAME, 'PUBLIC', TABLE_NAME FROM ALL_SYNONYMS ) s -- Add this predicate to prevent cycles WHERE (s.OWNER , s.SYNONYM_NAME) != ((s.TABLE_OWNER , s.TABLE_NAME))) sCONNECT BY s.TABLE_OWNER = PRIOR s.OWNERAND s.TABLE_NAME = PRIOR s.SYNONYM_NAMESTART WITH EXISTS ( SELECT 1 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:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
// Some reusable variablesAllObjects 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 qeuryDSL.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.
| Ссылка: | Все, что вам нужно знать о рекурсивном SQL от нашего партнера по JCG Лукаса Эдера из блога JAVA, SQL и JOOQ . |