Saturday, March 09, 2013

Volviendonos flexibles con SAP HANA


Muchos de ustedes no se habran dado cuenta de una caracteristica introducida en SAP HANA SPS5. Esta nueva caracteristica se llama "Flexible Tables", lo cual significa que puedes definir una tabla que va a crecer dependiendo de tus necesidades. Veamos un ejemplo...

Defines un tabla con ID, NAME y LAST_NAME. La tabla funciona bien, pero te das cuenta de que tambien tienes que agregar PhoneNumber y Address...en una situacion normal, tendrias que abrir la definicion y agregar esos campos...pero usando Flexibles Tables, so deberias agregar esos campos como parte del INSERT y dejar que SAP HANA haga su magia...

Por supuesto, este escenario es muy dificil que suceda, porque para un par de campos, no tiene ningun sentido...asi que...donde utilizamos las tablas Flexible Tables? Busquedas empresariales, donde necesitamos todos los productos en una sola tabla, y esta tabla puede tener una gran cantidad de columnas...puesto que diferentes productos puedes tener diferentes categorias...

Asi que...para este blog...realmente me rompi la cabeza tratando de buscar un escenario simple donde pudiera demostrar las Flexible Tables...que fue lo que se me ocurrio? SAP HANA, Flexible Tables, R y Twitter...

El codigo es bastante complejo, asi que no voy a explicar linea por linea como funcionar...pero por supuesto, voy a dar una explicacion interesante...

El API de Twitter (Estoy usando version 1 aun cuando esta deprecada, simplemente porque la version 1.1 maneja autenticacion y realmente no queria invertir mucho tiempo en eso...) nos permite obtener informacion desde Twitter...asi que en este caso, estaba interesado en los Hashtags...los que comienzan con un "#" y son usados para identificar y organizar ciertos tweets relacionados con un evento, tecnologia o una persona famosa. (Solo estoy utilizando Tweets, sin tener en cuenta los Retweets)...


Utilizando R, leo el User Timeline para obtener los ultimos 200 tweets de una cuenta en particular. Esta informacion sera enviada de vuelta a  SAP HANA para ser almacenada.



Con los 200 tweets, extraigo los Hashtags, los sumarizo y guardo la informacion tanto en la tabla final como en un tabla intermedia (para ser usada por el siguiente usuario). En esta tabla intermedia, voy a almacenar un String bastante largo con todos los Hashtags separados por coma.

Cuando llega el siguiente usuario, los 200 tweets van a ser leidos, los Hashtags extraidos, combinados con los que estaban almacenados en la tabla intermedia, sumarizados (Esto es muy importante porque queremos llevar un control de los Hashtags anteriores, tanto los que son comunes a ambos usuarios como los que solamente existen en el primer o segundo usuario) y la informacion va a ser guadarda en la tabla final y la informacion del siguiente usuario va a ser grabada (reemplazando a la anterior) como un String largo en la tabla intermedia.

Porque necesitamos esto? Simple...digamos que el primer usuario tiene 3 Hashtags...#SAPHANA, #R y #Python con los valores 3, 2 y 1. El siguiente usuario tendra 5 Hashtags...#SAPHANA, #SAP, #Ruby, #IPhone y #Android con los valores 2, 5, 1, 4 y 3.

Cuando guardemos al primer usuario tendremos:

UserNameSAPHANARPython
First_User321

Cuando guardemos al segundo usuario tendremos:

UserNameSAPHANARPython SAPRubyAndroidIPhone
First_User321 ????
Second_User321 5143

Ahora...se preguntaran...porque R tiene "0" para el siguiente usuario y SAP tiene "?" para el primer usuario? Facil...como pueden ver...como hemos agregado mas campos (en tiempo de ejecucion) la tabla crece...el campo R ya estaba ahi para el primer usuario asi que tiene un "0" para el siguiente usuario, sin embargo SAP no estaba antes ahi, asi que no sabemos exactamente cuando deberia ser el valor para el primer usuario asi que un "?" va a ser utilizado.

Estoy seguro de que van a tener las cosas mas claras cuando vean las imagenes de la tabla despues de que les muestre el codigo fuente...

Primero, necesitamos crear una tabla llamada "TWITTER_USERS", que va a almacenar a los usuarios con los cuales queremos trabajar...


Luego, necesitamos otra table donde vamos a almacenar los Hashtags y su valores un String largo. Esta tabla se llamara "FIRST_HASH".


Ahora, las cosas se ponen interesantes, puesto que vamos a crear nuestra Flexible Table utilizando un comando muy simple...esta tabla se llamara "TWITTER_HASHTAGS".

Twitter_Hashtags.sql
CREATE COLUMN TABLE TWITTER_HASHTAGS(
USERNAME NVARCHAR(10)
) WITH SCHEMA FLEXIBILITY;

La tabla se ve bastante normal cuando revisamos su definicion...pero es una Flexible Table...como pueden ver...solo hemos definido un campo...asi que esta tabla crecera, crecera y crecera -;)


Lo siguiente que necesitamos crear es un par de TABLE TYPES que nos ayuden en la interaccion de SAP HANA y R...

Table_Types.sql
CREATE TYPE T_COL_NAMES AS TABLE(
COL_NAMES NVARCHAR(1000)
);
 
CREATE TYPE T_COL_VALUES AS TABLE(
COL_VALUES NVARCHAR(1000)
);

Y ahora...estamos listos para comenzar con el codigo...un procedimiento R y dos procedimientos SQLScript...

Get_Hashtags.sql
CREATE PROCEDURE GET_HASHTAGS(IN twittername TWITTER_USERS,IN first_hash FIRST_HASH,
                              OUT out_col_names T_COL_NAMES, OUT out_col_values T_COL_VALUES)
LANGUAGE RLANG AS
BEGIN
UserName = twittername$USERNAME
hashline = first_hash$HASH_LINE
hashvalues = first_hash$HASH_VALUES
 
Get_Twitter<-function(p_source,p_pattern){
  datalines = grep(p_pattern,web_page,value=TRUE)
  getexpr = function(s,g)substring(s,g,g+attr(g,'match.length')-1)
  g_list = gregexpr(p_pattern,datalines)
  matches = mapply(getexpr,datalines,g_list)
  result = gsub(p_pattern,'\\1',matches)
  names(result) = NULL
  return(result)
}
 
Get_Hashtags<-function(p_source){
  check<-!length(grep('\\"([^,\\"]+)\\"', as.character(p_source)))
  if(!check){
    mypattern = '\\"([^,\\"]+)\\"'
    datalines = grep(mypattern,p_source,value=TRUE)
    getexpr = function(s,g)substring(s,g,g+attr(g,'match.length')-1)
    g_list = gregexpr(mypattern,datalines)
    matches = mapply(getexpr,datalines,g_list)
    result = gsub(mypattern,'\\1',matches)
    names(result) = NULL
    return(result)
  }else{
    result<-p_source
    return(result)
  }
}
 
url<-paste("http://api.twitter.com/1/statuses/user_timeline.xml?count=200&screen_name=",UserName,sep="")
mypattern = '<text>([^<]*)</text>'
web_page<-readLines(url)
tweets<-Get_Twitter(web_page,mypattern)
mypattern = '[^\\&]#(\\.?\\w+)'
hash_list<-Get_Twitter(tweets,mypattern)
hashtags<-sapply(hash_list,Get_Hashtags)
hashtags<-as.vector(unlist(hashtags))
hashtags<-toupper(hashtags)
 
dt.hashtags<-data.frame(UserName,hashtags)
tab.hashtags<-table(dt.hashtags)
dt.hashtags<-as.data.frame.matrix(tab.hashtags)
hashtags_names<-names(dt.hashtags)
hashtags_names<-gsub("^\\.",'',hashtags_names)
 
if(length(hashline>=1)){
          hash_line<-gsub("^(\\w)+\\,",'',hashline)
          hash_line<-unlist(strsplit(hash_line, split=","))
          hash_values<-gsub("^(\\'+\\w+\\')+\\,",'',hashvalues)
          hash_values<-as.numeric(unlist(strsplit(hash_values, split=",")))
          hash_frame<-data.frame(names=hash_line,values=hash_values)
          hash_frame["values"]<-0
 
          Col_Names<-""
          Col_Values<-""
 
          for(i in 1:length(hashtags_names)){
                      Col_Names<-paste(Col_Names,hashtags_names[i],sep=",")
                      Col_Values<-paste(Col_Values,dt.hashtags[,i],sep=",")
          }
 
           Col_Names<-gsub("^\\,|\\.",'',Col_Names)
          Col_Values<-gsub("^\\,|\\.",'',Col_Values)
          Col_Names<-unlist(strsplit(Col_Names, split=","))
          Col_Values<-as.numeric(unlist(strsplit(Col_Values, split=",")))
          new_hash_frame<-data.frame(names=Col_Names,values=Col_Values)
          new_hash_frame<-rbind(hash_frame,new_hash_frame)
          new_hash_frame<-aggregate(values ~ names, FUN = "sum", data = new_hash_frame)
          new_hash_names<-new_hash_frame$names
          new_hash_values<-new_hash_frame$values
 
          Col_Names<-"USERNAME"
          Col_Values<-paste("'",UserName,"'",sep="")
 
           for(i in 1:length(new_hash_names)){
                      Col_Names<-paste(Col_Names,new_hash_names[i],sep=",")
                      Col_Values<-paste(Col_Values,new_hash_values[i],sep=",")
          }
}else{
          Col_Names<-"USERNAME"
          Col_Values<-paste("'",UserName,"'",sep="")
 
          for(i in 1:length(hashtags_names)){
                      Col_Names<-paste(Col_Names,hashtags_names[i],sep=",")
                      Col_Values<-paste(Col_Values,dt.hashtags[,i],sep=",")
          }
}
 
col_names<-gsub("^\\,\\.?",'',Col_Names)
col_values<-gsub("^\\,",'',Col_Values)
 
out_col_names<-data.frame(COL_NAMES=col_names)
out_col_values<-data.frame(COL_VALUES=col_values)
END;

Save_Hashtags.sql
CREATE PROCEDURE SAVE_HASHTAGS(IN in_col_names T_COL_NAMES, IN in_col_values T_COL_VALUES)
LANGUAGE SQLSCRIPT AS
v_select VARCHAR(2000);
v_col_names_char NVARCHAR(1000);
v_col_values_char NVARCHAR(1000);
CURSOR c_cursor1 FOR
SELECT COL_NAMES FROM :in_col_names;
CURSOR c_cursor2 FOR
SELECT COL_VALUES FROM :in_col_values;
BEGIN
                    OPEN c_cursor1;
                    FETCH c_cursor1 into v_col_names_char;
                    CLOSE c_cursor1;
                    OPEN c_cursor2;
                    FETCH c_cursor2 into v_col_values_char;
                    CLOSE c_cursor2;
                    DELETE FROM FIRST_HASH;
                    INSERT INTO FIRST_HASH VALUES(:v_col_names_char,:v_col_values_char);
                    v_select := 'INSERT INTO TWITTER_HASHTAGS (' || v_col_names_char || ') 
                                VALUES (' || v_col_values_char || ')';
                    EXEC v_select;
END;

Get_Twitter_Users.sql
CREATE PROCEDURE GET_TWITTER_USERS(UserName NVARCHAR(10))
LANGUAGE SQLSCRIPT AS
BEGIN
          Twitter_Users = SELECT USERNAME FROM TWITTER_USERS WHERE USERNAME = :UserName;
          First_Hash = SELECT HASH_LINE, HASH_VALUES FROM FIRST_HASH;
          CALL GET_HASHTAGS(:Twitter_Users,:First_Hash,T_COL_NAMES,T_COL_VALUES);
          CALL SAVE_HASHTAGS(:T_COL_NAMES,:T_COL_VALUES);
END;

Para que esto funcione, debemos insertar algunos valores en nuestra tabla "TWITTER_USERS"...


Y luego, simplemente llamar al procedimiento "GET_TWITTER_USERS"...

Call_Get_Twitter_Users.sql
CALL GET_TWITTER_USERS('Blag');
CALL GET_TWITTER_USERS('Schmerdy');
CALL GET_TWITTER_USERS('ggread');

Cuando ejecutamos la primera llamada...esto es con el usuario @Blag tendremos la informacion en la tabla  "FIRST_HASH"...


Y esto en nuestra Flexible Table "TWITTER_HASHTAGS"...


Como pueden ver...nuestra tabla comenzo solamente con USERNAME...pero como le pasamos los Hashtags y sus valores...la tabla crece para poder almacenarlos...

Cuando llamamos al siguiente usuario...es decir @Schmerdy tendremos esto en nuestra Flexible Table...


Como pueden ver...en todos los Hashtags que pertenecen a @Blag pero no pertenecen a @Schmerdy tenemos un valor "0"...asi que, que va a pasar con los que pertenecen a @Schmerdy pero no a @Blag?


Esos campos tendran un valor "?", puesto que no existian antes de que los agregaramos...y nuevamente...la tabla crece para almacenar los nuevos valores....

Ahora...algo interesante es que @Schmerdy tiene mas Hashtags que @Blag...asi que, que va a pasar cuando llamemos al ultimo usuario que es @ggread que dicho sea de paso...tiene menos Hashtags que @Schmerdy y @Blag...


@ggread tendra un valor "0" en todos los Hashtags que no perteneces a su usuario...pero tendra valores en aquellos que son similares a @Schmerdy...


Asi que...que pasara con los Hashtags que pertenecen a @ggread pero que no existen en @Schmerdy ni en @Blag? Facil...seran agregados y los valores "?" puesto en aquellos Hashtags que no existian antes...


Me gustaria poder poner la tabla completa...pero contiene mas de 50 columnas...asi que mejor...puedo exportarla a un archivo .CSV...y hacer algun analisis utilizando Visual Intelligence...


Aqui, podemos ver que tan seguido estos tres usuarios han utilizados los Hashtags #SAPHANA, #SAP y #SAPTECHED en sus ultimos 200 tweets...

Asi que...eso es todo...una simple y bonita forma de demostrar como funcionan las Flexible Tables en SAP HANA utilizando mi siempre amado R -:)

Saludos,

Blag.

No comments: