Tuesday, March 16, 2010

Executing DMX DDL from a linked server

Luckily before I left MSFT, I had the foresight to change my contact email on that old wet blog of mine that I’m no longer able to contribute to – no hard feelings.  I received a question which is something that has come up frequently enough that it just needs to be dealt with so for all future posts, you can just say “look at the Executing DMX DML from a link server post on Jamie’s new blog – the only blog that matters,” and be done with it.

So just for definition’s sake – DMX – Data Mining eXtensions to SQL, DDL – Data Definition Language, DMX DDL – DMX statements that create or modify objects!  You would think you can add two TLA’s and get an SLA, but that stands for “service level agreement” which has nothing to do with this post.  This post could also have been named “how to execute non-rowset returning commands on Analysis Services from SQL Server”, but not only do I digress, I like the actual title better with the dual unpronounceable acronyms..

Anyway, in my DMX Digest post, I referenced this post which showed how to execute DMX statements from SQL and put the results in SQL table.  In short (just in case you don’t want to click those links), you set up a linked server and then use OPENQUERY to make the DMX call.  One (well, at least one) adventurous reader sought fit to try other kinds of statements than queries – in particular a DROP MINING STRUCTURE statement.  The problem with DROP MINING STATEMENTS – and other DDL statements is that they don’t return a rowset, which is a requirement for OPENQUERY – which really wants some output columns to bind to.

The nice way to do this would be to take advantage of the SQL EXECUTE command, which, at least in SQL Server 2008, has been extended to execute commands on linked servers.  Such a command would look very elegant, like this:

EXECUTE ( 'DROP MINING STRUCTURE [MyMiningStructure]' )
AT MyDataMiningServer


Wow – that would be nice!  If only it worked, that is.  If you endeavor to try such a think you’ll get the pleasant response of “Server 'MyDataMiningServer' is not configured for RPC.”  What this means, evidently, is that the nice way of doing things isn’t going to happen.



But, never fear, we can take advantage of all that boundless flexibility built in to SQL Server Data Mining to make it happen.  All we need to do is to create some kind of statement that can be called from SQL Server’s OPENQUERY that executes a statement of our choosing.  And the way to do this is to write a stored procedure that executes a statement and returns some sort of table.  This is the really big hammer solution to the problem.



And what do you know, I happen to have that stored procedure right here…..



using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using Microsoft.AnalysisServices.AdomdServer;

namespace DMXecute
{
public class Class1
{
[SafeToPrepare(true)]
public DataTable DMXecute(string statement)
{
DataTable Result = new DataTable("Table");
Result.Columns.Add("Column", typeof(int));
if (Context.ExecuteForPrepare)
return Result;

AdomdCommand cmd = new AdomdCommand(statement);
cmd.ExecuteNonQuery();

return Result;
}
}
}



And calling it from SQL Server – easy



EXEC sp_addlinkedserver @server='MyDataMiningServer', -- local SQL name given to the linked server
@srvproduct='', -- not used
@provider='MSOLAP', -- OLE DB provider
@datasrc='localhost', -- analysis server name (machine name)
@catalog='MyDMDatabase' -- default catalog/database

GO

SELECT * FROM OPENQUERY(MyDataMiningServer,'CALL DMXecute.DMXecute("DROP MINING STRUCTURE [MyMiningStructure]")')

GO



Of course, you can execute any DMX or MDX statement you want there, so this is simply dangerous is general – you definitely shouldn’t be sending unvalidated user input through here for fear of SQL Injection style attacks.  A better way, in general, would be to write stored procedures that performed exactly the operations you need taking just the object name as a parameter.


7 comments:

  1. all the Wiley SQL 2008 DM book left MSFT to Predixion?! Why?
    Do you think to create new DM products!?
    Regards,
    Pedro

    ReplyDelete
  2. Not all - I heard Zhaohui went to work for a startup in China, but the rest are here!

    ReplyDelete
  3. Hello, you have created a very useful stored procedure . I need this procedure for a data mining - project.

    I write a SQL script that should execute DMX statements.

    I've tried to implement the stored procedure in my SQL Server, unfortunately I have problems with the reference "Microsoft.AnalysisServices.AdomdServer;". I can not add .dll reference for this in SQL Server. I get the following error message when I try to create this "CREATE ASSEMBLY for assembly 'msmgdsrv' failed because assembly 'msmgdsrv' is malformed or not a pure. NET assembly.
    Not verifiable PE headers / native stub. "
    I wanted to ask you how you deploy the stored procedure on SQL Server?

    I would be very pleased with the answer.

    Thanks in advance.

    My email: radik86@gmx.de

    ReplyDelete
  4. Radik86 - you don't deploy this stored procedure to SQL Server - you deploy this stored procedure to Analysis Services. Once this is deployed, you call it using linked servers as described in the last part of the post.

    HTH
    -Jamie

    ReplyDelete
  5. I am very grateful for your quick help. You have helped me a lot. It works now everything fine.

    Thank You.

    Regards,

    Radik

    ReplyDelete
  6. Thanks for the tip. you are great! I can't believe they didn't build in an easier interface for this.

    I had trouble finding the reference too...

    For Microsoft.AnalysisServices.AdomdServer:
    C:\Program Files\Microsoft SQL Server\MSAS11.MSSQLSERVER\OLAP\bin\msmgdsrv.dll

    ReplyDelete
  7. This may be obvious but I figured I'd post it anyway. If your like me and probably vastly misusing this you have ran into the 8000 character limit on OpenQuery.

    so using the same concept I added a function to execute a file instead...


    [SafeToPrepare(true)]
    public DataTable DMXecuteFile(string filename)
    {
    DataTable Result = new DataTable("Table");
    Result.Columns.Add("Column", typeof(int));
    if (Context.ExecuteForPrepare)
    return Result;

    string command = System.IO.File.ReadAllText(filename);

    AdomdCommand cmd = new AdomdCommand(command);
    cmd.ExecuteNonQuery();

    return Result;
    }



    then you use it like....

    if not exists (select * from sys.objects (nolocK) where name = 'SSASCommand')
    create table SSASCommand (SSASCommandID int identity(1,1), SPID int, command varchar(max))

    delete from SSASCommand where SPID = @@SPID
    declare @cmd varchar(max)
    set @cmd = ''

    insert into SSASCommand
    select @@SPID, @cmd

    declare @xpcmd varchar(8000)
    set @xpcmd = 'bcp "SELECT command from SSASCommand (nolock) where SPID = ' + cast(@@SPID as varchar) + ' order by SSASCommandID" queryout C:\SSASCommands\' + cast(@@SPID as varchar) + '.txt -c -T'
    exec xp_cmdshell @xpcmd, no_output

    set @xpcmd = 'SELECT 1 FROM OPENQUERY(' + @linkedservername + ',''CALL DMXecute.DMXecuteFile("C:\SSASCommands\' + cast(@@SPID as varchar) + '.txt")'')'
    exec(@xpcmd)

    ReplyDelete