Пример применения SQL DSO

Наталия Елманова

В статье «Введение в OLAP, часть 11. Применение SQL DSO для создания серверных OLAP-кубов», которую вы сможете найти в нашем журнале, обсуждается использование  Decision Support Objects для решения таких задач, как обновление данных в уже существующих OLAP-кубах, создание и удаление многомерных баз данных и содержащихся в них объектов — кубов, коллективных измерений, источников данных.

Как следует из представленного в этой статье материала, с помощью SQL DSO можно создавать приложения, реализующие всю функциональность Analysis Manager, и даже обладающие большими возможностями.  Однако на практике DSO используют для создания средств управления OLAP-кубами, имеющих ограниченную функциональность по сравнению с Analysis Manager. В то время как администратор OLAP-сервера, как правило, создает, модифицирует, обновляет кубы и применяет для этой цели  Analysis Manager, конечный пользователь обычно только просматривает кубы и обновляет их содержимое.  Сам по себе просмотр OLAP-кубов не требует применения DSO (это можно сделать с помощью ADO или ADO MD), но при обновлении данных без DSO не обойтись.

В настоящей статье вашему вниманию предлагается один из примеров применения SQL DSO.  Это будет небольшая утилита, позволяющая конечным пользователям просматривать метаданные OLAP-кубов, свойства их измерений и уровней, выполнять простейшие MDX-запросы и, при необходимости, обновлять данные кубов, но не создавать кубы и не модифицировать их метаданные. При этом пользователь не обязан знать, какие из объектов многомерной базы данных являются коллективными или виртуальными.

Как и другие приложения подобного класса, наша утилита будет содержать простое дерево, отображающее иерархию объектов DSO — баз данных, кубов, измерений, уровней, а также компонент TMemo для отображения списка свойств, список ключевых слов MDX  и компонент TDBGrid для отображения результатов выполнения MDX-запросов. Кроме того, мы реализуем в ней возможность копировать имена ветвей дерева объектов DSO и ключевые слова MDX из списка в редактор MDX-запросов.

Для решения этой задачи создадим новый проект и разместим на главной форме следующие компоненты: TToolBar с несколькими кнопками, компоненты TEdit и ЕDBNavigator, компоненты TMainMenu, TTreeView, TListBox, TDBGrid, TADOConnection, TADODataSet, TDataSource, два компонента TMemo. Установим свойство  DataSource компонента DBGrid1 равным DataSource1,  свойство Connection компонента ADODataSet1 равным ADOConnection1, а свойство DataSet компонента DataSource1 равным ADODataSet1. В компонент ListBox1 поместим список наиболее распространенных ключевых слов языка MDX, таких как CHILDREN, AS, SET, MEMBERS, DESCENDANTS и др.

При работе с нашим приложением пользователь должен в первую очередь соединиться с OLAP-сервером, расположенным на компьютере, имя которого указано в компоненте Edit1:

 
 dsoServer:=CreateOleObject('DSO.Server');
 dsoServer.Connect(ServerNameEdit.Text);
 TreeView1.Items.Clear;
 RootNode := TreeView1.Items.Add(nil, dsoServer.Name);
 RootNode.ImageIndex:=9;
 . . .

Здесь и далее мы опускаем фрагменты кода,  несущественные с точки зрения применения DSO и реализующие исключительно пользовательский интерфейс приложения, а также подробное описание пунктов меню приложения. Все эти подробности вы сможете найти в исходном тексте приложения — он находится на этом же компакт-диске.

Отображение иерархии объектов DSO реализовано в обработчике событий OnMouseDown компонента TreeView1. Здесь с помощью свойства Level мы определяем, чему соответствует ветвь, по которой щелкнул мышью пользователь (базе данных, кубу, измерению, уровню), и получены ли уже ее дочерние ветви из многомерной базы данных. Если они еще не получены (то есть свойство Count  данной ветви равно нулю), мы обращаемся к базе данных, получаем сведения о дочерних объектах и создаем дочерние ветви, используя свойство Name соответствующего объекта  SQL DSO:

HitTest := TreeView1.GetHitTestInfoAt(X,Y);
 //проверяем, выбрана ли одна из ветвей
 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
  //ветвь представляет сервер
  0: begin
    for I:=1 to dsoServer.MDStores.Count do begin
      dsodb := dsoServer.MDStores.Item(I);
      MDStoreNode := TreeView1.Items.AddChild(RootNode, dsoDB.Name);
      MDStoreNode.ImageIndex:=1;
    end;
   end;
  //ветвь представляет базу данных
  1: begin
      SetDBParams;
      RefreshButton.Visible:=False;
      //получаем имена кубов базы данных
      for I := 1 to dsodb.Cubes.Count do
      begin
       dsoCube :=dsodb.Cubes.Item(i) ;
       CubeNode := TreeView1.Items.AddChild(CurrNode, dsoCube.Name);
       CubeNode.ImageIndex:=3;
      end;
     end;
  //ветвь представляет куб
  2: begin
      SetDBParams;
      SetCubeParams;
      for I := 1 to dsoCube.Dimensions.Count do
      begin
       dsoDim := dsoCube.Dimensions.Item(I);
       DimNode := TreeView1.Items.AddChild(CurrNode, dsoDim.Name);
       DimNode.ImageIndex:=4;
      end;
     end;
  //ветвь представляет измерение
  3: begin
      SetDBParams;
      SetCubeParams;
      dsoDim:=dsoCube.Dimensions.Item(CurrNode.Text);
      RefreshButton.Visible:=False;
      //получаем уровни иерархии измерения
      for I := 1 to dsoDim.Levels.Count do
      begin
       dsoLevel := dsoDim.Levels.Item(i);
       LevelNode := TreeView1.Items.AddChild(CurrNode, dsoLevel.Name);
       LevelNode.ImageIndex:=8;
       end;
     end;
    end;
   end
. . .

Если из базы данных уже больше нечего получить (дочерние ветви получены либо их нет), и мы работаем с MDX-запросами, копируем имя ветви в  компонент Memo1, начиная с текущей позиции курсора:

   //если дочерние ветви получены
   //и мы выполняем запросы,
   //копируем имя ветви в редактор MDX-запросов
    if Query1.Enabled then begin
       //если это не корневая ветвь
     if Currnode.Level>0 then
     begin
      SetDBParams;
      if Currnode.Level>1 then SetCubeParams;
      CurrNode:=TreeView1.GetNodeAt(X, Y);
      NodeName:=CurrNode.Text;
      //копируем в редактор MDX-запросов имя объекта в соответствии с    

      //синтаксисом MDX

      if (CurrNode.Level<3)
      then AddString:='['+NodeName +']'
      else      AddString:='['+NodeName +'].';
      Memo1.SetSelTextBuf(PChar(AddString));
     end;
    end;

Наконец, если нам нечего получить из базы данных, и мы занимаемся не выполнением запросов, а просмотром метаданных,  мы просто отобразим их в компоненте Memo2. Кроме того, в этом случае мы предоставим пользователю возможность обновить содержимое физических OLAP-кубов:

    //если дочерние ветви уже получены
    //и мы обновляем кубы и просматриваем метаданные,
    //отобразим свойство объекта
  if Action1.Enabled then begin
  case CurrNode.Level of
  //ветвь представляет сервер
  0: begin
       Memo2.Lines.Clear;
     end;
  //ветвь представляет базу данных
  1: begin
      SetDBParams;
      Memo2.Lines.Add('Data Sources:'+CHR(13)+CHR(10));
      for I:=1 to dsoDB.DataSources.Count do
      begin
       dsoDS:= dsoDB.DataSources.Item(i);
       Memo2.Lines.Add (dsoDS.ConnectionString + CHR(13)+CHR(10));
      end;
     end;
  //ветвь представляет куб
   2: begin
       SetDBParams;
       SetCubeParams;
       Memo2.Lines.Add('Measures: ');
       //отображаем список измерений и другие свойства куба
       for i:=1 to dsoCube.Measures.Count do
        Memo2.Lines.Add('           ' + dsoCube.Measures.Item(i).Name);
        Memo2.Lines.Add(CHR(13)+CHR(10));
        If dsoCube.IsVirtual then begin
         Memo2.Lines.Add ('This cube is virtual');
         RefreshButton.Enabled:=False;
         RefreshCube1.Enabled:=False;
        end else begin
        Memo2.Lines.Add('Source Table: '+dsoCube.SourceTable+CHR(13)+CHR(10));
        Memo2.Lines.Add('Source Table Filter: '+ dsoCube.SourceTableFilter+CHR(13)+CHR(10));
        Memo2.Lines.Add('Join Clause: '+ dsoCube.JoinClause+CHR(13)+CHR(10));
        Memo2.Lines.Add('From Clause: '+ dsoCube.FromClause+CHR(13)+CHR(10));
        RefreshButton.Enabled:=True;
       end;
      end;
   3: begin
       SetDBParams;
       SetCubeParams;
       SetDimParams;
       //отображаем список свойств измерения
       Memo2.Lines.Add('Source Table: '+ dsoDim.SourceTable+CHR(13)+CHR(10));
       Memo2.Lines.Add('Source Table Filter: '+ dsoDim.SourceTableFilter+CHR(13)+CHR(10));
       Memo2.Lines.Add('Join Clause: '+ dsoDim.JoinClause+CHR(13)+CHR(10));
       If dsoDim.IsShared then Memo2.Lines.Add('This dimension is shared'+CHR(13)+CHR(10));
      end;
   4: begin
       SetDBParams;
       SetCubeParams;
       SetDimParams;
      end;
     end;
    end;

SetDBParams, SetCubeParams и SetDimParams представляют собой процедуры, устанавливающие текущие имена базы данных, куба и измерения, когда пользователь выбирает ветвь дерева:

procedure TForm2.SetDBParams;
begin
 case CurrNode.Level of
  1: dsodb := dsoServer.MDStores.Item(WideString(CurrNode.Text));
  2: dsodb := dsoServer.MDStores.Item(WideString(CurrNode.Parent.Text));
  3: dsodb := dsoServer.MDStores.Item(WideString(CurrNode.Parent.Parent.Text));
  4: dsodb := dsoServer.MDStores.Item(WideString(CurrNode.Parent.Parent.Parent.Text));
 end;
 StatusBar1.Panels.Items[1].Text:='Database: '+dsodb.Name;
 StatusBar1.Panels.Items[2].Text:='';
 CurrentDBName:=dsodb.Name;
 Memo2.Lines.Clear;
end;
 
procedure TForm2.SetCubeParams;
begin
 case CurrNode.Level of
  2: dsoCube:=dsodb.Cubes.Item(CurrNode.Text);
  3: dsoCube:=dsodb.Cubes.Item(CurrNode.Parent.Text);
  4: dsoCube:=dsodb.Cubes.Item(CurrNode.Parent.Parent.Text);
 end;
 CurrentCubeName:=dsoCube.Name;
 RefreshButton.Visible:=True;
 StatusBar1.Panels.Items[2].Text:='Cube: '+dsoCube.Name;
 Refreshcube1.Enabled:=True;
 Memo2.Lines.Clear;
end;
 
procedure TForm2.SetDimParams;
begin
 case CurrNode.Level of
  3: dsoDim:=dsoCube.Dimensions.Item(CurrNode.Text);
  4: dsoDim:=dsoCube.Dimensions.Item(CurrNode.Parent.Text);
 end;
 // CurrentDimName:=dsoDim.Name;
 RefreshButton.Visible:=False;
 Refreshcube1.Enabled:=False;
 Memo2.Lines.Clear;
end;

Процедура обработки куба выглядит примерно так же, как это описано  в статье «Введение в OLAP, часть 11. Применение SQL DSO для создания серверных OLAP-кубов», поэтому мы не будем повторять этот фрагмент кода. 

Результат просмотра метаданных  с помощью созданного приложения  изображен на рис. 1.

В обработчике события OnClick компонента ListBox1 мы должны скопировать выбранное пользователем ключевое слово языка MDX в компонент Memo1:

AddString:=Listbox1.Items[Listbox1.ItemIndex]+' ';
Memo1.SetSelTextBuf(PChar(AddString));

Для выполнения  MDX-запроса следует соединиться с базой данных OLAP того же самого сервера с помощью  ADO, установить свойство  CommandText  компонента ADODataSet1 равным содержимому компонента Memo1 и выполнить его:

procedure TForm2.ExecuteButtonClick(Sender: TObject);
begin
 try
  ADOCOnnection1.Connected:=False;
  // ADO Connection String
  DS:='Provider=MSOLAP.2; Data Source='+
  ServerNameEdit.Text+'; Initial Catalog='+  CurrentDBName;
  ADOCOnnection1.ConnectionString:=DS;
  //Выполняем MDX-запрос
  ADODataSet1.CommandText:=Memo1.Text;
  ADOConnection1.Connected:=True;
 except
  ShowMessage('Invalid login, password, database or server name');
 end;
 try
  ADODataSet1.Open;
 except
  ShowMessage('Invalid MDX query');
 end;
end; 

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

Чтобы получить вместо строк '(MEMO)'  в компоненте DBGrid1 значения полей набора данных, получаемого в результате выполнения MDX-запроса, создадим обработчик события  OnDrawColumnCell :

DBGrid1.Canvas.FillRect(Rect);
DBGrid1.Canvas.TextOut(Rect.Left+2,Rect.Top+2, Column.Field.AsString);

Результат выполнения MDX-запроса с помощью созданной утилиты представлен на рис. 2.

Отметим, однако, что для выполнения MDX-запросов можно использовать не только ADO, но и ADO MD. Примеры применения ADO MD вы сможете найти в статьях «Введение в OLAP, часть 9» (КомпьютерПресс № 12’2001) и «Введение в расширения ADO» (КомпьютерПресс № 10’2000).

Таким образом, мы создали утилиту, позволяющую просматривать метаданные многомерных баз данных Microsoft SQL Server Analysis Services и обновлять содержимое кубов с помощью SQL DSO, а также выполнять MDX-запросы. И, как видите, это не слишком сложно.

КомпьютерПресс 2'2002

1999 1 2 3 4 5 6 7 8 9 10 11 12
2000 1 2 3 4 5 6 7 8 9 10 11 12
2001 1 2 3 4 5 6 7 8 9 10 11 12
2002 1 2 3 4 5 6 7 8 9 10 11 12
2003 1 2 3 4 5 6 7 8 9 10 11 12
2004 1 2 3 4 5 6 7 8 9 10 11 12
2005 1 2 3 4 5 6 7 8 9 10 11 12
2006 1 2 3 4 5 6 7 8 9 10 11 12
2007 1 2 3 4 5 6 7 8 9 10 11 12
2008 1 2 3 4 5 6 7 8 9 10 11 12
2009 1 2 3 4 5 6 7 8 9 10 11 12
2010 1 2 3 4 5 6 7 8 9 10 11 12
2011 1 2 3 4 5 6 7 8 9 10 11 12
2012 1 2 3 4 5 6 7 8 9 10 11 12
2013 1 2 3 4 5 6 7 8 9 10 11 12
Популярные статьи
КомпьютерПресс использует