packages feed

opaleye-0.3: Doc/Tutorial/TutorialAdvanced.lhs

> {-# LANGUAGE FlexibleContexts #-}
>
> module TutorialAdvanced where
>
> import           Prelude hiding (sum)
>
> import           Opaleye.QueryArr (Query)
> import           Opaleye.Column (Column)
> import           Opaleye.Table (Table(Table), required, queryTable)
> import           Opaleye.PGTypes (PGText, PGInt4)
> import qualified Opaleye.Aggregate as A
> import           Opaleye.Aggregate (Aggregator, aggregate)
>
> import qualified Opaleye.Sql as Sql
> import qualified Opaleye.Internal.Unpackspec as U
>
> import           Data.Profunctor.Product.Default (Default)
> import           Data.Profunctor (dimap)
> import           Data.Profunctor.Product ((***!), p2)


Combining Aggregators
=====================

Opaleye allows you to straightforwardly combine aggregators to create
new aggregators in a way that is inconvenient to do directly in
Postgres.

We can define an aggregator to calculate the range of a group, that is
the difference between its maximum and minimum.  Although we can write
this easily in SQL as `MAX(column) - MIN(column)`, Opaleye has the
advantage of treating `range` as a first-class value able to be passed
around between functions and manipulated at will.

> range :: Aggregator (Column PGInt4) (Column PGInt4)
> range = dimap (\x -> (x, x)) (uncurry (-)) (A.max ***! A.min)

We can test it on a person table which contains rows containing
people's names along with the age of their children.

> personTable :: Table (Column PGText, Column PGInt4)
>                      (Column PGText, Column PGInt4)
> personTable = Table "personTable" (p2 ( required "name"
>                                       , required "child_age" ))

> rangeOfChildrensAges :: Query (Column PGText, Column PGInt4)
> rangeOfChildrensAges = aggregate (p2 (A.groupBy, range)) (queryTable personTable)


TutorialAdvanced> printSql rangeOfChildrensAges 
SELECT result0_2 as result1,
       (result1_2) - (result2_2) as result2
FROM (SELECT *
      FROM (SELECT name0_1 as result0_2,
                   MAX(child_age1_1) as result1_2,
                   MIN(child_age1_1) as result2_2
            FROM (SELECT *
                  FROM (SELECT name as name0_1,
                               child_age as child_age1_1
                        FROM personTable as T1) as T1) as T1
            GROUP BY name0_1) as T1) as T1


Idealised SQL:

SELECT name,
       MAX(child_age) - MIN(child_age)
FROM personTable
GROUP BY name


Helper function
===============

> printSql :: Default U.Unpackspec a a => Query a -> IO ()
> printSql = putStrLn . Sql.showSqlForPostgres