Извлечение исходных данных из модели PowerPivot с помощью Python

то, что казалось тривиальной задачей, превратилось в настоящий кошмар, когда мне пришлось читать некоторые данные из модели PowerPivot с использованием Python. Я считаю, что я исследовал это очень хорошо за последние пару дней, но теперь я попал в кирпичную стену и был бы признателен за помощь от сообщества Python/SSAS/ADO.

в основном, все, что я хочу сделать, это программный доступ к необработанным данным, хранящимся в моделях PowerPivot - моя идея заключалась в подключении к базовому PowerPivot (т. е. MS Analysis Службы) engine с помощью одного из перечисленных ниже методов перечислите таблицы, содержащиеся в модели, а затем извлеките необработанные данные из каждой таблицы с помощью простого запроса DAX (что-то вроде EVALUATE (table_name)). Проще простого, да? А может, и нет.

0. Некоторая Справочная Информация

как вы можете видеть, я пробовал несколько разных подходов. Я постараюсь документировать все как можно тщательнее, чтобы непосвященные в функциях PowerPivot имели хорошее представление о том, что я люблю делать.

прежде всего, некоторые сведения о программном доступе к Analysis Services engine (он говорит 2005 SQL Server, но все это должно быть применимо):программируемость интеллектуального анализа данных SQL Server и поставщики данных, используемые для подключения служб Analysis Services.

пример файла Excel / PowerPivot, который я буду использовать в приведенном ниже примере, можно найти здесь:Microsoft PowerPivot для Excel 2010 и PowerPivot в Excel 2013 Образцы.

кроме того, обратите внимание, что я использую Excel 2010, поэтому часть моего кода зависит от версии. Е. Г. wb.Connections["PowerPivot Data"].OLEDBConnection.ADOConnection должно быть wb.Model.DataModelConnection.ModelConnection.ADOConnection если вы используете Excel 2013.

строка подключения, которую я буду использовать в этом вопросе, основана на информации, найденной здесь:подключение к механизму PowerPivot с помощью C#. Кроме того, некоторые методы, по-видимому, требуют некоторой инициализации модели PowerPivot перед извлечением данных. Видеть здесь: автоматизация операции обновления PowerPivot из VBA.

наконец, вот несколько ссылок, показывающих, что это должно быть достижимо (обратите внимание, однако, что эти ссылки в основном относятся к C#, а не к Python):

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. С помощью АМО!--27-->
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()

это похоже на каков самый простой способ доступа к mssql с помощью python или ironpython?. К сожалению, это также не работает:

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. С помощью ADO через модуль adodbapi

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

Connection = adodbapi.connect(ConnString)

аналогично противоположные работы OLEDB / ODBC между Python и 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. Использование ADO через модуль Excel / win32com

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:экспорт таблицы или DAX-запроса из Power Pivot в CSV с помощью 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:WindowsHELPADO270.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. Использование ADO через Excel / win32com, прямое использование Объект adodb.Связи

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 [дубликат] и доступ к запросу с помощью ADO в платформе Win32 (рецепт Python). К сожалению, ошибка 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. Использование ADO через Excel / win32com, прямое использование ADODB.Обновление модели Connection plus

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 в виде таблицы? - обратите внимание, что строка подключения отличается от того, который я использовал в другом месте). К сожалению, это не работает, и я предполагаю, что Python запускает ADODB.Процесс подключения в отдельном экземпляре (поскольку я получаю то же сообщение об ошибке при выполнении последние три строки без первой инициализации 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)

3 ответов


Lo и вот, мне наконец удалось взломать проблему-оказывается, что доступ к данным Power Pivot с помощью Python действительно возможен! Ниже краткий обзор того, что я сделал - вы можете найти более подробное описание здесь: службы Analysis Services (SSAS) на шнурке. Примечание: код не был оптимизирован ни для эффективности, ни для элегантности.

  • установите Microsoft Power BI Desktop (поставляется с бесплатным сервером служб Analysis Services, поэтому нет необходимости в дорогостоящем SQL Server лицензия-однако тот же подход, очевидно, также работает, если у вас есть надлежащая лицензия).
  • запустите as engine, сначала создав msmdsrv.файл настроек ini, затем восстановите базу данных из файла ABF (используя AMO.NET), затем извлеките данные с помощью ADOMD.NET.

вот код Python, который иллюстрирует as engine + AMO.NET части:

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)

проблема с получением данных из PowerPivot заключается в том, что табличный движок в PowerPivot запускается в процессе внутри Excel и только способ подключения к этому движку заключается в том, чтобы ваш код работал внутри Excel. (Я подозреваю, что он может использовать общую память или какой-то другой транспорт, но он определенно не прослушивает TCP-порт или именованный канал или что-то подобное, что позволило бы внешнему процессу подключиться)

мы делаем это в DAX Studio, запустив C# VSTO Надстройка Excel в Excel. Однако это было предназначено только для тестирования аналитических запросов, а не для массового извлечения данных. Мы маршализуем данные через надстройку к пользовательскому интерфейсу, используя строковую переменную, поэтому весь набор данных должен быть меньше 2 ГБ или ответ усекается, и вы увидите ошибку "неузнаваемый ответ" (данные сериализуются в набор строк XMLA, который является довольно подробным, поэтому может увидеть его разрыв при извлечении только нескольких сотен МБ данных)

Если вы хотели чтобы создать скрипт для автоматизации извлечения всех необработанных данных из модели, я не думаю, что вы сможете сделать это с помощью Python, поскольку я не верю, что вы можете запустить интерпретатор python в Excel. Я бы посмотрел на использование макроса vba, подобного этому http://www.powerpivotblog.nl/export-a-table-or-dax-query-from-power-pivot-to-csv-using-vba/

вы должны найти, что вы можете запросить модель для списка таблиц с чем-то вроде "SELECT * FROM $СИСТЕМА.DBSCHEMA_TABLES " - затем вы можете выполнить цикл над каждой таблицей и извлечь с изменением кода в приведенной выше ссылке.


Я связался с Томом Глисоном (он же Гобан САОР), который был достаточно любезен, чтобы позволить мне разместить его электронную почту здесь. В них есть интересные самородки, поэтому, надеюсь, другие также найдут их полезными.

Email #1

когда вы говорите Python, вы имеете в виду запуск Python.NET как автономный exe? Если это так, вам не повезло с моделями Excel PP (разные история для Power BI desktop). Я получил доступ к моделям PP (2010+) успешно как от VBA, так и от Python.NET (через AMO) использование подобный код в ваш вопрос так. Разница в том, что (в версия VBA и .NET) заключается в том, что мой код выполняется в процессе внутри Excel с использованием различных технологий надстройки Excel. (Вероятно, таблица также работает как надстройка или имеет встроенный Excel внутри себя подобное поведение.) DAX Studio (полезная база кода на C# для изучения how-tos of PP access) работает как как надстройка Excel, так и как автономная ИСПОЛНЯЕМЫЙ, но только в качестве надстройки он может получить доступ к моделям PP на основе Excel.

Email #2

вы можете найти процесс использования Python.NET для этого несколько бросающий вызовом. Вам нужно будет встроить движок Python с помощью C# / VB.Сетка Код надстройки Excel. Я использовал Excel-DNA (фантастический открытый исходный код project), а не очень громоздкий "официальный" метод MS для разработка таких .NET addins в прошлом, но я в основном придерживаюсь VBA где вообще вероятный.

используя VBA, вы не сможете получить доступ к .NET-только AMO (поэтому нет возможности создавать вычисляемые столбцы на лету), но, загрузив результирующий набор данных в набор записей ADO, вы должны возможность вывода на рабочий лист или корпоративную базу данных / MS Access Или к плоскому файлу / CSV и т. д.

В отличие от предела листа 1M, для ограничивающим фактором будет выходная память плоского файла или базы данных (ОЗУ) , но, предполагая, что вы используете 64bit Excel и есть достаточно памяти, чтобы держать уплотненная модель и рабочее пространство для самой большой модели таблицы в несжатой форме (т. е. на основе строки, а не на основе столбца формат, который будет результатом запроса DAX), умноженный на 2ish (один экземпляр в рабочей области PP другой в рабочей области ADO VBA) вы все будет хорошо.

сказав это, я никогда не пытался извлечь очень большой набор данных и использование моделей в качестве среды обмена данными не один из PP "use-cases"; таким образом, очень большие таблицы могут ударить по некоторым другие ошибки / ограничения!