作者: Bogdan Crivat,微软公司
时间:2005年3月
适用于:
微软 SQL Server 2005
SQL Server 数据挖掘(SQL Server Data Mining)
摘要:介绍SQL Server 2005数据挖掘的新API以及几种常用的开发场景。
版权
在这篇文章中所包含的信息代表了从发布日起微软对所讨论的问题的当前观点。因为微软必须对市场的变换做出响应,它不应该被理解为微软所必须承担的任务的一部分,微软也不能保证在发布日之后所提出的信息的精确性。
这个白皮书仅仅是为了信息的目的,微软对本文中的信息不做任何授权、表示、暗含或规定。
依从所有可适用的版权法是用户的责任。没有限制权利在版权之下,这个文档的部分不允许被再生产,存放或介绍入检索系统, 或被以任何形式传送或通过任何手段(电子, 机械, 影印, 记录, 或其他) 或为任何目的,没有微软的明确书面允许。
微软对于在这篇文章中所包含的主题拥有专利、专利申请、商标、版权或其他的一些知识产权。除了微软明确提供的一些书面的特许契约,这个文档的并不提供给您任何专利、商标、版权或其他知识产权的执照。
版权所有2005 Microsoft Corporation。
Microsoft 和Visual Studio在美国或其他国家都有注册商标或微软的商标。
在这里的实际的公司和产品的名字可能是他们各自的商标。
QUOTE:
目录
概述
与 Microsoft Analysis Services 2005通讯
数据定义语言(DDL) 和 数据挖掘扩展语言(DMX)
数据挖掘任务
通用API
Adomd.NET 作为通用API
OLE DB作为通用API
Analysis Management Objects – AMO
进度通知:使用跟踪对象
无服务器的数据挖掘:本地挖掘模型
扩展SQL Server 数据挖掘功能
使用Adomd.NET Server 扩展DMX
插件算法和内容查看器
应用场景建议
应用场景1:简单的数据挖掘预测
应用场景2:Web应用——使用服务器上现有模型的规则
应用场景3:为当前数据在服务器上创建和训练一个新的模型
小结
附录 1:Microsoft Analysis Services 2005服务器上的常用操作以及请求的协议格式
附录 2:在Ado.NET 和 C++中使用 OLE DB
非托管 C++
ADO.NET
概述
随着微软SQL Server 2005的诞生,统计技术和机器学习算法的综合产物,也就是众所周知的数据挖掘,被带入了一个新的阶段。在SQL Server 2005中,数据挖掘最重要的转变是改变了它的目标用户。除了作为一个科学的实验工具,面向有限的专业人士,如今SQL Server 数据挖掘已广泛存在,成为开发者捶手可得的工具,并且已经做好了在更广阔的领域中应用的准备。从电子数据表格到网络游戏,从点到点通讯系统到应用服务器,绝大多数应用都有这样一个共同点:它们不得不进行数据处理。在进行数据处理的时候,它们使用特定的标准API来访问数据。在SQL Server 2005数据挖掘的数据处理系统中,这些API也同样可以被智能的嵌入到应用中。
这篇文章带你思考将SQL Server数据挖掘嵌入到应用中的契机。文章重点关注可编程内容,即通过写代码来使用数据挖掘技术和增强服务器特性。我们将给出一系列可以被数据挖掘引擎执行的常用任务,并针对它们展示微软SQL Server数据挖掘体系结构的解决方案。之后,我们将列举一些客户端产品(Adomd.NET 和 OLE DB)的可编程API。之后的一个章节专注于Analysis Management Objects所管理的API。随后,我们将展示服务器创建新的存储过程以及添加新的算法插件和查看器等扩展功能.
数据挖掘应用也可以结合联机分析处理(OLAP)、Reporting Services或者Integration Services来创建。然而,这篇文章不介绍这些内容,而是严格通过代码来介绍数据挖掘功能和如何增强数据挖掘功能。
数据挖掘和联机分析处理都是微软分析服务的组件。本篇文章中介绍的客户端API同时适用于这两种组件。但是,本文的介绍仅针对数据挖掘。其中的场景、代码实例、元数据对象都是针对数据挖掘的。
与 Microsoft Analysis Services 2005通讯
让我们从Microsoft Analysis Services 2005(与数据挖掘服务器之间)的通讯协议开始。客户端通过Analysis Services 2005来执行这个通讯协议(比如使用Microsoft OLE DB Provider for Analysis Services 9.0 或者 Adomd.NET),并且这个协议也必须被其它客户端执行。
Microsoft Analysis Services 2005 使用SOAP作为最外层的通讯层。(更多关于SOAP的内容请访问这个网页。)SOAP为应用程序定义了一系列可以通过XML消息来调用的方法。 这些方法由Microsoft Analysis Services 发布,使用XML for Analysis或者 XMLA来定义。
XMLA规范是由一个超过20家研究商业智能的龙头企业(包括微软公司、Hyperion和SAS 学会)组成的组织提出的,它是一个标准的OLAP和数据挖掘技术接口。 更多关于XMLA的信息,请点击这里。
XMLA定义了两种发送给服务器的请求,以及服务器进行响应返回的信息的格式。请求的类型是“发现(Discover)”和“执行(Execute)”。“发现”用来从服务器获取信息和元数据。例如, “发现”可以用来获取服务器上一系列挖掘模型以及它们的属性(列描述、算法等等)。“执行”用来执行对服务器的命令,如创建一个新的目录或者挖掘模型、训练一个模型、或者执行一个查询。
数据定义语言(DDL) 和 数据挖掘扩展语言(DMX)
XMLA的“执行”命令可应用于多种任务。这里有几个例子:
· 创建(Create)命令:这些命令在服务器上创建一个新的元数据对象,它们包含被创建的对象的全部或部分属性,例如名称、数据绑定、挖掘模型和挖掘结构中的列、挖掘模型的数据挖掘算法等等。
· 修改(Alter)命令:这些命令修改已存在的服务器元数据对象的属性。
· 删除(Drop)命令:这些命令用来从服务器上去掉元数据对象。
· 处理(Process)命令:这些命令用来初始化那些基于当前绑定的训练数据集定义的元数据对象上的训练序列。
· 语句 (查询语句)。
当一个XMLA“执行”请求描述一个对象(如创建Create或者修改Alter语句)或者将一个对象定义为一个动作的目标(如处理Process或删除Drop)时,命令的内容由Microsoft Analysis Services 数据定义语言(Data Definition Language —— DDL)组成。DDL是Analysis Services 2005中元数据以及元数据操作的内部表示方法。 服务器上的对象存储在DDL中,商业智能化项目由若干DDL片段组成。关于DDL的更多细节,请查看SQL Server 2005 在线文档中的“分析服务脚本语言(Analysis Services scripting language)”。
另一方面,在数据挖掘任务中,当XMLA请求是一个语句的时候,XMLA “执行”请求使用一种查询语言——DMX(数据挖掘扩展Data Mining eXtensions语言)作为它的请求的内容。DMX语言在针对数据挖掘的OLE DB 规范中定义,在这里可以使用。
Microsoft Analysis Service 2005 也可以使用另一种查询语言来执行语句——MDX。MDX语言是为OLAP组件所设计的。所以,这里我们只关注DMX。
区分DDL和DMX是非常重要的:DDL是一种由Analysis Services 2005使用的类XML语言,用来描述、管理和指定元数据。DDL可以很方便的扩展到XMLA。
而DMX是一种为数据挖掘而设计出的类SQL语言。DMX与SQL非常类似,DMX包含这样的结构,它们允许创建和操作元数据对象(想想SQL中的CREATE TABLE 或者INSERT INTO语句,DMX中有与其等价的CREATE MINING MODEL 和INSERT INTO 语句)。然而,对于管理元数据对象的任务来说,DMX的灵活性比DDL要差。DMX语句无法扩展到XMLA;但是它们可以被固定的XMLA结构所包括。
文章结尾处的附录1提供更多Microsoft Analysis Services 2005协议部署的细节内容。
SOAP相对来说比较容易操作;大多数开发工具都提供针对创建、传输、接收SOAP包的帮助。但是,选择使用DDL请求还是DMX请求,并把它按照适当的格式封装到XMLA语句中就不是一件那么容易的事情了。所以开发者需要比仅仅使用XML流作为通讯方法更好的工具。这也就是为什么如今可编程API存在的原因。这些API将XMLA请求的相关操作作业打包,分析XMLA的响应信息,并且向开发者提供一个更具逻辑性的通讯视图,用来操作内部细节。也有一小部分API可以被Microsoft SQL Server 数据挖掘应用。选择哪个API取决于客户端执行的请求的类型和客户端的开发环境。
下一节将分析典型的数据挖掘在客户端/服务器端通讯(C/S)时的客户端请求。之后的章节将从各种数据挖掘可编程API处理典型请求时的方法以及它们支持的客户端环境的角度,具体展示这些API以及它们之间的差别。
数据挖掘任务
SQL Server数据挖掘服务器支持多种请求。对于每一种请求,我们将给出可用的DDL命令和等价的DMX:
· 元数据的发现:这些请求允许客户端应用根据一些属性,如挖掘模型的列、使用的算法、被训练的数据集,迭代服务器上已存在的元数据对象,并且从中选择一个或多个有用的元数据。XMLA规范为这类请求定义了发现(Discover)命令,没有与其直接对应的DMX或DDL。
· 元数据的定义:这些请求是一系列对服务器上的数据结构的定义。这些数据结构是挖掘结构和挖掘模型,它们由目录(数据库)组合在一起。针对这些请求的XMLA命令是执行(Execute),包括DDL用来创建和修改的Create 或 Alter 语句。也可以使用DMX的 CREATE语句,尽管它的灵活性要比DDL 的Create语句差。
· 模型的训练:这些请求为服务器端的数据挖掘模型执行训练处理。训练可以使用预先绑定的数据(在元数据定义时决定),也可以只描述当前正在处理的操作的数据集。XMLA命令还是 执行(Execute),包含一个DDL的处理(Process)语句。在DMX中,训练可以由插入(INSERT INTO)命令得到。
· 查询模型:这些请求包括(但不局限于)在数据挖掘中我们常说的记分(Scoring)和预测(Predicting)。 我们将查询定义为使用数据挖掘所建立的模型的过程。这些请求由发送到服务器端的DMX语句组成。
· 订阅进度通知:这些请求非常方便地显示一个进度条,这个进度条用来显示在执行一个很长的操作时或者在指定时间检查服务器状态时的实际进展情况。这类请求不能使用DMX创建。它们拥有跟踪的功能,作为带DDL订阅(Subscribe)语句的XMLA执行(Execute)命令来进行传输。
对这些任务有了基本概念后,我们可以正式开始列举可编程API及它们的特性了。上述任务中的最后一项,关于进度的通知,我们将单立一节的内容进行讲解,下面的API章节就不再讨论这个任务了。
通用API
我们将探讨两种常用的API:Adomd.NET和OLE DB。它们的差别在于它们的目标环境不同: OLEDB (OLE for Databases)主要针对非托管应用(尽管它也可以被用于托管应用);而Adomd.NET针对托管应用。下边我们将看到,这些API具有非常相似的结构。它们都基于非常有名的数据库访问范例。
Adomd.NET 作为通用API
Adomd.NET是由Microsoft Analysis Services 2000提供的旧ADOMD库的托管版本。这个库专门为Microsoft Analysis Services 2005的客户端而设计。它执行ADO.NET数据访问范例(包括一系列标准接口,如IDbConnection、IDbCommand、IDataReader等等),并且使用Microsoft Analysis Services 2005提供的许多特性来扩展这些范例。
在.NET框架中,System.Data命名空间使你可以建立从多个数据源有效管理数据的组件。关于此点的更多内容请参考关于System.Data 命名空间的MSDN文档。System.Data拥有多种特性,它定义一系列可以被.NET数据提供程序执行的用来访问关系型数据库的接口。微软分析服务(Microsoft Analysis Services)是一个多维数据库,它比普通关系型数据库拥有更强大的功能。Adomd.NET是一个.NET数据提供程序,能执行提供给.NET数据提供程序的一系列标准System.Data接口,这些接口的功能在Analysis Services 2005中得以增强,也增加了很多在关系型数据库中不使用的新特性。
Adomd.NET作为SQL Server 2005连接组件的一部份被安装,也可以单独下载进行安装。在从应用程序使用它之前,必须在机器上安装Adomd.NET数据提供程序。
这一节的代码示例使用Visual Studio 2005的C# 2.0实现。经过一个简单的“翻译”过程,它们也可以被Visual Studio 套件的任意托管语言(如Visual Basic .NET或Visual C++ .NET)运转。
在应用程序中使用 Adomd.NET时,必须先添加一个Microsoft.AnalysisServices.AdomdClient.dll程序集,使用类似如下的代码进行添加:
using Microsoft.AnalysisServices.AdomdClient;
下一步,连接Microsoft Analysis Services 2005 服务器:
AdomdConnection conn = new AdomdConnection();
conn.ConnectionString = "Data Source=localhost; " +
"Initial Catalog=MyCatalog";
conn.Open();
在数据挖掘任务列表中,第一个是元数据对象的发现。就像我们前边所说的一样,元数据的发现由XMLA发现(Discover)命令实现。Adomd.NET提供两种简单的方法来发现元数据。第一种与AMO非常相似:
foreach (MiningModel model in conn.MiningModels)
{
Console.WriteLine(model.Name);
}
微软分析服务开发团队尽了很大的努力来确认常用的计划都已包含于Adomd.NET Connection类所发布的程序集中。你可以使用之前的这些代码来访问数据库中的挖掘结构集、一个结构中或者整个数据库中的挖掘模型列表、以及模型和结构中的列。Adomd.NET 拥有远超过AMO 的功能,它允许代码这样构成分级模型:
foreach (MiningContentNode node in model.Content)
{
foreach( MiningContentNode in node.Children )
{
// 使用节点的属性
}
}
对Microsoft Analysis Services 2005中的OLAP计划来说,Adomd.NET Connection 对象所发布的程序集比数据挖掘所提供的程序集更加适用。所以在Adomd.NET中你可以使用相同的方法来浏览立方体和维度。
发现服务器元数据的第二种方法与OLE DB的方法非常相似,也就是执行一个发现(Discovery)命令来传输一个GUID计划和一系列约束,然后返回一个可以被遍历的表型结果。有些计划返回的是分级表型结果(嵌套表)。这也就是为什么在Adomd.NET 中,一个发现操作的返回值是一个数据集(DataSet);对比ADO.NET ,它返回的是一个数据表。数据集可以描述表之间的关系,所以数据集可以包含一些发现操作的嵌套表型结果。下边的一段代码片段发现了一个挖掘模型的内容,并且使用数据集得到了响应NODE_DISTRIBUTION嵌套表计划的嵌套行:
Guid modelContent = new Guid("{3ADD
8A
76-D8B9-11D2-8D
2A
-00E029154FDE}");
object[] arRestrictions = new object[3];
// 约束发现(Discovery)MyCatalog目录中DecisionTree1模型的内容。
// 第二个约束(MODEL_SCHEMA) 在这里忽略。
arRestrictions[0] = "MyCatalog";
arRestrictions[1] = null;
arRestrictions[2] = "DecisionTree1";
DataSet dsContent = conn.GetSchemaDataSet(
modelContent, arRestrictions);
// 一致性检查:确认关系值为1
Debug.Assert( dsContent.Relations.Count == 1);
DataRelation relation = dsContent.Relations[0];
DataTable topTable = relation.ParentTable;
foreach (DataRow row in topTable.Rows)
{
// 使用最上层表中的列
Console.WriteLine("NODE_CAPTION=" + row["NODE_CAPTION"]);
Console.WriteLine("NODE_UNIQUE_NAME" + row["NODE_UNIQUE_NAME"]);
// 根据关系描述,取得嵌套行
DataRow[] distRows = row.GetChildRows(relation);
foreach (DataRow distRow in distRows)
{
// 使用嵌套表中的列
Console.WriteLine(distRow["ATTRIBUTE_VALUE"]);
}
}
与OLE DB非常相似,Adomd.NET提供发送请求到服务器的命令。这些请求可以是DDL或者DMX的;所以它们可以创建或者修改已经存在的、用来训练挖掘结构和挖掘模型的元数据对象(DDL和DMX),也可以查询已存在的模型(只支持DMX)。我们也就可以通过此点来区分元数据的创建、处理,和DMX的查询。它们的区别在于:DMX查询返回一个表型结果(分层的或者单层的),而元数据创建和处理语句只返回一个成功或者失败。Adomd.NET公开了一些执行(Execute)方法,其中两种对于返回成功/失败的请求和返回表格型数据的请求非常有用。
第一种:我们使用一个DDL处理语句作为示例,任何其它的DDL语句(如Alter、Create或者Drop)也可以使用相同的代码。
AdomdCommand cmd = new AdomdCommand();
cmd.Connection = conn;
cmd.CommandText = "
"xmlns=/"http://schemas.microsoft.com/analysisservices/2003/engine/">"+"<Type>ProcessDefault</Type>" +
" <Object>" +
" <DatabaseID>MyCatalog</DatabaseID>" +
" <MiningStructureID>Structure1</MiningStructureID>" +
" </Object>" +
" </Process>";
cmd.ExecuteNonQuery();
同样,ExecuteNonQuery也可以执行一个DMX语句来产生相同的结果:
"INSERT INTO MINING STRUCTURE Structure1"
当 ExecuteNonQuery被调用的时候,如果请求执行成功了,语句将返回;如果执行失败将报一个异常。在之后的例子中,将给出异常的具体信息,如服务器返回的错误信息。
第二种执行方法是ExecuteReader。这种方法应该在语句的返回值肯定是表型的时候被调用。就像我们前边所提到的一样,Microsoft Analysis Services 2005返回的结果有时会是分层表型结果。例如,让我们考虑这样的一个模型, Model1,它根据消费者的年龄、他/她汽车的颜色来预测消费者的性别和购买商品的清单。当然,这样的模型可能并没有现实意义,但是,它却是一个很容易使DMX语句返回分层表结构结果的例子。
下面的代码使用 AdomdCommand 来传输一个预测查询给服务器,并读取最上层返回值(预测的性别)和嵌套返回值(预测的商品列表):
cmd.CommandText = "SELECT Gender, Products FROM Model2 " +
"NATURAL PREDICTION JOIN " +
"( SELECT 30 AS Age, 'Black' as CarColor) AS T";
AdomdDataReader rdr = cmd.ExecuteReader();
while (rdr.Read())
{
// 使用上层结果,预测性别
Console.WriteLine( rdr.GetString(0) );
// 为第一列的嵌套内容获得一个nested reader
AdomdDataReader nestedReader = rdr.GetDataReader(1);
// 读嵌套内容并使用嵌套数据
while (nestedReader.Read())
{
Console.WriteLine(nestedReader.GetString(0));
}
nestedReader.Close();
}
调用 GetDataReader 将返回一个新的实例AdomdDatareader 类,它被初始化后用来访问对列编号了的嵌套表。如果指定的列并不是一个被嵌套的表,“执行”方法将返回一个异常。我们可以使用类似如下代码的语言来判断Datareader中的列是不是一个嵌套表:
if (rdr.GetFieldType(1) == typeof(AdomdDataReader) )
{
// 如果进入If循环,rdr的第一列是一个嵌套表
}
在现实生活中,最有可能根据用户的实际输入来进行预测。一个常见的Web应用场景是这样的,用户给出年龄和汽车的颜色,应用程序代码产生一个DMX请求来预测该客户购买商品的清单。当开发者对DMX和相关的数据类型有一个很好的理解的时候,查询可以使用将客户录入的所有字符串连接起来的方式进行创建。然而,这样的方法具有潜在的威胁,它可能引起DMX嵌入错误或研发错误。一种更好的方式是使用参数来建立查询,将用户输入的内容作为参数的值。Adomd.NET对DMX的参数化查询提供了大量的支持,就像下面的例子一样。这段代码不包括读取返回值的内容,因为它与之前的代码片段是一样的。
cmd.CommandText = "SELECT Gender, Products FROM Model2 " +
"NATURAL PREDICTION JOIN " +
"( SELECT @Age AS Age, @Color as CarColor) AS T";
AdomdParameter paramAge, paramColor;
paramAge = cmd.CreateParameter();
paramAge.Direction = ParameterDirection.Input;
paramAge.ParameterName = "Age";
cmd.Parameters.Add(paramAge);
paramColor = cmd.CreateParameter();
paramColor.Direction = ParameterDirection.Input;
paramColor.ParameterName = "Color";
cmd.Parameters.Add(paramColor);
cmd.Parameters["Age"].Value = 30; // 用户在这里输入
cmd.Parameters["Color"].Value = "Black"; // 用户在这里输入
AdomdDataReader rdr = cmd.ExecuteReader();
请注意,参数不一定非是数值。请考虑这样的DMX语句:
INSERT INTO MyModel(Col1, Col2)
OPENQUERY(DataSource, "SELECT ColA, ColB FROM Table") AS T
或者是:
SELECT PREDICT(Products, 5) FROM MyModel NATURAL PREDICTION JOIN
OPENQUERY(DataSource, "SELECT ColA, ColB FROM Table") AS T
这两个语句都用了一个OPENQUERY 函数来描述执行时被服务器使用的表型内容(训练挖据模型,分别进行预测)。使用Adomd.NET,我们可以使用一个参数来取代OPENQUERY函数,并且向服务器传输一些表型内容。查询可能是这样的:
INSERT INTO MyModel(Col1, Col2)
@MyTabularContent AS T
或者是:
SELECT PREDICT(Products, 5) FROM MyModel NATURAL PREDICTION JOIN
@MyTabularContent AS T
在这些例子中, 参数MyTabularContent可以是一个数据表(DataTable)或者执行一个IDataReader .NET接口。相比来说,数据表更容易使用,IDataReader接口具有不需要将所有数据保存在内存中的优势。IDataReader执行的一个例子是客户端应用执行的SQL查询所返回的SqlDatareader。当然,一个SQL查询请求也可以在OPENQUERY 功能中被提交,但是这种请求的前提是服务器已经访问到了SQL数据库。表型参数为两种情况所设计:
· 数据库中的数据是客户端可见但是服务器端不可见的(这种情况应该使用IDataReader)
· 数据在客户端应用程序所占用的内存中(DataTable可以在这种情况时使用)
Adomd.NET 一个独有的特点是可以使用XML返回选定的列。Microsoft Analysis Services 2005为指定的查询返回这样的列。例如:
SELECT * FROM MyModel.PMML
这个语句将返回PMML 2.1格式下MyModel 模型的内容(即被支持PMML的MyModel使用的数据挖掘算法)。除了例如模型名称、PMML缓冲区大小这类元信息外,这个语句的返回值还包括指定的列和包含PMML的MODEL_PMML(一种被数据挖掘团队设计出来的XML格式,用来描述挖掘模型的内容)。
这个XML的内容可以以字符串方式被访问,但是Adomd.NET拥有分析XML列的能力(基于服务器端发来的指定的列类型来判断) 并且将它们发布为一个System.Xml.XmlReader对象。这些列也可以作为字符串被读取。以下的示例代码使用这一特性来访问带XML目录的列,同时使用字符串和XmlReader两种方法。
cmd.CommandText = "SELECT * from [MyModel].PMML";
AdomdDataReader rdr = cmd.ExecuteReader();
// 遍历响应结果,读取第5列,MODEL_PMML
while (rdr.Read())
{
// 取得列值
object objXMLValue = rdr.GetValue(5);
if (objXMLValue is System.Xml.XmlReader)
{
//使用一个XML reader
System.Xml.XmlReader pmmlReader =
(System.Xml.XmlReader)objXMLValue;
//在这里读PMML
}
// 使用字符串来获取这列
string strPMMLAsString = rdr.GetString(5);
}
Adomd.NET 的另一个特性是连续访问服务器响应,将在 进度通知:使用跟踪对象 一节中具体阐述。
Adomd.NET是Microsoft Analysis Services 2005中最具灵活性,最容易使用的客户端API。在编写以微软分析服务为目标的.NET应用操作时也同样推荐使用它。它提供了类AMO访问服务器元数据的大量特性,它允许执行DDL和DMX语句,并且它对DMX语句的参数也有很好的支持。
OLE DB作为通用API
在微软Windows操作系统中,OLE DB是进行数据访问最常用的API。OLE DB是针对OLE对象的规范。这些OLE对象有一系列标准接口。这些接口的标准化使得一个编程模型几乎可以访问所有类型的数据源。微软SQL Server数据挖掘有一个OLE DB提供程序。这个提供程序能将OLE DB编程模型翻译成数据挖掘所需要的内容。
关于 OLE DB的更多内容,请访问MSDN OLE DB页。
OLE DB可以被很多种编程语言直接使用。在本机(非托管)C++中,可以创建OLE DB对象,并且可以直接调用OLE DB的方法。ALT使用者模版(ALT Consumer Templates)也为OLE DB使用者应用程序提供了一系列有用的类。在Visual Basic或者VBA中,OLE DB提供程序可以在ADO(ActiveX Data Objects,ActiveX数据对象)中使用。在托管语言中,我们可以使用ADO.NET 库来调用OLE DB提供程序。
OLEDB编程模型以如下对象为中心。首先,数据源必须由服务器端初始化和控制。其次,在这个数据源上必须初始化一个通讯对话。OLE DB架构的第三个重要组成部分就是命令,它们将服务器请求打包。每个服务器请求都是对话的一部分。OLE DB架构的第四个组成部分是服务器响应。大多数数据挖掘任务可以被OLE DB命令所执行。例如,可以使用一个命令来传输DMX语句,也可以传输一个DDL语句来创建一个新的元数据。但是命令并不能传输一个发现元数据语句(Discovery)。所以针对此点,OLE DB定义了一个可以被对话执行的IDBSchemaRowset接口。这里我们需要注意的是,ADO和ADO.NET有一个封装了数据源和OLE DB对话的连接对象(Connection)。这两种标准的OLE DE对象(数据源和OLE DB对话)公开的功能在ADO和ADO.NET的连接对象中可用。
所有的OLE DB对象(数据源、对话、命令和响应)经常在COM组件中执行来调用OLE提供程序。
下面的图1给出了一个OLE DB解决方案的框架:
screen.width*0.7) {this.resized=true; this.width=screen.width*0.7; this.alt='点击在新窗口查看全图/nCTRL+鼠标滚轮放大或缩小';}" border=0>
图 1. OLE DB解决方案框架图
如图中所示,首先,各个客户端上安装的OLE DB提供程序使用OLE DB来连接服务器。Microsoft Analysis Services 2005的OLE DB提供程序是在安装SQL Server 2005的连接组件时安装的。
OLEDB连接经常通过连接字符串的方法来初始化。连接字符串包含一系列有分号分隔开的“名字-值”字符对,即属性。这些属性描述了OLE DB连接初始化时的各个参数。所有的OLE DB包(如ATL使用者模版、ADO、或者ADO.NET)都根据连接字符串来进行初始化。这里给出一个用来连接Microsoft Analysis Services 2005的OLE DB连接字符串的例子:
"Provider=MSOLAP; Data Source=localhost; "
第一个属性 Provider描述示例中指定的提供程序。
"MSOLAP" 是微软分析服务中OLE DB提供程序的名字。请注意这个名字是依赖版本的。如果一台机器上安装了多个分析服务OLE DB提供程序(如一个来自Analysis Services 2005,一个来自Analysis Services 2000),就需要使用更加准确的名字来区分版本:Analysis Services 2000使用“MSOLAP
.2”
,Analysis Services 2005使用“MSOLAP
.3”
。第二个属性“Data Source”表示要连接的数据源。它一般都是一个机器名,但是也可以是其它表达方式。例如,它可以是一个文件名,或者是一个网络URL,如我们将在下文“HTTP Pump”章所见到的一样。关于分析服务OLE DB提供程序属性的更多信息,你可以从SQL Server在线信息中得到。
下面这段代码是一个VBA应用程序中使用OLE DB的例子,它使用了ADO。具有相似功能的C#代码(使用ADO.NET)和非托管C++代码在附录2中。
为使用ADO,需要为你的Visual Basic (或者VBA)工程添加一个指向Microsoft ActiveX Data Object库(你机器中最新版本的库)的引用。以下的代码片段是在2.8版本上进行测试的:
1 Dim Conn As ADODB.Connection
2
3 Set Conn = New ADODB.Connection
4 Conn.Open ("Provider=MSOLAP.3; Data Source=localhost;" _
5 & "Initial Catalog=MyCatalog")
在第一行和第三行创建并初始化了一个ADODB连接对象。在第四行,就像我们前边所说的,ADO连接对象封装了两种OLE DB对象:数据源和对话。在连接字符串中,你将看到一个之前没有讨论过的属性:“Initial Catalog”。它定义服务器上将被这个连接使用的数据库。让我们使用这个连接对象来完成我们之前列举的数据挖掘任务。
对于元数据发现(Discovery),ADO连接提供了一个OpenSchema函数(在OLE DB对话中打包了IDBSchemaRowset接口)。
OpenSchema 包含3个参数:
· 枚举,用来描述将要被发现的计划。
· 约束集,将被应用于发现操作的一系列约束。
· 全局唯一标识符(GUID),描述提供程序指定的计划。
我们尝试在MyCatalog 数据库上发现服务器端的挖掘模型。这不是数据挖掘模型计划(指定的提供程序计划)中预先计划好的枚举,所以第一个参数就是adSchemaProviderSpecific。第二个参数,约束集,包含用来查找模型(“MyCatalog”)的目录名。第三个参数包含Analysis Services OLE DB提供程序中,能识别挖掘模型计划的GUID的字符表。
6 Const DMSCHEMA_MINING_MODELS="{3add
8a
77-d8b9-11d2-8d
2a
-00e029154fde}"
7 Dim Restrictions()
8 Restrictions = Array("MyCatalog")
9 Dim rsSchema As ADODB.Recordset
10 Set rsSchema = Conn.OpenSchema(
adSchemaProviderSpecific,
Restrictions,
DMSCHEMA_MINING_MODELS)
数据挖掘的OLE DB规范包含分析服务的OLE DB提供程序支持的计划的完整定义,包括发现(Discovery)语句返回的列和约束。
OpenSchema返回一个ADODB.Recordset对象。Recordset对象封装了一个表型服务器响应。下面我们将看到如何遍历这个对象以及如何从中提取信息。这段代码的目的是枚举挖掘模型的名字。像我们所知道的一样,在数据挖掘OLE DB里,挖掘模型计划中的每一行对应一种挖掘模型,并且包含一个“MODEL_NAME”列,列中存储挖掘模型的名字。
下面一段代码显示如何从Recordset 对象中查找指定的列,以及如何从这些列中提取信息。
11 ' 从(MODEL_NAME)中查找列
12 Dim iModelNameColumn As Integer
13 For iModelNameColumn = 0 To rsSchema.Fields.Count - 1
14 If rsSchema.Fields(iModelNameColumn).Name = "MODEL_NAME" Then
15 GoTo Found
16 End If
17 Next
18 Error (1)
19 Found:
20 ' 读取Recordset的返回值
21 rsSchema.MoveFirst
22 While Not rsSchema.EOF
23 Debug.Print rsSchema.Fields(iModelNameColumn).Value
24 rsSchema.MoveNext
25 Wend
如你所看到的一样,这段代码先遍历了Recordset 对象的所有字段。每个字段表示响应的一列。我们根据这些字段的索引MODEL_NAME进行查找。如果找到了这样的一列,就开始遍历它的行内容,否则就报错。
为了遍历行, Recordset指针先移到数据的开始位置。之后一行一行的读取行数据。对于每一行都读取和使用MODEL_NAME字段的值。
一些数据挖掘任务(如创建新元数据对象或训练已存在的对象)可以通过发送DDL语句到服务器来执行。让我们看看DDL语句是如何被OLE DB通过ADO来传输的。我们将使用与之前一样的连接对象,并介绍一个新的OLE DB对象的ADO包, ADODB.Command对象:
26 Dim Cmd As ADODB.Command
27 Set Cmd = New ADODB.Command
28 Cmd.ActiveConnection = Conn
29
30 Dim strProcessDDLStmt As String
31 strProcessDDLStmt = "" _
32 & " </Process
xmlns=""http://schemas.microsoft.com/analysisservices/2003/engine"">" _
33 & " <Type>ProcessStructure</Type>" _
34 & " <Object>" _
35 & " <DatabaseID>CheckInTestDB</DatabaseID>" _
36 & " </Object>Structure1" _
37 & " </Process>" _
38 & " "
39
40 Cmd.CommandText = strProcessDDLStmt
41 Cmd.Execute
42
命令在一个活跃连接中执行。这个活跃的连接就是第28行中的内容。一般来说,这个命令包含着执行语句。这在CommandText 属性(第40行)中设置。当ADO为Analysis Services 2005打包OLE DB提供程序时,CommandText属性支持DMX语句和DDL语句(就像之前所展示的一样)。命令的执行被“执行(Execute)”方法初始化(第41行)。执行经常返回一个ADODB.Recordset 对象(可用于之前的发现(Discovery)代码段的表型服务器响应)。但是,处理操作是没有服务器响应的,它不返回成功或失败。如果出现了一个错误,ADO将报一个异常并停止正在运行的代码。因此,如果执行到了第42行,就说明执行成功了。
在之前的代码中,可以将CommandText 属性改为一个DDL语句,比如Alter、Create或者Drop;或改为一个DMX语句,如CREATE MINING MODEL或者INSERT INTO,它可以实现绝大多数挖掘任务。唯一一个需要附加补充的是查询挖掘模型。DMX查询与一般的无响应信息语句不同,因为:
· 它返回一个表型结果。
· 表型结果可能包含多级的表(嵌套表)。
· DMX 支持参数。
我们可以从返回单级表的DMX查询开始看起,使用我们最开始使用的对象来对比(命令和连接):
43 Cmd.ActiveConnection = Conn
44 Cmd.CommandText = "SELECT NODE_CAPTION FROM DecisionTree1.CONTENT" _
45 &"where NODE_TYPE=2"
46
47 Dim rs As ADODB.Recordset
48 Set rs = Cmd.Execute()
49 rs.MoveFirst
50 While Not rs.EOF
51 Debug.Print rs.Fields(0).Value
52 Wend
53 rs.Close
如我们之前所提到的一样,DMX语句由CommandText属性来传输。与发现(Discovery)使用相同的方式来遍历Recordset。第44行用到的查询只返回一列,这里并不真正需要从recordset 字段来查找列;相反的,它应该由它的索引得到(第51行)第0字段的内容。也请注意第53行中对Recordset 的配置。当Recordset 是活跃的时候,命令对象不能执行之后的语句。
OLE DB规范也允许返回更加复杂的结果集,如表型列或者嵌套表。下面给出一个服务器响应是嵌套表的例子,我们将使用DMX语句来添加查询更复杂的第二列NODE_DISTRIBUTION。所以新的查询如下所示:
"SELECT NODE_CAPTION, NODE_DISTRIBUTION FROM DecisionTree1.CONTENT WHERE NODE_TYPE=2"
NODE_DISTRIBUTION 是一个典型的嵌套表例子。根据数据挖掘的OLE DB规范,模型中的NODE_DISTRIBUTION列包含当前行所在节点的属性值的分布。例如,在一棵预测头发颜色的决策树中,对每一个树节点,这一列都描述有多少实例是黑发,多少实例是金发,多少实例是灰发。
执行命令在新的一列中并不改变。实际上唯一需要改变的就是Recordset的遍历代码,需要进行适合表的新列的改变。在代码中可以很容易的从Recordset 作为字段值的属性返回一列的值。如果实例中的列是一个嵌套表,列值将产生一个新的Recordset,再遍历整个嵌套表。
所以,在51行以下应该执行如下的代码:
52 Debug.Assert( rs.Fields(1).Type = adChapter)
53 Dim nestedRS As ADODB.Recordset
54 Set nestedRS = rs.Fields(1)
55 nestedRS.MoveFirst
56 While Not nestedRS.EOF
57 Debug.Print nestedRS.Fields(0).Value
58 Wend
59 nestedRS.Close
第52行语句用来在将值写入嵌套Recordset前,确认列的类型是正确的。这行语句也可以用来判断制定的列是不是一个嵌套表。
应为拥有遍历嵌套表的能力,此时数据挖掘查询产生的任何返回值都可以在你的应用中被使用了。
DMX也支持查询中的参数。参数可以取代DMX查询中的任何值。例如,可以使用一个参数来代替上述查询语句中where子句里的NODE_TYPE 的值“
2”
。在数据挖掘应用中,也有少数参数非常有用的场景,比如生成一个单独的查询(关于单独查询的详细内容,请看“Adomd.NET”一节)。
想在DMX查询中将一个值变为一个参数,我们使用参数指示标志@来开始替换,使用“@唯一的参数名”。这样VBA代码片段中的第45行就可以变成:
45 &"where NODE_TYPE=@typeParam"
然后,在执行命令之前,我们应该插入这样的一段代码来确保能正确的使用新的参数。请注意,尽管实际数据可能与参数的值很不同,但是使用参数并不会改变服务器响应的格式。所以上述遍历Recordset 的代码可以并不改变。
46 Cmd.NamedParameters = True
47
48 Dim typeParameter As ADODB.Parameter
49 Set typeParameter = Cmd.CreateParameter()
50 typeParameter.Direction = adParamInput
51 typeParameter.Name = "typeParam"
52 typeParameter.Value = 2
53 typeParameter.Type = adInteger
54 typeParameter.Size = 4
55
56 Cmd.Parameters.Append typeParameter
在分析服务的OLE DB提供程序中使用参数时,如下的几步非常重要:
· 命令必须使NamedParameters可用(第46行)。分析服务的OLE DB提供程序只支持命名了的参数。
· 参数的名字必须符合查询中的相应参数,但是在这里不使用@前缀(第51行)。
· 可以只传输参数(第50行)。
· 参数的类型和大小必须声明(第53和54行)。
想对数据挖掘的OLE DB规则进行很好的理解,需要充分使用Microsoft Analysis Services 2005数据挖掘的特性。一旦设计好了数据挖掘查询,OLE DB就成为了一个可以执行它们、可以管理数据挖掘服务器的完整且具有通用性的API。关于在非托管C++或ADO.NET中C#使用OLE DB 的代码,请看本文附录2中的代码片段。
Analysis Management Objects – AMO
如同名字所显示的一样,AMO是一个用来管理任务的API。它非常适用于描述元数据属性的细节信息。AMO是一种非常符合微软分析服务的数据定义语言(DDL)的对象模型,在AMO中描述元数据对象。它允许迭代元数据对象、创建新对象、修改已存在对象。在AMO对象模型中,用DDL描述的每个属性都可以被检查和修改。AMO也可以用来检查和修改服务器属性,包括注册/非注册型插件算法,或者可注册/不可注册数据挖掘算法。AMO在元数据的定义和发现,以及服务器对象的训练上都非常有用。但是,它并不支持执行查询语句。
在讨论细节信息之前,我们应该先来说明一下AMO是一个托管库。所以,它可以在由通用执行时间组件(CLR——Common Language Runtime)兼容的编程语言开发的应用程序中使用,如C#、托管执行的C++、或者Visual Basic .NET。要想使用AMO, 我们需要在客户机上安装SQL Server 2005连接组件。
在使用AMO的时候,我们首先应该在应用程序的开始处引用Microsoft.AnalysisServices.dll 库。之后,AMO对象模型就可以使用了。先来连接服务器:
Microsoft.AnalysisServices.Server server = new Server();
Server.Connect("localhost");
一旦连接成功,服务器端的元数据对象就可以被分级检查到:
Databases dbCollection = server.Databases;
foreach( Database db in dbCollection )
{
MiningStructures structCollection = db.MiningStructures;
foreach( MiningStructure struct in structCollection)
{
Console.WriteLine( struct.Name );
}
}
如果想修改已存在的元数据对象,我们只需要修改它的属性。使用如下代码:
model.Algorithm="Microsoft_Decision_Trees".
then call "Update".
model.Update();
当Update被调用时,你所做的修改就将被提交到服务器,同时刷新本地的信息集。
在服务器上添加一个新的元数据、在各自的集合上创建新成员这两个方法,与修改功能也是非常相似的:
MiningStructure myStructure;
MiningModel myModel = myStructure.Models.Add();
之后填充对象的内容:
myModel.Name = "New Model"
myModel.Algorithm = "Microsoft_Clustering"
然后通过调用Update()来提交对服务器端所作的修改:
myModel.Update();
当操作未能成功执行完毕时,更新操作(Update)将返回一个异常。通常产生异常的情况是:
· 被更新的属性不完整或者不一致。
· 由于当前用户未能拥有足够的权限而引起的服务器不能更新对象。
当出现异常的时候,我们可以找到问题的所在并修复它。
AMO在处理Analysis Services 2005服务器端元数据上具有很强的功能,但是它并不支持之前所提到的数据挖掘任务中的部分任务。它不提供进度通知的功能,也不提供查询挖掘模型的功能(一般情况下,它不支持DMX语句的执行)。如果你的应用需要浏览和检查Analysis Services 2005服务器,使用AMO是最好的选择。但是,如果除此以外你还需要执行语句或者显示进度通知,AMO的功能就不够了。下一章将为大家介绍两种通用API,它们支持分析服务所公开的所有特性。
进度通知:使用跟踪对象
当在服务器端同时执行多个事件时,微软分析服务就会发送一系列通知。这些事件包括用户的登入登出、 执行请求的开始位置和结束位置、不同服务器对象正在执行的进度情况。在接收到通知以后,管理员就可以检查服务器在每一刻的状态。正在处理服务器对象的用户也可以从中了解正在执行的操作是什么,以及还需要执行多长时间。
这一节中,我们将讨论应用程序是如何从Microsoft Analysis Services 2005服务器来接收进度通知的。我们从如何发现服务器发出的通知看起。之后,我们简短的介绍应用程序如何订阅它所感兴趣的服务器通知。最后,我们给出一个需要处理进度通知的应用程序的编程模型。
在进行深入的讨论之前,我们先来明确一下,SQL Server 2005使用一个客户端分析器(Profiler)来处理这些通知。使用这个分析器,用户可以真正根据自己的需要进行通知的选择,然后检查这些通知的具体内容。分析器应用程序也允许记录服务器通知,可以为了以后检查通知内容而先将它们保存下来。它也可以使用性能计数器(Performance Counter)将正在执行的不同的性能指示器的相关服务器通知整合起来。大多数应用程序并不需要在代码中执行服务器通知,这一节针对那些需要使用数据挖掘客户端高级用户接口的开发者来进行讲解。
服务器发出的每种通知都是一个表型行。这一行包含一些基本信息,例如通知的种类、时间戳、服务器进程ID、以及服务器的名称。其它列分别对应特定的事件,例如处理通知的Progress Total、起始时间、结束时间、在服务器上已经执行的时间。服务器发出的所有通知被存放在一张虚拟表中,它为每个事件保留一行,以及被任何服务器事件支持的所有列的集合。这意味着:如果列A只被一个指定事件支持,它将在这张虚拟表中所有的事件行中出现,但是不支持A的事件的列A字段是空值。说这张表是一张虚拟表,是因为它并不存放在内存中,也不能被直接访问到。当没有人去看服务器事件进展情况的时候,它被存放在一个只有管理员可以访问到的“flight-recorder”文件中,用来提供过去出现错误的原因相关的有价值的信息。
这张虚拟表中的列集可以用常规XMLA发现语句(Discovery)来发现。这个计划的XMLA名称是DISCOVER_TRACE_COLUMNS,它的GUID是{a07ccd18-8148-11d0-87bb
-00c
04fc33942}。这个计划中的每一行描述事件表中的一列,计划至少有一列,使用事件的XML格式进行描述。XML格式的描述如下所示:
<COLUMN>
<ID>0</ID>
<TYPE>1</TYPE>
<NAME>EventClass</NAME>
<DESCRIPTION>Event Class is used to categorize events.</DESCRIPTION>
<FILTERABLE>false</FILTERABLE>
<REPEATABLE>false</REPEATABLE>
<REPEATEDBASE>false</REPEATEDBASE>
</COLUMN>
就这本白皮书来说,我们所感兴趣的属性有 ID、 名字(Name)和描述(Description)。
ID 是列的数值型标志符。像我们关于事件的讨论一样,一个事件通过定义ID来指定列。列的名字和描述对于客户端来说非常重要。基于这些属性,客户端应用程序可以判断哪些列是自己所感兴趣的列,哪些列是可以忽略的。ID为0的列是“服务器通知”这张虚拟表中最重要的列,因为它是用来定义事件类型的。Microsoft Analysis Services 2005发布的所有通知都包含此列。
发现服务器发布的事件是一个与发现列很相似的任务。使用的XMLA计划是 DISCOVER_TRACE_EVENT_CATEGORIES,它的GUID是 {a07ccd19-8148-11d0-87bb
-00c
04fc33942}。这个任务中的每一行描述一个服务器发布的事件类别,任务记录事件类别中的全部事件。当使用XML形式定义事件类别时,任务中的代码如下所示:
<EVENTCATEGORY>
<NAME>Queries Events</NAME>
<TYPE>0</TYPE>
<DESCRITION>Collection of events for queries.</DESCRITION>
<EVENTLIST>
<EVENT>
<ID>9</ID>
<NAME>Query Begin</NAME>
<DESCRIPTION>Query begin.</DESCRIPTION>
<EVENTCOLUMNLIST>
<EVENTCOLUMN>
<ID>0</ID>
</EVENTCOLUMN>
<EVENTCOLUMN>
<ID>2<ID>
</EVENTCOLUMN>
...
</EVENTCOLUMNLIST>
</EVENT>
<EVENT>
<ID>10</ID>
<NAME>Query End</NAME>
<DESCRIPTION>Query end.</DESCRIPTION>
<EVENTCOLUMNLIST>
<EVENTCOLUMN>
<ID>0</ID>
</EVENTCOLUMN>
<EVENTCOLUMN>
<ID>2</ID>
</EVENTCOLUMN>
...
</EVENTCOLUMNLIST>
</EVENT>
....
</EVENTLIST>
</EVENTCATEGORY>
所以,一个事件类别可以包含多个带不同ID的事件。如之前所说的一样,每个事件拥有它们自己的列,并且它们都包含提供事件类信息的列0。
另一个特殊的列是列1,事件子类(event subclass)。它被多个事件共享。事件子类这一列允许跟踪用户,例如,我们可以用它来区一个ProgressStart事件到底是来自于数据挖掘,还是OLAP维度处理。如果一个事件中出现了列1,这一列要比其它列的定义复杂一些。如下文所示:
<EVENTCOLUMN>
<ID>1</ID>
<EVENTCOLUMNSUBCLASSLIST>
<EVENTCOLUMNSUBCLASS>
<ID>1</ID>
<NAME>Process</NAME>
</EVENTCOLUMNSUBCLASS>
<EVENTCOLUMNSUBCLASS>
<ID>2</ID>
<NAME>Merge</NAME>
</EVENTCOLUMNSUBCLASS>
...
</EVENTCOLUMNSUBCLASSLIST>
</EVENTCOLUMN>
这个更加复杂的定义描述了当前事件中列1可能出现的所有值的含义。
当一个应用程序决定了它感兴趣的事件或者事件列是哪些的时候,它可以订阅这些信息作为服务器通知。这个订阅功能在服务器创建跟踪对象的时候被创建。跟踪对象的功能与包含服务器通知的虚拟表的视图的功能相似。除此功能以外,跟踪对象存有服务器对象的所有属性。它可以被创建、修改、删除以及根据许可情况进行约束。一个跟踪声明会指定它感兴趣的事件(类似于关系型视图中的WHERE子句)以及事件将返回哪些列。定义跟踪的DDL可以包含更多的过滤条件,如“只返回那些列C是特定值的返回值”,但是这些过滤条件并不是本文要讨论的内容。这些高级选项可以从SQL Server 2005 在线文档中进行了解,在文档中有专门一节“跟踪要素(分析服务脚本语言)”。
让我们来考虑这样的一个应用程序,它用来训练挖掘模型,而且只订阅了如下进度报告事件:
· Progress Report Begin (事件ID是5)
· Progress Report Current (事件ID是7)
· Progress Report End (事件ID是6)
· Progress Report Error (事件ID是8)
对每一个事件来说,感兴趣的列是:
· 列 0, EventClass。
· 列9, ProgressTotal,完成当前任务所需要执行的步骤数(此列仅在Progress Report Current事件可用)。
· 列10, IntegerData,数据挖掘进度通知中的属性,存储正在执行的任务的当前步骤(此列仅在Progress Report Current事件可用)。
· 列42, TextData,包含当前步骤的详细描述。
我们使用下面的DDL语句创建这个跟踪:
<Create
xmlns="http://schemas.microsoft.com/analysisservices/2003/engine">
<ObjectDefinition>
<Trace>
<ID>DemoTrace</ID>
<Name>DemoTrace</Name>
<Events>
<Event>
<EventID>5</EventID>
<Columns>
<ColumnID>0</ColumnID>
<ColumnID>42</ColumnID>
</Columns>
</Event>
<Event>
<Event ID>6</Event ID>
<Columns>
<ColumnID>0</ColumnID>
<ColumnID>42</ColumnID>
</Columns>
</Event>
<Event>
<EventID>7</EventID>
<Columns>
<ColumnID>0</ColumnID>
<ColumnID>9</ColumnID>
<ColumnID>10</ColumnID>
<ColumnID>42</ColumnID>
</Columns>
</Event>
<Event>
<EventID>8</EventID>
<Columns>
<Columns>0</Columns>
<Columns>42</Columns>
</Columns>
</Event>
</Events>
</Trace>
</ObjectDefinition>
</Create>
一旦跟踪被创建,客户端应用程序就将订阅这个跟踪。订阅跟踪的功能与执行返回表型结果的服务器请求的功能非常相似。它将返回一个表,表中包含由select事件选出的列。与普通的服务器查询相比,订阅(Subscribe)语句将一行行的保存返回的事件,直到发送一个停止订阅(Unsubscribe)命令给服务器,订阅语句才终止执行。进度通知仅在操作未完成时有用;在所有的操作都执行完毕后,进度通知就没有什么用处了。这也就是为什么带进度通知的模型使用两个不同线程进行处理的原因。
第一个线程创建跟踪,然后通知第二个线程进行订阅。
第二个线程进行订阅,并确认它设置了所有访问响应信息表而需要的执行属性。只要有服务器端响应出现,它马上能一行一行的对表进行访问,而不需要等所有响应都发送回来后再进行操作。
之后,第一个线程发出处理命令,并等待处理完成。
同时,第二个线程收到进度通知,并在用户界面显示它们。
当处理操作进行完毕,不论成功还是失败,第一个线程必须停止订阅跟踪并将跟踪删除。
在删除跟踪之前,第二个进程将接收到表结束的通知,第二个进程结束。
在之前的章节中,我们已经知道了如何在OLE DB和Adomd.NET中提交语句。为了在传输结束前得到所需的行,即得到结束响应,执行命令应该接收一个特定的API标志。例如,在OLE DB中,DBPROP_CANFETCHBACKWARDS 和DBPROP_CANSCROLLBACKWARDS属性必须为否(FALSE)。在Adomd.NET中,ExecuteReader 函数有一个可选属性CommandBehavior,它应该被设置为只进(Forward only)执行模型SequentialAccess。
下面的代码是包含第二个线程的函数以及主线程的程序框架:
AdomdConnection conn = new AdomdConnection();
conn.ConnectionString = "Data Source=localhost; " +
"Initial Catalog=MyCatalog";
conn.Open();
AdomdCommand cmd = new AdomdCommand();
cmd.Connection = conn;
cmd.CommandText = // 将创建跟踪的代码拷到这里!!
// 执行创建查询的语句
cmd.ExecuteNonQuery();
// 启动一个新的线程来读取跟踪
Thread traceThread = new Thread(new ThreadStart(TraceProc));
traceThread.Start();
// 继续进行处理操作
cmd.CommandText = // Processing statement,DDL or DMX, here
// 执行处理命令
try{
cmd.ExecuteNonQuery();
}
catch (System.Exception ex){
// 报错
Console.WriteLine(ex.Message);
}
finally{
// 不论处理结果是什么都删除跟踪
cmd.CommandText =
"
"xmlns=/"http://schemas.microsoft.com/analysisservices/2003/engine/">"+
" " +
" DemoTrace" +
" " +
"";
cmd.ExecuteNonQuery();
}
// 跟踪读取线程进度
public static void TraceProc()
{
// 在相同服务器上开始一个新的联接AdomdConnection
AdomdConnection conn = new AdomdConnection ();
conn.ConnectionString = "Data Source=localhost; " +
"Initial Catalog=MyCatalog";
conn.Open();
AdomdCommand traceCmd = new AdomdCommand();
traceCmd.Connection = conn;
// DDL订阅(Subscribe)命令
traceCmd.CommandText = "<Subscribe "+
"xmlns=/"http://schemas.microsoft.com/analysisservices/2003/engine/">"+
" <Object>" +
" <TraceID>DemoTrace</TraceID>" +
" </Object>" +
"</Subscribe>";
// 得到一个前向Data Reader
AdomdDataReader traceRdr =
traceCmd.ExecuteReader(CommandBehavior.SequentialAccess);
try{
// 从跟踪程序中读取进度通知并显示
while (traceRdr.Read()){
string textData = traceRdr.GetString(3);
if (textData != null)
Console.WriteLine(textData);
}
}
catch(System.Exception {
// 跟踪的异常情况:有可能是“服务器上的跟踪已经被删除(The trace was deleted
// on the server)”终止了跟踪
}
finally{
// 关闭用户界面(UI)
Console.WriteLine("Trace Completed");
}
}
向我们之前所说到一样,SQL Server 的分析器使用起来非常容易,创建跟踪也很容易。它提供了很多跟踪对象的高级功能,如行过滤器。
无服务器的数据挖掘:本地挖掘模型
Microsoft Analysis Services 2005 为进程内服务器提供有限的功能。它提供了一种可以将服务器代码装载到你的应用程序的内存空间中的方法,而不用真正打开一个到服务器的连接。这个功能可以在面向数据挖掘的文件“本地挖掘模型”或者面向OLAP的文件“本地立方体”中查询。在本文中,我们使用“本地服务器”。
本地服务器的目的是允许在脱机情况下进行与数据挖掘有关的操作。要用到这一特性的场景多为(但不限制于)嵌入式应用。
例如,我们可以在真正的服务器上设计并训练一个非常有用的挖掘模型,这个模型可以在集群上区分WEB请求,例如“小负荷的快速请求”、“小负荷的慢速请求”、“大负荷的慢速请求”。这样的一个挖掘模型可以用来解决基于集群的不同WEB服务器的平衡装载问题。但是,平衡装载解决方案也可能在分析服务器上执行请求,用来判断发送请求的集群是哪个,哪台机器存在潜在的时间消耗问题和可能出现安全问题(分析服务所在的机器应该在平衡装载方案所在的网络内,只不过它一般在防火墙以外)。而进程内服务器就是此种情况的解决方案。一旦训练模型从服务器上传输到具有装载平衡解决方案的机器上,这个模型就可以使用进程内服务器来执行请求。此时,请求的速度非常快(这些请求在同一个进程中,远强于通过网络进行传输),微软分析服务也是安全的被保护在防火墙内的。
微软分析服务保存所有的被调用的数据文件夹中的元数据(带挖掘结构和挖掘模型的数据库),这个文件夹是在运行安装程序时进行配置的。本地服务器在一个文件中保存所有的元数据,这个文件一般都使用扩展名.cub。一旦连接到本地服务器,应用程序就可以开始创建数据库、挖掘结构、挖掘模型了,并可以像在真正的服务器上一样使用它们。所有的元数据都将被创建和存储到.cub 文件中。如果应用程序被关闭了,当本地服务器被旧的.cub 文件重新初始化的时候,所有的已存在元数据将被保存和加载。
所有之前所提到的数据访问API都可以连接到本地服务器。开发者只需要使用.cub 文件的名字来代替之前的连接字符串中的服务器名(在代码示例中是“localhost”)即可。下面,给出一个Adomd.NET的例子:
AdomdConnection conn = new AdomdConnection();
conn.ConnectionString = "Data Source=c://test.cub; ";
conn.Open();
Similar code for AMO looks like:
Microsoft.AnalysisServices.Server server = new Server();
server.Connect("c://test.cub");
我们需要记住的是,在执行创建之前,.cub 文件中并没有数据库。
所以,我们需要做的第一件事就是创建一个新的数据库。需要这样做的一个例外是DMX的CREATE语句。当使用CREATE语句在没有可用数据库的情况下创建新的挖掘模型或者新的挖掘结构时,.cub 文件中将自动生成一个新的目录。
在连接成功以后,本地服务器就可以像真正的服务器一样被访问了。但是,本地服务器所能提供的连接也具有一些局限性:
· 本地服务器不支持多个同步连接(所以,像在之前一节讨论的一样,我们无法收到进度通知)
· 本地服务器不支持数据库备份和存储。
· 本地服务器不支持DMX 导入(IMPORT)语句。但是它支持DMX导出(EXPORT)语句。
· 本地服务器只支持有限的数据挖掘算法(Microsoft_Decision_Trees 和 Microsoft_Clustering).
扩展SQL Server 数据挖掘功能
到目前为止,我们已经讨论了很多种数据访问API在服务器上执行语句的方法。无论是数据定义语言(DDL)还是DMX,这些语句都是数据挖掘的类SQL语言。在这一节中,我们将讨论扩展服务器功能的若干方法:
· 添加新的函数和存储过程。
· 添加新的服务器端算法。
· 添加新的模型查看器(客户端)。
使用Adomd.NET Server 扩展DMX
Adomd.NET服务器是一个托管库,它和Microsoft Analysis Services 2005服务器产品一起进行安装。所以,它可以被任何托管语言所使用(如C# 或Visual Basic .NET)。Adomd.NET服务器可以用来创建两类服务器扩展:函数和存储过程。
存储过程是一个单机执行单元。它可以通过DMX语句被自己调用,例如:
CALL TestAssembly.MyNamespace.MyClass.MyStoredProcedure()
存储过程可以像任何DMX请求一样返回表型返回值;或者只执行服务器端操作,返回成功或失败。
服务器函数是一个辅助执行单元,它参与常规DMX查询。查询可以是这样的:
SELECT Function1() FROM MyModel WHERE Function2()
在Microsoft Analysis Services 2005中,为了创建一个函数或者存储过程,我们需要创建一个托管类库(或程序集),然后在服务器上部署它。程序集可以让所有服务器可视(需要服务器管理许可),或者仅存在于数据库中(需要数据库管理许可)。程序集对象有一些安全特性,包括许可集(描述授权给.NET代码的许可)和模拟信息(当程序集中的代码被执行时,什么帐号将被模拟)。SQL Server 2005在线文档包含关于程序集的许可集以及模拟模式的细节信息。就本文的来说,我们将使用Safe许可集,并模拟当前用户。程序集中公共类的每个公共方法都可以用作服务器函数或者存储过程。它们之间的区别在于服务器函数应该有返回值(一个数值或者一个表型目录),而存储过程返回一个空值(表示执行成功)或者一个表型目录(DataTable对象或者被IDataReader接口执行的对象),存储过程不能返回一个数值(实际上它们可以返回数值,但是数值不能被调用者当作CALL DMX语句的返回值而返回)。
当引用托管存储过程或者服务器函数的时候,必须使用完整的限定名。完整的限定名的BNF格式如下所示:
<AssemblyName>[.<Namespace>]+[.<ClassName>].<MethodName>
AssemblyName 是程序集的名字,它在程序集被部署的时候定义。它是必须被提供的。<Namespace> 是简单(或嵌套)命名空间,包含目标类和方法。<ClassName> 是包含方法的类。<MethodName>是真正的方法。<MethodName> 也是必须被提供的。请注意<Namespace> 和 <ClassName> 并不是强制性必须提供的。当在一个程序集中的多个函数/存储过程相互之间不明确时,必须使用这两个字段。完整的限定名(包括Namespace和ClassName)可以被本章内所有的语句使用。
当创建服务器函数或者存储过程时,时刻注意这些对象可能被多个用户同时调用是非常重要的。 类实例不能被多个调用所共享。所以当一个存储过程或者函数被调用的时候,没有一个动作来改变类的状态。而是根据传输给方法的参数来重新初始化状态的。说到参数:它们只能是数值。服务器函数和存储过程不能使用表作为参数。
下面我们使用一个DMX查询例子来演示一个简单的服务器函数是如何丰富用户经验的。这个查询在挖掘模型Model1中使用,这个模型用来根据性别列的F值和M值预测性别。我们尝试将这个查询进行改进:
SELECT Gender FROM Model1 [PREDICTION JOIN ... AS T]
这个查询将返回一列是M或F的值。
下面的服务器函数包含的代码将性别指示器转换为更加可读的字符串。它将M返回为Male ,将F返回为Female 。
namespace TestSP
{
public class StoredProc
{
public string RecodeGender(string strGender)
{
if (strGender == "M") return "Male";
if (strGender == "F") return "Female";
return strGender;
}
}
}
一旦类库包含了已经建立和部署了的类定义,服务器函数可以这样被调用:
SELECT TestSP.TestSP.StoredProc.RecodeGender(Gender) FROM Model1PREDICTION JOIN ... AS T
新的查询将返回包含Male 或 Female的列。
让我们现在来关注一下服务器函数的参数:列名。DMX分析引擎检测到Gender 是一个列名,它是一个可预知的列,这一列在服务器函数调用的时候被赋值。我们也可以使用PREDICTION JOIN 操作(类似于T.Gender)的输入数据中来传输列,用以代替这个可预知的列。这个函数可以被修改成多个参数,并且通过这些参数执行多个操作(比如串联字符串、带数字参数的算术运算符等等)。
简单的存储过程可以使用相同的方式来定义。在相同的类中,我们可以添加一个返回表型目录的新函数(在本例中是一个DataTable)。
public DataTable GetWeekDays()
{
DataTable tbl = new DataTable();
tbl.Columns.Add("Day", typeof(int));
tbl.Columns.Add("Name", typeof(string));
object[] row = new object[2];
row[0] = 0;
row[1] = "Sunday";
tbl.Rows.Add(row);
row[0] = 1;
row[1] = "Monday";
tbl.Rows.Add(row);
...
row[0] = 6;
row[1] = "Saturday";
tbl.Rows.Add(row);
return tbl;
}
存储过程可以被DMX语句调用:
CALL TestSP.TestSP.StoredProc.GetWeekDays()
现在让我们来看更加复杂的情况。使用[College Plans]挖掘模型,它根据父母的鼓励和IQ来预测学生的大学计划,我们需要将每个带说明的预测结果联系起来。我们可以使用之前的查询来获得预测结果。预测的原因可以在挖掘模型中每个节点的NODE_DESCRIPTION列中找到。预测节点可以通过通用DMX功能PredictNodeId 来判断。所以,这个查询可以修改成这样:
SELECT CollegePlans, PredictNodeId(CollegePlans) FROM [College Plans]
[PREDICTION JOIN ...]
作为本节的补充内容,我们可以忽略请求中的PREDICTION JOIN部分,因为它与服务器函数和存储过程的设计无关。
PredictNodeId 返回节点的唯一名字NODE_UNIQUE_NAME,这个名字决定了预测的内容。关于NODE_UNIQUE_NAME 的描述NODE_DESCRIPTION可以通过执行内容查询来判断。例如:
SELECT NODE_UNIQUE_NAME, NODE_DESCRIPTION FROM [College Plans].Content
现在,为了得到带预测描述的预测结果,我们需要将第一个查询的结果和使用了NODE_UNIQUE_NAME 的第二个查询的结果连接起来,使用它们二者的查询结果作为连接的关键字。但是,至少在当前版本中,DMX是不支持连接(JOIN)子句的。
此时,我们可以写一个返回节点描述的服务器函数,使用节点唯一的名字作为参数。假设我们已经定义了这样的一个函数,它的名字是GetNodeDescription,查询就变成了这样:
SELECT
CollegePlans,
TestSP.TestSP.StoredProc.GetNodeDescription(
PredictNodeId(CollegePlans) )
FROM [College Plans] PREDICTION JOIN ...
如我们之前所提到的一样,DMX执行引擎将在调用服务器函数之前执行PredictNodeId。因此,服务器函数将接收到参数——节点唯一的名字,此时它唯一需要做的就是去访问服务器当前的上下文、浏览挖掘模型的内容、以及确定各个节点的节点描述。
这时Adomd.NET服务器就显得非常有用了。为使用Adomd.NET服务器,类库必须包含一个特殊程序集的引用,它将服务器内部开放给存储过程和函数。这个程序集是msmgdsrv.dll,它在Microsoft Analysis Services 2005 服务器安装目录中。
在添加好引用以后,我们可以这样使用它:
using Microsoft.AnalysisServices.AdomdServer;
在使用程序集后,服务器上下文已经作为静态Context类可用了。
此时,程序集中的每种方法都可以使用Context 来访问服务器当前的上下文了。
使用Context类与在应用中使用动态Adomd.NET客户端库的 AdomdConnection 对象是一样的。在关于Adomd.NET 的章节中,我们已经讨论了使用Adomd.NET的类AMO方式公开服务器元数据的方法。在客户端API,元数据被发现(Discovery)方法重新得到;于此同时,在服务器端Adomd.NET库,它们直接访问服务器内存空间。
再回来看我们尝试编写的函数,Context 对象提供了所有我们需要的内容,如下:
public string GetNodeDescription(string nodeUniqueName)
{
MiningContentNode node =
Context.CurrentMiningModel.GetNodeFromUniqueName(nodeUniqueName);
return node.Description;
}
这时发生了什么:
· Context “知道”哪个是CurrentMiningModel,因为函数调用发生在DMX语句执行的时候,这个DMX语句拥有当前的挖掘模型(在我们的例子中是[College Plans]模型)。
· MiningModel 作为 CurrentMiningModel 公开的方法被返回, GetNodeFromUniqueName 根据节点的唯一名称返回 MiningContentNode节点。
· MiningContentNode 有一个Description 属性,这个属性包含该节点的NODE_DESCRIPTION 值。
一旦程序集被部署完毕——被家长鼓励且智商在110以上的学生,用服务器函数进行查询得到的结果是这样的:
College Plans = Does not plan to attend
Node Description = "ParentEncouragement='Encouraged' and IQ >= 101"
我们的目标实现了;我们使用一个简单的查询得到了之前需要使用连接(JOIN)才能得到的结果, Analysis Services 2005并不支持JOIN。
Adomd.NET服务器库公开的Context 类包含所有关于发现(Discovery)的程序集,它使用客户端AdomdConection对象。所以,在服务器函数或者存储过程中,开发者可以遍历一个或多个挖掘模型的内容,或者OLAP对象如立方体和维度。服务器函数和存储过程的一个非常重要的区别在于CurrentMiningModel 只可以在服务器函数中使用。这是因为DMX分析引擎不能通过如下的语句来判断当前的挖掘模型:
CALL MyStoredProcedure()
即使存储过程将挖掘模型名称作为参数(一种常用的方法),也不存在简单的描述目标挖掘模型将哪些参数作为考虑对象的方式。除此以外,存储过程不能只支持当前挖掘模型这样的概念,因为有时候它不得不处理复杂的模型。
Microsoft Analysis Services 2005所提供的数据挖掘工具使用存储过程来减少服务器和客户端间的总通讯量。大多数挖掘模型查看器需要通过完全遍历来识别服务器提供的模式。不使用存储过程,整个挖掘模型不得不在客户端被全部下载,存储在内存中,并且需要对其进行遍历才能知道哪些模式是所感兴趣的。使用存储过程,内容遍历在服务器上进行(在服务器上模型的内容已经装载到内存中了),并且存储过程的结果只包括查看器需要的信息(例如在微软集群查看器中查看两台机器的区别,或者在微软相关规则查看器中查看含有指定内容的规则)。
与客户端Adomd.NET 库相似,在服务器端,我们也定义一个AdomdCommand 对象。可以通过在当前数据库上执行DMX查询来使用这个对象。与客户端命令的一个不同在于,服务器端命令只执行DMX语句(它不支持DDL或者OLAP MDX查询)。服务器端 AdomdCommand 与其客户端命令非常相似:它提供ExecuteReader 和 ExeuteNonQuery 两个方法,并且支持参数,也支持表型参数。
下面的代码段向我们展示了一个服务器端命令对象的例子,以及如何执行查询的方法:
public IDataReader ExecuteContentQuery(string strModel)
{
AdomdCommand cmd = new AdomdCommand();
cmd.CommandText = "SELECT NODE_UNIQUE_NAME, NODE_RULE " +
"FROM [" + strModel + "].CONTENT";
return cmd.ExecuteReader();
}
这个机制也可以包含DXM查询语句,修改后的语句如下:
CALL TestSP.testSP.StoredProc.ExecuteContentQuery( "College Plans" )
请注意,当前的服务器并不支持返回嵌套表的存储过程。但是,存储过程可以返回表型结构(例如,DataTable 或者IDataReader对象),这些表型结构可以是服务器端连续发出的普通表型查询响应。这条规则有一个例外:存储过程也可以返回一个DataSet 对象。此时,DataSet 对象被完全串联成一个字符串。不论DataSet 的结构是什么样子的,服务器响应都包含一条线和一列值,列中存储着DataSet 的串联字符串。服务器端负责存储新DataSet 对象中的串联字符串。客户端 Adomd.NET API会自动进行存储。
由服务器端AdomdCommand 对象执行的DMX语句不一定非要被查询。例如CREATE MINING MODEL 或者INSERT INTO语句都可以在这里使用。如果一个挖掘模型在存储过程中被创建或者删除,Context类公开的关系型数据模型集合就需要被更新(通过调用这些集合中的Refresh方法来实现)。
存储过程和服务器函数是DMX框架最有力的扩展。它们可以增强内容查看器的性能,并且用一个全新的方式来展示训练挖掘模型的一系列规则。并且,它们也增强了DMX查询的功能和DMX语言的灵活性。
插件算法和内容查看器
Microsoft Analysis Services 2005 提供一系列强大的算法,这些算法可以用来解决很大范围内的商业问题。但是,一些特殊问题所需要的数据挖掘模型算法并不在常用的模型集合中。这也就是为什么服务器要提供插件控件来扩展算法的原因。新的算法必须是COM服务器,它执行一系列指定的接口,并且使用另外一些由服务器提供的接口。这样的插件算法将自动使用新数据挖掘模型平台所提供的很多特性:
· 元数据定义和管理
· 粒度访问许可模型
· 具有高可扩展性的查询
· 训练和预测事例集标记
· DMX查询语言及分析
· 对存储过程和服务器功能的支持
基本上,新的算法只需要关心如何在训练集中查找模式以及如何得到一个输入事例,而不需要关心这两个动作以外的事情。“SQL Server数据挖掘:插件算法”白皮书提供书写插件算法的具体内容。
在客户端,数据挖掘尝试着提供了一些通用的算法查看器。不管当前要处理的商业问题是什么,这些查看器都可以显示每个算法有用的可视信息。用来解决具体问题的数据挖掘应用程序则需要特定的查看器,这些特定的查看器可以更好的解决具体的问题。插件算法也可能并不公开被内置查看器所管理的内容。这也就是为什么数据挖掘工具提供了一种习惯模型查看器插件的原因。关于这个插件的具体内容,请看《建立插件查看器指南》。
应用场景建议
这一章试图为开发者提供一些数据挖掘应用常见场景的快速开发建议。这些建议并不是全面的,而且有些建议可能具有一定的局限性。但是,它们是数据挖掘开发团队在使用Microsoft Analysis Services 2005开发具体应用程序时的经验之谈。
先给出一个基本的建议:因为数据挖掘OLE DE规范包含DMX的所有内容,所以充分理解DMX是使用Microsoft Analysis Services的数据挖掘进行开发最快的方式。
应用场景1:简单的数据挖掘预测
以下应用程序的目的是进行简单的预测。
· 要编写执行数据挖掘查询操作(包含预测)的代码,最简单的方式是使用Adomd.NET。Adomd.NET一节包含的代码可以直接复制到你的应用中,根据应用程序的具体情况修改后即可使用。
· 一个非常常见的场景是根据一个单一的实例进行预测(单一预测)。如果数据来自客户的输入,强烈建议你使用参数化语句,从而可以避免畸形DMX语句、用户插入的DMX等语句。
· 如果应用程序进行预测以外的动作(允许客户选择模型、列之类的),使用Adomd.NET 公开的程序集是最简单的发现服务器元数据的方法。
应用场景2:Web应用——使用服务器上现有模型的规则
除了上述场景外,WEB应用也应该可以将所有的用户输入作为查询的参数进行传输。WEB应用除了有可能在执行DMX语句时报错,也可能是被恶意用户发布的。这些恶意用户最可能将一些DMX代码加入到你的语句中。尽管DMX语句在设计时尽量注意缩小被感染后的危害(很多语句不许使用;SELECT语句不能随意的连接到DELETE 或DROP语句),恶意用户仍然可以连接到钻取(drill through)结果,或者至少可以产生一段畸形查询,这段畸形查询潜在的使你的应用变慢甚至摧毁你的应用。
WEB应用应该在明确定义的用户帐号下运行。不允许公共WEB应用模拟远程用户。所以,我们应该小心的挑选运行这类应用的用户,并且给这类账号很小的权限。
当微软分析服务器持续运行的时候,WEB应用的性能将增强。这与其它请求创建新连接时的情况完全不同,它们可能引起连接池中的连接被终止。在执行其它请求的时候,想从连接池中返回请求必须先确定该连接活跃可用。
Microsoft Analysis Services 2005提供一系列WEB控制(在进行安装的时候可以方便的选择它们的例子),它们可以使用丰富的图表来演示挖掘模型的内容,类似于数据挖掘查看器中的图表。这些控制可以从图表中看到。
对于联合预测——常见的WEB数据挖掘应用类型——来说,DMX中新的语法要素明显增强了性能(仅在Microsoft Analysis Services 2005中可用)。一个典型的例子是:Microsoft Analysis Service 2000中执行联合预测使用TopCount 函数,用它来根据预测概率挑选预测的结果(例如,可能被当前用户所购买的商品的列表)。而在Microsoft Analysis Service 2005中不再需要TopCount 。如下的查询语句将更快的提供相同的结果:
SELECT Predict([Products], 5) FROM Model PREDICTION JOIN
应用场景3:为当前数据在服务器上创建和训练一个新的模型
在服务器上部署一个数据模型需要拥有数据库管理许可。挖掘模型会话不需要管理许可(如果服务器管理员允许这个特性的话)。会话并不会使用无用的元数据来增加服务器的负荷,所以在可能的情况下都可以使用会话。
如果训练集是客户端应用可用的,这里有两种训练挖掘模型的方法:
· 关系型数据库上的数据可以同时被服务器端(读)和客户端应用(写)看到。一旦数据进入到数据库中,就执行指向刚进入数据库的数据的绑定(DDL中的)来训练模型,或者通过执行带OPENQUERY 的INSERT INTO 语句来训练模型,其中OPENQUERY 指向刚进入数据库的那些数据。相比来说INSERT INTO语句更容易执行,但是它的性能比DDL绑定的处理性能要差些。这是因为DDL绑定允许平行关系的查询语句并联处理模型的列集;而带OPENQUERY的执行语句只能处理一次,它将数据存储到临时存储区后,一列列的进行处理。在数据集不是很大的时候,二者性能的差异并不明显。
· 数据可以作为表型参数传给INSERT INTO语句。Adomd.NET为此提供了强大的支持功能,数据可以由内存中的表中或者通过执行IDataReader 接口传给INSERT INTO语句。此时,使用DDL绑定进行处理的性能优势就失去了,但是语句更容易被执行了。有时,客户端和服务器端不在同一个网段内,这个网段内有它们都可以看到的那个关系型数据库,这时候对数据库的置入操作就无法完成。
小结
我们希望本文能涉及Microsoft Analysis Services 2005可编程挖掘模型的所有重要的内容。但是MAS2005这个产品目前还没有完全完成,很多特性也将跟随时间的变迁而改变。MSDN的SQL站点包括SQL Server 2005 的常用信息,并且特别包括了数据挖掘的内容。
数据挖掘团队的站点也正在不停的更新着最新的信息、诀窍和技巧、白皮书、以及可下载的代码示例。
数据挖掘新闻组,microsoft.public.sqlserver.datamining 和 microsoft.private.sqlserver2005.analysisservices.datamining,也一直在关注着数据挖掘团队,所以它们也拥有大量有用的信息。
附录 1:Microsoft Analysis Services 2005服务器上的常用操作以及请求的协议格式
发送到Microsoft Analysis Services 2005的消息结构如图2所示:
screen.width*0.7) {this.resized=true; this.width=screen.width*0.7; this.alt='点击在新窗口查看全图/nCTRL+鼠标滚轮放大或缩小';}" border=0>
图 2. MSAS2005消息格式
下面的例子包含一些常用的分析服务请求,使用SOAP、XMLA、以及DDL或 DMX来标示它们。
例1. 使用纯XMLA来发现服务器上的数据库
SOAP <Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/">
SOAP <Body>
XMLA <Discover xmlns="urn:schemas-miscrosoft-com:xml-analysis">
XMLA <RequestType>DBSCHEMA_CATALOGS</RequestType>
XMLA <Restrictions>
XMLA <RestrictionList/>
XMLA </Restrictions>
XMLA <Properties />
XMLA </Discover>
SOAP </Body>
SOAP </Envelope>
例2.在服务器上创建新的挖掘模型。使用XMLA执行(Execute)命令,中间使用DDL语句
SOAP <Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/">
SOAP <Body>
XMLA <Execute xmlns="urn:schemas-microsoft-com:xml-analysis">
XMLA <Command>
DDL <Alter
DDL AllowCreate="true"
DDL ObjectExpansion="ExpandFull"
DDL xmlns="http://schemas.microsoft.com/analysisservices/2003/engine"
DDL >
DDL <Object>
DDL <DatabaseID>TT</DatabaseID>
DDL <MiningStructureID>TestModel</MiningStructureID>
DDL </Object>
DDL <ObjectDefinition>
DDL <MiningStructure
DDL xmlns:xsd="http://www.w3.org/2001/XMLSchema"
DDL xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
DDL <ID>TestModel</ID>
DDL <Name>TestModel</Name>
DDL ...
DDL </MiningStructure>
DDL </ObjectDefinition>
DDL </Alter>
XMLA </Command>
XMLA <Properties />
XMLA </Execute>
SOAP </Body>
SOAP </Envelope>
例 3.在挖掘模型上执行一个DMX查询
SOAP <Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/">
SOAP <Body>
XMLA <Execute xmlns="urn:schemas-microsoft-com:xml-analysis">
XMLA <Command>
XMLA <Statement>
DMX SELECT Cluster() FROM CL
XMLA </Statement>
XMLA </Command>
XMLA <Properties />
XMLA </Execute>
SOAP </Body>
SOAP </Envelope>
附录 2:在Ado.NET 和 C++中使用 OLE DB
非托管 C++
非托管C++是使用OLE DB提供程序的常见开发环境。使用它需要具有一些COM知识和对OLE DB模型很好的理解。ATL使用者模版将复杂的COM隐藏起来,并提供了一个编写应用程序的简单方法。使用ATL使用者模版并不像使用分析服务的特定API(如ADOMD.NET)那么容易,但是它是一个能获得非托管应用程序的方法。这里,我们假定用户已经拥有了ATL库的知识以及对数据挖掘的OLE DB规范有了很好的理解,所以我们直接给出一些非托管C++代码。
在下面的代码中,我们假设我们已经有了一个预先设置好的宏CHECK_ERROR,它用来管理COM错误。
一个非常简单(虽然不是非常有用)的执行这个宏的方法是:
#define CHECK_ERROR(x) hr=(x);if(FAILED(hr)) return 1;
下面,我们将给出与OLE DB一节中通过ADO来执行的相同的任务的C++代码,因为篇幅的原因不再给出参数部分的代码。
我们这样初始化一个数据源和一个对话:
CoInitialize(NULL);
{
HRESULT hr;
CDataSource dataSrc;
CSession session;
LPOLESTR szConnStr =
L"Provider=MSOLAP.3; Data Source=localhost;"
L"Initial Catalog=MyCatalog";
CHECK_ERROR(dataSrc.OpenFromInitializationString(szConnStr));
CHECK_ERROR(session.Open(dataSrc));
请注意程序开始处必须要调用CoInitialize(NULL)。CoInitialize 初始化COM库,它必须在试图装载OLE DB提供程序前被当前线程所调用。
ATL 使用者模版提供一个常用的访问发现(Discovery)计划的方法:
CRestrictions
CDynamicAccessor, 1,
&DMSCHEMA_MINING_MODELS> schemaModels;
schemaModels.SetBlobHandling(DBBLOBHANDLING_NOSTREAMS);
CHECK_ERROR( schemaModels.Open( session, _T("MyCatalog") ));
CHECK_ERROR( schemaModels.MoveFirst() );
do
{
if( hr == S_OK )
{
wchar_t* szModelName = NULL;
if( schemaModels.GetValue((DBORDINAL)3, &szModelName) )
{
printf("%S/n", szModelName);
}
}
hr = schemaModels.MoveNext();
}while(hr == S_OK);
DMSCHEMA_MINING_MODELS 是被分析服务使用的挖掘模型提供程序指定计划的GUID。它在oledbdm.h文件中定义,这个文件是数据挖掘的OLE DB规范。上例中开始处用到的CRestrictions 类使用一个通用访问器(CDynamicAccessor)来自动绑定由发现操作返回的所有的列。
MoveFirst/MoveNext/GetValue操作在功能上与我们在OLE DB一节中所介绍的ADO的相应功能非常相似。
如果一个指定计划在一个应用(或多个应用)中被多次使用,我们需要定义一个专用访问器来简化和优化数据访问。下面的这段代码展示了这样的专用访问器是什么样子的,并且为之前用到的通用CRestrictions定义了一个用来发现挖掘模型的专用版本。挖掘模型计划访问器CMiningModelInfo应该完全填充挖掘模型计划感兴趣的所有的列。
class CMiningModelInfo
{
public:
// 构造函数
CMiningModelInfo ()
{
memset(this, 0, sizeof(*this));
}
// 属性
TCHAR m_szCatalog[129];
TCHAR m_szSchema[129];
TCHAR m_szName[129];
TCHAR m_szType[129];
GUID m_guidModel;
TCHAR m_szDescription[129];
...
TCHAR m_szService[129];
...
// Binding Map
BEGIN_COLUMN_MAP(CMiningModelInfo)
COLUMN_ENTRY(1, m_szCatalog)
COLUMN_ENTRY(2, m_szSchema)
COLUMN_ENTRY(3, m_szName)
COLUMN_ENTRY(4, m_szType)
COLUMN_ENTRY(5, m_guidTable)
COLUMN_ENTRY(6, m_szDescription)
...
COLUMN_ENTRY(10, m_szService)
...
END_COLUMN_MAP()
};
typedef CRestrictions<CAccessor<CMiningModelInfo>, 7,
&DMSCHEMA_MINING_MODELS > CMiningModels;
根据数据挖掘OLE DB规范,这里的7是一个挖掘模型计划中的约束值。
下面我们使用上述类,并且使用CMiningModels 来代替通用CRestrictions<CDynamicAccessor>。这个例子中获取模型名称的这段代码:
wchar_t* szModelName = NULL;
if( schemaModels.GetValue((DBORDINAL)3, &szModelName) )
printf("%S/n", szModelName);
将改变成这句代码:
printf("%S/n", schemaModels.m_szName);
我们可以使用相同的动态访问机制(或者为常用任务设计一个专用的访问器)来执行命令(DDL语句或者DMX语句)。
这样初始化命令:
CStringW strDMXStmt;
strDMXStmt = L"SELECT NODE_CAPTION FROM DecisionTree1.CONTENT";
CCommand<CDynamicAccessor> cmdDMX;
cmdDMX.SetBlobHandling( DBBLOBHANDLING_NOSTREAMS );
CHECK_ERROR( cmdDMX.Open( session, strDMXStmt.GetBuffer(), NULL) )
CCommand 类的Open 方法传输一个字符串作为第二个参数。当连接到Microsoft Analysis Services 2005时,这个字符串可以是DMX语句也可以是DDL语句。当命令被执行的时候(Open方法),它的响应格式可以被GetColumnInfo 检测到。这就使得之后的pCol 参数指向了一个包含每一列(如名称、大小、类型)的向量。可以通过这个向量来识别得到的列的顺序。
DBORDINAL ulColumns = 0;
DBCOLUMNINFO* pCol=NULL;
LPOLESTR ppStrings = NULL;
CHECK_ERROR( cmdDMX.GetColumnInfo(&ulColumns, &pCol, &ppStrings) );
定义了目标列(在上述代码中是字符型的列0)以后,我们就可以遍历服务器响应来获取有用的数据了。
wchar_t* pszBuffer = NULL;
CHECK_ERROR( cmdDMX.MoveFirst() );
do
{
wchar_t* szNodeCaption = NULL;
if( cmdDMX.GetValue((DBORDINAL)1, &szNodeCaption) )
{
printf("%S/n", szNodeCaption);
}
hRet = cmdDMX.MoveNext ();
}while(hRet == S_OK );
如果查询返回的是一个嵌套表,每一行将成为一个IRowset 对象。IRowset的子对象可以通过IParentRowset接口由最上层的行集合获得,IParentRowset接口是分层行集合必须提供的接口。
我们假设DMX查询语句是这样的:
"SELECT NODE_CAPTION, NODE_DISTRIBUTION FROM DecisionTree1.CONTENT"
循环读取这个行集合的数据的方法如下所示。例如,我们要读嵌套行集合NODE_DISTRIBUTION的第2列的值,根据数据挖掘OLE DB规范,NODE_DISTRIBUTION行集合必须包含一个挖掘属性值VARIANT。
// 外层循环,读最上层行集合
do
{
// 获得最上层行集合的IParentRowset接口
CComPtr<IParentRowset> spParentRowset;
CHECK_ERROR(cmdDMX.m_spRowset.QueryInterface( &spParentRowset));
// 使用一个动态访问器来读取嵌套行集合
CAccessorRowset<CDynamicAccessor> nestedRowset;
nestedRowset.SetBlobHandling( DBBLOBHANDLING_NOSTREAMS );
// 得到指向nestedRowset变量中的子行集合的指针
CHECK_ERROR( spParentRowset->GetChildRowset(
NULL,
(DBORDINAL)2,
nestedRowset.GetIID(), (IUnknown**)nestedRowset.GetInterfacePtr() ) );
// 将动态访问器绑定到子行集合
CHECK_ERROR( nestedRowset.Bind() );
CHECK_ERROR( nestedRowset.MoveFirst() );
// 内层循环,为了遍历嵌套子集合
while (hr == S_OK )
{
VARIANT varTmp;
::VariantInit( &varTmp );
if( nestedRowset.GetValue((DBORDINAL)2, &varTmp) )
{
// 在这里使用 VARIANT
}
// 继续执行内层循环
CHECK_ERROR( nestedRowset.MoveNext() );
}
// hr不是OK时,在以下两种情况下不是错误:
// DB_S_ENDOFROWSET 或显示了额外的确认代码
// 继续执行外层循环
hr = cmdDMX.MoveNext ();
}while(hr == S_OK );
参数nestedRowset是CAccessorRowset<CDynamicAccessor>类型的,它自动绑定所有的列,并且它可以方便的访问嵌套行集合所有字段的值。它提供了方法MoveFirst、MoveNext和GetValue,这些方法与cmdDMX命令非常相似。这是因为CCommand 类是源自CAccessorRowset 类的。它们将不同的行集合包含在一起: cmdDMX 对象包含命令执行结果的上层行集合,nestedRowset 对象包含子行集合。
设置OLE DB命令参数的详细内容,请到MSDN上查找,MSDN上提供了很多相关的例子和文档。
ADO.NET
ADO.NET被设计来在托管应用中代替ADO。它可以在托管应用中非常方便和容易的使用。ADO.NET是从托管应用连接到Microsoft Analysis Services 2005的首选解决方案,因为它是专为托管应用所设计的。ADO.NET一个非常重要的局限性是它不支持命名参数。也就是说,它只支持通过参数的序号来访问它们;所以ADO.NET不能使用带参数的DMX语句。
使用ADO.NET,需要在托管项目开始位置添加System.Data.dll引用,并在代码中这样使用这个引用:
using System.Data.OleDb;
像我们之前提过的一样,当使用单个连接将OLE DB对象数据源和对话打包在一起的时候, ADO.NET与ADO非常相似。要使用ADO.NET,需要先初始化一个ADO.NET连接,并使用OLE DB连接字符串来配置它:
OleDbConnection conn = new OleDbConnection();
conn.ConnectionString = "Provider=MSOLAP.3; Data Source=localhost; " +
"Initial Catalog=MyCatalog";
conn.Open();
一旦初始化连接完毕,就可以使用这个连接来发现服务器上的各种对象了。下面的代码是用来获取分析服务器上的挖掘模型列表的:
Guid miningModels = new Guid("{3ADD
8A
77-D8B9-11D2-8D
2A
-00E029154FDE}");
object[] arRestrictions = new object[1];
arRestrictions[0] = "CheckInTestDB";
DataTable tblModels =
conn.GetOleDbSchemaTable(miningModels, arRestrictions);
foreach( DataRow row in tblModels.Rows )
{
string modelName = row["MODEL_NAME"];
// 在这里使用modelName
}
在这段代码中,我们使用一个约束,目录名称,来限制在挖掘模型的目录MyCatalog 中的发现操作。DMX语句和DDL语句可以通过命令来执行,与ADO使用的方法非常相似。然而,ADO.NET命令(Command)对象给出了执行(Execute )方法的多个版本。
其中之一就是ExecuteReader。ExecuteReader返回一个Data Reader对象(执行.NET IDataReader接口)。数据阅读器可以用来遍历服务器上的表型结果。如果这个表型结果包含嵌套表,可以为每一行获得一个数据阅读器。下面的代码执行一个DMX查询,它将返回上层中的列(NODE_CAPTION)和被嵌套表中的列(NODE_DISTRIBUTION)。然后它遍历上下两层的结果,这段代码体现了它是如何从服务器响应中获取信息的:
OleDbCommand cmd = new OleDbCommand();
cmd.Connection = conn;
cmd.CommandText = "SELECT NODE_CAPTION, NODE_DISTRIBUTION FROM " +
" DecisionTree1.CONTENT WHERE NODE_TYPE=2";
OleDbDataReader rdr = cmd.ExecuteReader();
while (rdr.Read())
{
// 使用上层字段
Console.WriteLine(rdr.GetString(0));
// 控制嵌套阅读器
// 首先,确认列1种的对象是一个嵌套阅读器
object objNestedRowset = rdr.GetValue(1);
Debug.Assert(objNestedRowset is IDataReader);
OleDbDataReader nestedReader = (OleDbDataReader)objNestedRowset;
// 现在,遍历嵌套阅读器
while (nestedReader.Read())
{
Console.WriteLine(nestedReader.GetValue(1).ToString() );
}
}
与ADO命令非常相似, ADO.NET的 OleDbCommand 对象必须使用活跃连接(Connection)。它的CommandText属性可以被DDL语句或者DMX语句赋值。在ADO.NET阅读器对象(OleDbDataReader)中,根据列的序号来访问列。在列值类型已知的情况下,可以根据类型重新获得列值,就像上述程序中“使用上层字段”部分的代码。列值也可以作为普通对象而重新获得。在这个例子中,对象的类型可以在重新获得后被赋值,如上述代码段中“控制嵌套阅读器”中的代码所示。
另一种执行(Execute)方式是ExecuteNonQuery。这个方式在执行DDL语句或DMX语句后,不希望有返回值的时候特别适用(而不是返回成功或失败),例如CREATE MINING MODEL 或者 INSERT INTO。在下面的代码中,使用 ExecuteNonQuery 来执行DDL 处理(Process)语句。
cmd.CommandText = " <Process " +
"xmlns=/"http://schemas.microsoft.com/analysisservices/2003/engine/">"+
" <Type>ProcessStructure</Type>"+
" <Object>"+
" <DatabaseID>CheckInTestDB</DatabaseID>"+
" <MiningStructureID>Structure1</MiningStructureID>"+
" </Object>"+
" </Process>";
cmd.ExecuteNonQuery();
与ADO非常相似,如果语句执行失败,将报一个异常。C# 异常处理允许开发者获得这个异常并研究产生错误的原因。