How to send a text message with SAS

A colleague approached me with this very important business problem:

Every Friday at SAS HQ, SAS cafe staff provides a breakfast goodie in our breakrooms. Often the supplied goodie is delicious, but sometimes it's more...well...healthy. I want to know whether I should eat my breakfast before I leave home on Friday morning, or if it's better to save my calories for the breakfast goodie at SAS. Can I write a SAS program to send me a text message on Friday morning with the "goodie news" so I can make an informed decision?

Find the data source

bgoodieSAS HQ publishes the cafeteria menus on our intranet each day, and on Thursday the published menu includes an entry for the planned Friday goodie. Thus, we know that this "data" is available somewhere. My colleague had the idea of using FILENAME URL or PROC HTTP to have SAS read the cafe's menu web page and parse the details about the breakfast goodie. That's one way to do it, but here's something that I know about web pages: they all have source files somewhere that are used to generate the content you see in the browser. If you can gain access to that source file instead of going through the web server, you'll have a much simpler process.

With a little spelunking on the SAS network, I found the source files that feed the cafe menus. ("Feed" -- see what I did there?) The file with the goodie news is an HTML file that is named with a predictable date stamp. Here's the name for today: "../menus/DailyMenus/BreakfastWeb20160812.html". The source file looks like this:

<!-- Published on Thursday, August 11, 2016 01:02 PM-->
<h2 class="menuHeader">Friday Breakfast Goodie</h2>
<div class="entry fridayGoodie"><div class="menuLeft"><div class="menuLeftContent">  
<span class="entryName">Blueberry Almond Crunch Teacake</span>
</div></div></div>

We want just the goodie name. With a file reference to the menu file, we can easily parse this out with SAS. After this step, the goodie text is stored in the GOODIE macro variable.

/* Assuming this is run on Friday */
%let day = %sysfunc(compress(%sysfunc(today(),yymmdd10.),'-'));
filename bg "../menus/DailyMenus/BreakfastWeb&day..html";
data _null_;
  infile bg dsd;
  length line $ 80 goodie $ 40;
  input line;
  /* Line with the goods */
  if (find(line,'span class="entryName"') >0) then do;
    startpos = find(line, '>');
	endpos=find(line, '</');
	goodie = substr(line,startpos+1,endpos-startpos-1);
	call symput('GOODIE',goodie);
  end;
run;

Sending the SMS message with SAS

Finally, we're getting to the part of this article that you probably came to read: how to send the text message with SAS. This feels a bit like cheating, but there isn't any magic function in SAS that sends SMS messages. Instead, we're going to rely on a mobile phone service trick. Most phone service providers allow you to send text messages via e-mail by using a special address scheme for the text message recipient. Each carrier is a little different, but you can find the details with a simple internet search: "sms via email".

My current carrier is AT&T, and so to receive a text message as e-mail I would send it to the address my_number@txt.att.net. With the FILENAME EMAIL method, it's easy to send an e-mail using SAS. The trickiest part might be to find your SMTP server host name and other details. Using a service like Gmail? I've written a blog post about how to send e-mail with that method. Note: as with all phone text messages, text-message charges from your carrier may apply.

options emailhost='mailserver.company.com' emailsys=smtp;
 
/* NOT my real phone number */
filename msg email to="9198675309@txt.att.net" 
  FROM = "Cafe Bot <youremail@company.com>"
  subject="Breakfast goodie: &Goodie.";
 
data _null_;
 file msg;
 put 'Bon appetit!';
run;

Here's an example of the text message from my phone:

bgoodiesms
"Blueberry Almond Crunch Teacake" - yum! I'm skipping the Cheerios this morning and taking breakfast at work instead.

Scheduling the SAS job to run automatically

This entire process is valuable only if we can run it unattended, without having to remember to trigger it every Friday morning. After all, if you can log in to run a SAS job that sends yourself a text message, you can (probably more easily) just check the breakfast menu for the day. So my colleague scheduled this program to run early every Friday morning at SAS using a cron job, the ubiquitous scheduler on UNIX. I imagine that his crontab -l output looks something like:

00 05 * * 5 /usr/local/bin/sas -nodms -sysin '/u/userid/food/breakfastgoodie.sas'

That sets up the job to run at 05:00 on day 5 (Friday), running SAS with this program as input. In the immortal words of Ron Popeil: Set it, and forget it!

Post a Comment

Using Jupyter and SAS together with SAS University Edition

A few months ago I shared the news about Jupyter notebook support for SAS. If you have SAS for Linux, you can install a free open-source project called sas-kernel and begin running SAS code within your Jupyter notebooks. In my post, I hinted that support for this might be coming in the SAS University Edition. I'm pleased to say that this is one time where my crystal ball actually worked -- Jupyter support has arrived!

(Need to learn more about SAS and Jupyter? Watch this 7-minute video from SAS Global Forum.)

Start coding in the notebook format

If you download or update your instance of SAS University Edition, you'll be able to point your browser to a slightly different URL and begin running SAS programs in Jupyter. Of course, you can continue to use SAS Studio to learn SAS programming skills. Having trouble deciding which to use? You don't have to choose: you can use both!

jupyter_ue
If you've started SAS University Edition within Oracle Virtual Box, you can find SAS Studio at its familiar address: http://localhost:10080/. And you can find the Jupyter notebook environment at: http://localhost:18888/. (If you're using VMWare, the URLs are slightly different. Check the documentation.)

Why did SAS add support for Jupyter notebooks? The answer is simple: you asked for it. While we believe that SAS Studio provides a better environment for producing and managing SAS code, Jupyter notebooks are widely used by students and data scientists who want to package their code, results, and documentation in the convenient notebook format. Notebook files (*.ipynb format) are even supported on GitHub, easily shareable and viewable by others.

Now, what are the limitations?

jupyter_uemenuWithin SAS University Edition, the Jupyter environment supports only SAS programs. The Jupyter project can support other languages, including Python, Julia, and R (the namesake languages) and dozens of others with published language kernels. However, because of the virtual-machine core of the SAS University Edition, those other languages are not available.

Support for other languages (as well as for the Jupyter console) is available when you use Jupyter in a standalone SAS environment. In fact, the sas_kernel project recently received some exciting updates. You can now host the Jupyter environment on a different machine than your SAS server (although Linux is still the only supported SAS host), and the installation process has been streamlined. See more on the sassoftware GitHub home for the sas_kernel project.

Where can you learn more about Jupyter in SAS University Edition?

Check out the help topics for SAS University Edition, beginning with this one: What is Jupyter Notebook in SAS University Edition?

And if you need help or advice about how to make the best use of SAS University Edition, check out the SAS Analytics U community. There are plenty of experts in the forum who would love to help you learn!

Post a Comment

How to publish to a Slack channel with SAS

Slack is a tremendously popular app for team collaboration. At its core, it's an open messaging app that allows team members to communicate with each other in real time. It works well with "startup"-style teamwork, which requires constant communication among people who aren't always located together. I've heard a lot about Slack from colleagues and from podcasts (Slack is a frequent advertiser on tech shows), but I'd never tried it myself until recently.

I was most curious about their APIs, which allow you to integrate other applications into the Slack messaging platform. Months ago, a colleague had asked me whether it was possible to integrate SAS output with Slack. I suspected that we could do something with PROC HTTP, but didn't get around to trying until today. I can now answer for certain: Yes! It's possible...and it's sort of fun!

Get started with a Slack "webhook"

I won't go into how you get started with Slack, except to say that it's really easy (and free) to create an account and a "channel" (your message space) for a small team. Assuming that you have that going, I'm going to show you how to use SAS to publish to your channel.

Slack supports a feature called "Incoming Webhooks," which is basically a simple endpoint URL that you can send messages to. From your Slack channel, you can select to Add Configuration, which takes you to the option to add a new Incoming Webhook:

slackwebhook
Click the button and Slack will provision a cryptic URL that's your unique endpoint. Any process that uses HTTP calls to POST content to that URL can publish content. The content itself (called the payload) is delivered in JSON format to the API.

Example 1: Simple in-line message with PROC HTTP

The Slack documentation supplies examples that use curl (command-line URL tool), but PROC HTTP is the SAS equivalent. Here's a simple example:

%let webhookUrl = https://hooks.slack.com/services/<your Webhook URL>;
 
/*
  Simple example with direct text 
*/
 
filename resp temp;
proc http
 url="&webhookUrl"
 method="POST"
 /* IN= supports text in SAS 9.4m3.  Earlier release? Use fileref with content */
 in='payload={"channel": "#fromsas", 
     "username": "sasprogram", 
     "text": "Created using PROC HTTP!"}'
 out=resp
 ;
run;

To try this, you'll have to first get your own Webhook URL and plug it into the program. I'd loan you mine, but you're not on my channel so you can't check the results...which look like this:

Slack simple

Example 2: Share rich messages with PROC HTTP and JSON

Slack also allows multipart messages with simple formatting, including some colors, custom icons, and working links. This requires a little bit more JSON content in the payload, including an array of attachments. Here's a more complex example:

/*
  More complex messages, with multiple parts.
  Use the attachments fields that Slack supports
*/
filename rich temp;
 
data _null_;
 file rich;
 infile datalines4;
 input;
 put _infile_;
datalines4;
payload=
   {   
    "channel": "#fromsas", 
    "username": "sasprogram",
    "icon_emoji": ":fax:",
   "attachments":[
      {
  "fallback":
   "New SAS Dummy post!: <http://blogs.sas.com/content/sasdummy|The SAS Dummy blog>",
 "pretext":
   "New SAS Dummy post!: <http://blogs.sas.com/content/sasdummy|The SAS Dummy blog>",
         "color":"#3030F0",
         "fields":[
            {
               "title":"Great news!",
               "value":"That Chris...he's done it again!",
               "short":false
            }
         ]
      }
   ]
  }	
;;;;
 
proc http
 url="&webhookUrl"
 method="POST"
 in=rich
 out=resp
 ;
run;

Here's the result. See how I selected a nice modern emoji as the account icon? Slack has hundreds of these available.

Slack message

Example 3: Data-driven JSON payload published to Slack using PROC HTTP

But the real power of this integration from SAS is the ability to push dynamic, data-driven content to your Slack channel. To accomplish that, you need to dynamically generate your JSON content with the fields that you want to share. Here's an example that publishes the output of a SAS procedure (read from a data set) to the channel:

/*
 And finally an example that publishes values from data!
*/
 
/* Calculate some data */
proc means data=sashelp.class noprint;
var age;
output out=stats;
run;
 
/* file to hold the JSON payload */
filename msg temp;
 
/* Create the start of the JSON payload */
data _null_;
 file msg ;
 infile datalines4;
 input;
 put _infile_;
datalines4;
payload=
   {   
    "channel": "#fromsas", 
    "username": "sasprogram",
    "icon_emoji": ":fax:",
   "attachments":[
      {
         "fallback":"Latest Stats for AGE in SASHELP.CLASS",
         "pretext":"Latest Stats for AGE in SASHELP.CLASS",
         "color":"#D000FF",
         "fields":[
;;;;
 
/* fill in the data fields in the middle */
data _null_;
 file msg mod;
 set stats end=eof;
 put '{ "short":false, "title": "' _stat_ '",';
 put '"value": "' age '" }';
 /* separate values with commas, except the last */
 if not eof then put ",";
run;
 
/*
  And finish with the tail end of the payload
*/
 
data _null_;
 file msg mod;
 infile datalines4;
 input;
 put _infile_;
datalines4;
         ]
      }
   ]
  }	
;;;;
 
proc http
 url="&webhookUrl"
 method="POST"
 in=msg
 out=resp
 ;
run;

Here's the result -- the latest figures from SASHELP.CLASS!
Slack data

I've shared the complete example code on a public Gist on GitHub. Remember, to try it yourself you'll need to:

  • Create a Slack account, if you don't have one. Along with a new Slack channel.
  • Use the Slack site to add a new Incoming Webhook for your channel
  • Replace the webhookURL macro value in my example code with your specific Webhook URL.

Have fun! And if you create anything really interesting, I hope you'll invite me to your Slack channel!

Post a Comment

Build your Pokémon library using SAS and the Pokéapi

Definitely NOT a copyrighted Pokémon

Definitely NOT a copyrighted Pokémon

Today is #EmbraceYourGeekness day, and you are either reveling in this new crazy town inhabited by Pokémon GO, or you are hiding in your house trying to avoid all of the Pokémon GO zombies wandering around.

But since I'm living in SAS these days -- not just the place (at SAS headquarters), but the software -- I decided to see if I could use my SAS tools to "find" some Pokémon in my work. Thanks to PROC HTTP and fantastic service called the Pokéapi, I've managed some success.

Calling the Pokéapi REST API with SAS

PROC HTTP is the the SAS procedure that you can use to call REST APIs. And the Pokéapi site is a REST API that yields on-demand information about our new favorite creatures. Here's a quick example:

/* utility macro to put file contents to SAS log */
%macro echoResp(fn=);
data _null_;
 infile &fn;
 input;
 put _infile_;
run;
%mend;
 
filename resp temp;
 
/* Call the Pokeapi to list all available Pokemon */
proc http 
  url="http://pokeapi.co/api/v2/pokemon/?limit=1000"
  out=resp
  method="GET";
run;
 
%echoResp(fn=resp);

Here's a snippet of my "Pokémon log":

pokelog
I need a DATA step to read and parse some of the API response, which is in JSON. I'm using a simple INFILE with SCANOVER to parse out just a few bits and create a data set of all the character names (811 of them). The API response is basically one huge line of text, so I'm using the @@ directive to keep the INPUT statement working on the same "record."

data pokemon;
 infile resp lrecl=65635 scanover truncover;
 length name $ 20;
 input @'"name":' name $quote20. @@;
run;

If you're using the free SAS University Edition, this code should work there too! The Pokéapi site is accessed using HTTP and not HTTPS. (HTTPS doesn't work from SAS University Edition because the secure/encryption components are not included.)

pokenames
I can also use PROC HTTP and the API to gather an incredible amount of detail about each character. I found Jigglypuff at record 39, so here's my code to retrieve and parse some more details. Note that there are hundreds of attributes available for each character, and I'm pulling just a couple of them.

proc http 
  url="http://pokeapi.co/api/v2/pokemon/39"
  out=resp
  method="GET";
run;
 
data jiggly;
 infile resp lrecl=500000 scanover truncover;
 length weight 8 base_experience 8;
 input @'"weight":' weight 2. @@;
 input  @'"base_experience":' base_experience 2. @@;
run;

And the results:

jiggly

Going to "the source" for raw Pokémon data

Parsing JSON using SAS is fun and all, but sometimes you just want access to the raw data. And it turns out that the Pokéapi folks have a project on GitHub with everything we need. We can use PROC HTTP to get to that too! And then use SAS to join and analyze/visualize the results! These calls are to the GitHub site to access the "raw" view of data files in the repository. Note: GitHub does use HTTPS (sorry, SAS University Edition users...).

Update 15Jul2016: Since I originally published this post, I've heard from Pokémon experts and data visualization experts. They correctly pointed out that my default PROC FREQ plot did not represent the best of SAS graphics nor Pokémon abilities. I've adjusted my code and republished with these changes:

  • Used PROC SGPLOT to show the PROC FREQ output with just the 20 most common abilities, instead of cramming ALL abilities into a single chart.
  • Added GUESSINGROWS=MAX to my PROC IMPORT steps to ensure variables are assigned the proper lengths. Robert Allison pointed out that some names were being truncated.
  • Added a tabular report of the less-common abilities -- those that show up in just one Pokémon character each.
  • Cleaned up the SAS code for readability here, and updated the entire thing on my GitHub Gist.
/* Location of the PokeAPI project on GitHub */
%let githubRoot=https://raw.githubusercontent.com/PokeAPI/pokeapi/master/data/v2/csv;
 
/* temp location for CSV files, works on UNIX or Windows */
%let csvOut = %sysfunc(getoption(WORK));
 
filename pk_csv "&csvOut./pokemon.csv";
 
proc http
 url="&githubRoot./pokemon.csv"
 method="GET"
 out=pk_csv;
run;
 
proc import file=pk_csv out=pokemon dbms=csv replace;
guessingrows=max;
run;
 
filename pk_ab "&csvOut./pokemon_ab.csv";
 
proc http
 url="&githubRoot./pokemon_abilities.csv"
 method="GET"
 out=pk_ab;
run;
 
proc import file=pk_ab out=abilities dbms=csv replace; 
guessingrows=max;
run;
 
filename pk_abn "&csvOut./pokemon_abnames.csv";
 
proc http
 url="&githubRoot./abilities.csv"
 method="GET"
 out=pk_abn;
run;
 
proc import file=pk_abn out=abnames dbms=csv replace;
guessingrows=max;
run;
 
/* Join the 3 data sets */
proc sql;
   create table work.withabilities as 
   select t3.identifier as pokemon, 
          t1.identifier as ability
      from work.abilities t2, work.pokemon t3, work.abnames t1
      where (t2.pokemon_id = t3.id and t2.ability_id = t1.id);
quit;
 
/* Frequency of Abilities among the characters */
proc freq data=work.withabilities noprint
	order=freq
;
	tables ability / nocum  
        scores=table 
        out=ability_freq;
run;
 
/* Create a more readable plot based on the most common abilities */
ods graphics on / height=800 width=800;
title "Most common abilities among Pokemon";
proc sgplot data=ability_freq (obs=20);
  hbar ability / response=count barwidth=.4; 
  yaxis discreteorder=data display=(nolabel);
  xaxis label="# Pokemon who possess it" grid ;
run;
 
/* Join the FREQ output with pokemon names and abilities */
/* for a report on "rare" abilities                      */
proc sql; 
  create table rareabilities
    as select t1.pokemon, t1.ability from
      withabilities t1 inner join ability_freq t2 on 
      (t1.ability=t2.ability AND t2.count=1)
    order by t1.ability;
quit;
 
title "Rare abilities among Pokemon (each possessed by only one character)";
proc print data=rareabilities noobs;
var ability pokemon;
run;

Here's what PROC FREQ and SGPLOT shows about how common some of the abilities are among the Pokémon. "Levitate" appears to be common (good thing, because I'm not sure that they all have legs).

pokeabilities
And the table of less common abilities and who has them? Simple to show with PROC PRINT. I see that "slow start" is uncommon (but that's an ability that I think I can claim for myself...).
pokerare
Full code: I placed all code presented here in a public Gist on GitHub. Enjoy!

Post a Comment

How to read the contents of a file into a SAS macro variable

I've been working on a SAS program that can add content to the SAS Support Communities (more on that in a future post). Despite my 20+ years of SAS experience, there are a lot of SAS programming tricks that I don't know. Or that I use so infrequently that I always need to remind myself how to accomplish them.

Here's one. I needed to read the contents of an external text file into a SAS macro variable, so that I could then use that value (a very long text string) as part of an API call. In searching for a technique that would work for me, I came across a similar question on SAS Support Communities -- one that had been solved by our resident SASJedi, Mark Jordan. Perfect!

Here's the solution that worked for me:

FILENAME msghtml "path-to-text-file" ;
data _null_;
   length text $32767;
   retain text '';
   infile msghtml flowover dlmstr='//' end=last;
   input;
   text=cats(text,_infile_);
   if last then call symput('MSGBODY',text);
run;
/* file contents is now in &MSGBODY macro var */

The RETAIN statement allows me to build up the "text" variable as the DATA step processes multiple lines. The END=last on the INFILE statement sets a flag when we hit end-of-file, so I know that we're done and I can CALL SYMPUT the macro value. The FLOWOVER option tells the INPUT statement to keep reading even if no input values are found in the current record. (FLOWOVER is the default behavior, so the option probably isn't needed here.) DLMSTR allows you to specify a multichar delimiter string that's different than the default delimiter (a space character). We're using the CATS function to concatenate a trimmed version of the input buffer (_INFILE_) to the RETAINed "text" variable.

For my project I needed to URL-encode the text value for use in an HTTP-based REST API. So for me, the last line is really:

if last then call symput('MSGBODY',urlencode(trim(text)));

The SAS Support Communities has been a big help to me during this project -- a project that is designed to improve the communities even more. It's a virtuous cycle! I hope that this helps some of you out there, too.

Post a Comment

Video: Demonstrating the new features in SAS Enterprise Guide 7.1

Would you like to see the latest features of SAS Enterprise Guide in action? Of course you would! That's why it's well worth the 12 minutes of your time to watch this video from SAS Global Forum 2016.


In the video, Casey Smith (SAS' R&D manager of the SAS Enterprise Guide team) shows off the favorite new features, including:

Casey also talks about his unique perspective as a second-generation SAS user. His Mom is a long-time SAS user; Casey was raised with SAS in the house! It's only appropriate that Casey went on to join SAS as an employee. He frequently presents for user groups and you can often find Casey (as CaseyS_SAS) on the SAS Enterprise Guide discussion board in SAS Support Communities.

Post a Comment

Assign a SAS library to a different path depending on your OS

One thing that we have a lot of at SAS: installations of SAS software that we can run. I have SAS for Windows on my laptop, and I have access to many centralized instances of SAS that run on Linux and Windows servers. (I also have access to mainframe SAS, though it's been a while since I've used it. When I log in, I picture a Rube-Goldberg style mechanism that pokes an intern to mount a tape so my profile can be reloaded.)

I often develop programs using my local instance of SAS and SAS Enterprise Guide, but deploy them for use on a central server. I might run them as batch jobs or interactively with SAS Enterprise Guide or SAS Studio or even in SAS/IntrNet.

Our IT department wants SAS employees to have seamless access to their files whether on Windows or on Unix-style file systems, and so they make it easy to access the same network path from Windows (using UNC notation, or "\\server\path" syntax) and Unix (using "/node/usr/path" syntax). As I develop my SAS programs, I want the programs to work the same whether run from Windows or Unix, and I don't want to have to change LIBNAME paths each time. Fortunately, SAS programs are usually portable across different operating systems, and while SAS data sets might have different encodings across systems, SAS can always read a data set that was created by a different version.

I have a simple technique that references the proper path for the operating system that I'm using. I build a SAS macro variable by using the IFC function and the &SYSSCP automatic variable to check whether I'm running on Windows, then assign the path accordingly.

/* Use the IFC function as a shorthand for if-then, returning a character string */
%let tgtpath = %sysfunc(
  ifc(&SYSSCP. = WIN,
       \\sasprod\root\dept\mydept\project,
       /r/node/vol/vol01/mydept/project
    )
  );
 
libname tgt "&tgtpath.";

When I run this on SAS for Linux, I see this in the log:

NOTE: Libref TGT was successfully assigned as follows: 
      Engine:        V9 
      Physical Name: /r/node/vol/vol01/mydept/project

And on Windows:

NOTE: Libref TGT was successfully assigned as follows: 
      Engine:        V9 
      Physical Name: \\sasprod\root\dept\mydept\project

Post a Comment

How SAS Support Communities can expedite your tech support experience

SAS Technical Support has earned a wonderful reputation for being friendly, knowledgeable, and thorough. Every customer that I talk to is delighted by the experience. That's why what I'm about to say might be heresy, but here it goes. If you have a question about how to accomplish a task using SAS software, you can probably find your answer faster on SAS Support Communities.

With over 50,000 topics already cataloged, the chances are high that your question has already been asked and answered on the communities. And if you need to post a new question, the peer network of SAS communities members represent thousands of experts who can respond to your question immediately.

Search communities.sas.com. Find solutions.

Search communities.sas.com. Find solutions.

Self-service is self-satisfying

No matter how pleasant the experience, calling customer support is a last resort for many people. Studies show that millennials are much happier when they can solve a question themselves by using a self-serve option (like an online community!). I'm not a millennial (missed that cutoff by a decade or two) -- but I feel exactly the same way.

There are additional benefits to using SAS Support Communities. For starters, you'll come to learn who the experts are in your field. By following them and reading their work, you can learn more about questions that you don't even have yet. In addition, many SAS employees read and reply to topics in the communities. For example, you might read an answer about SAS Enterprise Miner from a developer who actually works on the product. That direct line of communication is relatively rare in the software industry, especially for a company like SAS that has so many products and customers worldwide.

SAS Support Communities DO work for SAS users

Because we're SAS and we measure everything, we have ways of measuring user success on our communities. First, we can look at the data around the topics viewed. Solved topics make up 30% of all page views on the site. Many users who visit the communities site look at just one topic per visit -- the one that solves their immediate issue. That tells me that they found what they needed right away, and then moved on with their lives.

The second way we measure success: we ask. In the past few months, nearly 4000 people completed our "Tell us what you think" questionnaire. 72% of survey respondents say they found what they were looking for on SAS Support Communities. That's a solid benchmark that we strive to improve -- but our industry experts tell us that our success rate compares very favorably to other communities sites.

Communities respond fast -- no SLA needed

Communities reply time, past 90 days

Communities reply time, past 90 days

If you've used SAS Technical Support, you might be aware of their policies around response times. Support tracks can have different levels of severity, and the more severe tracks have a quicker SLA (service-level agreement, or promised response time.) In practice, most customers experience much faster service than the SLA policies promise, but that's not guaranteed. While SAS Support Communities don't offer a service-level agreement, they are "open" 24 hours a day, every day, around the world. Our data show that community members respond quickly, often within minutes of your question. 92% of well-phrased questions receive a reply within a day. For questions that eventually show as solved, the reply that solves the question arrives in 8 hours -- 72% of the time. Can you see how tapping a community of thousands of experts can expedite your path to learning and to a solution?

How to ask a good question and receive a fast reply

Experts who respond on the communities have tremendous experience and intuition, but they aren't mind readers. You have to form good questions if you want to receive a helpful response. Here are some tips for success:

  • Use a precise subject line. Try to include your goal, error message, SAS procedure name, function -- whatever keywords will help an expert to "pick up" your question as something he/she could answer. (Pro tip: "Urgent help needed!" or "SAS question" are not effective subject lines.)
  • Share example data. Many questions can be answered properly only when the responders can see the "shape" and characteristics of your data. Don't share anything proprietary, of course.
  • Show what you have tried. Community members love to nudge you towards the proper solution, but it helps if you share what you've already tried and if you hit any walls...explain. If you have special constraints (must use a older version of SAS, certain product set, etc), share that too.
  • Search on communities site first, before posting a new question. The act of entering a new question helps with this because you'll see the subject line "autocomplete" with suggested matching topics, even before you post. That's another reason that the first tip (precise subject line) is so important.
  • Post into most appropriate board. There are boards for most SAS products, and these are monitored regularly by experts who specialize. Posting on the correct board helps your topic to be seen by the best experts.
  • When you receive a helpful reply, come back and mark it as an Accepted Solution, or at least click Like for the replies that are helpful. This action will help you and others to find the answer in the future.

When to call SAS Technical Support

Your peers in the community cannot solve every problem that you encounter. If you're experiencing slow performance that you can't explain, or having installation troubles, or seeing "crashes" -- you probably need to open a track with SAS Technical Support. The support consultants are experts in diagnosing and getting to the bottom of such issues. You'll most likely need to share details and logs that you would not typically share in a public forum. However, the sweet spot of the communities is the "how do I" question -- a syntax, best practice, or simple usage query that you encounter as you learn to use the software. And SAS users never stop learning -- even those of us who have decades of SAS experience.

askfindshare_icon

Post a Comment

Tell SAS to read a database field as CHAR instead of INT or BIGINT

Yesterday a frustrated SAS user complained on Twitter. He's working with a database that stores an ID field as a big long number (perhaps using the database BIGINT type), and SAS can't display a number greater than 15 digits. Well, it's actually 16 digits, depending on the value:

%put Biggest Exact Int = %sysfunc(constant(EXACTINT,8));
>> Biggest Exact Int = 9007199254740992

It's a controversial design decision to use an integer to represent an ID value in a database. You might save a few bytes of storage, but it limits your ability to write programs (not just SAS programs) that have to store and manipulate that value. And if you don't need to do math operations with the ID, your data consumers would rather see a nice character value there.

Fortunately, when working with databases, you can tell SAS to read numeric values as character values into your SAS data sets. In addition to solving the precision problem I've just described, this can also help when you need to join database fields with other source systems that store their key fields differently. It's usually much easier to convert the field "on the way in" rather than try to mangle it after you've already read in the records. Use the DBSASTYPE= data set option to tell SAS how to read database fields. Here's a sample SAS program that shows how I access a table using ODBC, one step without and one step with the DBSASTYPE= option.

libname wpblogs odbc datasrc="wpblogs";
 
options obs=10;
data users_IDint (keep=ID display_name);
  set wpblogs.wp_users;
run;
 
data users_IDchar (keep=ID display_name);
  set wpblogs.wp_users 
    (dbsastype=(ID='char(20)'));
run;

Here are the resulting tables; you can see the simple difference. One has ID as a number, and one has it as a character. Magic!

dbsastype_out
The DBSASTYPE= option is supported for virtually all SAS/ACCESS database engines, including the ubiquitous SAS/ACCESS to ODBC.

Oh, and you might be wondering how things turned out for our frustrated user on Twitter. Our SAS Cares social media team heard his plea and responded -- as they always do. And our user not only found the information useful, he took it a step further by replying back with an additional syntax tip.

Post a Comment

Copy McCopyface and the new naming revolution

As a parent of children who love books, I can tell you that there is something humorous about taking a first name, adding a "Mc" and then a rhyming surname to make up a brand new character name. My daughters always loved to read the adventures of Harry Mclary from Donaldson's Dairy, and we loved to read it aloud to them. It was just fun.

The Boaty McBoatface phenomenon has taken this to the next level by adding "face" as a suffix, which often has a funny punctuating effect ("silly face", "Chu chi face", "doody face," etc. Hilarious!).

I thought that I was done writing blogs about Boaty McBoatface, but I've been hearing from so many people about this topic that I need at least this one more to finish it off.

Name our ship: final results

Spoiler: NERC is not going to christen the new vessel "Boaty McBoatface." Instead the name comes from the 4th-highest vote-getter, "David Attenbourough." The famous explorer earned over 11,000 votes, or 2.78% of all votes cast. However, as a crowd-pleasing nod to the plebians, NERC will name one of the ship's remotely operated submarines "Boaty McBoatface." Hooray! I grabbed the final voting results from the NameOurShip website and re-ran my analysis. Here's the final top 10 standings.

boatyfinal
I also ran a three-panel visualization, using the method that Rick Wicklin shared, so you can see how unevenly distributed the votes remained at the end.

3panelboaty

Many imitators, but original stays on top

The original entry of "Boaty McBoatface" inspired many copycats who submitted names with a similar formula. None of them seemed to have the wide appeal of Boaty, probably because they weren't first and original, but here they are with their vote counts.

boatycopy
I found these in the data with a simple SQL LIKE operator, finding those names that had the pattern "blank-y Mc-blank-y".

proc sql;
   create table work.TheMCs as 
   select t1.title, 
            (sum(t1.likes)) format=comma20. as totalVotes
      from work.votes t1
      where t1.title like '%y Mc%'
      group by t1.title
      order by totalVotes desc;
quit;

Boaty Mac: start of a popular movement

rockymc
Silly names are not limited to research vessels. The world has embraced the Boaty McBoatface pattern. A colleague sent me news about Parsey McParseface, an open-source project from Google. Grumpy McNoisybutt was proposed as a name for a rattlesnake. Even my own daughter has created Rocky McRockface, a major character in her rock cycle project.

I won't say that this is my final Boaty post. Who knows? In a couple of years I might be reporting on "Boaty McBoatface"-inspired baby names. I'm confident that at least one poor child will bear the name; that's the sort of world we live in. Fortunately, children usually find a way to have revenge on their parents (which is why I have nothing but praise for Rocky McRockface).

Post a Comment