lunes, 18 de febrero de 2013

When SAP HANA met R - What's new?


Since I wrote my blog When SAP HANA met R - First kiss I had received a lot of nice feedback...and one those feedbacks was..."What's new?"...

Well...as you might now SAP HANA works with R by using Rserve, a package that allows communication to an R Server, so really...there can't be too many new features...but...the cool thing is that SAP HANA has been tested with R 2.15 and Rserve 0.6-8 so any new features added on R and Rserve and instantly available on SAP HANA -;)

But hey! I wouldn't write a blog is there wasn't at least one new cool feature, right? You can read more about it here SAP HANA R Integration Guide.

Of course...for this you need SAP HANA rev. 48 (SPS5)

So what's the cool feature? Well...you can store a train model like lm() or ksvm() directly on a table for later use. This is really cool, because if you have a big calculation to be made, you only need to store the model and use it later without having to reprocess everything again.

Let's make an example...and hope all the R fans doesn't kill me for this...because when it comes to statistics...I'm really lost in the woods -:(

Let's say we have two tables from the SFLIGHT package...SPFLI and STICKET, so we want to predict how many times a customer is going to flight to different destinations (CITYFROM-CITYTO) depending on how many times all the customers has flights to those very same locations.

We're going to create one SQLScript file to get the information, transform it, create the model and store it in the database...

Build_Flight_Model.sql
--Create a TYPE T_FLIGHTS to grab the information from the SPFLI and STICKET tables.
 
DROP TYPE T_FLIGHTS;
CREATE TYPE T_FLIGHTS AS TABLE (
CARRID NVARCHAR(3),
CUSTOMID NVARCHAR(8),
CITYFROM NVARCHAR(20),
CITYTO NVARCHAR(20)
);
 
--Create a TYPE FLIGHT_MODEL_T and a table FLIGHT_MODEL to get and store the model in the database.
 
DROP TYPE FLIGHT_MODEL_T;
CREATE TYPE FLIGHT_MODEL_T AS TABLE (
ID INTEGER,
DESCRIPTION VARCHAR(255),
MODEL BLOB
);
 
DROP TABLE FLIGHT_MODEL;
CREATE COLUMN TABLE FLIGHT_MODEL (
ID INTEGER,
DESCRIPTION VARCHAR(255),
MODEL BLOB
);
 
--This R procedure will receive the T_FLIGHTS information, create table containing a field call FLIGHT_NAME 
--that will contain the concatenation
--of the CARRID, CITYFROM and CITYTO. ie: AA-NEW YORK-SAN FRANCISCO.
--We're going to convert the table into a data.frame, so all the similar values in FLIGHT_NAME are going to 
--be summarized.
--Using the subset() function, we're going to get rid of all the FLIGHT_NAME's that has a frequency lower 
--or equal than 0.
--We're going to use the nrow() function to count all the FLIGHT_NAME occurrences and multiply that by 
--10 (Stored in f_rows)
--We're going to use the sum() function to sum all the frequencies and the divide it by f_rows 
--(Stored in f_sum)
--We're going to use the mapply() function to divide each of the frequencies by f_sum
--We're going to use the order() function to sort by FLIGHT_NAME
--We're going to use the colnames() function to assign names to our data.frame
--We're going to use the lm() function to generate a Linear Regression based on the FLIGHT_NAME 
--and it's frequency
--Finally, we're going to use the generateRobjColumn() custom created function to store the result of the 
--model in the buffer.

DROP PROCEDURE FLIGHT_TRAIN_PROC;
CREATE PROCEDURE FLIGHT_TRAIN_PROC (IN traininput "T_FLIGHTS", OUT modelresult FLIGHT_MODEL_T)
LANGUAGE RLANG AS
BEGIN
generateRobjColumn <- function(...){
          result <- as.data.frame(cbind(
                    lapply(
                              list(...),
                              function(x) if (is.null(x)) NULL else serialize(x, NULL)
                    )
          ))
          names(result) <- NULL
          names(result[[1]]) <- NULL
          result
}
tab<-table(FLIGHT_NAME=paste(traininput$CARRID,traininput$CITYFROM,traininput$CITYTO,sep="-"))
df<-data.frame(tab)
ss<-subset(df,(df$Freq>0))
freq<-ss$Freq
f_rows<-(nrow(ss)) * 10
fsum<-sum(freq) / f_rows
ss$Freq<-mapply("/",ss$Freq, fsum)
flights<-ss[order(ss$FLIGHT_NAME),]
colnames(flights)<-c("FLIGHT_NAME","FREQUENCY")
lmModel<-lm(FREQUENCY ~ FLIGHT_NAME,data=flights)
modelresult<-data.frame(
ID=c(1),
DESCRIPTION=c("Flight Model"),
MODEL=generateRobjColumn(lmModel)
)
END;
 
--This SQLSCRIPT procedure will grab all the needed information from the tables SPFLI and STICKET 
--and will assign it to flights
--We're going to call the R procedure FLIGHT_TRAIN_PROC
--We're going to do an INSERT to finally store the model from the buffer into the database
 
DROP PROCEDURE POPULATE_FLIGHTS;
CREATE PROCEDURE POPULATE_FLIGHTS ()
LANGUAGE SQLSCRIPT AS
BEGIN
flights = SELECT SPFLI.CARRID, CUSTOMID, CITYFROM, CITYTO
             FROM SFLIGHT.SPFLI INNER JOIN SFLIGHT.STICKET
             ON SPFLI.CARRID = STICKET.CARRID
             AND SPFLI.CONNID = STICKET.CONNID;
CALL FLIGHT_TRAIN_PROC(:flights, FLIGHT_MODEL_T);
INSERT INTO "FLIGHT_MODEL" SELECT * FROM :FLIGHT_MODEL_T;
END;
 
CALL POPULATE_FLIGHTS();

When we call POPULATE_FLIGHTS(), our FLIGHT_MODEL table should look like this...



If you are wondering why we have an "X"...it's because the content is serialized and stored in a BLOB field...if you inspect the content, you will receive a bunch of weird hexadecimal numbers...

Anyway...it took 6.165 seconds to SAP HANA to process 1,842,160 records.

Now the we have our model safely stored in the database, we can move to our next SQLScript file...

Get_and_Use_Flight_Model.sql
--We're going to create a TYPE T_PREDICTED_FLIGHTS and a table PREDICTED_FLIGHTS to store the information
--of the current number of flights and
--the estimated (according to our prediction) number of flights
 
DROP TYPE T_PREDICTED_FLIGHTS;
CREATE TYPE T_PREDICTED_FLIGHTS AS TABLE (
CUSTOMID NVARCHAR(8),
FLIGHT_NAME NVARCHAR(60),
FREQUENCY INTEGER,
PREDICTED INTEGER
);
 
DROP TABLE PREDICTED_FLIGHTS;
CREATE TABLE PREDICTED_FLIGHTS (
CUSTOMID NVARCHAR(8),
FLIGHT_NAME NVARCHAR(60),
FREQUENCY INTEGER,
PREDICTED INTEGER
);
 
--In this R procedure, we're going to receive the flight for a given customer, the model stored in the database 
--and we're going to return the result so it can be
--stored in our PREDICTED_FLIGHTS table.
--We're going to use the unserialize() function to extract the model.
--We're going to create a table containing a field call FLIGHT_NAME that will contain the concatenation of the 
--CARRID, CITYFROM and CITYTO.
--ie: AA-NEW YORK-SAN FRANCISCO. and also the CUSTOMID
--We're going to convert the table into a data.frame, so all the similar values in FLIGHT_NAME are going to be 
--summarized.
--We're going to use the colnames() function to assign names to our data.frame
--We're going to use the nrow() function to get the number of records in the data.frame (Stored in dfrows)
--We're going to use the rep() function to repeat the CUSTOMID value of the first record dfrows times
--We're going to use the predict() function to predict the amount of flights based on our model (retrieved 
--from the database) and the new data that we recovered
--Finally, we're going to create a data.frame containing all the information that should be stored in our 
--table PREDICTED_FLIGHTS

DROP PROCEDURE USE_FLIGHT;
CREATE PROCEDURE USE_FLIGHT(IN flights T_FLIGHTS, IN modeltbl FLIGHT_MODEL_T, OUT out_flights T_PREDICTED_FLIGHTS)
LANGUAGE RLANG AS
BEGIN
lmModel<-unserialize(modeltbl$MODEL[[1]])
tab<-table(FLIGHT_NAME=paste(flights$CARRID,flights$CITYFROM,flights$CITYTO,sep="-"),CUSTOMID=flights$CUSTOMID)
df<-data.frame(tab)
colnames(df)<-c("FLIGHT_NAME","CUSTOMID","FREQUENCY")
dfrows<-nrow(df)
customid<-rep(df$CUSTOMID[1],dfrows)
prediction=predict(lmModel,df,interval="none")
out_flights<-data.frame(CUSTOMID=customid,FLIGHT_NAME=df$FLIGHT_NAME,FREQUENCY=df$FREQUENCY,PREDICTED=prediction)
END;
 
--This SQLSCRIPT procedure will select the information from the FLIGHT_MODEL table and store in the flight_model 
--variable
--We're going to select all the needed information from the table SPFLI and STICKET based on the customer ID number
--We're going to call the R procedure USE_FLIGHT and it will return us the PREDICTED_FLIGHTS that we're going to 
--store in the database
 
DROP PROCEDURE GET_FLIGHTS;
CREATE PROCEDURE GET_FLIGHTS(IN customId NVARCHAR(8))
LANGUAGE SQLSCRIPT AS
BEGIN
flight_model = SELECT * FROM FLIGHT_MODEL;
out_flights = SELECT SPFLI.CARRID, CUSTOMID, CITYFROM, CITYTO FROM SFLIGHT.SPFLI INNER JOIN SFLIGHT.STICKET
                   ON SPFLI.CARRID = STICKET.CARRID AND SPFLI.CONNID = STICKET.CONNID
                   WHERE CUSTOMID = :customId;
CALL USE_FLIGHT(:out_flights, :flight_model, PREDICTED_FLIGHTS);
INSERT INTO "PREDICTED_FLIGHTS" SELECT * FROM :PREDICTED_FLIGHTS;
END;

Now that we have all our Stored Procedures ready...we can create the last SQLScript file to actually fill our PREDICTED_FLIGHTS with some data...

Predict_Flights_for_Customers.sql
CALL GET_FLIGHTS('00000156');
CALL GET_FLIGHTS('00002078');
CALL GET_FLIGHTS('00002463');

As you can see...we only need to call the GET_FLIGHTS procedure, passing the Customer ID's...

This process took only 970ms and generate 122 records for the 3 customers...

Now I'm sure you realize how cool is this...if we haven't stored our model in the database...then we would have to calculate the model for each customer...and it would have took us around 7 seconds for each (get the lm(), plus the predict)...that would have been around 21 seconds for the 3 customers...while we can say that the whole process took us only 7 seconds...if you needed to calculate the prediction for all the more than 1 million customers...you will be in sort of trouble -:)

Let's see the content of our PREDICTED_FLIGHTS table...of course, I'm going to only show a part of it...


We can dump this information to a CSV file from SAP HANA Studio and let's say...use it on SAP Visual Intelligence to generate a nice graphic...


Hope you like it -:)

Greetrings,

Blag.

miércoles, 13 de febrero de 2013

A Shiny example - SAP HANA, R and Shiny


As you may already know...I love R...a fancy, open source statistics programming language. So today, I decided to learn something new using R.

There aren't much Web Servers for R, but there's one that I really like called Rook, that I covered on my blog RSAP, Rook and ERP.

Today, I tried a new one that's is making a lot of noise in the R community, called Shiny. I gotta say...I felt instantly in love with it...

So you may ask...so what's so cool about Shiny? Well...besides the fact that it allows you to create web applications using R...it's completely dynamic...meaning that once you change a parameter, the graphic and not the whole web page is reloaded automatically...awesome, huh?

So...when I wrote about Ruby and SAP in my blog Ruby joins the SAP HANA party, I tried to emulate an SE16 to browse tables from the SFLIGHT package...this time...I will do the same but with a little twist...the application will allow you to choose a table, but also to choose how many records you want to display...

So, what we need? Simply...if you haven't already...install this two packages..."shiny" and "RODBC"...


After this, we need to create a folder called Shiny and after this create a new one called SAP_HANA_R (This is just to be organized).

We now need to create two files, called ui.R and server.R


ui.R
library("shiny")
library("RODBC")
 
ch<-odbcConnect("HANA_TK",uid="SYSTEM",pwd="manager")
odbcQuery(ch,"SELECT table_name from SYS.CS_TABLES_ where schema_name = 'SFLIGHT'")
tables<-sqlGetResults(ch)
odbcClose(ch)
 
shinyUI(pageWithSidebar(
 
  headerPanel("SAP HANA and R using Shiny"),
 
  sidebarPanel(
    selectInput("Table", "Choose a table:",
                choices = tables$TABLE_NAME),
    numericInput("Records", "Number of Records to view:", 10)
  ),
 
  mainPanel(
    tableOutput("view")
  )
))

server.R
library("shiny")
library("RODBC")
 
shinyServer(function(input, output) {
 
  output$view <- reactiveTable(function() {
    ch<-odbcConnect("HANA_TK",uid="SYSTEM",pwd="manager")
    schema_table<-paste("SFLIGHT.",input$Table,sep="")
    query<-paste("SELECT TOP",input$Records,"* FROM",schema_table)
    odbcQuery(ch,query)
    result<-sqlGetResults(ch)
    odbcClose(ch)
 
    head(result, n = input$Records)
  })
})

When we have finished with the two files...we can create a new one just to call our application.

Shiny_HANA.R
library(shiny)
setwd("C:/Blag/R_Scripts")
runApp("Shiny/SAP_HANA_R")

Keep in mind that the setwd("C:/Blag/R_Scripts") is my main R Script folder, as setwd stands for "Set Working Directory"...

When we run Shiny_HANA.R, the browser will popup showing the parameters and the table by default.


As you can see, we can choose a new table to display.


We can also choose how many lines or records we want to display...



I hope you like it... -:)

Greetings,

Blag.

sábado, 9 de febrero de 2013

PHP rocks on SAP HANA too! (Linux version)


If you know me...you know I'm not a Linux boy...however...I still have my laptop LG T1 Express Dual that I bought on 2007...for my first SAP TechEd ever...as you may assume...running Windows and installing more software on this laptop was a nice way to slowly kill it...so the laptop stayed on my closed for a long time...until I decided to install Ubuntu on it and bring to live once again -:)

What they say it's true...even the crappiest and oldest laptop will behave great on Linux...and BTW, I'm writing this blog on my Linux box -;)

A couple of weeks ago I wrote a blog called PHP rocks on SAP HANA too! as I realized that I haven't blog about PHP and SAP HANA and also because I knew that a lot of people were facing issues trying to make it work together...but...I wrote for Windows...and Linux users are still struggling to get this working...so...time for a new blog...

I gotta say...without this awesome blog HANA with odbc on Ubuntu 12.04 written by Ethan Zhang I will be probably still struggling with the connection details...but anyway...not being a Linux boy...I thought it would be a good idea to detail all the steps and problems that I overcome to finally make it work...

Of course...I didn't have PHP installed on my Linux box...so the first step was install it...however, I didn't want to spend so much time installing everything separately, so I decided to use LAMP.

LAMP Installation
$ sudo apt-get install tasksel
$ sudo tasksel install lamp-server


After this two simple lines...PHP was up and running...so next step was install the SAP HANA Client. 32bits Linux Version.

Now, I needed something to connect to my SAP HANA Server...so unixODBC was the best choice.

unixODBC Installation
$ sudo apt-get install unixODBC unixODBC-dev

With this, it was time to configure the ODBC connection...

odbc.ini configuration
$ sudo vi odbc.ini
 
[HDB]
driver = /usr/sap/hdbclient32/libodbcHDB32.so
ServerNode = hana_server:30115

It was time to the first test...so I did the following...

ODBC Testing
$ isql -v HDB SYSTEM manager

After this...I got an error saying that libodbcHDB32.so couldn't be found because the file or directory didn't exist...it was weird...I decided to take a look at the dependencies...

Checking Dependencies
$ ldd /usr/sap/hdbclient32/libodbcHDB32.so

This point me out to the fact that libaio.so wasn't found on my system...so using the Ubuntu Software System...I just installed it...


My next test of isql was successful, so...being kind of lazy...I just copied the code from my other blog and run it -;)



PHP_HANA.php
<?php
$conn = odbc_connect("HDB","SYSTEM","manager",SQL_CUR_USE_ODBC);
if (!($conn))
{
          echo "<p>Connection to DB via ODBC failed: ";
        echo odbc_errormsg ($conn);
        echo "</p>\n";
}
else{
          if(isset($_POST["CARRID"]) == false){
                  $sql = "SELECT CARRID, CARRNAME FROM SFLIGHT.SCARR WHERE MANDT = 300";
               $rs = odbc_exec($conn,$sql);
               print("<DIV ALIGN='CENTER'>");
               print("<H1>SAP HANA from PHP</H1>");
               print("<FORM NAME='Get_Data' ACTION='$_SERVER[PHP_SELF]' METHOD='POST'>");
               print("<SELECT NAME='CARRID'>");
               while($row = odbc_fetch_array($rs)){
                     $carrid = $row["CARRID"];
                     $carrname = $row["CARRNAME"];
                     print("<OPTION VALUE='$carrid'>$carrname");
               }
        print("</SELECT>");
        print("<INPUT TYPE='SUBMIT' VALUE='Get Data'>");
        print("</FORM>");
        print("</DIV>");
        }
        else{
                  $carrid_param = $_POST["CARRID"];
                $sql = "SELECT * FROM \"_SYS_BIC\".\"blag/AV_FLIGHTS\"
                        WHERE CARRID = '$carrid_param'";
                $rs = odbc_exec($conn,$sql);
                print("<DIV ALIGN='CENTER'><TABLE BORDER=1>");
                print("<TR><TH>MANDT</TH><TH>CARRID</TH><TH>CONNID</TH>
                       <TH>COUNTRYFR</TH><TH>CITYFROM</TH>
                       <TH>AIRPFROM</TH><TH>COUNTRYTO</TH>
                       <TH>CARRNAME</TH><TH>DISTANCE</TH></TR>");
                while($row = odbc_fetch_array($rs)){
                          $mandt = $row["MANDT"];
                $carrid = $row["CARRID"];
                $connid = $row["CONNID"];
                $countryfr = $row["COUNTRYFR"];
                $cityfrom = $row["CITYFROM"];
                $airpfrom = $row["AIRPFROM"];
                $countryto = $row["COUNTRYTO"];
                $carrname = $row["CARRNAME"];
                $distance = $row["DISTANCE"];
                print("<TR><TD>$mandt</TD><TD>$carrid</TD>
                         <TD>$connid</TD><TD>$countryfr</TD>
                         <TD>$cityfrom</TD><TD>$airpfrom</TD>
                         <TD>$countryto</TD><TD>$carrname</TD>
                         <TD>$distance</TD></TR>");
               }
               print("</TABLE>");
               print("<A HREF='PHP_HANA.php'>Go Back</A></DIV>");
          }
}
?>



As you can see...not too hard...even for a Windows boy -:D

Greetings,

Blag.

martes, 29 de enero de 2013

CodeJam - SAP HANA


I have successfully delivered two SAP Code Jams...one in Montreal and the other in Lima. For that, I created a Workbook so everyone could gain experience and follow up on the Hands-On section of the event.

After those two events...I decided that it's time for this document to get retired...what does that means? I will not use it again...so I will share it with community.

It took me a lot of time and effort and mostly passion to write it...so I hope everybody find it useful.


Greetings,

Blag.

lunes, 21 de enero de 2013

PHP rocks on SAP HANA too!


A couple of days ago, a friend told me that some people were trying to make PHP and SAP HANA work without having any success...of course...I took that as my new goal...I haven't done any PHP is a very long time...but that never stops me -;)

First thing...was to create an ODBC connection and try to make it work...it didn't...but only because I create it using a "User DSN" instead of a "System DSN"...when do we need to use one or the other...I have no clue...but while it works...I don't care...

Now that it was working...it was time to make a simple query...this failed too...with a nasty and weird message...



Scrollable Result is not yet implemented...what's that suppose to mean? At first...I thought that maybe NVARCHAR is not supported, so I created a temp table using only two VARCHAR fields...same result...so it was something else...in the end...after looking in Google...I found out that some Databases allows cursors to go back and forth...and somehow...with SAP HANA it was giving me an error...easiest solution was to specify the cursor

$conn = odbc_connect("HANA_KT_SYS","SYSTEM","manager", SQL_CUR_USE_ODBC);

The use of SQL_CUR_USE_ODBC was all I needed to keep going...

For this example, I decided to create an Attribute View joining the tables SPFLI and SCARR. For the first screen I will show all the available CARRIDs and on the second screen, I will show a table with some of the fields on a HTML table. Something simple and direct.


Now, let's take a look at the PHP code...

PHP_SAPHANA.php
<?php
$conn = odbc_connect("HANA_KT_SYS","SYSTEM","manager", SQL_CUR_USE_ODBC);
if (!($conn)) { 
 echo "<p>Connection to DB via ODBC failed: ";
 echo odbc_errormsg ($conn );
 echo "</p>\n";
}
else{
 if(isset($_POST["CARRID"]) == false)
 {
  $sql = "SELECT CARRID, CARRNAME FROM SFLIGHT.SCARR 
                        WHERE MANDT = 300";
  $rs = odbc_exec($conn,$sql);
  print("<DIV ALIGN='CENTER'>");
  print("<H1>SAP HANA from PHP</H1>");
  print("<FORM NAME='Get_Data' 
                       ACTION='$_SERVER[PHP_SELF]' METHOD='POST'>");
  print("<SELECT NAME='CARRID'>");
  while($row = odbc_fetch_array($rs)){
      $carrid = $row["CARRID"];
   $carrname = $row["CARRNAME"];
   print("<OPTION VALUE='$carrid'>$carrname");
  }
     print("</SELECT>");
      print("<INPUT TYPE='SUBMIT' VALUE='Get Data'>");
      print("</FORM>");
      print("</DIV>");
 }
 else{
  $carrid_param = $_POST["CARRID"];
  $sql = "SELECT * FROM \"_SYS_BIC\".\"blag/AV_FLIGHTS\" 
          WHERE CARRID = '$carrid_param'";
  $rs = odbc_exec($conn,$sql);
  print("<DIV ALIGN='CENTER'><TABLE BORDER=1>"); 
          print("<TR><TH>MANDT</TH>
                       <TH>CARRID</TH>
                       <TH>CONNID</TH>
                       <TH>COUNTRYFR</TH><TH
                       >CITYFROM</TH>                        
                       <TH>AIRPFROM</TH>
                       <TH>COUNTRYTO</TH>
                       <TH>CARRNAME</TH>
                       <TH>DISTANCE</TH></TR>");
  while($row = odbc_fetch_array($rs)){
   $mandt = $row["MANDT"];
   $carrid = $row["CARRID"];
   $connid = $row["CONNID"];
   $countryfr = $row["COUNTRYFR"];
   $cityfrom = $row["CITYFROM"];
   $airpfrom = $row["AIRPFROM"];
   $countryto = $row["COUNTRYTO"];
   $carrname = $row["CARRNAME"];
   $distance = $row["DISTANCE"];
   print("<TR><TD>$mandt</TD><TD>$carrid</TD>
           <TD>$connid</TD><TD>$countryfr</TD>
        <TD>$cityfrom</TD><TD>$airpfrom</TD>
        <TD>$countryto</TD><TD>$carrname</TD>
        <TD>$distance</TD></TR>");
  }
  print("</TABLE>");
  print("<A HREF='PHP_SAPHANA.php'>Go Back</A></DIV>");
 }
}
?>

Now, we can call it from any Web Browser...




As you can see...there's no limitation to what you can do with SAP HANA...sometimes...it's just take a little bit more of research -;)

Greetings,

Blag.

SAP CodeJam Lima - The Experience


This Saturday 19, 2013 we held at the Aula Magna of the Universidad Peruana de Ciencias Aplicadas the first SAP CodeJam Lima.

(I had some problems with my camera but Lennon Shimokawa and Raúl Vides Mosquera Pumaricra came to the rescue...so...thanks guys!)

We have around 20 people and I talked about SAP HANA.


As I'm from Peru...and haven't come back in 2 years and 8 months...I decided to start the day with a little session called "From Lima to Montreal...from Consultant to Development Expert" to tell people about my experiences, motivations, problems and successes.


Sadly...we had some unexpected WIFI connection problems, so we couldn't connect to out TK ucloud SAP HANA Servers...but that didn't stop us -;) As I provided them an 86 pages Workbook and I use CloudShare.com as backup....we could continue learning and having fun -:)



This SAP CodeJam was a replicate of the one I did in Montreal as I simply translate the whole Workbook into Spanish...we talk about Tables, Views, Attribute, Analytical and Calculation Views, connection with Microsoft Excel and SQLScript.


As always, I tried to show people how versatile and easy to use SAP HANA is with some examples and real life applications.



Of course, people ask me about SAP HANA implementations and how ERP fits into SAP HANA...



The whole day lasted from 9:00am to 2:00pm...time when the Pizza and Soda arrived...there are no pictures because everybody was really busy eating and talking...

In a nutshell, it was a fun and nice experience and everybody is looking forward for the next event -:)


Greetings,

Blag.

domingo, 13 de enero de 2013

From ERP to SAP HANA (Small ABAP App)


Disclaimer: This is a personal project. It's not endorsed or supported by SAP in any means. It's not aimed or supposed to replace any SAP migration tool. It's just something I did for fun and it's still on beta phase. Use it at your own risk.

In my daytime job, I don't need to use ABAP...but after 11 years...it's hard not to use it sometimes

As everything is SAP HANA, I decided (some time ago) to build a small ABAP program to move tables from the ERP to SAP HANA. It's still on Beta and of course it's not the best way of doing this kind of job, because as you will see later, for each record I generate an INSERT clause...and the SAP HANA editor has a limit of lines. Why didn't I generate an CSV file and upload with SAP HANA Studio? Because...this is a personal project...and I'm sharing it only because someone might find it useful...

I need to thank my friend Kumar Mayuresh who took some quality time to beta test and send me all the error he found so I could fix them.

Here's the source code...

ZERP_TO_HANA
*&---------------------------------------------------------------------*
*& Report  ZERP_TO_HANA                                                *
*&---------------------------------------------------------------------*
*& Author: Alvaro "Blag" Tejada Galindo.                               *
*& Developer Experience                                                *
*& Company: SAP Labs Montreal.                                         *
*& Date: June 04, 2012.                                                *
*&---------------------------------------------------------------------*
*& This program comes with no warranty. Use it at your own risk.       *
*& This is just a personal project no aimed for productive             *
*& environments and not sponsored or supported by SAP.                 *
*& I'm not responsible for any caused damage.                          *
*&---------------------------------------------------------------------*
*& Reviewed on: December 11, 2012.                                     *
*& Reviewer: Alvaro "Blag" Tejada Galindo.                             *
*& Reason: Definition of the Data download structure.                  *
*&---------------------------------------------------------------------*
*& Reviewed on: January 11, 2013.                                      *
*& Reviewer: Alvaro "Blag" Tejada Galindo.                             *
*& Reason: Definition of the Struct/Data download structure.           *
*&---------------------------------------------------------------------*
*& Reviewed on: January 13, 2013.                                      *
*& Reviewer: Alvaro "Blag" Tejada Galindo.                             *
*& Reason: Check the Outputlen of the Domain.                          *
*&---------------------------------------------------------------------*

REPORT ZERP_TO_HANA.

TYPES: BEGIN OF TY_DD03L,
       FIELDNAME TYPE DD03L-FIELDNAME,
       POSITION TYPE DD03L-POSITION,
       KEYFLAG TYPE DD03L-KEYFLAG,
       ROLLNAME TYPE DD03L-ROLLNAME,
       DATATYPE TYPE DD03L-DATATYPE,
       LENG TYPE DD03L-LENG,
       DECIMALS TYPE DD03L-DECIMALS,
       DOMNAME TYPE DD03L-DOMNAME,
       END OF TY_DD03L.

TYPES: BEGIN OF TY_DD04L,
       ROLLNAME TYPE DD04L-ROLLNAME,
       DOMNAME TYPE DD04L-DOMNAME,
       OUTPUTLEN TYPE DD04L-OUTPUTLEN,
       END OF TY_DD04L.

TYPES: BEGIN OF TY_LINES,
       LINE TYPE STRING,
       END OF TY_LINES.

TYPES: BEGIN OF TY_TYPES,
       ERP TYPE STRING,
       HANA TYPE STRING,
       END OF TY_TYPES.

DATA: T_DD03L TYPE TABLE OF TY_DD03L,
      T_DD04L TYPE TABLE OF TY_DD04L,
      T_LINES TYPE TABLE OF TY_LINES,
      T_TYPES TYPE TABLE OF TY_TYPES.

DATA: V_FILENAME TYPE STRING.

FIELD-SYMBOLS: <FS_DD03L> LIKE LINE OF T_DD03L,
               <FS_DD04L> LIKE LINE OF T_DD04L,
               <FS_LINES> LIKE LINE OF T_LINES,
               <FS_TYPES> LIKE LINE OF T_TYPES.

SELECTION-SCREEN BEGIN OF BLOCK B1 WITH FRAME TITLE TEXT-T01.
PARAMETERS:
           P_SCHEMA(12) TYPE C OBLIGATORY,
           P_TABLE TYPE DATABROWSE-TABLENAME OBLIGATORY,
           P_FOLDER TYPE STRING OBLIGATORY,
           P_STRUC RADIOBUTTON GROUP RDN DEFAULT 'X',
           P_DATA RADIOBUTTON GROUP RDN.
SELECTION-SCREEN END OF BLOCK B1.

AT SELECTION-SCREEN ON VALUE-REQUEST FOR P_FOLDER.
  CALL METHOD CL_GUI_FRONTEND_SERVICES=>DIRECTORY_BROWSE
    EXPORTING
      WINDOW_TITLE    = 'Select a Folder'
      INITIAL_FOLDER  = 'C:\'
    CHANGING
      SELECTED_FOLDER = P_FOLDER.

START-OF-SELECTION.
  PERFORM GET_TYPES.
  IF P_STRUC EQ 'X'.
    PERFORM GET_STRUCTURE USING P_TABLE.
  ELSE.
    PERFORM GET_DATA USING P_TABLE.
  ENDIF.

*&---------------------------------------------------------------------*
*&      Form  GET_TYPES                                                *
*&---------------------------------------------------------------------*
FORM GET_TYPES.

  SELECT FIELDNAME POSITION KEYFLAG ROLLNAME
         DATATYPE LENG DECIMALS DOMNAME
  INTO TABLE T_DD03L
  FROM DD03L
  WHERE TABNAME EQ P_TABLE.

  SORT T_DD03L BY POSITION ASCENDING.

  SELECT ROLLNAME DOMNAME OUTPUTLEN
  INTO TABLE T_DD04L
  FROM DD04L
  FOR ALL ENTRIES IN T_DD03L
  WHERE ROLLNAME EQ T_DD03L-ROLLNAME
    AND DOMNAME EQ T_DD03L-DOMNAME.

  "NVARCHAR
  APPEND INITIAL LINE TO T_TYPES ASSIGNING <FS_TYPES>.
  <FS_TYPES>-ERP = 'CLNT'.
  <FS_TYPES>-HANA = 'NVARCHAR'.
  APPEND INITIAL LINE TO T_TYPES ASSIGNING <FS_TYPES>.
  <FS_TYPES>-ERP = 'CHAR'.
  <FS_TYPES>-HANA = 'NVARCHAR'.
  APPEND INITIAL LINE TO T_TYPES ASSIGNING <FS_TYPES>.
  <FS_TYPES>-ERP = 'NUMC'.
  <FS_TYPES>-HANA = 'NVARCHAR'.
  APPEND INITIAL LINE TO T_TYPES ASSIGNING <FS_TYPES>.
  <FS_TYPES>-ERP = 'UNIT'.
  <FS_TYPES>-HANA = 'NVARCHAR'.
  APPEND INITIAL LINE TO T_TYPES ASSIGNING <FS_TYPES>.
  <FS_TYPES>-ERP = 'CUKY'.
  <FS_TYPES>-HANA = 'NVARCHAR'.
  "INTEGER
  APPEND INITIAL LINE TO T_TYPES ASSIGNING <FS_TYPES>.
  <FS_TYPES>-ERP = 'INT4'.
  <FS_TYPES>-HANA = 'INTEGER'.
  "DECIMAL
  APPEND INITIAL LINE TO T_TYPES ASSIGNING <FS_TYPES>.
  <FS_TYPES>-ERP = 'QUAN'.
  <FS_TYPES>-HANA = 'DECIMAL'.
  APPEND INITIAL LINE TO T_TYPES ASSIGNING <FS_TYPES>.
  <FS_TYPES>-ERP = 'DEC'.
  <FS_TYPES>-HANA = 'DECIMAL'.
  "FLOAT
  APPEND INITIAL LINE TO T_TYPES ASSIGNING <FS_TYPES>.
  <FS_TYPES>-ERP = 'FLTP'.
  <FS_TYPES>-HANA = 'FLOAT'.
  "TINYINT
  APPEND INITIAL LINE TO T_TYPES ASSIGNING <FS_TYPES>.
  <FS_TYPES>-ERP = 'INT1'.
  <FS_TYPES>-HANA = 'TINYINT'.

ENDFORM.                    " GET_TYPES

*&---------------------------------------------------------------------*
*&      Form  GET_STRUCTURE                                            *
*&---------------------------------------------------------------------*
FORM GET_STRUCTURE USING P_TABLE.

  DATA: PKEY TYPE STRING,
        L_TYPE TYPE STRING.

  CONCATENATE P_FOLDER '\' P_TABLE '_STRUCT.txt'
  INTO V_FILENAME.

  APPEND INITIAL LINE TO T_LINES ASSIGNING <FS_LINES>.
  CONCATENATE 'CREATE COLUMN TABLE' P_SCHEMA
  INTO <FS_LINES>-LINE SEPARATED BY SPACE.
  CONCATENATE <FS_LINES>-LINE '."' P_TABLE  '" (' INTO
  <FS_LINES>-LINE.
  LOOP AT T_DD03L ASSIGNING <FS_DD03L>.
    FIND REGEX '\.' IN <FS_DD03L>-FIELDNAME.
    IF SY-SUBRC EQ 0.
      CONTINUE.
    ENDIF.
    APPEND INITIAL LINE TO T_LINES ASSIGNING <FS_LINES>.
    READ TABLE T_TYPES ASSIGNING <FS_TYPES>
    WITH KEY ERP = <FS_DD03L>-DATATYPE.
    L_TYPE = <FS_TYPES>-HANA.
    PERFORM DELETE_ZEROS CHANGING <FS_DD03L>-LENG.

    READ TABLE T_DD04L ASSIGNING <FS_DD04L>
    WITH KEY ROLLNAME = <FS_DD03L>-ROLLNAME
             DOMNAME = <FS_DD03L>-DOMNAME.
    IF SY-SUBRC EQ 0 AND NOT <FS_DD04L> IS INITIAL.
      PERFORM DELETE_ZEROS CHANGING <FS_DD04L>-OUTPUTLEN.
      IF <FS_DD04L>-OUTPUTLEN GT <FS_DD03L>-LENG.
        <FS_DD03L>-LENG = <FS_DD04L>-OUTPUTLEN.
      ENDIF.
    ENDIF.

    PERFORM DELETE_ZEROS CHANGING <FS_DD03L>-DECIMALS.
    CASE L_TYPE.
      WHEN 'NVARCHAR' OR 'FLOAT' OR 'TINYINT'.
        CONCATENATE L_TYPE '(' <FS_DD03L>-LENG ')'
        INTO L_TYPE.
      WHEN 'DECIMAL'.
        CONCATENATE L_TYPE '(' <FS_DD03L>-LENG ',' <FS_DD03L>-DECIMALS ')'
        INTO L_TYPE.
    ENDCASE.
    FIND REGEX '\/' IN <FS_DD03L>-FIELDNAME.
    IF SY-SUBRC EQ 0.
      CONCATENATE '"' <FS_DD03L>-FIELDNAME '"'
      INTO <FS_DD03L>-FIELDNAME.
    ENDIF.
    CONCATENATE <FS_DD03L>-FIELDNAME L_TYPE
    INTO <FS_LINES>-LINE SEPARATED BY SPACE.
    CONCATENATE <FS_LINES>-LINE ',' INTO <FS_LINES>-LINE.
    IF <FS_DD03L>-KEYFLAG EQ 'X'.
      CONCATENATE PKEY '"' <FS_DD03L>-FIELDNAME '",'
      INTO PKEY.
    ENDIF.
  ENDLOOP.
  REPLACE REGEX ',\Z' IN PKEY WITH SPACE.

  APPEND INITIAL LINE TO T_LINES ASSIGNING <FS_LINES>.
  CONCATENATE 'PRIMARY KEY (' PKEY '));'
  INTO <FS_LINES>-LINE.

  PERFORM DOWNLOAD_FILE USING V_FILENAME
                              T_LINES.

ENDFORM.                    " GET_STRUCTURE

*&---------------------------------------------------------------------*
*&      Form  GET_DATA                                                 *
*&---------------------------------------------------------------------*
FORM GET_DATA USING P_TABLE.

  DATA: L_TABLE TYPE REF TO DATA,
        L_LINE TYPE STRING,
        L_LINEAUX TYPE STRING,
        L_WHERE TYPE STRING,
        L_TYPE TYPE STRING.

  FIELD-SYMBOLS: <FS_TABLE> TYPE ANY TABLE,
                 <FS_TABLE_HEADER> TYPE ANY,
                 <FS_LINE>.

  CONCATENATE P_FOLDER '\' P_TABLE '_DATA.txt'
  INTO V_FILENAME.

  CREATE DATA L_TABLE TYPE TABLE OF (P_TABLE).
  ASSIGN L_TABLE->* TO <FS_TABLE>.

  read table t_dd03l ASSIGNING <fs_dd03l>
  with key domname = 'SPRAS'.
  IF SY-SUBrC EQ 0.
    CONCATENATE <fs_dd03l>-FIELDNAME 'EQ ''E''' INTO L_WHERE
    SEPARATED BY SPACE.
    SELECT *
    FROM (P_TABLE)
    INTO TABLE <FS_TABLE>
    WHERE (L_WHERE).
  ELSE.
    SELECT *
    FROM (P_TABLE)
    INTO TABLE <FS_TABLE>.
  ENDIF.

  LOOP AT <FS_TABLE> ASSIGNING <FS_TABLE_HEADER>.
    APPEND INITIAL LINE TO T_LINES ASSIGNING <FS_LINES>.
    CONCATENATE 'insert into "' P_SCHEMA '"."' P_TABLE '" values(' into <FS_LINES>-LINE.
    LOOP AT T_DD03L ASSIGNING <FS_DD03L>.
      FIND REGEX '\.' IN <FS_DD03L>-FIELDNAME.
      IF SY-SUBRC EQ 0.
        CONTINUE.
        DELETE T_LINES FROM <FS_LINES>.
      ENDIF.
      CONCATENATE '<FS_TABLE_HEADER>-' <FS_DD03L>-FIELDNAME
      INTO L_LINE.
      ASSIGN (L_LINE) TO <FS_LINE>.
      MOVE <FS_LINE> TO L_LINEAUX.
      CONDENSE L_LINEAUX NO-GAPS.
      READ TABLE T_TYPES ASSIGNING <FS_TYPES>
      WITH KEY ERP = <FS_DD03L>-DATATYPE.
      L_TYPE = <FS_TYPES>-HANA.
      CASE L_TYPE.
        WHEN 'NVARCHAR'.
          CONCATENATE <FS_LINES>-LINE '''' L_LINEAUX ''',' into <FS_LINES>-LINE.
        WHEN 'DECIMAL' OR 'INTEGER' OR 'TINYINT' OR 'FLOAT'.
          CONDENSE L_LINEAUX NO-GAPS.
          CONCATENATE <FS_LINES>-LINE L_LINEAUX ',' into <FS_LINES>-LINE.
      ENDCASE.
    ENDLOOP.
    REPLACE REGEX ',\Z' IN <FS_LINES>-LINE WITH ');'.
  ENDLOOP.

  PERFORM DOWNLOAD_FILE USING V_FILENAME
                              T_LINES.

ENDFORM.                    " GET_DATA

*&---------------------------------------------------------------------*
*&      Form  download_file                                            *
*&---------------------------------------------------------------------*
FORM DOWNLOAD_FILE USING P_FILENAME
                         P_TABLE.

  DATA: SIZE TYPE I.

  CALL METHOD CL_GUI_FRONTEND_SERVICES=>GUI_DOWNLOAD
    EXPORTING
      BIN_FILESIZE = SIZE
      FILENAME     = P_FILENAME
      FILETYPE     = 'ASC'
    CHANGING
      DATA_TAB     = P_TABLE.

ENDFORM.                    "download_file

*&---------------------------------------------------------------------*
*&      Form  DELETE_ZEROS                                             *
*&---------------------------------------------------------------------*
FORM DELETE_ZEROS CHANGING P_VALUE.

  CALL FUNCTION 'CONVERSION_EXIT_ALPHA_OUTPUT'
    EXPORTING
      INPUT  = P_VALUE
    IMPORTING
      OUTPUT = P_VALUE.

ENDFORM.                    "DELETE_ZEROS

The usage is very simple...we execute it and we need to provide the Schema, Table and the Folder were we're going to store the files. We can download the Structure or the Data.




With the two files ready, we simply copy and paste in an SQL Editor of SAP HANA and let it run.


Of course...the downside is that we need to copy the records in batch mode...meaning...100 lines or something like that...run them...and then continue with the other 100 or so...


As you can see...this is only for fun and for small testing...and might not work with all the tables...hope you like it anyway -:)

Greetings,

Blag.

sábado, 29 de diciembre de 2012

SAP HANA goes mobile with PhoneGap (Cordova)


Three days ago, my friends at Packt Publishing send me a free ebook called PhoneGap Beginner's Guide. And while I knew about PhoneGap I had never really use it...so of course, my first thought was...How can I make this work with SAP HANA?

Two days ago, I started to read the book and make the PhoneGap installation...which was a total pain and didn't even work...so I simply put it aside for next year...

Yesterday...my developer spirit could more than me...and since 9 am to 11:30 pm I embarked myself on a crusade to have PhoneGap up and running and of course...to make it work with SAP HANA...here's my story...so you don't have to break your head


With all that...I was almost ready to go...as I was missing the Cordova-2.2.0.jar file...for that...I did the following...

  • Download and copy into the Cordova\libs folder the commons-codec-1.7.jar.
  • I went to Cordova\android\framework\scr\org\apache\cordova and modified the file CordovaWebView.java by commenting out this two lines...
    • if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB)                            settings.setNavDump(true);
  • Using CMD, I went to the Cordova\android\framework directory and execute ==> ant jar.

Now...I was really ready to go...so I create a folder called Projects inside Corbova...and again on CMD I did the following...

  • Inside Corbova\Projects ==> create C:\Cordova\Projects com.BlagTest BlagTest

That created a Blag_Test folder with all the related files from the Cordova project. Then I simply upload it to Eclipse, and made a couple of test to see if everything was working as expected...it did...so the new odyssey for SAP HANA was going to start...

At first...my initial thought was...this should be easy...I have already do it on PowerBuilder, so basically I need to import the ngdbc.jar into my Eclipse Project and that's it...wrong! Didn't work...and after several tries and fails...I finally see the light...I delete the project from Eclipse...copy the ngdbc.jar inside my libs folder of BlagTest...re-imported on Eclipse...and magically...I got a connection to SAP HANA...

Now...make that connection work was another nightmare...for this blog I needed to undust my almost forgotten knowledge of Java and JavaScript...and also...learn new things like PhoneGap and JQueryMobile...

But...I'm going to try to keep the long story short, so you don't get bored...

  • I create a new class called MyClass.java (I was tired...so forget about the silly name)

MyClass.java
package com.BlagTest;

import java.sql.*;
import java.util.ArrayList;

public class MyClass {
 public ArrayList<String> getData(String p_carrid){
     Connection connection = null;
     ArrayList<String> carrid = new ArrayList<String>();
     String data = "";
  try{
   Class.forName("com.sap.db.jdbc.Driver");
   try{
    connection =    
                       DriverManager.getConnection("jdbc:sap://XX.XX.XXX.XXX:30115",
                       "SYSTEM","manager");
   }
   catch (SQLException e){
   }
  } 
  catch (ClassNotFoundException e){
  }    
  if(connection != null){
   try{
    String sqlstring = "select CONNID, FLDATE, PRICE from 
                                SFLIGHT.SFLIGHT where carrid = '" + p_carrid + "'";
    Statement stmt = connection.createStatement();
    ResultSet rs = stmt.executeQuery(sqlstring);
    while(rs.next()){
     data = rs.getString("CONNID") + "/" + 
                                        rs.getString("FLDATE") + "/" +  
                                        rs.getString("PRICE"); 
     carrid.add(data);
    }
   }
   catch(SQLException e){  
   }
  }
  return carrid;
   }
}


In this file, what I'm doing is establishing an JDBC connection to my SAP HANA Server hosted on TK ucloud biz. The I'm selecting the CONNID, FLDATE and PRICE from the SFLIGHT table where the CARRID is going to be a parameters send from the application. As I didn't want to pass a multidimensional array, or an array of arrays, or anything like that...I simply concatenate the values using a "/" to split them later.


  • I modified the already existing BlagTest.java file


BlagTest.java
package com.BlagTest;

import android.app.Activity;
import android.os.Bundle;
import org.apache.cordova.*;

public class BlagTest extends DroidGap
{
 
 private MyClass mc;
 
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
     super.onCreate(savedInstanceState);
        super.init(); 
        mc = new MyClass();
        super.appView.getSettings().setJavaScriptEnabled(true);
        super.appView.addJavascriptInterface(mc, "MyCls");
     super.loadUrl("file:///android_asset/www/index.html");        
    }
}


Here, basically we saying that we want to be able to send data from Java to JavaScript by using the setJavaScriptEnabled(true) and then adding the addJavaScriptInterface(mc, "MyCls") we're telling how our class is going to be called...when we call them from JavaScript.


  • Finally...I delete everything from the already generated index.html file and put this code...


index.html
<html>
<head>
<title>SAP HANA from PhoneGap</title>
<meta name="viewport" content="width=device-width, initialscale=1.0"></meta>
<link rel="stylesheet" href="http://code.jquery.com/mobile/1.1.0/jquery.mobile-1.1.0.min.css"/>
<script src="http://code.jquery.com/jquery-1.7.1.min.js"></script>
<script src="http://code.jquery.com/mobile/1.1.0/jquery.mobile-1.1.0.min.js"></script>
<script>

 function getData(){
 var carridField = document.getElementById('carrid');

  var getCarrid = carridField.value;
  var myArrayList = window.MyCls.getData(getCarrid);
  carridField.value = "";
  $("#content").append("<ul id='list' data-role='listview' data-inset='true'</ul>");
  $("#content").trigger("create");
  for(var i = 0; i < myArrayList.size(); i++){
   var array = "" + myArrayList.get(i);
   array = array.split('/');
   var _connid = array[0], _fldate = array[1], _price = array[2];
   var list = "<li><p>CONNID: " + _connid + "</p><p>FLDATE: " + 
                        _fldate + "</p><p>PRICE: " + _price + "</p></li>";
   $("#list").append(list);
  }
  $("#list").listview("refresh");
 }

</script>
</head>
<body>

<div data-role="page">
 <div data-role="content" id="content">
  <div align="center"><h1>SAP HANA from PhoneGap</h1></div>
  Carrid: <input type="text" id="carrid" size="2"/>
  <button id="submitCarrid" onClick="getData()">Submit</button>
 </div>
</div>
</body>
</html>


What I'm doing here...is as following...


  • I have an input text and a button. In the input text, we're going pass an CARRID value and when pressing the button, we're going to call a JavaScript function.
  • The JavaScript function will collect the value from the input text, will call our Java function using window.MyCls.getData() and pass the CARRID parameter. This should return an ArrayList...but instead...it return an Object...so we need to handle it later...
  • Using JQueryMobile we're going to create a ListView which is like an HTML Table on steroids...and the thing I love about JQueryMobile is that we only need to include one "link rel" and two "script src" lines to make it work...as it grabs it from an on-line location.
  • We're going to do a FOR beginning from 0 till the size of our Object, and then will extract it's content using .get() will turning it into an String using "".
  • We simply split the newly created String and assign it to variables.
  • We add the lines to our ListView and update it when we finish.


After that, we can simply go to Project --> Clean to rebuild our project and then right click on our Project Folder and choose Run As --> Android Application.





It took so long time...but the rewards can't be greater...hope you like this -:)

Greetings,

Blag.

lunes, 17 de diciembre de 2012

MySQL, PostreSQL and SAP HANA - A friendly comparison

UPDATE! Thanks to a comment by Alan Suffolk I fixed my script a little bit, just moving the end = time.clock() and time_taken = end - start after the cur.execute(query) because that's exactly when the SAP HANA query ends...you can see the new processing time in the image below...

My good friend Pakdi Decnud gave a great idea while we were having lunch the very same day of the SAP CodeJam Montreal event.

Pakdi told me..."Why don't you make a comparison between SAP HANA and let's say...MongoDB"...I thought that it was of course a great idea...so yesterday I start exploring MongoDB...so you may ask yourselves..."Why then are you talking about MySQL and PostreSQL?"

Easy answer...and here are my thoughts...
  • I really don't get MongoDB...the whole No-SQL is really alien to me...
  • MongoDB is "Document" based, meaning that you create collections of documents, no databases nor tables...
  • MongoDB doesn't support Inner Joins and aggregates need a framework that it's more weird than MongoDB itself...
  • MongoDB is not meant for enterprise applications

That's why I decide to make a little bit shift and grab the two most used databases by start-ups and developers...MySQL and PostgreSQL.

For this blog, I wanted to have a lot of information...so as always, I grab my beloved Python and create a little script to generate 1 million records for two tables. One script per table.

The structure of the tables is as follows...

DOC_HEADER
Field NameData TypeLength
DOCUMENT_IDVARCHAR8
YEARVARCHAR4
AREAVARCHAR2
NAMEVARCHAR20
LAST_NAMEVARCHAR20

DOC_DETAIL
Field NameData TypeLength
DOCUMENT_IDVARCHAR8
YEARVARCHAR4
AREAVARCHAR2
AMOUNTVARCHAR

And here are the script to generate the 1 million records in a nice .CSV file

Doc_Header_Generator.py
import random
import csv
 
names = ["Anne", "Gigi", "Juergen", "Ingo", "Inga", "Alvaro", "Mario",
"Julien", "Mike", "Michael", "Karin", "Rui", "John", "Rocky", "Sebastian",
"Kai-Yin", "Hester", "Katrin", "Uwe", "Vitaliy"]
last_names = ["Hardy", "Read", "Schmerder", "Sauerzapf", "Bereza", "Tejada",
"Herger", "Vayssiere", "Flynn", "Byczkowski", "Schattka",
"Nogueira", "Mayerhofer", "Ongkowidjojo", "Wieczorek", "Gau", "Hilbrecht",
"Staehr", "Kylau", "Rudnytskiy"]
area = ["001", "002", "003", "004", "005", "006", "007", "008", "009", "010"]
year = ["2000", "2001", "2002", "2003", "2004", "2005", "2006", "2007",
"2008", "2009", "2010", "2011", "2012"]
 
def Generate_File(pSchema, pNumber):
    iNumber = 0
    c = csv.writer(open("Doc_Header.csv", "wb"))
 
    while iNumber < pNumber:
        queries = []
        r_doc_id = random.randrange(1, 999999)
        r_names = random.randrange(0, 20)
        r_lastnames = random.randrange(0, 20)
        r_areas = random.randrange(0, 10)
        r_years = random.randrange(0, 13)
        iNumber += 1
        queries.append(r_doc_id)
        queries.append(year[r_years])
        queries.append(str(area[r_areas]))
        queries.append(names[r_names])
        queries.append(last_names[r_lastnames])
        c.writerow(queries)
 
num_files = input("How many records?: \n")
Generate_File(num_files)

Doc_Detail_Generator.py
import random
import csv
 
area = ["001", "002", "003", "004", "005", "006", "007", "008", "009", "010"]
year = ["2000", "2001", "2002", "2003", "2004", "2005", "2006", "2007",
"2008", "2009", "2010", "2011", "2012"]
 
def Generate_File(pSchema, pNumber):
    iNumber = 0
    c = csv.writer(open("Doc_Detail.csv", "wb"))
 
    while iNumber < pNumber:
        queries = []
        r_doc_id = random.randrange(1, 999999)
        r_areas = random.randrange(0, 10)
        r_years = random.randrange(0, 13)
        r_amount = random.randrange(0, 10000, 1)
        iNumber += 1
        queries.append(r_doc_id)
        queries.append(year[r_years])
        queries.append(str(area[r_areas]))
        queries.append(r_amount)
        c.writerow(queries)
 
num_files = input("How many records?: \n")
Generate_File(num_files)


With the two files ready, I upload them to MySQL, PostgreSQL and SAP HANA.

To measure the speed, I create three Python scripts using...yes...again Bottle...

The basic idea is to join the two tables, select the Document_Id, Year, Area and the sum of Amount.

Let's start with the MySQL Script...

MySQL_Bottle_Documents.py
from bottle import get, run
import mysql.connector
import time


@get('/show_query')
def show_form():
    counter = 0
    start = time.clock()
    conn = mysql.connector.Connect(host='localhost', user='root',
                        password='root', database='P075400')
    cur = conn.cursor()

    query = '''SELECT A.DOCUMENT_ID, A.YEAR, A.AREA, SUM(AMOUNT)
                  FROM DOC_HEADER AS A INNER JOIN DOC_DETAIL AS B
                  WHERE A.DOCUMENT_ID = B.DOCUMENT_ID
                      AND A.YEAR = B.YEAR
                      AND A.AREA = B.AREA
                  GROUP BY DOCUMENT_ID, YEAR, AREA'''
    cur.execute(query)

    output = "<DIV ALIGN='CENTER'><TABLE BORDER='1'>"
    output += "<TR BGCOLOR='#B9C9FE'>"
    output += "<TH>Document</TH><TH>Year</TH>"
    output += "<TH>Area</TH><TH>Amount</TH>"
    output += "</TR>"
    for row in cur:
        counter += 1
        document_id = str(row[0])
        year = str(row[1])
        area = str(row[2])
        amount = str(row[3])
        output += "<TR BGCOLOR='#E8EDFF'>"
        output += '''<TD>%s</TD><TD>%s</TD>
                         <TD>%s</TD><TD>%s</TD>''' %     
                   (document_id, year,area, amount)
        output += "</TR>"
    output += "</TABLE>"
    end = time.clock()
    time_taken = end - start
    output += "<H1>SAP HANA - %s records in %s seconds</H1></DIV>"\
    % (counter, time_taken)
    return output

run(host='localhost', port=8080)

I let the script run...and after more than one hour...I simply got bored and interrupt the process...

So, I continue with PostgreSQL...

PostgreSQL_Bottle_Documents.py
from bottle import get, run
import psycopg2
import time


@get('/show_query')
def show_form():
    counter = 0
    start = time.clock()
    conn = psycopg2.connect("dbname=P075400 user=postgres password=root")
    cur = conn.cursor()

    query = '''SELECT "DOC_HEADER"."DOCUMENT_ID", "DOC_HEADER"."YEAR",
                "DOC_HEADER"."AREA", SUM("DOC_DETAIL"."AMOUNT") FROM
                public."DOC_HEADER", public."DOC_DETAIL" WHERE
                "DOC_HEADER"."DOCUMENT_ID" = "DOC_DETAIL"."DOCUMENT_ID"
                AND "DOC_HEADER"."YEAR" = "DOC_DETAIL"."YEAR"
                AND "DOC_HEADER"."AREA" = "DOC_DETAIL"."AREA"
                GROUP BY "DOC_HEADER"."DOCUMENT_ID", "DOC_HEADER"."YEAR",
                "DOC_HEADER"."AREA"'''
    cur.execute(query)

    output = "<DIV ALIGN='CENTER'><TABLE BORDER='1'>"
    output += "<TR BGCOLOR='#B9C9FE'>"
    output += "<TH>Document</TH><TH>Year</TH>"
    output += "<TH>Area</TH><TH>Amount</TH>"
    output += "</TR>"
    for row in cur:
        counter += 1
        document_id = str(row[0])
        year = str(row[1])
        area = str(row[2])
        amount = str(row[3])
        output += "<TR BGCOLOR='#E8EDFF'>"
        output += '''<TD>%s</TD><TD>%s</TD>
                         <TD>%s</TD><TD>%s</TD>''' % 
                     (document_id, year, area, amount)
        output += "</TR>"
    output += "</TABLE>"
    end = time.clock()
    time_taken = end - start
    output += "<H1>PostgreSQL - %s records in %s seconds</H1></DIV>"\
    % (counter, time_taken)
    return output

run(host='localhost', port=8080)

This time...I was lucky...


Out of 2 millions records, PostgreSQL managed to aggregate the amount field and generate 7669 records in 36 seconds...not bad at all...

For SAP HANA, I decided to take fully advantage of the Calculation Views, so I create the following...



I joined both tables, used a projection, applied the aggregation and specified the result...then I wrote this Python script...

SAPHANA_Bottle_Documents.py
from bottle import get, run
import dbapi
import time


@get('/show_query')
def show_form():
    counter = 0
    start = time.clock()
    conn = dbapi.connect('hanasvr-02', 30015, 'P075400', '5pA5kb6i')
    cur = conn.cursor()

    try:
        ret = cur.execute("drop type test_out")
    except dbapi.Error:
        pass

    try:
        ret = cur.execute("drop procedure pyTest")
    except dbapi.Error:
        pass

    queries = ['''create type test_out as table (DOCUMENT_ID NVARCHAR(8),
               YEAR VARCHAR(4), AREA VARCHAR(2), AMOUNT BIGINT)''',
               '''create procedure pyTest (OUT out_test TEST_OUT)\
               language sqlscript reads sql data with result view ProcView as\
               begin\
               out_test = CE_CALC_VIEW("_SYS_BIC"."blag/CV_DOCUMENTS",
               ["DOCUMENT_ID","YEAR","AREA","AMOUNT"]);\
               end''']

    for query in queries:
        cur.execute(query)
        conn.commit()

    query = '''select DOCUMENT_ID, YEAR, AREA, AMOUNT from ProcView'''

    cur.execute(query)
    ret = cur.fetchall()

    output = "<DIV ALIGN='CENTER'><TABLE BORDER='1'>"
    output += "<TR BGCOLOR='#B9C9FE'>"
    output += "<TH>Document</TH><TH>Year</TH>"
    output += "<TH>Area</TH><TH>Amount</TH>"
    output += "</TR>"
    for row in ret:
        counter += 1
        document_id = str(row["DOCUMENT_ID"])
        year = str(row["YEAR"])
        area = str(row["AREA"])
        amount = str(row["AMOUNT"])
        output += "<TR BGCOLOR='#E8EDFF'>"
        output += '''<TD>%s</TD><TD>%s</TD>
                     <TD>%s</TD><TD>%s</TD>''' % 
                     (document_id, year, area, amount)
        output += "</TR>"
    output += "</TABLE>"
    end = time.clock()
    time_taken = end - start
    output += "<H1>SAP HANA - %s records in %s seconds</H1></DIV>"\
    % (counter, time_taken)
    return output

run(host='localhost', port=8080)

After the execution...I couldn't be happier...here's the result...



SAP HANA managed the same 2 million records...generate the same 7669 aggregated records in only 18 seconds...that's 50% faster than PostgreSQL and...well...let's only say...way faster than MySQL...

Now...tell me that SAP HANA is not the coolest and fastest Database around...I dare you -:)

By doing that little fix on my Python Script for SAP HANA...the new processing time, without generating the Bottle table is...


Greetings,

Blag.

sábado, 15 de diciembre de 2012

A small tribute to Innovation

I believe myself to be an innovator...a great developer...someone that goes beyond to make the impossible...but...there's always someone else making things that to me...seem impossible...

This is a small tribute to those people who had motivate me and always will...in no particular order...some of my all time heroes...

Ivan Femia - abap2xlsx - Generate your professional Excel spreadsheet from ABAP

I remember, a couple of years ago that I need to developer a really complex ALV report...that seems impossible to build...so my first thought was to use OLE and build it as an Excel file...I had done that before, but it was slow and buggy...somehow...like send from heaven...abap2xlsx came down to me and hit me in the face...it was totally awesome! How this guy Ivan was? How could he build something like this? It was just what I needed! I quickly install it, make a short report and show it to the functional...she love it as well...and I start working on the real report...after some hard work (and I'm proud to say that I even made my small contribution to the project) the report was finished and really shining...the customer love it and asked for more...really awesome...

Gregor Wolf - Community Project ZGEOCODE

It's really hard and even maybe a blasphemy to name Mr. Wolf and show only one blog...Gregor has contribute (and he's still doing it) to so many community projects...that it's think it's fair to make this analogy..."Gregor Wolf is to the community, what the flight tables are to every ERP installation". He's just an amazing guy with the biggest heart...and ZGEOCODE is just an example of what he's capable of...pure magic...

Juergen Schmerder - New from SAP Research: Blue Ruby - a Ruby VM in ABAP

Juergen doesn't blog as much as I would like him to do...but he do...you gotta hold on your chair...Blue Ruby was to me...a revelation...almost made me cry when I read about it...I couldn't believe that something like that could happen in the ABAP world...and while it didn't lasted too long...Blue Ruby will be always in my heart...

Thomas Jung - New ABAP Editor Backport!

Talk about Thomas, is talk about raw programming energy...this guy is so good, you can't believe he's a regular human person...when he wrote about porting the new ABAP Editor to older releases was like being blind and open your eyes for the first time...or waking up from the Matrix...whatever you like...Thomas has been the king of ABAP, BSP, WebDynpro and now SAP HANA...talking about SAP and not mention Thomas should be a crime...because this guy has contribute so much, that I could never speak talking about him...just amazing!

Piers Harding - Scripting - Whats your fancy - Perl, Python or Ruby?

Piers is and always will be my totally and complete hero...I mean...he created connectors RFC connector for Perl, Ruby and Python...how crazy is that?! You know why I learned Ruby and Python? Because of Piers! Sure I did...it was because his RFC connectors that I said to myself..."Dude...you like Scripting Languages...you like PHP...but this guy is offering a whole new world with Ruby and Python...go get them!"...but the lovely story doesn't end there...you know that I have dedicated myself to promote the integration of SAP HANA and R in many blogs...and because my current title, I haven't done any ABAP in more than a year...well...Piers created an RFC connector for R too! I couldn't believe it when I saw it...and one of my biggest achievements and something that will make proud forever, is that I worked very closed to him to create the Windows version of the R RFC connector...it was one of my "Wayne's World" moments...

Daniel McWeeney and Ed Herrmann - SAPlink One Week, One New Version -- UPDATE

I remember...that a long time ago...in my first years in the ABAP world...my team needed to make a backup of an SAP system...so we had to copy and paste tables and programs in notepads to reproduce them on a newer system (Looking back...I could say...WTF?!)...anyway...at that time I decided to create a couple of applications to automated the process...ABAP applications that would read all the Z tables and programs and create notepad files that could be easily ported into a new system...some years later...SAPLink saw the light...it was an amazing program...not a couple...but a single program that did what my program did...only...100 times better...in single XML file you could have a complete application (The main program, includes, tables, structures, etc)...these guys, that sadly are not involved with the community any more (We miss you guys), create the tool that every abaper in the world was looking for...a simple application that in my humble opinion...changed the world of ABAP consulting...

To finish this blog, I just would like to say..."Never stop dreaming...never stop creating...never stop being innovative...because your passion, it's what makes our world go round"...

Greetings,

Blag.