Стивен Ферстайн

Новые типы данных, новые возможности
(New Datatypes, New Possibilities, by Steven Feuerstein)

Источник: сайт PinnaclePublishing, Oracle Professional, mart 2002,
http://www.pinnaclepublishing.com/op/
OPMag.nsf/WebIndexByIssue/C988BC42837E928B85256B6A0006981B?
opendocument&login

Oracle9i предоставляет набор новых типов данных, которые значительно повышают мощность и эффективность интуитивного программирования на языке PL/SQL. В этой статье рассматривается большинство этих типов и некоторые примеры их использования. Новые типы данных Oracle9i перечислены в Таблице1.

Таблица 1. Новые типы данных Oracle9i.

Название

Описание

TIMESTAMP

Разновидность типа DATE, которая полезна для хранения особо точных значений даты и времени с точностью до мельчайших долей секунды.

TIMESTAMP WITH TIMEZONE

Разновидность типа TIMESTAMP, включающая мощную логику поддержки временных зон (в Oracle предыдущих версий реализация этой возможности была очень слабой).

TIMESTAMP WITH LOCAL TIMEZONE

Разновидность типа TIMESTAMP с автоматическим подключением и отключением использования локальной временной зоны.

INTERVAL YEAR TO MONTH

Показывает разность по времени между двумя датами (типа date или timestamp), где значимы только год и месяц.

INTERVAL DAY TO SECOND

Показывает точную разность между двумя датами (типа date или timestamp) с точностью до секунды или долей секунды.

SYS.XMLTYPE

Используется для хранения, управления и запроса XML-документов непосредственно из базы данных.

SYS.URITYPE

Корень в иерархии объектных типов, который может использоваться для хранения URI (Universal Resource Identifiers – универсальных идентификаторов ресурсов) внешних Web-страниц и файлов, а также для обращения к данным, хранящимся в базе данных.

SYS.ANYTYPE

Общий или "Любой" тип данных, который содержит описание любого SQL-типа (скалярные типы, вложенные таблицы, объектные типы и т.д.).

SYS.ANYDATA

Экземпляр заданного типа. Содержит данные вместе с описанием типа; хранится в DB.

SYS.ANYDATASET

Описание заданного типа плюс набор экземпляров этого типа; хранится в DB.

Работа с временными отметками (timestamps)

Всем нравится тип данных DATE, однако давайте приглядимся к нему: в нем есть не все, что требуется от типа данных “временная отметка”. А именно, тип данных DATE:

*   Поддерживает временные отметки только с точностью до секунды. Видимо, когда база данных Oracle была спроектирована и впервые выпущена (в начале 1980-х) этого было вполне достаточно. А сейчас настала "Эпоха Интернета", когда и доли секунд кажутся мяуканьем котят.

*   Совершенно не позволяет манипулировать временной зоной. Функция NEW_TIME создает впечатление работы с различными временными зонами. Однако, она является только полумерой.

Oracle возместил этот дефицит в Oracle9i, создав новый тип данных TIMESTAMP. Для TIMESTAMP можно задать точность (до 9 цифр) долей секунды. Кроме того, можно воспользоваться преимуществами развитого встроенного распознавания временных зон, манипулирования ими и выполнения над ними арифметических операций. Рассмотрим несколько примеров.

В этом примере объявлена переменная типа TIMESTAMP с точностью до тысячной доли секунды:

DECLARE 

test_endpoint TIMESTAMP(3);

BEGIN

test_endpoint :=

'1999-06-22 ' ||

'07:48:53.275';

Ей присваивается значение путем неявного преобразования типов. Код почти такой же, какой можно написать для присваивания значения переменной типа DATE, исключив из него дробную часть секунды (275/1000).

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

Oracle предоставляет множество новых встроенных функций для получения и преобразования моментов времени, которые демонстрируются в следующем скрипте:

DECLARE
-- Получение текущего момента времени
-- с точностью до 4 знаков
right_now TIMESTAMP (4) := 
CURRENT_TIMESTAMP;
-- Получение текущего момента времени
-- с сохранением информации о временной зоне.
over_there TIMESTAMP (0) 
WITH TIME ZONE:= 
CURRENT_TIMESTAMP;
-- Использование LOCAL TIME ZONE
-- при получении момента времени
right_here TIMESTAMP (2) 
WITH LOCAL TIME ZONE:= 
CURRENT_TIMESTAMP; 
BEGIN
-- Вывод результатов
DBMS_OUTPUT.put_line (
SYSTIMESTAMP);
DBMS_OUTPUT.put_line (
CURRENT_TIMESTAMP);
DBMS_OUTPUT.put_line (
right_now);
DBMS_OUTPUT.put_line (
over_there);
DBMS_OUTPUT.put_line (
right_here); 
END;
А вот полученные значения:
SYSTIMESTAMP
05-FEB-02 12.57.44.000000000 PM -08:00
CURRENT_TIMESTAMP
05-FEB-02 12.57.44.000000107 PM -08:00
TIMESTAMP (4)
05-FEB-02 12.57.44.0000 PM
TIMESTAMP (0) WITH TIME ZONE
05-FEB-02 12.57.44 PM -08:00
TIMESTAMP (2) WITH TIME ZONE
05-FEB-02 12.59.59.00 PM

Использование временных зон может значительно усложнить работу, к тому же документация, описывающая эту возможность Oracle, все еще дорабатывается. Необходимо убедиться, что временная зона базы данных установлена, ибо по умолчанию она не устанавливается. Для этого надо выполнить предложение следующего типа (после чего перезапустить базу данных):

ALTER DATABASE SET time_zone = 'US/Central'

Временную зону можно установить и для сессии, например:

ALTER SESSION SET time_zone = 'US/Central'

Для временной зоны можно установить формат, используемый по умолчанию, для ее преобразования и отображения:

ALTER SESSION
SET NLS_TIMESTAMP_TZ_FORMAT =
'DD-Mon-YYYY HH24:MI:SSXFF TZR TZD';

Просмотреть полный (и значительно расширенный) список распознаваемых Oracle временных зон можно с помощью следующего запроса:

SELECT DISTINCT tzname 
FROM v$timezone_names;

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

Листинг 1. Установка и просмотр информации о временной зоне.

CREATE OR REPLACE PROCEDURE 
tz_set_and_show (tz_in IN VARCHAR2 := null)
IS
BEGIN
IF tz_in IS NOT NULL
THEN
EXECUTE IMMEDIATE 'alter session set time_zone = '''
|| tz_in
|| '''';
END IF;
DBMS_OUTPUT.put_line ( 'SESSIONTIMEZONE = '
|| SESSIONTIMEZONE);
DBMS_OUTPUT.put_line ( 'CURRENT_TIMESTAMP = '
|| CURRENT_TIMESTAMP);
DBMS_OUTPUT.put_line ( 'LOCALTIMESTAMP = '
|| LOCALTIMESTAMP);
DBMS_OUTPUT.put_line (
'SYS_EXTRACT_UTC (LOCALTIMESTAMP) = '
|| sqlexpr ('SYS_EXTRACT_UTC (LOCALTIMESTAMP)')
);
END;
/

Работа с интервалами

Переменные типа INTERVAL используются для хранения и манипулирования промежутками между двумя различными датами или моментами времени. Раньше эти данные интерпретировались как числа, однако способ интерпретации этих чисел мог существенно увеличить вероятность появления ошибки и значительно снизить удобство чтения, а значит, уменьшить управляемость кодом.

Теперь, благодаря переменным типа INTERVAL, можно написать легко понимаемый код, который естественным интуитивным образом позволяет манипулировать промежутками между моментами времени.

Приняв к сведению, что существует только две основные “системы исчисления” интервалов, которые могут потребоваться, можно объявлять и использовать INTERVAL типа YEAR TO DAY и DAY TO SECOND. Тип YEAR TO DAY позволяет хранить интервал лет и месяцев и манипулировать им. Ниже показан его синтаксис:

INTERVAL YEAR[(точность)] TO MONTH

Где точность может принимать значения от 0 до 4 и имеет значение по умолчанию 2. Точность для MONTH не задается.

Тип DAY TO SECOND позволяет получать интервал в днях, часах, минутах и секундах и манипулировать им. Для этого интервала можно установить два уровня точности:

INTERVAL DAY[(основная_точность)] 
TO 
SECOND[(точность_долей_секунды)]

Значения по умолчанию, соответственно, 2 и 6. При объявлении переменных такого типа могут использоваться только числовые целые значения, а переменные или именованные константы не допускаются.

Предположим, что имеется объектный тип person. Добавим в этот тип процедуру определения возраста. Для его вычисления не требуется очень большой точности. Достаточно числа лет и месяцев. Поэтому опишем эту функцию следующим образом:

MEMBER FUNCTION age RETURN INTERVAL YEAR TO MONTH
IS
retval INTERVAL YEAR TO MONTH;
BEGIN
RETURN (SYSDATE - SELF.dob) 
YEAR TO MONTH; 
END;

Заметьте, что арифметическое действие выполняется над датами: между сегодняшней датой и днем рождения (dob) текущего экземпляра объекта (SELF). Затем этот период возвращается как INTERVAL.

Oracle позволяет очень гибко описывать интервалы, как видно из следующего ниже набора операторов присваивания. В этом блоке переменной типа INTERVAL присваивается продолжительность 14 лет и семи месяцев моей работы с Oracle (я с августа 1987 г в течение пяти лет работал в Oracle Corporation!):

DECLARE
oracle_career 
INTERVAL YEAR(2) TO MONTH;
BEGIN
-- Пример числа типа INTERVAL
oracle_career := 
INTERVAL '14-7' YEAR TO MONTH;
-- Неявное преобразование из строки
oracle_career := '14-7'; 
 
   -- Присваивание года и месяцев отдельно
 
oracle_career := INTERVAL '14' YEAR; 
oracle_career := INTERVAL '7' MONTH;
END;

Работа с типом XML

Типы INTERVAL и TIMESTAMP очень интересны. А вот тип XMLType является частью существенного преобразования в базе данных и технологии Oracle. Как вы, вероятно, знаете (и, конечно, следите за серией Oracle Professional, состоящей из 5 частей по XML, где я являюсь соавтором Стива Менча - Steve Meunch - в технологиях Oracle), Oracle быстро перешел к Oracle8i, чтобы позволить программистам на PL/SQL и Java выполнять синтаксический разбор, манипулировать и хранить XML-документы.

Oracle9i сделал гигантский скачок вперед, который Oracle называет своей "XDB," т.е. XML DataBase, предоставив возможность использования “встроенного” типа данных XML SYS.XMLTYPE. Благодаря этому типу данных Oracle теперь позволяет выполнять SQL-операции над содержимым XML и XML-операции над содержимым SQL. Кроме того, можно применять стандартную функциональность XML, такую как XPath, напрямую над данными без необходимости преобразования в CLOB или другие типы данных.

Теперь можно, например, создать таблицу базы данных со столбцом типа SYS.XMLTYPE:

CREATE TABLE env_analysis (
company VARCHAR2(100),
site_visit_date DATE,
report SYS.XMLTYPE);

Тип данных XMLType является объектным типом, а это означает, что в нем содержится набор методов для манипулирования экземпляром объекта этого типа. Для вставки строки в таблицу env_analysis можно написать код, который показан на Листинге 2.

В основном статический метод CreateXML типа данных XMLType вызывается для преобразования строки в тип XML. К другим методам типа данных XMLType относятся:

*   existsNode: возвращает 1, если заданное выражение XPath возвращает какой-либо узел.

*   extract: применяет выражение XPath к данным XML, чтобы получить экземпляр XMLType, содержащий требуемый фрагмент.

*   isFragment: возвращает 1, если XMLtype содержит фрагмент, а не весь документ.

*   getCLOBval, getStringval, getNumberval: возвращают XML-документ или фрагмент в виде CLOB, строки или числа.

Листинг 2. Вставка в столбец типа XMLType.

INSERT INTO env_analysis VALUES (
'ACME SILVERPLATING',
TO_DATE (
'15-02-2001', 'DD-MM-YYYY'),
SYS.XMLTYPE.CREATEXML(
'<?xml version="1.0"?>
<report>
<site>1105 5th Street</site>
<substance>PCP</substance>
<level>1054</level>
</report>'));

С помощью этих методов можно выполнять более сложные операции над XMLTypes. В предложении, показанном на Листинге 3, синтаксис XPath используется для функции, возвращающей первые 30 символов имени подэкземпляра, анализируемого в отчете об окружении.

Листинг 3. Создание индекса, основанного на функции, которая является методом XMLType.

CREATE UNIQUE INDEX i_purchase_order_reference
ON env_analysis ea ( 
SUBSTR(
SYS.XMLTYPE.GETSTRINGVAL ( 
SYS.XMLTYPE.EXTRACT( 
ea.report, '/Report/Substance/text()')),1,30))

В Oracle9i Release 2 (сейчас проводится бета-тестирование) появятся новые существенные возможности управления XML-документами, включающие поддержку доступа к управляющим таблицам, поддержку директорий (позволит создать иерархии директорий и утилиты для поиска и управления ими), и поддержку доступа через FTP и WEBDav (Web-based Distributed Authoring and Versioning, расширения HTTP для совместного редактирования и управления файлами удаленного Web-сервера).

Замечание для программистов на PL/SQL: если необходимо выбрать (или установить приоритет) между изучением Java и изучением XML, то я рекомендую как можно скорее перейти к XML. Эта функциональность будет все больше развиваться в приложениях, разрабатываемых на Oracle.

Работа с типом данных "любой"

Завершим обзор некоторых новых типов данных Oracle9i рассмотрением типа "любой". Звучит слишком обобщенно, не правда ли? Это происходит, потому что так и есть. В языке PL/SQL Oracle9i наконец-то появились некоторые ощутимые возможности “отражения”: способности обращаться к элементам исполняемого модуля, чтобы получить как значения данных, так и их структуру. Когда это может потребоваться? При создании программ общего назначения для запуска и использования во множестве приложений и систем, которые требуют либо немного изменений, либо не изменяеются вообще.

Многим, а на самом деле большинству разработчиков никогда не потребуется эта возможность, однако все же необходимо отдавать себе отчет в том, что она существует. В этой статье представлен беглый обзор типа “любой”. Более подробно его функциональность будет рассматриваться в следующей статье.

Во-первых, Oracle предоставляет новый встроенный пакет DBMS_TYPES, в котором содержатся именованные константы для всех различных SQL-типов, поддерживаемых в базе данных (и которые доступны через тип данных “любой”). На Листинге 4 показана спецификация пакета DBMS_TYPES; сам пакет содержится в поставляемом Oracle файле Rdbms/Admin/dbmsany.sql.

Листинг 4. Спецификация пакета DBMS_TYPES.

CREATE OR REPLACE PACKAGE DBMS_TYPES 
AS
TYPECODE_DATE PLS_INTEGER := 12;
TYPECODE_NUMBER PLS_INTEGER := 2;
TYPECODE_RAW PLS_INTEGER := 95;
TYPECODE_CHAR PLS_INTEGER := 96;
TYPECODE_VARCHAR2 PLS_INTEGER := 9;
TYPECODE_VARCHAR PLS_INTEGER := 1;
TYPECODE_MLSLABEL PLS_INTEGER := 105;
TYPECODE_BLOB PLS_INTEGER := 113;
TYPECODE_BFILE PLS_INTEGER := 114;
TYPECODE_CLOB PLS_INTEGER := 112;
TYPECODE_CFILE PLS_INTEGER := 115;
TYPECODE_TIMESTAMP PLS_INTEGER := 187;
TYPECODE_TIMESTAMP_TZ PLS_INTEGER := 188;
TYPECODE_TIMESTAMP_LTZ PLS_INTEGER := 232;
TYPECODE_INTERVAL_YM PLS_INTEGER := 189;
TYPECODE_INTERVAL_DS PLS_INTEGER := 190;
TYPECODE_REF PLS_INTEGER := 110;
TYPECODE_OBJECT PLS_INTEGER := 108;
/* COLLECTION TYPE */
TYPECODE_VARRAY PLS_INTEGER := 247; 
/* COLLECTION TYPE */
TYPECODE_TABLE PLS_INTEGER := 248; 
TYPECODE_NAMEDCOLLECTION PLS_INTEGER := 122; 
/* OPAQUE TYPE */
TYPECODE_OPAQUE PLS_INTEGER := 58; 
SUCCESS PLS_INTEGER := 0;
NO_DATA PLS_INTEGER := 100;
/* Exceptions */
invalid_parameters EXCEPTION;
PRAGMA EXCEPTION_INIT(invalid_parameters, -22369);
incorrect_usage EXCEPTION;
PRAGMA EXCEPTION_INIT(incorrect_usage, -22370);
type_mismatch EXCEPTION;
PRAGMA EXCEPTION_INIT(type_mismatch, -22626);
END dbms_types;
/

Для получения структуры данных необходимо создать ссылку на одну или несколько из этих констант.

Итак, рассмотрим, какие чудеса можно творить с этим типом. Предположим, что необходимо создать структуру данных, которая содержит разнородные или различающиеся по виду данные. Такая задача может возникнуть при использовании механизма Advanced Queuing (Продвинутое Управление Очередями). Вместо того чтобы требовать от каждого очередного сообщения принадлежности конкретному объектному типу, можно сделать так, чтобы они были различных типов.

Теперь можно создать "общую таблицу", в которой виртуально будут содержаться любые типы данных (число, строка, объектный тип и т.д.). Далее можно...

Сначала создадим объектный тип для домашних животных pet:

CREATE TYPE pet_t IS OBJECT (
tag_no INTEGER,
name VARCHAR2 (60),
breed VARCHAR2(100);
/
Затем общую таблицу:
CREATE TABLE wild_side ( 
id number, 
data SYS.ANYDATA);

Каждая строка этой таблицы содержит идентификационный номер животного и какую-нибудь информацию о нем, что можно легко увидеть в следующем блоке для вставки в эту таблицу:

DECLARE
my_bird pet_t := 
pet_t (5555, 
'Меркьюри', 
'Африканский Серый Попугай'); 
BEGIN
INSERT INTO wild_side
VALUES (1, 
SYS.ANYDATA.CONVERTNUMBER (5));
INSERT INTO wild_side
VALUES (2, 
SYS.ANYDATA.CONVERTOBJECT 
(my_bird));
END;

В таблицу добавляются две строки, одна из которых содержит число, а другая - экземпляр объекта pet. Вставка выполнена с помощью вызова двух методов Convert, связанных с объектным типом AnyData (также описанным в файле dbmsany.sql).

Этот пример показывает, как поместить разнотипные данные в столбец AnyData. Это довольно интересно, однако еще более впечатляет возможность запрашивать строки таких данных и затем разбираться, какой тип данных находится в столбце.

На Листинге 5 можно найти спецификацию пакета, в котором содержится функция, извлекающая из общей таблицы только те строки, которые: 1) содержат числа; и 2) содержат числа, которые удовлетворяют булеву выражению (по сути, условие WHERE).

На Листинге 6 показано тело этой функции с пронумерованными строками. Сначала рассмотрим, как она может использоваться. А затем перейдем к более интересующей нас части - коду.

Листинг 5. Спецификация пакета, содержащего функцию, которая работает с типом "Любой".

CREATE OR REPLACE PACKAGE anynums_pkg
IS
TYPE numbers_t IS TABLE OF NUMBER;
FUNCTION getvals (
tab_in IN VARCHAR2,
anydata_col_in IN VARCHAR2,
num_satisfies_in IN VARCHAR2 := NULL
)
RETURN numbers_t;
END anynums_pkg;
/

Листинг 6. Тело пакета для функции "Any".

1 CREATE OR REPLACE PACKAGE BODY anynums_pkg
2 IS
3 FUNCTION getvals (
4 tab_in IN VARCHAR2,
5 anydata_col_in IN VARCHAR2,
6 num_satisfies_in IN VARCHAR2 := NULL
7 )
8 RETURN numbers_t
9 IS
10 retval numbers_t := numbers_t ();
11 l_query VARCHAR2 (1000)
12 := 'SELECT '
13 || anydata_col_in
14 || ' FROM '
15 || tab_in;
16 l_type SYS.ANYTYPE;
17 l_typecode PLS_INTEGER;
18 l_value NUMBER;
19 l_dummy PLS_INTEGER;
20 l_filter VARCHAR2 (32767);
21 l_include BOOLEAN;
22 BEGIN
23 FOR rec IN (SELECT DATA
24 FROM wild_side)
25 LOOP
26 l_typecode := rec.DATA.gettype (l_type /* OUT */);
27
28 IF l_typecode = dbms_types.typecode_number
29 THEN
30 l_dummy := rec.DATA.getnumber (l_value /* OUT */);
31 l_include := num_satisfies_in IS NULL;
32
33 IF NOT l_include
34 THEN
35 l_filter :=
36 'DECLARE l_bool BOOLEAN; BEGIN l_bool := :invalue '
37 || num_satisfies_in
38 || '; IF l_bool THEN :intval := 1; ELSE :intval := 0; END IF; END;';
39 EXECUTE IMMEDIATE l_filter USING IN l_value, OUT l_dummy;
40 l_include := l_dummy = 1;
41 END IF;
42
43 IF l_include
44 THEN
45 retval.EXTEND;
46 retval (retval.LAST) := l_value;
47 END IF;
48 END IF;
49 END LOOP;
50
51 RETURN retval;
52 EXCEPTION
53 WHEN OTHERS
54 THEN
55 pl (SQLERRM);
56 pl (l_filter);
57 RETURN NULL;
58 END;
59* END anynums_pkg;

Вот пример использования этой функции:

SQL> l
1 DECLARE
2 mynums anynums_pkg.numbers_t;
 
3 BEGIN
 
4 mynums := anynums_pkg.getvals (
5 'wild_side', 'data');
6 
7 mynums := anynums_pkg.getvals (
8 'wild_side', 'data', '> 100');
9 END;

В строке 2 объявляется локальная вложенная таблица для хранения полученных по запросу данных. В строках 4-5 вызывается функция getVals, которой передаются имя таблицы "wild_side" и имя столбца типа AnyData, "data". Она возвращает значения каждой строки, в которой столбец AnyData фактически содержит число, пропуская все остальное. В строках 7-8 снова запрашиваются числовые значения из wild_side.data, однако в этот раз указывается, что необходимо возвратить только те данные, значения которых больше 100.

Теперь рассмотрим Листинг 6 и логику, которая позволяет выполнять любые обобщенные запросы (см. Таблицу 2). Чтобы достичь гибкости, требуемой в наших примерах, необходимо использовать возможности динамического SQL и собственных методов типа данных. Каждое числовое значение должно вычисляться динамически, чтобы увидеть, соответствует ли оно фильтру, представленному в виде строки; фактически для этого динамически создается PL/SQL-блок. Если значение соответствует фильтру, то оно помещается в результирующую таблицу.

Таблица 2. Описание Листинга 6.

Строки

Описание

11-15

Создание основного запроса для извлечения из таблицы всех столбцов типа AnyData.

26

Вызов метода AnyData.gettype для запроса этого "трудного для понимания" типа данных, чтобы определить, соответствует ли тип условию. Вот забавная часть!

28

Сравнение извлеченного типа с константой DBMS_TYPES. Это число? Если да, то вычисление продолжается.

30

Известно, что это число, но каково же его значение? Для его получения используется метод AnyData.getnumber.

35-40

Процедуре передан фильтр, поэтому необходимо узнать, соответствует ли это числовое значение фильтру. Например, если передается фильтр "> 100", то необходимо определить, превышает ли значение 100. Как это делается? Динамически формируется анонимный PL/SQL-блок, который выполняет связывание переменной логического типа с этим выражением. Динамический блок, который формируется и выполняется для фрагмента "> 100", показан на Листинге 7.

43-47

Если значение соответствует фильтру (или фильтр имеет значение NULL), то значение добавляется в структурную таблицу.

Листинг 7. Динамический PL/SQL-блок для вычисления фильтра.

DECLARE
l_bool BOOLEAN;
BEGIN
l_bool := :invalue > 100;
IF l_bool
THEN
:intval := 1;
ELSE
:intval := 0;
END IF;
END;

Множество новых возможностей

Вообще-то мне уже несколько лет нравится программировать на PL/SQL. А с Oracle9i возможности для действительно увлекательного и захватывающего программирования значительно увеличились. Теперь все будут довольны PL/SQL!

Планируете перейти на XML? Теперь это возможно благодаря встроенной напрямую в базу данных XML-функциональности. Вы разочарованы ограничениями типа данных DATE? Измените типы на TIMESTAMP и INTERVAL. Хотите произвести впечатление на друзей, семью и менеджеров выполнением магических трюков с AnyData и AnyDataSet? Найдите достаточно времени для обыгрывания объектных типов, потому что документация не просто минимальна, она еще и обманчива.