読者です 読者をやめる 読者になる 読者になる

Creatable a => a -> IO b

Haskellと数学とちょびっと音楽

Haskell-rerational-recordでDB操作するのが楽しすぎる件〜その1〜

どーも、いつものちゅーんさんです。

一ヶ月くらい前に、もくもくHaskell会というハッカソンで日比野さん(@khibino)とご一緒したので、 日比野さんの開発したhaskell-rerational-record(以下HRR)というライブラリを(色々教わりまくりつつ)いじいじしていました。 お仕事のちゅーんさんはわりとゴリゴリSQLを書くわけですけど、あの、あれですよ、辛いんです。 もくもく会でも日比野さんと只管愚痴りまくってましたけど、SQLとかね、こんなもの人間の書くものじゃないです。

ね?

そんなわけで、Haskellを用いて如何に苦しまず楽しくRDBと戯れるか、というのが本日のお題です。

HRRの特徴

基本的にはSQLをモナディックに生成するためのライブラリで。 普通のアプリケーションで必要になるようなほとんどのクエリはこれだけで書く事ができます。 HRRには、ざっと次のような特徴があります。

強い静的型付け

他のこの手のライブラリをご存知の型は解ると思いますが、SQLの構文に型を付けるのってものすごい難しいです。
その点HRRは業務で良く書くようなSQLはほぼ型安全に書くことができ、 実行時に構文や型のエラーを悩ます心配はありません。

THによる自動的なデータ型生成

テーブルの定義は、TemplateHaskellによってコンパイル時に自動的にスキーマ情報を読みに行き、 勝手に生成してくれます。
そのため、DB上でテーブル定義が変更され、プログラムとの整合性が取れなくなった場合は、 即座にコンパイルエラーが発生するようになります。

高い再利用性

複雑なSQLを書く場合、同じサブクエリを何度も書かなくてはいけない事が多々あります。 HRRは一度書いたクエリを再利用する事が出来るので、複雑なクエリもDRYを意識しながら効率的に作成して行く事ができます。

HRRのインストールとか

RDBといっても色々ありますが、もともとPostgresをターゲットにしたライブラリなので、Postgresqlが圧倒的に導入が楽です。 いちお、MySQLとかのドライバもあるっぽいですが、インストールにつらみが溜まったので、素直にPostgresqlに乗り換えました。 MySQLに出会って約半年、たいして使わなかったし、未練なんてねーしな!!

で、Postgresqlの環境作りが一通り終わったら、以下のチュートリアルを元にちょこちょこインストール。

https://github.com/khibino/haskell-relational-record/wiki/%E3%83%81%E3%83%A5%E3%83%BC%E3%83%88%E3%83%AA%E3%82%A2%E3%83%AB

Hackageには上がっていないそうなので、githubリポジトリをcloneしてcabal installするのが基本。
基本的にこの通りにやれば動かせるようになってる・・・はず。

テーブルの準備

CreateしてInsert

まず、練習用に適当なデータを作ってやります。

DROP TABLE TUTORIAL.product;
CREATE TABLE TUTORIAL.product (
  id int primary key,
  name varchar(10) not null,
  price int not null
);
INSERT INTO TUTORIAL.product VALUES (1, 'Apple', 100);
INSERT INTO TUTORIAL.product VALUES (2, 'Grape', 200);
INSERT INTO TUTORIAL.product VALUES (3, 'Orange', 300);
INSERT INTO TUTORIAL.product VALUES (4, 'Banana', 400);
INSERT INTO TUTORIAL.product VALUES (5, 'Pineapple', 500);

DROP TABLE TUTORIAL.marketHistory;
CREATE TABLE TUTORIAL.marketHistory (
  id int primary key,
  product int not null,
  quantity int not null
);
INSERT INTO TUTORIAL.marketHistory VALUES (1, 1, 10);
INSERT INTO TUTORIAL.marketHistory VALUES (2, 1, 5);
INSERT INTO TUTORIAL.marketHistory VALUES (3, 1, 3);
INSERT INTO TUTORIAL.marketHistory VALUES (4, 1, 6);
INSERT INTO TUTORIAL.marketHistory VALUES (5, 2, 6);
INSERT INTO TUTORIAL.marketHistory VALUES (6, 2, 5);
INSERT INTO TUTORIAL.marketHistory VALUES (7, 2, 8);
INSERT INTO TUTORIAL.marketHistory VALUES (8, 2, 4);
INSERT INTO TUTORIAL.marketHistory VALUES (10, 3, 3);
INSERT INTO TUTORIAL.marketHistory VALUES (11, 3, 5);
INSERT INTO TUTORIAL.marketHistory VALUES (12, 3, 4);
INSERT INTO TUTORIAL.marketHistory VALUES (13, 3, 9);
INSERT INTO TUTORIAL.marketHistory VALUES (14, 4, 10);
INSERT INTO TUTORIAL.marketHistory VALUES (15, 4, 14);
INSERT INTO TUTORIAL.marketHistory VALUES (16, 4, 12);
INSERT INTO TUTORIAL.marketHistory VALUES (17, 4, 11);
INSERT INTO TUTORIAL.marketHistory VALUES (18, 5, 1);
INSERT INTO TUTORIAL.marketHistory VALUES (19, 5, 3);
INSERT INTO TUTORIAL.marketHistory VALUES (20, 5, 2);
INSERT INTO TUTORIAL.marketHistory VALUES (21, 5, 4);

THに型を生成させる

チュートリアルに書かれているのそのまんまです。
フィールドの定義とか書かなくても、コンパイル時に勝手にDBにアクセスしてテーブル情報を持ってきてくれるので、 スキーマ名とテーブル名だけ指定してやればおk

productという関数がPreludeに定義されていて、名前が被ってしまうため、 Product.hsではPreludeからShowのみImportしています。

Product.hs

{-# LANGUAGE TemplateHaskell, MultiParamTypeClasses, FlexibleInstances #-}
module Product where
import Database.HDBC.Query.TH (defineTableFromDB)
import Database.HDBC.Schema.PostgreSQL (driverPostgreSQL)
import Database.Record.TH (derivingShow)

import DataSource (connect)
import Prelude (Show)

defineTableFromDB connect driverPostgreSQL "TUTORIAL" "product" [derivingShow]

MarketHistory.hs

{-# LANGUAGE TemplateHaskell, MultiParamTypeClasses, FlexibleInstances #-}
module MarketHistory where
import Database.HDBC.Query.TH (defineTableFromDB)
import Database.HDBC.Schema.PostgreSQL (driverPostgreSQL)
import Database.Record.TH (derivingShow)

import DataSource (connect)

defineTableFromDB connect driverPostgreSQL "TUTORIAL" "marketHistory" [derivingShow]

簡単なSelect文を書いてみる

クエリを作成する

基本的な書き方はチュートリアルにもありますし、仕組みを理解してるワケでは無いので、 詳細な説明は割愛します。(実装理解したらそのうち解説記事とか書くかも)
最初は見様見真似でも、モナドの型のあわせ方とかが解っていれば大体コンパイル通せるようになります。

Query.hs

module Query where 
import Database.Relational.Query 
import GHC.Int 

import Product (Product) 
import qualified Product as P 

selectProduct :: Relation () Product
selectProduct = relation $ query P.product

で、これをShowすると、生成されたSQL文を見る事ができます。

*Query> selectProduct
SELECT ALL T0.id AS f0, T0.name AS f1, T0.price AS f2 FROM TUTORIAL.product T0

exsampleとかチュートリアルとか見ると、(><)とかいう演算子(勝手に「女子力演算子」って呼んでる)を使ってSelectしてたりますが、 これは2引数のタプルを構築していくライブラリなので、結果の型がけっこうダサい事になってたりします。

今は(|$|)と(|*|)とかいう演算子を使って良い感じに描けます。(projectable-applicativeとかいうらしい、よく解ってない) 使い方は基本的に普通のApplicativeと同じような感じ。

Query.hs

module Query where 
import Database.Relational.Query 
import GHC.Int 

import Product (Product) 
import qualified Product as P 

selectProduct :: Relation () (String, Int32)
selectProduct = relation $ do 
  p <- query P.product
  return $ (,) |$| p ! P.name' |*| p ! P.price'

注意点としては、(!)演算子の左辺を忘れない事と、フィールド名の後ろのアポストロフィー(')を忘れない事。
P.nameやP.priceは、生成されたレコード型のフィールド名として生成されているので、わりとややこしい型エラーになります。

*Query> :t P.name
P.name :: Product -> String
*Query> :t P.name'
P.name' :: Pi Product String

生成されたSQL

SELECT ALL 
    T0.name AS f0
  , T0.price AS f1 
FROM 
    TUTORIAL.product T0

作成したクエリを発行する

基本的には先のチュートリアルに書かれていた事そのまんまです。まずDBコネクションを作ってやります。

DataSource.hs

module DataSource(connect) where
import Database.HDBC.PostgreSQL (connectPostgreSQL, Connection)

connect :: IO Connection
connect = connectPostgreSQL "dbname=testdb"

んで、relationQuery関数を使いーの、runQuery関数で作成したクエリを発行します。

DataSource.hs

 DataSource.hs  init.sql  Main.hs  Query.hs  Product.hs  MarketHistory.hs  DataSource.hs                                                         X
module Main where
import Database.Relational.Query (relationalQuery)
import Database.HDBC.Session (withConnectionIO, handleSqlError')
import Database.HDBC.Record.Query (runQuery)

import DataSource (connect)
import Query

main :: IO ()
main = handleSqlError' $ withConnectionIO connect $ \conn -> do
  products <- runQuery conn (relationalQuery selectProduct) ()
  mapM_ print products

実行結果:

("Apple",100)
("Grape",200)
("Orange",300)
("Banana",400)
("Pineapple",500)

クエリの結果を任意の型に格納する

次のように独自の型を使って名称と金額をSelectする事を考えた時、この型をHRRで使うためにいくつかの型クラスのインスタンスにする必要があります。
だだ、それを毎回やるのはだいぶ面倒です。

data ProductAmount = ProductAmount String Int32 deriving (Show, Read, Eq, Ord)

そこで以下のTHを使って、必要な型クラスのインスタンスを自動的に導出してやります。

-- import Database.HDBC.Query.TH しておく
$(makeRecordPersistableDefault ''ProductAmount)

後は、タプルの時と同じように(|$|)とか(|*|)を使って書いてやればおk

selectProduct :: Relation () ProductAmount
selectProduct = relation $ do
  p <- query P.product
  return $ ProductAmount |$| p ! P.name' |*| p ! P.price'

実行結果:

ProductAmount "Apple" 100
ProductAmount "Grape" 200
ProductAmount "Orange" 300
ProductAmount "Banana" 400
ProductAmount "Pineapple" 500

多相な型で扱いたい場合

ただ現状、上記のTHを使ってインスタンスを導出できるのは、単相な型の場合のみらしいです。
どうやら、kindが合わなかったりとか、型変数の型クラス制約とかの関係で、多相な型からインスタンスを導出するのは難しいもよう。

なので、どーしても多相な型を使いたいんだっ! という場合は、自力でProductConstructor、FromSql、ToSqlのインスタンスを書いてやる必要があるみたい。

module Query where
import Control.Applicative
import Database.Record
import Database.Record.ToSql
import Database.Relational.Query
import Database.HDBC (SqlValue)
import Database.HDBC.Query.TH
import Product (Product)
import qualified Product as P
import GHC.Int

data MyTuple a b c = MyTuple a b c deriving (Show, Read, Eq, Ord)
instance ProductConstructor (a -> b -> c -> MyTuple a b c) where
  productConstructor = MyTuple

instance (FromSql SqlValue a, FromSql SqlValue b, FromSql SqlValue c)
         => FromSql SqlValue (MyTuple a b c) where
  recordFromSql = MyTuple <$> recordFromSql <*> recordFromSql <*> recordFromSql

instance (ToSql SqlValue a, ToSql SqlValue b, ToSql SqlValue c)
         => ToSql SqlValue (MyTuple a b c) where
  recordToSql = createRecordToSql (\(MyTuple a b c) -> fromRecord a ++ fromRecord b ++ fromRecord c)

selectProduct'' :: Relation () (MyTuple Int32 String Int32)
selectProduct'' = relation $ do
  p <- query P.product
  return $ MyTuple |$| p ! P.id' |*| p ! P.name' |*| p ! P.price'

実行結果:

MyTuple 1 "Apple" 100
MyTuple 2 "Grape" 200
MyTuple 3 "Orange" 300
MyTuple 4 "Banana" 400
MyTuple 5 "Pineapple" 500

3要素のタプルとか4要素のタプルとか

デフォルトで上記インスタンスになってて欲しいところですが、現状は無いらしい(無くてもどうにでもなりますし)ので、 自分で書く必要がありました。近々実装されるっぽいです。

instance ProductConstructor (a -> b -> c -> (a, b, c)) where
  productConstructor = (,,) 
 
instance (FromSql SqlValue a, FromSql SqlValue b, FromSql SqlValue c)
         => FromSql SqlValue (a, b, c) where
  recordFromSql = (,,) <$> recordFromSql <*> recordFromSql <*> recordFromSql
 
instance (ToSql SqlValue a, ToSql SqlValue b, ToSql SqlValue c)
         => ToSql SqlValue (a, b, c) where
  recordToSql = createRecordToSql (\(a, b, c) -> fromRecord a ++ fromRecord b ++ fromRecord c)

selectProduct' :: Relation () (Int32, String, Int32)
selectProduct' = relation $ do 
  p <- query P.product
  return $ (,,) |$| p ! P.id' |*| p ! P.name' |*| p ! P.price'

JOINは楽ちん

単純に参照しに行くテーブルを増やせば、勝手にJOIN句を生成してくれます。
Monadic join styleと言うらしいです。

selectHistory :: Relation () (Product, MarketHistory) 
selectHistory = relation $ do  
  p <- query P.product 
  m <- query M.marketHistory  
  return $ (,) |$| p |*| m 

生成されたSQL

SELECT ALL 
    T0.id AS f0
  , T0.name AS f1
  , T0.price AS f2
  , T1.id AS f3
  , T1.product AS f4
  , T1.quantity AS f5 
FROM 
        TUTORIAL.product T0 
    INNER JOIN 
        TUTORIAL.markethistory T1 
            ON (0=0)

ON句の書き方

ON句を指定したい場合はon関数を使います。
この時、ONがどのJOIN句にくっつくかは、on関数の書かれた位置によって決まるらしい。

selectHistory :: Relation () (Product, MarketHistory)
selectHistory = relation $ do
  p <- query P.product
  m <- query M.marketHistory
  on $ p ! P.id' .=. m ! M.product'
  return $ (,) |$| p |*| m

生成されたSQL

SELECT ALL 
    T0.id AS f0
  , T0.name AS f1
  , T0.price AS f2
  , T1.id AS f3
  , T1.product AS f4
  , T1.quantity AS f5 
FROM 
        TUTORIAL.product T0 
    INNER JOIN 
        TUTORIAL.markethistory T1 
            ON (T0.id = T1.product)

比較演算子は両側にコロンが付いたものが用意されています。ANDはon演算子を書き連ねていけば良いっぽい。

*Query> :t (.=.)
(.=.)
  :: (SqlProjectable p, ProjectableShowSql p) =>
     p ft -> p ft -> p (Maybe Bool)
*Query> :t (.<.)
(.<.)
  :: (SqlProjectable p, ProjectableShowSql p) =>
     p ft -> p ft -> p (Maybe Bool)
*Query> :t (.>.)
(.>.)
  :: (SqlProjectable p, ProjectableShowSql p) =>
     p ft -> p ft -> p (Maybe Bool)
*Query> :t (.<=.)
(.<=.)
  :: (SqlProjectable p, ProjectableShowSql p) =>
     p ft -> p ft -> p (Maybe Bool)
*Query> :t (.>=.)
(.>=.)
  :: (SqlProjectable p, ProjectableShowSql p) =>
     p ft -> p ft -> p (Maybe Bool)
*Query> :t (.<>.)
(.<>.)
  :: (SqlProjectable p, ProjectableShowSql p) =>
     p ft -> p ft -> p (Maybe Bool)
*Query> :t (.||.)
(.||.)
  :: (Data.String.IsString a, SqlProjectable p,
      ProjectableShowSql p) =>
     p a -> p a -> p a

四則演算とかもありますね。

*Query> :t (.+.)
(.+.)
  :: (Num a, SqlProjectable p, ProjectableShowSql p) =>
     p a -> p a -> p a
*Query> :t (.-.)
(.-.)
  :: (Num a, SqlProjectable p, ProjectableShowSql p) =>
     p a -> p a -> p a
*Query> :t (.*.)
(.*.)
  :: (Num a, SqlProjectable p, ProjectableShowSql p) =>
     p a -> p a -> p a
*Query> :t (./.)
(./.)
  :: (Num a, SqlProjectable p, ProjectableShowSql p) =>
     p a -> p a -> p a

WHERE句の書き方

WHERE句はwheres関数。 この後さらにJOINしたりとかしようとすると、副問い合わせになるっぽい?

selectHistory :: Relation () (Product, MarketHistory)
selectHistory = relation $ do
  p <- query P.product
  m <- query M.marketHistory
  wheres $ p ! P.id' .=. m ! M.product'
  return $ (,) |$| p |*| m

生成されたSQL

SELECT ALL 
    T0.id AS f0
  , T0.name AS f1
  , T0.price AS f2
  , T1.id AS f3
  , T1.product AS f4
  , T1.quantity AS f5 
FROM 
        TUTORIAL.product T0 
    INNER JOIN 
        TUTORIAL.markethistory T1 
            ON (0=0) 
WHERE 
    (T0.id = T1.product)

OUTER JOINやLEFT/RIGHT JOINもqueryMaybeとかいうの使って簡単に出来ます、が、まだ試してないのでいずれ記事にします。 あと、もうちょっとSQLっぽく書けるDirect Join Styleなんてのもありますが、こっちはあんまり格好よく無いなーとか思ったので割愛。

https://github.com/tokiwoousaka/haskell-relational-record/blob/master/relational-query-HDBC-pgTest/example/1/QueryExample.hs#L75

Group By句の書き方

GroupBy句を書くと、Select出来る要素が変わってくるため、 とは言っても、ほとんど書き方は同じですし、型推論されるので得にその事を意識する必要は無いです。

data MarketHisoryWithNameGrouped = MarketHisoryWithNameGrouped   
  { mhgProductName :: String
  , mhgQuantity :: Maybe Int32
  } deriving (Show, Read, Eq, Ord)
$(makeRecordPersistableDefault ''MarketHisoryWithNameGrouped)

selectHistoryGroupBy :: Relation () MarketHisoryWithNameGrouped
selectHistoryGroupBy = aggregateRelation $ do --relationの代わりにaggregateRelationを使う 
  --Group元のテーブルをSelect
  p <- query P.product
  m <- query M.marketHistory
  on $ p ! P.id' .=. m ! M.product'
  --idとnameでGroup化、nameは後ほど使うので束縛
  groupBy $ p ! P.id'
  g <- groupBy $ p ! P.name'
  --pやmのフィールドは集約関数を適用させないと使えない
  return $ MarketHisoryWithNameGrouped |$| g |*| sum' (m ! M.quantity')

sum'関数はクエリを記述するために用意された集約関数です。 集約関数には以下のようなものがあります。

*Query> :t sum'
sum'
  :: (Num a, SqlProjectable (p ac),
      Database.Relational.Query.ProjectableExtended.AggregatedContext
        ac) =>
     Projection Flat a -> p ac (Maybe a)
*Query> :t sumMaybe
sumMaybe
  :: (Num a, SqlProjectable (p ac),
      Database.Relational.Query.ProjectableExtended.AggregatedContext
        ac) =>
     Projection Flat (Maybe a) -> p ac (Maybe a)
*Query> :t avg
avg
  :: (Fractional b, Num a, SqlProjectable (p ac),
      Database.Relational.Query.ProjectableExtended.AggregatedContext
        ac) =>
     Projection Flat a -> p ac (Maybe b)
*Query> :t avgMaybe
avgMaybe
  :: (Fractional b, Num a, SqlProjectable (p ac),
      Database.Relational.Query.ProjectableExtended.AggregatedContext
        ac) =>
     Projection Flat (Maybe a) -> p ac (Maybe b)
*Query> :t max'
max'
  :: (Ord a, SqlProjectable (p ac),
      Database.Relational.Query.ProjectableExtended.AggregatedContext
        ac) =>
     Projection Flat a -> p ac (Maybe a)
*Query> :t maxMaybe
maxMaybe
  :: (Ord a, SqlProjectable (p ac),
      Database.Relational.Query.ProjectableExtended.AggregatedContext
        ac) =>
     Projection Flat (Maybe a) -> p ac (Maybe a)
*Query> :t min'
min'
  :: (Ord a, SqlProjectable (p ac),
      Database.Relational.Query.ProjectableExtended.AggregatedContext
        ac) =>
     Projection Flat a -> p ac (Maybe a)
*Query> :t minMaybe
minMaybe
  :: (Ord a, SqlProjectable (p ac),
      Database.Relational.Query.ProjectableExtended.AggregatedContext
        ac) =>
     Projection Flat (Maybe a) -> p ac (Maybe a)
*Query> :t every
every
  :: (SqlProjectable (p ac),
      Database.Relational.Query.ProjectableExtended.AggregatedContext
        ac) =>
     Projection Flat (Maybe Bool) -> p ac (Maybe Bool)
*Query> :t any'
any'
  :: (SqlProjectable (p ac),
      Database.Relational.Query.ProjectableExtended.AggregatedContext
        ac) =>
     Projection Flat (Maybe Bool) -> p ac (Maybe Bool)
*Query> :t some'
some'
  :: (SqlProjectable (p ac),
      Database.Relational.Query.ProjectableExtended.AggregatedContext
        ac) =>
     Projection Flat (Maybe Bool) -> p ac (Maybe Bool)

そして、このクエリをShowすると、以下のようなSQLを得る事ができます。

生成されたSQL

SELECT ALL 
    T0.name AS f0
  , SUM (T1.quantity) AS f1 
FROM 
        TUTORIAL.product T0 
    INNER JOIN 
        TUTORIAL.markethistory T1 
            ON (T0.id = T1.product) 
GROUP BY 
    T0.id
  , T0.name

また、今回のクエリの場合、Productテーブルを基底に集約されれば良いので、次のような書き方をする事も出来ます。

selectHistoryGroupBy :: Relation () MarketHisoryWithNameGrouped
selectHistoryGroupBy = aggregateRelation $ do
  p <- query P.product
  m <- query M.marketHistory
  on $ p ! P.id' .=. m ! M.product'
  g <- groupBy p
  return $ MarketHisoryWithNameGrouped |$| g ! P.name' |*| sum' (m ! M.quantity')

生成されたSQL

SELECT ALL 
    T0.name AS f0
  , SUM (T1.quantity) AS f1 
FROM 
        TUTORIAL.product T0 
    INNER JOIN 
        TUTORIAL.markethistory T1 
            ON (T0.id = T1.product) 
GROUP BY 
    T0.id
  , T0.name
  , T0.price

複数のクエリを組み合わせる

以上、HRRによってSELECT文を書く方法を紹介してきました。 ここまでだと単なる型安全なクエリの記法(といっても、型安全である事がとても大きいのですが)ですが、 複数のクエリを組み合わせる事ができるのは、ただのSQLにはないHRRの強みです。

ここでは、先程作ったproductテーブルとmarketHistoryテーブルを結合するクエリを、 再利用する事を考えてみましょう。

selectHistory :: Relation () (Product, MarketHistory)
selectHistory = relation $ do 
  p <- query P.product
  m <- query M.marketHistory 
  on $ p ! P.id' .=. m ! M.product'
  return $ (,) |$| p |*| m

先程から、テーブルを指定する際にquery関数を使用しています。 この関数はRelation型を引数に取る関数です。

*Query> :t query
query
  :: Database.Relational.Query.Monad.Class.MonadQualify
       ConfigureQuery m =>
     Relation () r -> m (Projection Flat r)

もう分かりますね、作成したクエリはRelation型になっているためquery関数によって、 一度作成したクエリを使用する事ができます。

selectHistoryGroupBy'' :: Relation () MarketHisoryWithNameGrouped
selectHistoryGroupBy'' = aggregateRelation $ do
  sh <- query selectHistory'
  g <- groupBy $ sh ! fst'
  return $ MarketHisoryWithNameGrouped |$| g ! P.name' |*| sum' (sh ! snd' ! M.quantity')

生成されたSQL

SELECT ALL 
    T2.f1 AS f0
  , SUM (T2.f5) AS f1 
FROM 
    (SELECT ALL 
        T0.id AS f0
      , T0.name AS f1
      , T0.price AS f2
      , T1.id AS f3
      , T1.product AS f4
      , T1.quantity AS f5 
    FROM 
            TUTORIAL.product T0 
        INNER JOIN 
            TUTORIAL.markethistory T1 
                ON (T0.id = T1.product)
    ) T2 
GROUP BY 
    T2.f0
  , T2.f1
  , T2.f2

この記事を読んでる皆さんならお分かり頂けるかと思いますが、 このような書き方が出来るのが、HRRのシンプルながら強力な所です。

DRYを意識して上手く部品化しながら組み合わせる事によって、 ただのSQLではメンテナンスが困難になってしまうほど複雑なクエリも、 体系的に解りやすく記述する事ができるわけです。

まとめ

というわけで、今回は、

  • HRRの特徴
  • 入手方法
  • SELECT文の書き方
  • クエリの再利用

等を紹介してきました。 次回(いつになるかわかりませんが・・・)は、INSERT文やUPDATE文、Null許容なフィールドの扱い等について、 (調べつつw)書いていこうかと思います。

いじょ