Showing posts with label sqlite. Show all posts
Showing posts with label sqlite. Show all posts

Saturday, December 31, 2011

Using SQL for data management


Every weekday I pull out data from IBM DB2 database and do analysis in SAS. I use SQLite to record my personal chores, such as credit card transactions. And I am taking courses of database development by Microsoft SQL Server. Then I have pretty enough time to compare the 4 softwares. Recently I have the rough feeling -- the relational database end and the analytics end look more and more similar, while the databases brings more analysis procedures and every statically packages is eager to introduce SQL feature as enhancement. Thus, Structured Query Language (SQL) tends to be the universal language in the data world.

SQLs in DB2, Microsoft SQL Server (T-SQL), SQLite and SAS (PROC SQL) follow the same logic but contain different characteristics. We can treat them as the branches originated from the ANSI SQL standard. I play around their own SQL dialects and try to learn how to manage data in different ways.

Data
Using the SQL syntax below,  the SASHELP.CLASS data set, which contains the gender, name, age, weight and height information of 18 fake teenagers, will be able to be transferred to any database system.
create table class (name char(8), sex char(1), 
age numeric, height numeric, weight numeric );
insert into class values('ALFRED','M',14,69,112.5);
insert into class values('ALICE','F',13,56.5,84);
insert into class values('BARBARA','F',13,65.3,98);
insert into class values('CAROL','F',14,62.8,102.5);
insert into class values('HENRY','M',14,63.5,102.5);
insert into class values('JAMES','M',12,57.3,83);
insert into class values('JANE','F',12,59.8,84.5);
insert into class values('JEFFREY','M',13,62.5,84);
insert into class values('JOHN','M',12,59,99.5);
insert into class values('JOYCE','F',11,51.3,50.5);
insert into class values('JUDY','F',14,64.3,90);
insert into class values('LOUISE','F',12,56.3,77);
insert into class values('MARY','F',15,66.5,112);
insert into class values('PHILIP','M',16,72,150);
insert into class values('ROBERT','M',12,64.8,128);
insert into class values('RONALD','M',15,67,133);
insert into class values('THOMAS','M',11,57.5,85);
insert into class values('WILLIAM','M',15,66.5,112);

Random sampling
I randomly select 8 out of the 18 persons. The realization of this purpose mainly relies on the software's' random function and output control.

/********************** a. SQL Server **********************/
select top (8) *
from class
order by NEWID();

/********************** b. DB2 **********************/
select *
from class
order by rand()
fetch first 8 rows only;

/********************** c. SQLite **********************/
select *
from class
order by random()
limit 8;

/********************** d. SAS **********************/
proc sql outobs = 8;
select *
from class
order by ranuni(0);

Report subtotal of a variable as the last observation
It is a quite common task to make a table with subtotal for reporting. DB2 and SQL Server both have ROLLUP operator/function, while SAS may utilize its macro facility.

/********************** a. SQL Server **********************/
select coalesce(name, 'Total') as name, SUM(weight) as weight
from class
group by name with rollup;

/********************** b. DB2 **********************/
select case grouping(name)
when 0 then name
else 'Total'
end name,
sum(weight) as weight
from class
group by rollup(name);

/********************** c. SQLite **********************/
select name, sum(weight) as weight
from class
group by name
union all
select 'Total', sum(weight)
from class;

/********************** d. SAS **********************/
select sum(weight) into: sum_weight from class;
create table class2 as select name, weight from class;
insert into class2 values('Total', &sum_weight);

Retrieve column information of a table
Each database tries to store the metadata somewhere, so that we are able to retrieve the information of the columns/variables, such as their name, data type and position at the table. Only SQLite, a portable database, has no designated table to store this information. However, running the command line shell of SQLite has the same effect.

/********************** a. SQL Server **********************/
select column_name, data_type, ordinal_position
from information_schema.columns
where table_name = 'class';

/********************** b. DB2 **********************/
select colname, typename, colno
from syscat.columns
where tabname = 'class';

/********************** c. SQLite **********************/
pragma table_info(class);

/********************** d. SAS **********************/
select name, type, varnum
from sashelp.vcolumn
where libname = 'WORK' and memname = 'CLASS';

Obtain the running total of a variable
It seems like a difficult job for native SQL syntax. SQL Server and DB2 evolve with their ranking functions -- DB2 has sum() over() and SQL Server has row_number() over(), which can be used to solve this problem. In SAS, the better way is to use DATA step instead.

/********************** a. SQL Server **********************/
select name, weight, sum(weight) over (order by weight) as running_total
from class
order by weight

/********************** b. DB2 **********************/
create view v_class as
select ROW_NUMBER() OVER(ORDER BY name) as rownum, *
from class;
select a.name, a.weight,
(select SUM(b.weight) from v_class b
where b.rownum <= a.rownum) as running_total
from v_class as a

/********************** d. SAS **********************/
data class2;
class;
retain running_total;
running_total + weight;
keep name weight running_total;
run;

Monday, December 12, 2011

Those most productive R developers



The number of R packages on CRAN is 3,483 on 2011-12-12. The growth of R package in the past years can be fitted by a quadratic regression perfectly.

I am always interested in who are maintaining those packages. Then I wrote an R script to extract the package head information from CRAN’s website and stored them in a SQLite database. Most R developers are maintaining 1-3 R packages. Some of them are really productive. By the correspondence addresses (Email), the top 50 R developers are listed below:

developer package
1 Kurt Hornik 23
2 Martin Maechler 23
3 Hadley Wickham 21
4 Rmetrics Core Team 19
5 Achim Zeileis 17
6 Henrik Bengtsson 17
7 Paul Gilbert 17
8 Brian Ripley 14
9 Roger D. Peng 13
10 Torsten Hothorn 13
11 Karline Soetaert 12
12 Philippe Grosjean 12
13 Robin K. S. Hankin 12
14 Charles J. Geyer 11
15 Matthias Kohl 11
16 Charlotte Maia 10
17 Mikis Stasinopoulos 10
18 Simon Urbanek (1) 10
19 Thomas Lumley 10
20 Arne Henningsen 9
21 Gregory R. Warnes 9
22 Jonathan M. Lees 9
23 Michael Hahsler 9
24 Peter Ruckdeschel 9
25 A.I. McLeod 8
26 Brian Lee Yung Rowe 8
27 Dirk Eddelbuettel 8
28 John Fox 8
29 Kaspar Rufibach 8
30 Korbinian Strimmer 8
31 Michael Friendly 8
32 Peter Solymos 8
33 Roger Bivand 8
34 Simon Urbanek (2) 8
35 Christopher Brown 7
36 David Meyer 7
37 ORPHANED 7
38 Revolution Analytics 7
39 Rob J Hyndman 7
40 Romain Francois 7
41 Ulrike Groemping 7
42 Christophe Genolini 6
43 Frank Schaarschmidt 6
44 G. Grothendieck 6
45 Hana Sevcikova 6
46 Jeffrey A. Ryan 6
47 Kjetil Halvorsen 6
48 Pei Wang 6
49 Trevor Hastie 6
50 Yihui Xie 6


### A script of R to extract R package information and
### build a SQLite databse by hchao8@gmail.com
library(ggplot2)
library(XML)
library(RSQLite)

# Create and connect a SQLite database
conn <- dbConnect("SQLite", dbname = "c:/Rpackage.db")

# Extract names of R packages available from web
allPackageURL <-
"http://cran.r-project.org/web/packages/available_packages_by_name.html"
allPackage <- na.omit(melt(readHTMLTable(allPackageURL))[, c("V1")])

# Extract individual package information from web and store data in SQLite
for (i in 1:length(allPackage)){
packageName <- allPackage[i]
packageURL <- paste("http://cran.r-project.org/web/packages/",packageName,
"/index.html", sep="")
y <- melt(readHTMLTable(packageURL))
y$L1 <- packageName
if(dbExistsTable(conn, "Rpackage")) {
dbWriteTable(conn, "Rpackage", y, append = TRUE)
} else {
dbWriteTable(conn, "Rpackage", y)
}
}
# Pull out maintainer information from SQLite database
all <- fetch(dbSendQuery(conn, "
select v2 as author, count(v2) as package
from rpackage
where v1 = 'Maintainer:'
group by v2
order by package desc
;"))

# Disconnect SQLite database
dbDisconnect(conn)

# Draw a histogram
qplot(package, data = all, binwidth = 1, ylab = "Frequency",
xlab = "R packages maintained by individual developer")
ggsave("c:/Rlist.png")

# Find 50 most productive developers
head(all, 50)

Saturday, May 14, 2011

Macros communicate SQLite and SAS without ODBC


SQLite is an open-source relationship database management system with full functionality [Ref.1]. The light-weight (300k+ size) and zero configuration features distinguish it from its’ 800-pound counterparts like Oracle or MySQL. Thanks to the rise of mobile devices (plus SQLite-embedded Firefox), SQLite will probably be seen everywhere pretty soon.

I just love SQLite, since SQLite helped me learn not only writing SQL codes on Windows and Linux, but also managing complicated databases. Both Python and R have nice support for SQLite. And I always expect to implement SQLite as a frontend or backup for SAS. The shortcut is to apply some 3rd-party SQLite’s ODBC drivers [Ref. 2]. However, those drivers never worked very well on my workstations. To bypass the ODBC method, Wensui designed a macro to use SQLite’s ‘.dump’ operator to generate SQL file for SAS [Ref. 3]. To establish a two-way communication, I wrote two macros below to export SQLite’s table to SAS, and vice versa. The macros utilized tab-delimited text as medium, and SQLite’s batch mode to execute the script on a PC.

Reference:
1. Grant Allen, Mike Owens. ‘The Definitive Guide to SQLite’. Apress Publishing.
2. SQLite ODBC Driver. http://www.ch-werner.de/sqliteodbc/
3. Wensui Liu. ‘Sas Macro Importing Sqlite Data Table Without Odbc’.

/*******************READ ME*********************************************
* - Macros communicate SQLite and SAS without ODBC -
*
* SAS VERSION: 9.1.3
* SQLITE VERSION: 3.7.4
* DATE: 14may2011
* AUTHOR: hchao8@gmail.com
*
****************END OF READ ME******************************************/

****************(1) MODULE-BUILDING STEP********************************;
******(1.1) BUILD A MACRO FROM SAS TO SQLITE****************************;
%macro sas2sqlite(sastable = , path = , database = );
/*****************************************************************
* MACRO: sas2sqlite()
* GOAL: output a dataset in SAS to a table in SQLite
* PARAMETERS: sastable = dataset in SAS for SQLite
* path = destinate file path for SQLite database
* database = name of SQLite database
*****************************************************************/
proc export data = &sastable outfile = "&path\sas_2_sqlite.txt" dbms = tab
repalce;
putnames = no;
run;

ods listing close;
ods output variables = _varlist;
proc contents data = &sastable;
run;
proc sort data = _varlist;
by num;
run;

data _tmp01;
set _varlist;
if lowcase(type) = 'num' then vartype = 'real';
else if lowcase(type) = 'char' then vartype = 'text';
run;
proc sql noprint;
select trim(variable) ||' '|| trim(vartype)
into: table_value separated by ', '
from _tmp01
;quit;

proc sql;
create table _tmp02 (string char(800));
insert into _tmp02
set string = '.stats on'
set string = 'create table sas_table(sas_table_value);'
set string = '.separator "\t"'
set string = ".import 'sas_path\sas_2_sqlite.txt' sas_table"
;quit;

data _tmp03;
set _tmp02;
string = tranwrd(string, "sas_table_value", "&table_value");
string = tranwrd(string, "sas_table", "&sastable");
string = tranwrd(string, "sas_path", "&path");
run;
data _null_;
set _tmp03;
file "&path\sas_2_sqlite.sql";
put string;
run;
options noxsync noxwait;
x "sqlite3 -init &path\sas_2_sqlite.sql &path\&database";

proc datasets;
delete _:;
quit;
ods listing;
%mend;

******(1.2) BUILD A MACRO FROM SQLITE TO SAS***************************;
%macro sqlite2sas(sqlitetable = , path = , database = );
/*****************************************************************
* MACRO: sqlite2sas()
* GOAL: output a table in SQLite to a dataset in SAS
* PARAMETERS: sqlitetable = table in SQLite for SAS
* path = target file path for SQLite database
* database = name of SQLite database
*****************************************************************/
proc sql;
create table _tmp0 (string char(800));
insert into _tmp0
set string = ".output 'output_path\sqlite_2_sas.txt' "
set string = '.separator "\t" '
set string = '.headers on'
set string = 'select * from sqlite_table;'
set string = '.output stdout'
;quit;
data _tmp1;
set _tmp0;
string = tranwrd(string, "sqlite_table", "&sqlitetable");
string = tranwrd(string, "output_path", "&path");
run;
data _null_;
set _tmp1;
file "&path\sas_2_sqlite.sql";
put string;
run;

options noxsync noxwait;
x "sqlite3 -init &path\sas_2_sqlite.sql &path\&database ";

proc import datafile = "&path\sqlite_2_sas.txt" out = &sqlitetable
dbms = dlm replace;
delimiter = '09'x;
guessingrows = 10000;
run;
proc datasets nolist;
delete _:;
run;
%mend;

****************(2) TESTING STEP****************************************;
******(2.1) TESTING THE FIRST MACRO*************************************;
data iris;
set sashelp.iris;
run;
%sas2sqlite(sastable = iris, path = c:\tmp, database = sas_sqlite.sqlite);

******(2.2) TESTING THE SECOND MACRO************************************;
proc datasets;
delete iris;
quit;
%sqlite2sas(sqlitetable = iris, path = c:\tmp, database = sas_sqlite.sqlite);

****************END OF ALL CODING***************************************;