Translating between R and SQL: the basics

08 Nov 2013

An introductory comparison of using the two languages.


R was made especially for data analysis and graphics.  SQL was made especially for databases.  They are allies.

The data structure in R that most closely matches a SQL table is a data frame.  The terms rows and columns are used in both.

A mashup

There is an R package called sqldf that allows you to use SQL commands to extract data from an R data frame.  We will use this package in the examples. There are two basic steps to using an R package:

  • it must be installed on your machine (once)
  • it must be available in each R session where it is used

You can use sqldf by doing (one time only, and assuming an internet connection):


Then in each R session where you want to use it:


To simplify the examples, we’ll slightly modify one of the inbuilt data frames:

myCO2 <- CO2
attributes(myCO2) <- attributes(CO2)[
  c("names", "row.names", "class")]
class(myCO2) <- "data.frame"

Note that the character between C and 2 is a capital-O and not a zero.  The CO2 object has a complicated value for its class but the result of the sqldf function has only "data.frame" in its class.  We want to cleanly see if two objects are the same, and hence we want the classes to match.

Column names

In R the colnames function returns the names of the columns:

> colnames(myCO2)
[1] "Plant"     "Type"      "Treatment" "conc" 
[5] "uptake"

The result is a vector of character strings.

Subsetting columns

Columns in SQL are also called “fields”.  In R it is rather common for columns to be called “variables”.

In SQL the subset of columns is determined by select.  Here we want to get the Type and conc columns:

s01 <- sqldf("select Type, conc from myCO2")

Subsetting in R (commonly called “subscripting”) is done with square brackets.  When subscripting a data frame there will be two places inside the square brackets separated by a comma.  The R equivalent of the command above is:

r01 <- myCO2[, c("Type", "conc")]

The first part inside the square brackets (corresponding to rows) is empty.  The second part (corresponding to columns) has a character vector with the names of the two columns we want.

We can test that r01 and s01 are the same:

> all.equal(s01, r01)
[1] TRUE

In R the vector of column names could be created as an object and then used in the subscripting:

someCols <- c("Type", "conc")
r01b <- myCO2[, someCols]

The r01 and r01b objects are the same.

All columns

An asterisk is used in SQL to indicate that you want all columns:

s02 <- sqldf("select * from myCO2")

When you want all items in a dimension in R, you leave it blank:

r02 <- myCO2[ , ]

You might have been able to guess that because we’ve seen that done for rows already.  Note that spaces almost never matter in R — the command above has spaces either side of the comma, but would be exactly the same with no spaces.

Only one column

How to select only a single column is no surprise in either language:

s03 <- sqldf("select Type from myCO2")
r03 <- myCO2[ , "Type"]

But there is a surprise when you test if these two objects are equal:

all.equal(s03, r03)

The command above results in a bunch of stuff, indicating they are quite different.

The r03 object is not a data frame, it is an object of the type of the column.  While surprising to those used to SQL, this is quite natural for R’s purposes.  For example, we give the mean function a vector of numbers, not a data frame:

> mean(myCO2[, "uptake"])
[1] 27.2131

You can get a one-column data frame by slightly modifying the command:

r03d <- myCO2[ , "Type", drop=FALSE]

The s03 and r03d objects are the same.

Data frames are not natural inputs to some functions:

> mean(myCO2[, "uptake", drop=FALSE])
[1] NA
Warning message:
In mean.default(myCO2[, "uptake", drop = FALSE]) :
 argument is not numeric or logical: returning NA

Case sensitivity

SQL is not case-sensitive:

s04 <- sqldf("select type, coNC from myCO2")

s04 is the same as s01.

On the other hand, R is case-sensitive:

> r04 <- myCO2[, c("type", "coNC")]
Error in `[.data.frame`(myCO2, , c("type", "coNC")) : 
  undefined columns selected

R extensions

We’ve seen how to select columns of an R data frame with the names of the columns.  There are other ways of selecting columns as well.

The order of the columns in an R data frame is of significance.  You can select columns by number.  For example, you can select column 5 and then column 2:

myCO2[, c(5, 2)]

You can use negative numbers to exclude columns.  Here you are asking for all columns except the first and the fourth:

myCO2[, c(-1, -4)]

Column selection in R can also be done with logical values:


Those logical values can be created by a command:

myCO2[, colnames(myCO2) > "d"]

Subsetting rows

In SQL a common synonym for “row” is “record”.  In R a common synonym is “observation”.


The common way of getting a subset of rows in SQL is with the where command:

s05 <- sqldf("select * from myCO2 where uptake < 20")

In R the equivalent of the where is put in the first position inside the square brackets:

r05 <- myCO2[ myCO2[, "uptake"] < 20, ]

s05 and r05 are in most respects the same.  The difference is that the row names are different.  r05 has the row names from the original data frame while s05 has new ones that are sequential from 1.


The command that created r05 is a little convoluted (but logical once you stare at it long enough).  The with function allows a command that is more in the spirit of what is done in SQL:

r05w <- with(myCO2, myCO2[uptake < 20, ]) # same as r05

Inside the with call the columns of the data frame named in the first argument can be used as objects.  In this example uptake is used directly instead of pulling that column out of the data frame.

Logical operators

Logical comparisons in SQL are combined with AND and OR:

s06 <- sqldf("select * from myCO2 where uptake < 20 and Type='Quebec'")

Also note that testing equality is with =.

In R this type of ‘and’ operation is done with & and the ‘or’ is |:

r06 <- with(myCO2, myCO2[uptake < 20 & Type == 'Quebec', ])

A possible trouble spot is that equality in R is tested with == (while = is an assignment operator).

The s06 and r06 objects are the same except for their row names.

First few

The limit command in SQL limits the number of rows that are given:

s07 <- sqldf("select * from myC02 limit 6")

One way to see just the column names is to limit the number of rows to zero.

You can get the first few rows in R with head:

r07 <- head(myCO2)

The tail function gives you the last few rows, and the corner function is a logical extension of head and tail.

Row names versus numbers

A source of possible confusion is that row names are character even though they are, by default, representations of numbers.  Let’s experiment with r06:

> r06
   Plant   Type  Treatment conc uptake
1    Qn1 Quebec nonchilled   95   16.0
8    Qn2 Quebec nonchilled   95   13.6
15   Qn3 Quebec nonchilled   95   16.2
22   Qc1 Quebec    chilled   95   14.2
29   Qc2 Quebec    chilled   95    9.3
36   Qc3 Quebec    chilled   95   15.1

Select the first three rows:

> r06[1:3,]
   Plant   Type  Treatment conc uptake
1    Qn1 Quebec nonchilled   95   16.0
8    Qn2 Quebec nonchilled   95   13.6
15   Qn3 Quebec nonchilled   95   16.2

Now let’s select the characters one through three:

> r06[c("1", "2", "3"), ]
   Plant   Type  Treatment conc uptake
1    Qn1 Quebec nonchilled   95   16.0
NA  <NA>   <NA>       <NA>   NA     NA
36   Qc3 Quebec    chilled   95   15.1

What happened?  The first row is correct — the first row name is 1.  In the second row it looked for a row name called “2” and didn’t find one, so it put in missing values.  The third row is even weirder: it looked for a row name called “3”; there was a single row name starting with “3” so it did a partial match and gave us that row.

Trying to give numbers instead of the actual names doesn’t necessarily work either:

> r06[c(1, 8, 15), ]
     Plant   Type  Treatment conc uptake
1      Qn1 Quebec nonchilled   95     16
NA    <NA>   <NA>       <NA>   NA     NA
NA.1  <NA>   <NA>       <NA>   NA     NA

Additional details


In SQL NULL means missing value.  Confusingly R also has NULL but the equivalent of SQL NULL is NA in R.

Let’s create some data to play with:

r08 <- r06
r08[2:4, 1] <- NA
r08[5, 4] <- NA

This looks like:

> r08
   Plant   Type  Treatment conc uptake
1    Qn1 Quebec nonchilled   95   16.0
8   <NA> Quebec nonchilled   95   13.6
15  <NA> Quebec nonchilled   95   16.2
22  <NA> Quebec    chilled   95   14.2
29   Qc2 Quebec    chilled   NA    9.3
36   Qc3 Quebec    chilled   95   15.1

Get the rows where Plant is not missing:

s09 <- sqldf("select * from r08 where plant is not null")
r09 <- with(r08, r08[!, ])

We can also get the rows where Plant is missing:

s10 <- sqldf("select * from r08 where plant is null")
r10 <- with(r08, r08[, ])

To get the rows that have no missing values in R, you can do:

> na.omit(r08)
   Plant   Type  Treatment conc uptake
1    Qn1 Quebec nonchilled   95   16.0
36   Qc3 Quebec    chilled   95   15.1


In SQL single quotes are used to delimit character strings.  A single quote inside a string is given with two single quotes in a row.  Some implementations allow you to specify the delimiter.

In R either single quotes or double quotes can be used.  You can use whichever you find more convenient but R always prints using double quotes.  The backslash is used to escape a quote character that is the same as the delimiting quote:

> c("he's", 'he\'s', "she has \"it\"")
[1] "he's"           "he's"           "she has \"it\""


Semicolons are sometimes used at the end of statements in both SQL and R.

Some SQL implementations require a semicolon at the end of a statement.

Semicolons are used to separate R commands on the same line.  They can be used after all R commands, but probably shouldn’t be.

Single subscript

Note that both

r06[1:3, ]



are legal R commands, but they do different things.  The first gives the first three rows and all of the columns; the second gives all of the rows and the first three columns (for a reason you need not be concerned about initially).


If a data frame is large and the manipulation is complex, then R can be inefficient.  Why should someone with access to a database put up with such inefficiency?  One reason is the flexibility that R gives you.  It might surprise some people that not all data naturally fit into a structure of rows and columns.  Besides it usually doesn’t matter.  As has been asked facetiously by a certain someone: “What are you going to do with that extra millisecond?”

But if there are millions of those milliseconds, then you might start to care.  The data.table package provides an alternative form of data frames that is highly efficient.

Resources to learn R

“Impatient R” is a minimal set of things to learn about R.

“Some hints for the R beginner” suggests additional resources.

Resources to learn SQL

I’m certainly no expert at learning materials for SQL — please make suggestions.  But here are things I’ve found that seem at least okay:

SQLZoo provides quite a nice interactive set of exercises.

Tutorialspoint has information easily arranged for learning and refreshing.

See also

“R database interfaces”

“Select operations on R data frames”

Tags: , ,
11 replies
  1. Bob Muenchen says:

    Hi Pat,

    I thoroughly enjoyed reading this. I’ve already added a link to it in my Managing Data with R workshop notes ( I get a lot of SAS folks in there looking for an alternative to PROC SQL. There’s only one additional warning I mention in that class: fight the urge to separate SQL clauses with commas as you would separate arguments in R. I REALLY want to add them!


    • Patrick Burns says:


      Thanks — for both the link and the tip. Hopefully your students will find it useful.

  2. Clio A says:

    Hi there! I really like your page–thank you! I wish there was a way to use the SQL TOP command in R. There doesn’t seem to be a very good one….

    e.g. sqldf(“SELECT TOP 5 homeprice, city FROM mytable GROUP BY city”)

    Thanks again!

  3. Rick Tankard says:


    very helpful. I noticed that this line has “myC02” with a zero instead of “myCO2”:
    s07 <- sqldf("select * from myC02 limit 6")


  4. Igor says:

    ” r05 has the row names from the original data frame while s05 has new ones that are sequential from 1.”

    On my R v.3.2.0 ro05’s row names are also 1-30.
    Yet, comparison by all.equal gives a message
    “Attributes: ”

    This is probably the resuls of changing the attributes of myCO2 from the very beginning.


Trackbacks & Pingbacks

  1. […] for more information on how to work with sqldf, you can go here for a video tutorial or here for a written overview of the […]

  2. […] by SQL integration (link) […]

  3. […] Translating between R and SQL: the basics […]

  4. […] Поехали! 1. Computing for data analysis на coursera (стартует 23 сентября, 4 недели). R. 2. Introduction to Databases на coursera (с любой даты). SQL. 3. Bioinformatics algorithms. Python применительно к биоинформатике. Стартует 21 октября. 4. Data analysis стартует 28 октября. R 5. Computer Science with python 6. R+SQL […]

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply

Your email address will not be published. Required fields are marked *

© Copyright - Burns Statistics