使用 Python 从 PowerPivot 模型中提取原始数据

2024-01-30

当我必须使用 Python 从 PowerPivot 模型中读取一些数据时,看似微不足道的任务变成了真正的噩梦。我相信在过去的几天里我已经对此进行了很好的研究,但现在我遇到了困难,并且希望得到 Python/SSAS/ADO 社区的一些帮助。

基本上,我想要做的就是以编程方式访问存储在 PowerPivot 模型中的原始数据 - 我的想法是通过下面列出的方法之一连接到底层 PowerPivot(即 MS Analysis Services)引擎,列出模型中包含的表,然后使用简单的 DAX 查询从每个表中提取原始数据(类似于EVALUATE (table_name))。简单易行,对吧?嗯,也许不是。

0.一些背景信息

正如您所看到的,我尝试了几种不同的方法。我将尝试尽可能仔细地记录所有内容,以便那些不熟悉 PowerPivot 功能的人能够很好地了解我想要做什么。

首先,关于以编程方式访问 Analysis Services 引擎的一些背景知识(它说是 2005 SQL Server,但所有这些都应该仍然适用):SQL Server 数据挖掘可编程性 https://msdn.microsoft.com/en-us/library/ms345148.aspx and 用于 Analysis Services 连接的数据提供程序 https://msdn.microsoft.com/en-us/library/dn141152.aspx.

我将在下面的示例中使用的示例 Excel/PowerPivot 文件可以在此处找到:Microsoft PowerPivot for Excel 2010 和 PowerPivot in Excel 2013 示例 https://www.microsoft.com/en-us/download/details.aspx?id=102.

另请注意,我使用的是 Excel 2010,因此我的一些代码是特定于版本的。例如。wb.Connections["PowerPivot Data"].OLEDBConnection.ADOConnection应该wb.Model.DataModelConnection.ModelConnection.ADOConnection如果您使用的是 Excel 2013。

我将在整个问题中使用的连接字符串基于此处找到的信息:使用 C# 连接到 PowerPivot 引擎 https://social.technet.microsoft.com/Forums/sqlserver/en-US/0b9499f6-f80f-4dcc-8ab9-c52574b14a70/connect-to-powerpivot-engine-with-c。此外,某些方法显然需要在数据检索之前对 PowerPivot 模型进行某种初始化。看这里:从 VBA 自动执行 PowerPivot 刷新操作 https://gobansaor.wordpress.com/2011/08/31/automating-powerpivot-refresh-operation-from-vba/.

最后,这里有几个链接表明这应该是可以实现的(但请注意,这些链接主要指的是 C#,而不是 Python):

  • 已连接到 PowerPivot DataModel,如何用它填充数据集? https://stackoverflow.com/questions/24763513/made-connection-to-powerpivot-datamodel-how-can-i-fill-a-dataset-with-it
  • 使用 C# 连接到 PowerPivot https://stackoverflow.com/questions/8242557/connecting-to-powerpivot-with-c-sharp
  • 2013 C# 与 Power Pivot 数据模型的连接 https://stackoverflow.com/questions/24643366/2013-c-sharp-connection-to-powerpivot-datamodel
  • 连接 Tableau 和 PowerPivot。它就是有效的。 http://tableaulove.tumblr.com/post/76758976141/connecting-tableau-and-powerpivot-it-just-works(表明外部应用程序实际上可以读取 PowerPivot 模型数据 - 请注意,Tableau 加载项安装Interop.ADODB.dll程序集,我猜它是用来访问 PowerPivot 数据的)

1.使用ADOMD

import clr
clr.AddReference("Microsoft.AnalysisServices.AdomdClient")
import Microsoft.AnalysisServices.AdomdClient as ADOMD
ConnString = "Provider=MSOLAP;Data Source=$Embedded$;Locale Identifier=1033;
             Location=H:\\PowerPivotTutorialSample.xlsx;SQLQueryMode=DataKeys"

Connection = ADOMD.AdomdConnection(ConnString)
Connection.Open()

在这里,问题似乎是 PowerPivot 模型尚未初始化:

AdomdConnectionException: A connection cannot be made. Ensure that the server is running.

2.使用AMO

import clr
clr.AddReference("Microsoft.AnalysisServices")
import Microsoft.AnalysisServices as AMO
ConnString = "Provider=MSOLAP;Data Source=$Embedded$;Locale Identifier=1033;
             Location=H:\\PowerPivotTutorialSample.xlsx;SQLQueryMode=DataKeys"

Connection = AMO.Server()
Connection.Connect(ConnString)

同样的故事,“服务器没有运行”:

ConnectionException: A connection cannot be made. Ensure that the server is running.

请注意,从技术上讲,AMO 并不用于查询数据,但我将其作为连接 PowerPivot 模型的潜在方式之一。

3.使用ADO.NET

import clr
clr.AddReference("System.Data")
import System.Data.OleDb as ADONET
ConnString = "Provider=MSOLAP;Data Source=$Embedded$;Locale Identifier=1033;
             Location=H:\\PowerPivotTutorialSample.xlsx;SQLQueryMode=DataKeys"

Connection = ADONET.OleDbConnection()
Connection.ConnectionString = ConnString
Connection.Open()

这类似于使用 python 或 IronPython 访问 mssql 最简单的方法是什么? https://stackoverflow.com/a/301746。不幸的是,这也行不通:

OleDbException: OLE DB error: OLE DB or ODBC error: The following system error occurred:
The requested name is valid, but no data of the requested type was found.

4.通过adodbapi模块使用ADO

import adodbapi
ConnString = "Provider=MSOLAP;Data Source=$Embedded$;Locale Identifier=1033;
             Location=H:\\PowerPivotTutorialSample.xlsx;SQLQueryMode=DataKeys"

Connection = adodbapi.connect(ConnString)

如同Python 和 MS Access VBA 之间 OLEDB/ODBC 的相反工作方式 https://stackoverflow.com/questions/25951912/oppposite-workings-of-oledb-odbc-between-python-and-ms-access-vba。我得到的错误是:

OperationalError: (com_error(-2147352567, 'Exception occurred.', (0, u'Microsoft OLE DB
Provider for SQL Server 2012 Analysis Services.', u'OLE DB error: OLE DB or ODBC error: The
following system error occurred:  The requested name is valid, but no data of the requested
type was found...

这与上面的 ADO.NET 的问题基本相同。

5.通过Excel/win32com模块使用ADO

from win32com.client import Dispatch
Xlfile = "H:\\PowerPivotTutorialSample.xlsx"
XlApp = Dispatch("Excel.Application")
Workbook = XlApp.Workbooks.Open(Xlfile)
Workbook.Connections["PowerPivot Data"].Refresh()
Connection = Workbook.Connections["PowerPivot Data"].OLEDBConnection.ADOConnection
Recordset = Dispatch('ADODB.Recordset')

Query = "EVALUATE(dbo_DimDate)" #sample DAX query
Recordset.Open(Query, Connection)

这种方法的想法来自这篇使用 VBA 的博客文章:使用 VBA 将表或 DAX 查询从 Power Pivot 导出到 CSV https://www.powerpivotblog.nl/export-a-table-or-dax-query-from-power-pivot-to-csv-using-vba/。请注意,此方法使用显式刷新命令来初始化模型(即“服务器”)。这是错误消息:

com_error: (-2147352567, 'Exception occurred.', (0, u'ADODB.Recordset', u'Arguments are of
the wrong type, are out of acceptable range, or are in conflict with one another.',
u'C:\\Windows\\HELP\\ADO270.CHM', 1240641, -2146825287), None)

然而,ADO 连接似乎已建立:

  • type(Connection)回报instance
  • print(Connection)回报Provider=MSOLAP.5;Persist Security Info=True;Initial Catalog=Microsoft_SQLServer_AnalysisServices;Data Source=$Embedded$;MDX Compatibility=1;Safety Options=2;ConnectTo=11.0;MDX Missing Member Mode=Error;Subqueries=2;Optimize Response=3;Cell Error Mode=TextValue

问题似乎出在 ADODB.Recordset 对象的创建上。

6.通过Excel/win32com使用ADO,直接使用ADODB.Connection

from win32com.client import Dispatch
ConnString = "Provider=MSOLAP;Data Source=$Embedded$;Locale Identifier=1033;
             Location=H:\\PowerPivotTutorialSample.xlsx;SQLQueryMode=DataKeys"

Connection = Dispatch('ADODB.Connection')
Connection.Open(ConnString)

如同从 Python 连接到 Access [重复] https://stackoverflow.com/a/12761442 and Win32平台下使用ADO查询访问(Python秘籍) https://code.activestate.com/recipes/196462-query-access-using-ado-in-win32-platform/。不幸的是,Python 吐出的错误与上面两个示例中的错误相同:

com_error: (-2147352567, 'Exception occurred.', (0, u'Microsoft OLE DB Provider for SQL
Server 2012 Analysis Services.', u'OLE DB error: OLE DB or ODBC error: The following system
error occurred:  The requested name is valid, but no data of the requested type was found.
..', None, 0, -2147467259), None)

7.通过Excel/win32com使用ADO,直接使用ADODB.Connection加模型刷新

from win32com.client import Dispatch
Xlfile = "H:\\PowerPivotTutorialSample.xlsx"
XlApp = Dispatch("Excel.Application")
Workbook = XlApp.Workbooks.Open(Xlfile)
Workbook.Connections["PowerPivot Data"].Refresh()
ConnStringInternal = "Provider=MSOLAP.5;Persist Security Info=True;Initial Catalog=
                     Microsoft_SQLServer_AnalysisServices;Data Source=$Embedded$;MDX
                     Compatibility=1;Safety Options=2;ConnectTo=11.0;MDX Missing Member
                     Mode=Error;Optimize Response=3;Cell Error Mode=TextValue"

Connection = Dispatch('ADODB.Connection')
Connection.Open(ConnStringInternal)

我希望可以初始化 Excel 的实例,然后初始化 PowerPivot 模型,然后使用 Excel 用于嵌入 PowerPivot 数据的内部连接字符串创建连接(类似于如何将 powerpivot 数据作为表格复制到 Excel 工作簿中? https://stackoverflow.com/a/33580647/5803031- 请注意,连接字符串与我在其他地方使用的不同)。不幸的是,这不起作用,我的猜测是 Python 在单独的实例中启动 ADODB.Connection 进程(因为当我在没有首先初始化 Excel 的情况下执行最后三行时收到相同的错误消息等):

com_error: (-2147352567, 'Exception occurred.', (0, u'Microsoft OLE DB Provider for SQL
Server 2012 Analysis Services.', u'Either the user, ****** (masked), does not have access
to the Microsoft_SQLServer_AnalysisServices database, or the database does not exist.',
None, 0, -2147467259), None)

你瞧,我终于成功解决了这个问题 - 事实证明,使用 Python 访问 Power Pivot 数据确实是可能的!下面是我所做的简短回顾 - 您可以在这里找到更详细的描述:Analysis Services (SSAS) 资源有限 https://github.com/akavalar/SSAS-on-a-shoestring。注意:代码既没有针对效率也没有针对优雅进行优化。

  • 安装 Microsoft Power BI Desktop(附带免费的 Analysis Services 服务器,因此不需要昂贵的 SQL Server 许可证 - 但是,如果您拥有适当的许可证,显然同样的方法也适用)。
  • 首先创建 msmdsrv.ini 设置文件来启动 AS 引擎,然后从 ABF 文件恢复数据库(使用 AMO.NET),然后使用 ADOMD.NET 提取数据。

下面是说明 AS 引擎 + AMO.NET 部分的 Python 代码:

import psutil, subprocess, random, os, zipfile, shutil, clr, sys, pandas

def initialSetup(pathPowerBI):
    sys.path.append(pathPowerBI)

    #required Analysis Services assemblies
    clr.AddReference("Microsoft.PowerBI.Amo.Core")
    clr.AddReference("Microsoft.PowerBI.Amo")     
    clr.AddReference("Microsoft.PowerBI.AdomdClient")

    global AMO, ADOMD
    import Microsoft.AnalysisServices as AMO
    import Microsoft.AnalysisServices.AdomdClient as ADOMD

def restorePowerPivot(excelName, pathTarget, port, pathPowerBI):   
    #create random folder
    os.chdir(pathTarget)
    folder = os.getcwd()+str(random.randrange(10**6, 10**7))
    os.mkdir(folder)

    #extract PowerPivot model (abf backup)
    archive = zipfile.ZipFile(excelName)
    for member in archive.namelist():
        if ".data" in member:
            filename = os.path.basename(member)
            abfname = os.path.join(folder, filename) + ".abf"
            source = archive.open(member)
            target = file(os.path.join(folder, abfname), 'wb')
            shutil.copyfileobj(source, target)
            del target
    archive.close()

    #start the cmd.exe process to get its PID
    listPIDpre = [proc for proc in psutil.process_iter()]
    process = subprocess.Popen('cmd.exe /k', stdin=subprocess.PIPE)
    listPIDpost = [proc for proc in psutil.process_iter()]
    pid = [proc for proc in listPIDpost if proc not in listPIDpre if "cmd.exe" in str(proc)][0]
    pid = str(pid).split("=")[1].split(",")[0]

    #msmdsrv.ini
    msmdsrvText = '''<ConfigurationSettings>
       <DataDir>{0}</DataDir>
       <TempDir>{0}</TempDir>
       <LogDir>{0}</LogDir>
       <BackupDir>{0}</BackupDir>
       <DeploymentMode>2</DeploymentMode>
       <RecoveryModel>1</RecoveryModel>
       <DisklessModeRequested>0</DisklessModeRequested>
       <CleanDataFolderOnStartup>1</CleanDataFolderOnStartup>
       <AutoSetDefaultInitialCatalog>1</AutoSetDefaultInitialCatalog>
       <Network>
          <Requests>
             <EnableBinaryXML>1</EnableBinaryXML>
             <EnableCompression>1</EnableCompression>
          </Requests>
          <Responses>
             <EnableBinaryXML>1</EnableBinaryXML>
             <EnableCompression>1</EnableCompression>
             <CompressionLevel>9</CompressionLevel>
          </Responses>
          <ListenOnlyOnLocalConnections>1</ListenOnlyOnLocalConnections>
       </Network>
       <Port>{1}</Port>
       <PrivateProcess>{2}</PrivateProcess>
       <InstanceVisible>0</InstanceVisible>
       <Language>1033</Language>
       <Debug>
          <CallStackInError>0</CallStackInError>
       </Debug>
       <Log>
          <Exception>
             <CrashReportsFolder>{0}</CrashReportsFolder>
          </Exception>
          <FlightRecorder>
             <Enabled>0</Enabled>
          </FlightRecorder>
       </Log>
       <AllowedBrowsingFolders>{0}</AllowedBrowsingFolders>
       <ResourceGovernance>
          <GovernIMBIScheduler>0</GovernIMBIScheduler>
       </ResourceGovernance>
       <Feature>
          <ManagedCodeEnabled>1</ManagedCodeEnabled>
       </Feature>
       <VertiPaq>
          <EnableDisklessTMImageSave>0</EnableDisklessTMImageSave>
          <EnableProcessingSimplifiedLocks>1</EnableProcessingSimplifiedLocks>
       </VertiPaq>
    </ConfigurationSettings>'''

    #save ini file to disk, fill it with required parameters
    msmdsrvini = open(folder+"\\msmdsrv.ini", "w")
    msmdsrvText = msmdsrvText.format(folder, port, pid) #{0},{1},{2}
    msmdsrvini.write(msmdsrvText)
    msmdsrvini.close()

    #run AS engine inside the cmd.exe process
    initString = "\"{0}\\msmdsrv.exe\" -c -s \"{1}\""
    initString = initString.format(pathPowerBI.replace("/","\\"),folder)
    process.stdin.write(initString + " \n")

    #connect to the AS instance from Python
    AMOServer = AMO.Server()
    AMOServer.Connect("localhost:{0}".format(port))

    #restore database from PowerPivot abf backup, disconnect
    AMORestoreInfo = AMO.RestoreInfo(os.path.join(folder, abfname))
    AMOServer.Restore(AMORestoreInfo)
    AMOServer.Disconnect()

    return process

以及数据提取部分:

def runQuery(query, port, flag):
    #ADOMD assembly
    ADOMDConn = ADOMD.AdomdConnection("Data Source=localhost:{0}".format(port))
    ADOMDConn.Open()
    ADOMDCommand = ADOMDConn.CreateCommand() 
    ADOMDCommand.CommandText = query

    #read data in via AdomdDataReader object
    DataReader = ADOMDCommand.ExecuteReader()

    #get metadata, number of columns
    SchemaTable = DataReader.GetSchemaTable()
    numCol = SchemaTable.Rows.Count #same as DataReader.FieldCount

    #get column names
    columnNames = []
    for i in range(numCol):
        columnNames.append(str(SchemaTable.Rows[i][0]))

    #fill with data
    data = []
    while DataReader.Read()==True:
        row = []
        for j in range(numCol):
            try:
                row.append(DataReader[j].ToString())
            except:
                row.append(DataReader[j])
        data.append(row)
    df = pandas.DataFrame(data)
    df.columns = columnNames 

    if flag==0:
        DataReader.Close()
        ADOMDConn.Close()

        return df     
    else:   
        #metadata table
        metadataColumnNames = []
        for j in range(SchemaTable.Columns.Count):
            metadataColumnNames.append(SchemaTable.Columns[j].ToString())
        metadata = []
        for i in range(numCol):
            row = []
            for j in range(SchemaTable.Columns.Count):
                try:
                    row.append(SchemaTable.Rows[i][j].ToString())
                except:
                    row.append(SchemaTable.Rows[i][j])
            metadata.append(row)
        metadf = pandas.DataFrame(metadata)
        metadf.columns = metadataColumnNames

        DataReader.Close()
        ADOMDConn.Close()

        return df, metadf

然后通过如下方式提取原始数据:

pathPowerBI = "C:/Program Files/Microsoft Power BI Desktop/bin"
initialSetup(pathPowerBI)
session = restorePowerPivot("D:/Downloads/PowerPivotTutorialSample.xlsx", "D:/", 60000, pathPowerBI)
df, metadf = runQuery("EVALUATE dbo_DimProduct", 60000, 1)
endSession(session)
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用 Python 从 PowerPivot 模型中提取原始数据 的相关文章

随机推荐

  • 触发问题,更新面板不起作用?

    我创建动态 LinkBut ton 并将 LinkBut ton 的单击触发器添加到 UpdatePanel 现在 当我第一次单击任何一个链接按钮时 触发器运行良好 并显示我的选择 没有回发 之后 我单击其他 LinkBut ton 所有页
  • 哪种做法更好 - 全球导入或本地导入

    我正在 django 中开发一个应用程序 我怀疑在全局级别导入库是否比在本地 每个函数 级别导入库对内存或性能有任何影响 如果按功能或视图导入 则导入单独需要的模块可以节省空间 对吗 或者这样做有什么负面影响吗 您肯定已经注意到 几乎所有
  • Data.Map / Data.IntMap 是否存在 monad 实例?

    我有一个在 IntMap 上运行的算法 我认为最好以命令式的方式表达它 也就是说 我想说的是 在地图中查找值 X 如果它符合条件 则从地图中删除该值 循环直到地图中不再存在任何值 用两行递归来表达是相当简单的 但实际的算法有点复杂 涉及多次
  • 在 bash 中从 YAML 文件解析嵌套变量

    一个复杂的 yaml文件来自这个链接 https docs projectcalico org v3 3 getting started kubernetes installation hosted kubernetes datastore
  • Jmeter线程组中的RAMP UP

    我在 jmeter 中设置启动时遇到问题 我的测试场景如下所述 有3个线程组 Thread Group1 2 Users Ramp up 10 Thread Group2 3 users Ramp up 15 Thread Group3 5
  • 如何从子目录加载 Latex .sty 文件?

    我使用一些不属于 texlive 的 sty 文件 并希望将它们放在我的 main tex 文件夹的子目录中 我使用以下行来加载包 usepackage sty prettythesis 这是可行的 但是用 xelatex 编译 main
  • Ember数据:保存失去了belongsTo关系

    我有以下问题 带有选择字段的表单 用于选择帖子的类别 假设该帖子的类别为 100 在 Ember 检查器中 显示如下 category
  • 收缩网格项目就像 CSS 中的 Flex 项目一样

    是否可以像 CSS 中的 Flex 项目一样缩小网格项目 网格项目 container display grid grid gap 10px grid template columns repeat auto fill minmax 200
  • 使用 matlab/octave 将图像从 rgb 转换为 hsv 回 rgb

    我正在尝试将彩色图像从 RGB 转换为 HSV 进行更改 然后再转换回 RGB 作为测试 我编写此代码只是为了测试如何从 rgb 到 hsv 返回到 rgb 但是当我查看图像时 它只是显示为黑色 我缺少什么 PS我使用的是octave 3
  • PyQt5 信号和线程

    我在 youtube 上观看了有关 PyQt4 信号的简短教程 但在运行一个小示例程序时遇到了问题 如何将从线程发出的信号连接到主窗口 import cpuUsageGui import sys import sysInfo from Py
  • ./executable 和 ./executable 之间的区别可执行文件

    在 shell 中 有什么区别 executable and executable 在第一个中 点是快捷方式source http linux about com library cmd blcmdln source htm正确的 那么两者
  • 'git reset --hard HEAD~1' 和 'git reset --soft HEAD~1' 之间有什么区别?

    我试图撤消 git 中的提交 使用有危险吗git reset hard HEAD 1 不同选项之间有什么区别git reset git reset确实知道五种 模式 软 混合 硬 合并和保留 我将从前三种开始 因为这些是您通常会遇到的模式
  • Python中的邻接表和邻接矩阵

    你好 我理解邻接表和矩阵的概念 但我对如何在Python中实现它们感到困惑 实现以下两个示例的算法可以实现 但从一开始就不知道输入 因为他们在示例中对其进行了硬编码 对于邻接表 a b c d e f g h range 8 N b 2 c
  • JavaMail 无法在 Amazon EC2 上运行

    PROBLEM 我正在尝试使用 JavaMail 从 EC2 Ubuntu 12 04 服务器发送电子邮件 但它不起作用 背景 我打算从 Tomcat 服务器向一些团队成员发送自动生成的邮件 我使用 JavaMail 1 4 7 来完成这项
  • 生成多个相同的精灵

    我目前正在开发一款名为 Table Wars 的游戏 这是一款适合两名玩家的回合制策略游戏 进展一直很顺利 直到我遇到了产卵单位的问题 该程序不会产生多个相同的单位 也不会在旧的单位死亡后重生新的单位 以下是一些可能有帮助的信息 每个类都存
  • 在 UIImagePickerController 中隐藏快门

    我在 iPhone 应用程序中为相机视图设计了虹膜快门动画 不幸的是 即使我隐藏相机控件并创建自定义的cameraOverlayView 当视图出现时似乎也无法隐藏Apple的快门 当视图出现时 我使用 UIImagePickerContr
  • SQL - 提高 NOT EXISTS 查询性能

    有没有办法可以提高这种 SQL 查询性能 INSERT INTO WHERE NOT EXISTS Validation 问题是当我的表中有很多数据 例如数百万行 时 执行WHERE NOT EXISTS子句如果很慢 我必须进行此验证 因为
  • Keycloak :REST API调用通过管理员用户名和密码获取用户的访问令牌

    我有一个要求 我需要获取用户的访问令牌 我知道管理员用户名和密码 因此可以获得管理员的访问令牌 是否有任何 REST API 可以为我提供使用上述数据的用户访问令牌 有两种方法可以获取访问令牌 一种带有 Rest 客户端 keycloak
  • 如何使用 onclick 提交 Rails AJAX 表单

    如何使用按钮在 Rails 中提交 AJAX 表单 My Code Controller def list events ExternalEvent all if params city id nil events events where
  • 使用 Python 从 PowerPivot 模型中提取原始数据

    当我必须使用 Python 从 PowerPivot 模型中读取一些数据时 看似微不足道的任务变成了真正的噩梦 我相信在过去的几天里我已经对此进行了很好的研究 但现在我遇到了困难 并且希望得到 Python SSAS ADO 社区的一些帮助