Страница 8 из 9 Создание утилиты просмотра OLAP-кубов Выясним, как объекты ADO MD можно использовать в Delphi. В этих целях создадим приложение, с помощью которого пользователь сможет: - просматривать метаданные многомерной базы данных в виде иерархической структуры;
- копировать имена объектов многомерной базы данных и ключевые слова MDX из списка, заранее заданного в редактор MDX-запросов;
- выполнять MDX-запрос и копировать результаты в набор данных (компонент TClientDataSet) с целью представления их в компоненте TDBGrid.
Для этого создадим новый проект и поместим его главную форму будущего приложения компонента TToolBar с несколькими кнопками, TTreeView, TListBox, TDBGrid, TClientDataSet, TDataSource и TMemo. Затем установим значение свойства DataSource компонента DBGrid1 равным DataSource1, а значение свойства DataSet компонента DataSource1 равным ClientDataSet1. Компонент ListBox1 следует заполнить ключевыми словами MDX, такими как CHILDREN, MEMBERS, DESCENDANTS , и др. Далее следует сослаться в нашем приложении на библиотеку типов ADO MD, содержащуюся в файле MSADOMD.DLL, так как ADO MD не поддерживается в Delphi 5 на уровне компонентов. Для этого следует выбрать пункт Project | Import Type Library из главного меню среды разработки, а затем выбрать Microsoft ActiveX Data Objects (Multi-dimensional) 1.0 Library из списка доступных библиотек типов. Обратите внимание на то, что если вы уже импортировали библиотеку типов ADOX и не переименовали класс Delphi для объекта ADOX Catalog, то класс Delphi TCatalog окажется уже определенным. В этом случае во избежание конфликта имен можно переименовать TCatalog в TADOMDCatalog. Желательно также убедиться, что опция Generate Component Wrapper не выбрана, так как нам нужно создать только *.pas-файл для доступа к объектам ADO MD. Далее можно нажать кнопку Create Unit, что приведет к созданию файла ADOMD_TLB.PAS, представляющего собой интерфейсный модуль к библиотеке типов ADO MD. Наконец, нам нужно включить ссылку на этот файл в предложение Uses, так же как и ссылку на модули ComObj и ADODB . Следующий шаг в нашем приложении — соединение с многомерной базой данных. Параметр ConnectionString, используемый для этой цели, должен ссылаться на OLE DB Provider for OLAP Services (стоит убедиться, что он действительно установлен), а также на имя компьютера и имя базы данных, например: DS := 'Provider=MSOLAP.1;Data Source=localhost; Initial Catalog=FoodMart'; Можно также соединиться с локальными кубами, созданными с помощью Microsoft Excel и сохраненными в файлах *.cub. В этом случае параметр Connection String может выглядеть так: DS := 'Provider=MSOLAP.1;Data Source=C:\Data\Cubes\NW1.cub'; Код, отвечающий за соединение с базой данных, можно поместить в обработчик события OnClick одной из кнопок. Этот код, а также процедура, заполняющая компонент TreeView1 именами кубов, представлены ниже: procedure TForm1.Button1Click(Sender: TObject); begin DS := 'Provider=MSOLAP.1;Data Source=localhost;'+ 'Initial Catalog=FoodMart'; FillTreeView(DS); end; procedure TForm1.FillTreeView(DataSource: WideString); var I : Integer; begin //Создадим новый объект Catalog Catalog1 := CoCatalog.Create; TreeView1.Items.Clear; RootNode := TreeView1.Items.Add(nil, 'Catalog'); //Соединимся с многомерной базой данных Catalog1._Set_ActiveConnection(OleVariant(DataSource)); //Последовательно получим имена всех кубов в базе данных for I := 0 to Catalog1.CubeDefs.Count-1 do begin CubeDef1 := Catalog1.CubeDefs[I] as CubeDef; CubeDefNode := TreeView1.Items.AddChild(RootNode, CubeDef1.Name); end; end; Здесь мы соединяемся с базой данных, создаем объект Catalog, просматриваем по очереди все элементы его коллекции CubeDefs и извлекаем имена кубов (они содержатся в свойстве Name объектов CubeDef ). Реальная обработка метаданных куба реализована в обработчике события OnMouseDown компонента TreeView1: procedure TForm1.TreeView1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); var HitTest : THitTests; CurrNode : TTreeNode; I : integer; NodeName : string; AddString : String; begin HitTest := TreeView1.GetHitTestInfoAt(X,Y); //Если пользователь щелкнул мышью на одной из ветвей TTreeView if (htOnItem in HitTest) then begin CurrNode := TreeView1.GetNodeAt(X, Y); //Если у ветви могут быть дочерние ветви, но они еще не добавлены //добавим их if ((CurrNode.Count=0) and (CurrNode.Level<4)) then begin case CurrNode.Level of //Эта ветвь представляет куб 1: begin CubeDef1 := Catalog1.CubeDefs.Get_Item(CurrNode.Text); //Получаем имена всех размерностей куба for I := 0 to CubeDef1.Dimensions.Count-1 do begin Dimension1 := CubeDef1.Dimensions[I] as Dimension; DimNode := TreeView1.Items.AddChild(CurrNode, Dimension1.Name); end; end; //Эта ветвь представляет размерность 2: begin CubeDef1 := Catalog1.CubeDefs.Get_Item(CurrNode.Parent.Text); Dimension1 := CubeDef1.Dimensions.Get_Item(CurrNode.Text); //Получаем имена всех уровней иерархии данной размерности for I := 0 to Dimension1.Hierarchies[0].Levels.Count-1 do begin Level1 := Dimension1.Hierarchies[0].Levels[i] as Level; LevelNode := TreeView1.Items.AddChild(CurrNode, Level1.Name); end; end; //Эта ветвь представляет уровень иерархии 3: begin CubeDef1 := Catalog1.CubeDefs.Get_Item(CurrNode.Parent.Parent.Text); Dimension1 := CubeDef1.Dimensions.Get_Item(CurrNode.Parent.Text); Level1 := Dimension1.Hierarchies[0].Levels.Get_Item(CurrNode.Text); //Получаем имена всех членов данного уровня иерархии for I := 0 to Level1.Members.Count-1 do begin Member1 := Level1.Members[I] as Member; MemberNode := TreeView1.Items.AddChild(CurrNode, Member1.Name); end; end; end; end else //Если данная ветвь уже имеет дочерние ветви (или их не должно быть), //скопируем имя объекта в редактор MDX-запросов begin //Если ветвь не корневая if Currnode.Level>0 then begin CurrNode := TreeView1.GetNodeAt(X, Y); NodeName := CurrNode.Text; //Копируем имя ветви, сформатированное в соответствии //с синтаксисом MDX, в редактор MDX-запросов if ((CurrNode.Level=1) or (CurrNode.Parent.Parent.Text='Measures')) then AddString:='['+NodeName +']' else AddString:='['+NodeName +'].'; Memo1.SetSelTextBuf(PChar(AddString)); end; end; end; end; procedure TForm1.ListBox1Click(Sender: TObject); var AddString:string; begin //Добавим ключевое слово MDX из списка в редактор MDX-запросов AddString := Listbox1.Items[Listbox1.ItemIndex]+' '; Memo1.SetSelTextBuf(PChar(AddString)); end; Здесь мы определяем, что именно представляет ветвь, на которой пользователь щелкнул мышью (куб, размерность, уровень иерархии, член уровня), используя ее свойство Level, а также выясняем, имеются ли уже у нее дочерние ветви. Если дочерние ветви отсутствуют (свойство Count данной ветви равно нулю), мы обращаемся к базе данных и создаем соответствующие дочерние ветви, используя свойство Name соответствующего объекта ADO MD. Если же из базы данных уже нечего загружать, мы копируем имя объекта, представленного данной ветвью, в компонент Memo1, в то место, где находится курсор. Код для копирования ключевых слов MDX в компонент Memo1 приведен в этом же фрагменте кода. Таким образом, мы получили инструмент для просмотра метаданных куба и создания текста MDX-запросов с помощью щелчков мыши на ветвях дерева объектов ADO MD на элементах списка ключевых слов. Следующий шаг в создании OLAP-клиента заключается в выполнении MDX-запроса, содержащегося в компоненте Memo1, и в заполнении компонента TClientDataSet его результатами. Эта функциональность реализована в процедуре CDSFill, приведенной ниже: procedure TForm1.Button2Click(Sender: TObject); begin CDSFill(DS); end; procedure TForm1.CDSFill(DataSource: WideString); var I,J : Integer; V : OleVariant; begin //Создадим новый объект CellSet CellSet1 := CoCellSet.Create; try //Выполним MDX-запрос, содержащийся в компоненте Memo1, //и откроем объект CellSet CellSet1.Open(Memo1.Text,DataSource); with ClientDataSet1 do begin Close; with FieldDefs do begin //Уничтожим все определения полей в ClientDataset Clear; //Добавим новые определения полей //Первое поле нужно для ранения имен строк with AddFieldDef do begin Name := 'Rows'; DataType := ftString; end; //Перебираем коллекцию Positions первой оси for I := 1 to CellSet1.Axes[0].Positions.Count do begin with AddFieldDef do begin //Значение поля исходной базы данных станет именем колонки Name :=CellSet1.Axes[0].Positions[I-1].Members[0].Caption+ //Имена колонок в наборах данных должны быть уникальны, поэтому //добавим уникальное число, содержащееся в свойстве Ordinal //объекта Position, к значению поля ' ('+IntToStr(CellSet1.Axes[0].Positions[I-1].Ordinal) +')'; DataType := ftFloat; end; end; end; //Создаем и открываем ClientDataSet CreateDataSet; Open; //Добавляем к нему записи for J:=1 to CellSet1.Axes[1].Positions.Count do begin //Добавляем запись Append; //Добавляем имя строки, используя коллекцию Position второй оси Fields[0].Value := CellSet1.Axes[1].Positions[J-1].Members[0].Caption; //Перебираем ячейки в строке, извлекая из них данные for I := 1 to CellSet1.Axes[0].Positions.Count do begin //Создаем массив координат ячеек V:=VarArrayCreate([0,1], varVariant); V[0] := I-1; V[1] := J-1; //Если соответствующая ячейка в CellSet не пуста, if CellSet1.Item[PSafeArray(TVarData(V).VArray)].FormattedValue <> '' then //Значение поля будет равно значению в ячейке Fields[I].Value := Cellset1.Item[PSafeArray(TVarData(V).VArray)].Value else //иначе поместим в поле нулевое значение ClientDataSet1.Fields[I].Value:=0; end; end; //Закрываем Cellset и высвобождаем ресурсы CellSet1.Close; CellSet1 := nil; end; except ShowMessage('Invalid MDX Query'); end; end; В данном фрагменте кода мы создаем объект CellSet и используем его метод Open. Если MDX-запрос корректен, будет создан пустой набор данных типа TClientDataset с именами полей, равными свойству Caption первых элементов коллекций Members, являющихся свойствами коллекции Positions первой оси (Axis[0]) . Обратите внимание на то, что имена полей в наборах данных должны быть уникальны. Однако в реальных многомерных базах данных свойство Caption членов коллекции Members таковым не является. Например, в иерархии Year/Month свойство Caption для членов коллекции Members January 1999 и January 2000 будет равно одному и тому же значению January. Существует много способов избежать дублирования имен полей, и в данном примере мы использовали самый простой — добавление уникального числа, содержащегося в свойстве Ordinal объекта Member . После того как имена полей компонента ClientDataset1 определены, выполняется цикл перебора строк объекта CellSet, и для каждого ряда мы устанавливаем значение первого поля равным свойству Caption первого элемента коллекции Members соответствующего члена коллекции Positions второй оси (Axis[1]), а затем помещаем значения из соответствующих объектов Cell (доступных с помощью коллекции Item объекта CellSet) в оставшиеся поля. В результате получается набор данных, заполненный двухмерным сечением куба и отображенный в компоненте DBGrid1. Итак, мы создали простейшее приложение для просмотра OLAP-кубов и выполнения MDX-запросов, используя ADO MD. Это лишь элементарный пример для иллюстрации возможностей ADO MD, который можно расширить, например путем добавления бизнес-графики или более «интеллектуального» генератора запросов. |