SQLをdplyrに対応付けて覚えたい

[SQL 第2版 ゼロからはじめるデータベース操作（ミック） ｜ 翔泳社の本](http://www.shoeisha.co.jp/book/detail/9784798144450)を読んでいるけどSQLが今ひとつわからない。`{dplyr}`に対応付ければきっと理解できると思うので詰まったらメモしていく。

# 支度を整える

ローカルにPostgreSQL 9.6.2がインストールしてある状態。とりあえずRから繋ぐ。

```r:パッケージの準備.R
install.packages("DBI")
install.packages("RPostgreSQL")
install.packages("dplyr")
library(RPostgreSQL)
library(dplyr)
```

クエリを送って実行するためには`{RPostgreSQL}`等のパッケージで接続する。

```r 
# RPostgreSQL
con <- dbConnect(PostgreSQL(),
                 host = "localhost",
                 port = 5432,
                 dbname = "shop",
                 user = "your_user_name",
                 password = "your_password")
```

`{dplyr}`からも接続できて、この場合はdplyrっぽい書き方からクエリが生成されて実行されるので、よく考えるとこれで事は済む気もする。

```r 
# dplyr
con_dp <- src_postgres(
  host = "localhost",
  port = 5432,
  dbname = "shop",
  user = "your_user_name",
  password = "your_password")
shohin_tbl <- tbl(con_dp, from = "shohin")
```
`user`と`password`は適宜書き換え。データベースはテキストに従って作ってあるものとする。

クエリを送って実行するのはこんな感じ。

```r:クエリの実行_RPostgreSQL.R
> query <- "
+ select * from shohin;
+ "
> dbGetQuery(con, query)
  shohin_id     shohin_mei shohin_bunrui hanbai_tanka shiire_tanka   torokubi shohin_mei_kana
1      0001        Tシャツ          衣服         1000          500 2009-09-20            <NA>
2      0002   穴あけパンチ      事務用品          500          320 2009-09-11            <NA>
3      0003 カッターシャツ          衣服         4000         2800       <NA>            <NA>
4      0004           包丁  キッチン用品         3000         2800 2009-09-20            <NA>
5      0005         圧力鍋  キッチン用品         6800         5000 2009-01-15            <NA>
6      0006       フォーク  キッチン用品          500           NA 2009-09-20            <NA>
7      0007     おろしがね  キッチン用品          880          790 2009-04-28            <NA>
8      0008     ボールペン      事務用品          100           NA 2009-11-11            <NA>
```

`dplyr`からも`tbl()`と`sql()`でクエリを送って実行できる。

```r:クエリの実行_dplyr
tbl(con_dp, sql(query))
```

dplyrはいつものdplyrだけど`show_query()`に送ればクエリを確認できる。

```r:クエリの実行_dplyr.R
> shohin_tbl
Source:   query [?? x 7]
Database: postgres 9.6.2 [nozma@localhost:5432/shop]

  shohin_id     shohin_mei shohin_bunrui hanbai_tanka shiire_tanka   torokubi shohin_mei_kana
      <chr>          <chr>         <chr>        <int>        <int>     <date>           <chr>
1      0001        Tシャツ          衣服         1000          500 2009-09-20            <NA>
2      0002   穴あけパンチ      事務用品          500          320 2009-09-11            <NA>
3      0003 カッターシャツ          衣服         4000         2800       <NA>            <NA>
4      0004           包丁  キッチン用品         3000         2800 2009-09-20            <NA>
5      0005         圧力鍋  キッチン用品         6800         5000 2009-01-15            <NA>
6      0006       フォーク  キッチン用品          500           NA 2009-09-20            <NA>
7      0007     おろしがね  キッチン用品          880          790 2009-04-28            <NA>
8      0008     ボールペン      事務用品          100           NA 2009-11-11            <NA>
> shohin_tbl %>% show_query
<SQL>
SELECT *
FROM "shohin"
```

# SQL vs `{dplyr}`
## 相関サブクエリ

ここでは、商品分類(`shohin_bunrui`)毎に販売単価(`hanbai_tanka`)の平均値を算出し、それを商品毎に販売単価と比較して、分類毎の平均単価より平均単価が高い商品を抽出したい。

```r:SQL
> query <- "
+ select shohin_bunrui, shohin_mei, hanbai_tanka
+ from shohin as s1
+ where hanbai_tanka > (select avg(hanbai_tanka)
+                       from shohin as s2
+                       where s1.shohin_bunrui = s2.shohin_bunrui
+                       group by shohin_bunrui);
+ "
> dbGetQuery(con, query)
  shohin_bunrui     shohin_mei hanbai_tanka
1      事務用品   穴あけパンチ          500
2          衣服 カッターシャツ         4000
3  キッチン用品           包丁         3000
4  キッチン用品         圧力鍋         6800
```

サブクエリの中の`shohin`と外の`shohin`を対応付けるのがポイント。

```r:dplyr
> shohin_tbl %>%
+   group_by(shohin_bunrui) %>%
+   filter(hanbai_tanka > mean(hanbai_tanka)) %>%
+   select(shohin_bunrui, shohin_mei, hanbai_tanka)
Source:   query [?? x 3]
Database: postgres 9.6.2 [nozma@localhost:5432/shop]
Groups: shohin_bunrui

  shohin_bunrui     shohin_mei hanbai_tanka
          <chr>          <chr>        <int>
1          衣服 カッターシャツ         4000
2      事務用品   穴あけパンチ          500
3  キッチン用品         圧力鍋         6800
4  キッチン用品           包丁         3000
```

dplyrではSQLと逆っぽい順序になる。この例では`select`を先に実行しても支障はないが、グループ化に使う変数や集計に使う変数を必ずしも表示したいとは限らない点を考慮すると後回しにするのが懸命。

実際に生成されてるクエリはこんな感じ。

```r 
> shohin_tbl %>%
+   group_by(shohin_bunrui) %>%
+   filter(hanbai_tanka > mean(hanbai_tanka)) %>%
+   select(shohin_bunrui, shohin_mei, hanbai_tanka) %>% show_query
<SQL>
SELECT "shohin_bunrui" AS "shohin_bunrui", "shohin_mei" AS "shohin_mei", "hanbai_tanka" AS "hanbai_tanka"
FROM (SELECT "shohin_id", "shohin_mei", "shohin_bunrui", "hanbai_tanka", "shiire_tanka", "torokubi", "shohin_mei_kana"
FROM (SELECT "shohin_id", "shohin_mei", "shohin_bunrui", "hanbai_tanka", "shiire_tanka", "torokubi", "shohin_mei_kana", avg("hanbai_tanka") OVER (PARTITION BY "shohin_bunrui") AS "zzz10"
FROM "shohin") "liiegqoyxv"
WHERE ("hanbai_tanka" > "zzz10")) "donsqvrcih"
```

## 文字列連結

これはどちらかというと`{dplyr}`のほうが少し特殊。

```postgres:postgreSQL 
select str1, str2,
       str1 || str2 as str_concat
  from samplestr;
```

dplyrの例は[Databases](https://cran.r-project.org/web/packages/dplyr/vignettes/databases.html)のSQL transrationの部分に説明があるが、クエリに変換される際、`{dplyr}`が変換方法を知らない関数はそのまま送られるので、例えば`paste()`や`paste0()`を使ってもダメ。これらは自動で変換されないし、同名の関数はPostgreSQLには無いので。

```r:dplyr
samplestr %>%
  transmute(str1, str2,
            str_concate = paste0(str1, str2))
```

逆に言えば、Rに無かろうがDBMSが解釈できる関数なら使えるということ。二項演算子であれば`%||%`のようにパーセント記法を使う。

```r:dplyr
samplestr %>%
  transmute(str1, str2, 
            str_concat = (str1 %||% str2))
```

もちろん次の形でもOK。

```r 
samplestr %>%
  transmute(str1, str2, 
            str_concat = concat(str1, str2))
```

## SUBSTRING

これはRが特殊な例。

```postgres:PostgreSQL 
select str1,
  substring(str1 from 3 for 2) as sbu_str
from samplestr;
```

`dplyr`は次の形では通らない。

```r:dplyr_wrong 
samplestr %>%
  transmute(str1, 
            sbu_str = substring(str1 %from% 3 %for% 2))
```

なぜかというと、Rでは数値をそのまま入力すると`numeric`になるので、例えば`3`は`3.0`としてクエリに変換されてしまう。

Rで`integer`を得る方法はいくつかあるので、いずれかで対処する。

- 末尾に`L`をつける: `3L`
- コロン演算子を使う: `3:3`
- `as.integer()`を使う: `as.integer(3)`

```r:dplyr 
samplestr %>%
  transmute(str1, 
            sbu_str = substring(str1 %from% 3L %for% 2L))
```
## DISTINCT

`DISTINCT`は重複を除いた結果を返す。

```postgres:SQL
select distinct hanbai_tanka from shohin;
```

```r:dplyr
shohin_tbl %>% distinct(hanbai_tanka)
```

最初`select()`と`unique()`組み合わせてやろうとして少し悩んでしまったけど、落ち着いて探したらそのままの関数あった。