Monday, November 26, 2007

Demarcation

Good systems practice is to have demarcations between systems layers. This involves separation of the following layers (at a minimum):

Application
Business logic
Data access
Data

SAS programmers though tend to muddle all together:

libname indata ...; <-- Data Access
data mydata...; <--- Business Logic
ods html; proc report...; <--- Application Layer / UI

A better way to handle is to treat all layers as standalone and get them out of each other's space. The easiest way for a SAS coder to accomplish this is to use macro libraries to get it all started:

%GetFinanceData() ;
%DetermineProfit() ;
%OutputResults() ;

However, even this is constrained since everything is called together and a single program controls what happens top-to-bottom. Rather than doing the final piece as a part of the batch job, consider doing it on demand and perhaps through other means:

%GetFinanceData() ;
%DetermineProfit() ;

...wait until use requests information....

Get information on demand and make the presentation logic extraneous to SAS. Someone could even keep it in SAS ODS but the idea would be to pull it out to an application layer that is completely separated from how the information was formulated.

SAS programmers would be better off putting in extremely strong lines of demarcations between layers. This makes code easier to maintain, easier to change, and easier to understand.

Longer term, web service calls will simplify this even more but the concept of processing silos can be done now. Break your code apart so everything isn't glued together so tightly that code reuse is hard and code libraries are non-existent or little used.

C# and DBF files

I have a client that asked me to read DBF files generated by their SAS application. Well, this posting is to explain to someone what i have learned in reading DBF files using C#. The main thing I learned was no spaces in the file path.

Here is the code that worked for me:

string connectionString = "Driver={Microsoft dBase Driver (*.dbf)};SourceType=DBF;SourceDB=C:\projects\Client\Data\WeeklyAvailableComparison;Exclusive=No; Collate=Machine;NULL=NO;DELETED=NO;BACKGROUNDFETCH=NO;";
string selectCommand = @"SELECT * FROM C:\projects\Client\Data\WeeklyAvailableComparison\ac_141.dbf";
DataTable dt = new DataTable();

OdbcConnection oConn = new OdbcConnection();
oConn.ConnectionString = connectionString;
oConn.Open();
OdbcCommand oCmd = oConn.CreateCommand();
oCmd.CommandText = selectCommand;

dt.Load(oCmd.ExecuteReader());

I tried doing a 8.3 conversion and it wouldn't work for the select clause.

I hope this helps someone.

Alan

Wednesday, July 11, 2007

Using SAS\Share and C#

Ok, I went a little crazy getting SAS\Share working with OleDb from C#. Since I got it figured out, someone else may have the same issue.

Here is how I did it. First of all, make sure you client machine and the SAS\Share server can talk to each other. I tested from a development machine that has SAS on it. Otherwise, a test app will have to be built.

In the web.config, I have these settings:

<add key=\"ShareConnectionString\" value=\"Provider=sas.ShareProvider.1;Data Source=share1;Location=192.168.1.199;User ID=Administrator;Password=<span color=\">*****</span>;Mode=ReadShare Deny None\"/>
<add key=\"SasLibrary\" value=\"demo\">
<add key=\"SasDataSet\" value=\"user_info\">

Here is the C# code:

private static DataTable LoadSasDataSet()
{
try
{
string library = ConfigurationManager.AppSettings["SasLibrary"] ;
string dataset = ConfigurationManager.AppSettings["SasDataSet"] ;
string conn = ConfigurationManager.AppSettings["ShareConnectionString"];

DataSet sasDs = new DataSet();
OleDbConnection sas = new OleDbConnection(conn);
sas.Open();

OleDbCommand sasCommand = sas.CreateCommand();
sasCommand.CommandType = CommandType.TableDirect;
sasCommand.CommandText = library + "." + dataset;

OleDbDataAdapter da = new OleDbDataAdapter(sasCommand);
da.Fill(sasDs);
sas.Close();

return sasDs.Tables[0];
}
catch (Exception ex)
{
Common.AddLogEntry("ERROR", "Unable to load SAS dataset", "", DateTime.Now, ex.ToString());
return null;
}
}

Keep in mind that there is also SQL capability within Share so that would be an option as well.

Tuesday, February 13, 2007

SAS, ASP.NET AJAX, Web Services

In preparation for SAS Global Forum 2007, I have posted 2 videos showing 1) How to set up a web service to read a SAS dataset, 2) How to consume that service in an ASP.NET AJAX page. Simple demos but they should help get people started:

There are several videos demonstrating the concepts involved. They can be found here:

http://utilities.savian.net

Look under the video tab.

Friday, January 19, 2007

Code generation, macros, et al

As a SAS programmer evolves, they start to think:

"Hey, I can create SAS code using data step or macros!"

So they start to go down the happy path of code generation:

data _null_;
file "myplace";
put "data test ;
/ " set a; "
/ " x = wereHavingFunFunc(y)" ;
... etc.
run;

...and pretty soon they have elaborate SAS code in macros, etc. that does nothing but generate code. If you don't use my utility SasEncase to help you do this, you are doing a LOT of extra work but that is another story.

Well, I think the next level of thought (at least for me) was:

"Hey! Why can't I just use ANY programming language to generate SAS code"

Now, think about that for a sec. ANY programming language can be used to generate SAS code.

The reason why SAS is an entry into this area is because that is what all of us know and love. But, don't confine yourself to just using SAS for code generation. Instead, pick other languages as the need arises.

public void CreateSomeSasCode()
{
StreamWriter sw = new StreamWriter(@"c:\temp\mySasCode.sas") ;
sw.WriteLine("data test;") ;
....
}

Now, that's better, a little C# to play with.

Ok, so you would have to wrap a lot of code.

I think I have evolved. Now I actually write the SAS code with macro parms and store them on a server. Then you can just call the stored process and pass the parameters using web services:

DataSet ds = SasServer.Services.ExecuteStoredProcedure("mySasCode.sas", @"%let outdata = 'c:\temp'");

Now, I think we have nirvana: SAS doing what it does best being driven by a modern OOP environment.

Dropping flyballs in left field,
Alan

Monday, October 30, 2006

Inserting records into SQL Server

99+% of the time, I read records from SQL Server into SAS. I typically use C# and do it direct in code. However, I recently needed to write records into SQL Server from the SAS side.

Attempt #1 was to use PROC APPEND. This failed with the following:

"ERROR: During insert: Data was not set for one or more columns."

This was failing on the identity column.

Attempt #2 was to try a SQL Server insert:

proc sql ;
insert into SqlSrvr.Test
select * from newdata
;
quit;

ERROR: Attempt to insert fewer columns than specified after the INSERT table name.
ERROR: Value 1 on the SELECT clause does not match the data type of the corresponding column
listed after the INSERT table name.
ERROR: Value 2 on the SELECT clause does not match the data type of the corresponding column
listed after the INSERT table name.
ERROR: Value 17 on the SELECT clause does not match the data type of the corresponding column
listed after the INSERT table name.

Hmmmm, could it be a problem wit hthe identity column and me using the new XML filed type?

A little bit of sleep and attempt #3 worked:

libname SQLSrvr oledb provider=sqloledb init_string='Provider=SQLOLEDB.1;Integrated Security=SSPI;Persist Security Info=False;Initial Catalog=MyCustomer;Data Source=SERVER01' schema=dbo ;

data NewData;
attrib platform length=$200
periodicity length=$200
level0-level10 length=$200
image length=$1024
data length=$1024
help length=$1024
;
DateTime = DateTime() ;
Platform = "MVS" ;
Periodicity = "Daily" ;
Level0 = "CPU Utilization" ;
Image = "c:\temp\myimage.jpg" ;
Data = "" ;
Help = "c:\temp\myhelp.doc" ;
run;

proc sql ;
insert into SqlSrvr.Test
select * from newdata
;
quit;

proc sql ;
insert into SqlSrvr.Test (datetime,platform, periodicity, level0, image, data, help)
select datetime, platform, periodicity, level0, image, data, help from newdata
;
quit;

Tuesday, October 03, 2006

SAS and AJAX

SAS has no built in support for AJAX at this time. However, you can hack up some of it by using enabling technologies such as ASP.NET or just code it yourself in JavaScript.

However, I have coded AJAX bits in JavaScript using SAS/IntrNet and found the experience less than desirable. A better way to make this happen, IMO, is to use the new Atlas framework from Microsoft:

http://atlas.asp.net/Default.aspx?tabid=47

It's quick and easy and makes coding AJAX much easier. JavaScript is god-awful due to lack of true debugging support but it works. Make it easier though by focusing on doing the AJAX piece in Atlas and let them handle the JavaScript bits. My $0.01.

Alan

SAS throwing RPC error

If you are doing code in C#  and get this error when creating a LanguageService: The RPC server is unavailable. (Exception from HRESULT:...