В сложных комплексных системах часто встаёт вопрос интеграции данных из разных источников.
Такие системы получили название интегрированных, федеративных или мультибаз.
В СУБД Caché такая интеграция осуществляется с помощью специального шлюза (Caché SQL Gateway), который использует в своей основе ODBC/JDBC соединения к внешним источникам данных. Причём под источником в данном случае можно понимать не только СУБД, так как есть JDBC/ODBC драйвера для MS Excel, DBF, текстовых файлов, графических файлов, WMI и т.д.
Коротко, как задействовать Caché SQL Gateway:
- в Портале Управления Системой (SMP) создаёте соединение нужного типа, указав строку подключения, логин, пароль и т.д. Здесь же можно проверить созданное соединение;
- с помощью Мастера Связывания связываете из внешней СУБД требуемые таблицы и/или процедуры с Caché, используя соединение с шага выше. При этом никакие данные никуда не копируются, а создаются лишь специальные виртуальные классы, они же таблицы;
- теперь, подключившись к области — логическая БД в терминах Caché — вы увидите в ней таблицы, представления, хранимые процедуры (ХП) из внешних источников: Oracle, DB2, MSSQL, MySQL, Excel, DBF, CSV и т.д.
- далее вы можете работать с этими таблицами/ХП в обоих направлениях, как если бы они физически находились в Caché.
Примечание: предвидя вопросы про поддержку гетерогенных запросов к разным источникам данных, спешу заметить про наличие некоторых ограничений в запросах.
Другими словами, JOIN между таблицами из Oracle и MSSQL выполнить не удастся.
Данные ограничения применимы только для так называемых связанных таблиц. Если же воспользоваться Мастером Миграции структур и данных вместо Мастера Связывания, то таких ограничений уже не будет.
Создание связанных таблиц
Как уже было сказано выше, можно воспользоваться либо ODBC, либо JDBC драйвером для подключения к внешнему источнику данных. Рассмотрим оба варианта.
Для ODBC необходимо вначале настроить системные DSN:
А для JDBC — путь к виртуальной машине Java (далее все картинки кликабельны):
Далее в SMP необходимо создать соединения для Caché SQL Gateway нужного типа (JDBC или ODBC), от которого будут зависеть те или иные настройки. Здесь же можно проверить вновь созданное соединение:
Затем, воспользовавшись Мастером Связывания таблиц или процедур, следует создать необходимые виртуальные таблицы или ХП:
Мастер запросит для каждой таблицы её новое имя, новые имена для каждого из полей, первичный ключ и т.д. В большинстве случаев вы можете все имена оставить по умолчанию, но бывают ситуации, когда некоторые из этих идентификаторов являются зарезервированными словами СУБД Caché.
Пример сгенерированного виртуального класса для внешней таблицы job_titles:
Class dbo.jobtitles Extends %Library.Persistent [ ClassType = persistent, Not ProcedureBlock, SqlRowIdPrivate, SqlTableName = job_titles, StorageStrategy = GSQLStorage ]
{
Parameter CONNECTION = "ems,NOCREATE";
Parameter EXTDBNAME = "Microsoft SQL Server";
Parameter EXTERNALTABLENAME = "dbo.job_titles";
Property INTERETHNICVALUE As %String(EXTERNALSQLNAME = "INTERETHNIC_VALUE", EXTERNALSQLTYPE = 12, MAXLEN = 50) [ Required, SqlColumnNumber = 5, SqlFieldName = INTERETHNIC_VALUE ];
Property INTERNATIONALVALUE As %String(EXTERNALSQLNAME = "INTERNATIONAL_VALUE", EXTERNALSQLTYPE = 12, MAXLEN = 50) [ Required, SqlColumnNumber = 4, SqlFieldName = INTERNATIONAL_VALUE ];
Property NATIONALVALUE As %String(EXTERNALSQLNAME = "NATIONAL_VALUE", EXTERNALSQLTYPE = 12, MAXLEN = 50) [ Required, SqlColumnNumber = 6, SqlFieldName = NATIONAL_VALUE ];
Property id As %Integer(EXTERNALSQLNAME = "id", EXTERNALSQLTYPE = 4) [ Required, SqlColumnNumber = 2, SqlFieldName = id ];
Property priority As %Integer(EXTERNALSQLNAME = "priority", EXTERNALSQLTYPE = 4) [ Required, SqlColumnNumber = 3, SqlFieldName = priority ];
Index MainIndex On id [ IdKey, PrimaryKey ];
}
Примечание: при необходимости вы можете код сгенерированного класса дополнить некоторыми плюшками от Caché — методами класса и/или объекта, вычисляемыми полями, суперклассами, ХП, — которые отсутствуют в изначальных таблицах.
Но не следует забывать, что при повторной генерации связанных таблиц/ХП весь ваш дополнительный код потеряется.
Всё, теперь вы можете, подключившись к Caché с помощью любого ODBC/JDBC клиента (см. одну из предыдущих статей), увидеть все таблицы и процедуры, как внутренние, так и внешние; причём разницу между ними определить сразу сложно.
Ну, и конечно, можно вставлять/удалять/изменять данные, используя родной SQL-синтаксис тех или иных СУБД.
Механизм Caché SQL Gateway может также использоваться и для встроенной бизнес-аналитики DeepSee, но это уже другая тема.
Альтернативный пример использования
Однажды данная технология помогла моим коллегам, работающим с другими СУБД (не Caché). Поступили к нам на работу два новых сотрудника и им необходим был доступ к нескольким разным БД на разных серверах.
Поскольку бумага на открытие для них доступа к нужным серверам "блуждала" по кабинетам, а сроки поджимали, я предложил им с согласия своего руководства временный вариант с Caché: благо у них использовался какой-то фреймворк типа Hibernate, а у меня был доступ к нужным серверам. Создал в Caché область, добавил нужные таблицы из нужных БД, дал на них необходимые права.
Дальше они подключались через Caché, выступающей здесь как прокси-СУБД, к одной виртуальной БД и работали таким образом со своими таблицами из разрозненных БД. Коллегам даже не пришлось ставить драйвера для своих СУБД.
Программный доступ к внешним данным
Помимо создания связанных таблиц, с внешними данными можно работать и программно:
Пример программного ODBC-доступа, используя системный DSN:
set db = ##class(%SQLGatewayConnection).%New()
set res = db.Connect("DSNName","username","password")
set rs = ##class(%ResultSet).%New("%DynamicQueryGW:SQLGW")
do rs.Prepare("SELECT * FROM users WHERE id = ?",,db)
do rs.Execute(46)
while rs.Next() {
for i = 1:1:rs.GetColumnCount() {
write rs.GetData(i)
if i '= rs.GetColumnCount() {
write ", "
} else {
write !
}
}
}
do db.Disconnect()
В данном случае уже не нужно создавать соединения для Caché SQL Gateway, поскольку "DSNName" — это имя DSN, определённое в самой OC.
Пример выше демонстрирует так называемый высокоуровневый доступ работы с ODBC с помощью классов %ResultSet и %DynamicQueryGW, но возможен и низкоуровневый доступ. В этом случае используются методы только класса %SQLGatewayConnection.
Далее приведён пример запроса к внешней таблице с использованием низкоуровневого доступа. Данный запрос выбирает все поля из таблицы INFO внешнего ODBC-источника (с именем DSNName), для которых значение поля Age=21, а значение поля Name начинается с буквы "D".
set db=##class(%SQLGatewayConnection).%New()
// устанавливаем соединение
do db.Connect("DSNName","sa","pwd")
// создание новой команды
set sc=db.AllocateStatement(.Stat)
// подготовка запроса
set sc=db.Prepare(Stat,"SELECT * FROM INFO WHERE Age=? AND Name LIKE ?")
// подготовка параметров
set sc=db.BindParameters(Stat,$listbuild(1,1),$listbuild(4,12),$listbuild(4,50),$listbuild(0,0),$listbuild(4,50))
set sc=db.SetParameter(Stat,$listbuild(21),1)
set sc=db.SetParameter(Stat,$listbuild("D%"),2)
// выполнение запроса
set sc=db.Execute(Stat)
for {
quit:'db.Fetch(Stat)
set sc=db.GetOneRow(Stat,.Row)
for j=1:1:$listlength(Row) write $listget(Row,j)_" "
write !
}
// удаление команды
set sc=db.DropStatement(Stat)
// разрыв соединения
set sc=db.Disconnect()
Итак, рассмотрим этот пример подробнее.
• Для соединения с ODBC-источником данных (DSN) используется метод Connect(DSN,User,Password):
set db=##class(%SQLGatewayConnection).%New()
// устанавливаем соединение
do db.Connect("DSNName","sa","pwd")
• Для разрыва соединения используется метод Disconnect():
set sc=db.Disconnect()
• Прежде, чем выполнять запрос, необходимо сначала создать команду Statement. Для создания команды используется метод AllocateStatement(), аргумент Handle методу AllocateStatement() передаётся по ссылке (перед именем аргумента нужно поставить точку "."):
set sc=db.AllocateStatement(.Stat)
• Для удаления команды используется метод DropStatement():
set sc=db.DropStatement(Stat)
• Перед выполнением запроса его необходимо "подготовить", для чего используется метод Prepare(Stat, sql). В качестве аргументов методу передаются созданная команда и строка sql-запроса. Возможно выполнение запросов с параметрами, тогда в строке sql на месте параметров ставятся знаки "?":
// подготовка запроса
set sc=db.Prepare(Stat,"SELECT * FROM INFO WHERE Age=? AND Name LIKE ?")
• Если передаётся sql-запрос с параметрами, то эти параметры необходимо подготовить и присвоить им определённые значения. Для подготовки параметров используется метод BindParameters(). Аргументы метода BindParameters():
- созданная команда;
- типы параметров: 1 – in (входной), 2-in/out (входной/выходной), 4-out (выходной, ...);
- cписок типов данных ODBC (например: 4-INTEGER, 9-DATE, 12-VARCHAR, 8-DOUBLE, ...);
- размеры буферов в байтах;
- число знаков после точки (только для Decimal и Float);
- список длин типов данных в байтах.
• Все аргументы метода BindParameters(), начиная со второго, имеют тип %List и должны передаваться в качестве аргументов функции $listbuild(). Если в запросе несколько параметров, то в качестве аргументов $listbuild() передаются значения через запятую, соответствующие каждому параметру в порядке их следования в строке запроса, например:
set sc=db.BindParameters(Stat,$listbuild(1,1),$listbuild(4,12),$listbuild(4,50),$listbuild(0,0),$listbuild(4,50))
или
#include %occODBC ; не забудьте подключить необходимые файлы с макросами
set sc=db.BindParameters(
Stat,
$listbuild($$$SQLPARAMINPUT,$$$SQLPARAMINPUT),
$listbuild($$$ODBCTYPEinteger,$$$ODBCTYPEvarchar),
$listbuild(4,50),
$listbuild(0,0),
$listbuild(4,50)
)
Примечание: в представленном изначально коде используются числовые идентификаторы типов параметров. В реальном коде лучше использовать макросы из файлов %occODBC.inc или %msql.inc, например:
- $$$GetOdbcTypeNumber("INTEGER") или $$$ODBCTYPEinteger вернут 4;
- $$$SQLPARAMINPUT вернёт 1 (входной);
- $$$SQLPARAMINPUTOUTPUT вернёт 2 (входной/выходной);
- $$$SQLPARAMOUTPUT вернёт 4 (выходной);
- и т.д.
Полный список макросов можно найти в соответствующих файлах, используя, например, Caché Studio.
• Присваивание значения параметру осуществляется с использованием метода SetParameter(Stat, $listbuild(val), Numb). В качестве аргументов методу SetParameter() передаются:
- созданная команда;
- значение параметра как аргумент функции $listbuild();
- порядковый номер параметра в строке запроса.
• Метод SetParameter() вызывается отдельно для каждого переданного параметра:
set sc=db.SetParameter(Stat,$listbuild(21),1)
set sc=db.SetParameter(Stat,$listbuild("D%"),2)
• Для выполнения запроса используется метод Execute(Stat), которому в качестве аргумента передаётся созданная команда:
set sc=db.Execute(Stat)
Примечание: дополнительные примеры по программному доступу к внешним данным, используя JDBC/ODBC, можно найти в исходном коде классов %UnitTest.JDBCSQL и %UnitTest.ODBCSQL соответственно.
Автор: servitRM