$$\{ (x, y) | x \in X, y \in Y, \pi_1(x) = \pi_2(y) \}$$
[ (x, y) | x <- xs, y <- ys, fst x == snd y ] -- Comprehension
do { x <- xs; y <- ys; guard (fst x == snd y); return (x, y) } -- List Monad
Building a joined query like list comprehension or list Monad:
personAndBirthday :: Relation () (Person, Birthday)
personAndBirthday = relation $ do
p <- query person -- Join product accumulated
b <- query birthday
on $ p ! Person.name' .=. b ! Birthday.name'
return $ p >< b
A simple and useful method:
Haskell Relational Record's query-building DSL accumulates various context in a state monad context stack.
query :: (MonadQualify ConfigureQuery m, MonadQuery m)
=> Relation () r
-> m (Projection Flat r)
-- Used for outer join
queryMaybe :: (MonadQualify ConfigureQuery m, MonadQuery m)
=> Relation () r
-> m (Projection Flat (Maybe r))
on :: MonadQuery m => Projection Flat (Maybe Bool) -> m ()
'query' and 'queryMaybe' return a Projection type of table form results.
SELECT .. FROM ...
-- Accumulating uniquely qualified
-- ( like 'as T0', 'as T1' ... )
-- table forms of SQL FROM clause
personAndBirthdayL :: Relation () (Person, Maybe Birthday)
personAndBirthdayL = relation $ do
p <- query person
b <- queryMaybe birthday
on $ just (p ! Person.name') .=. b ?! Birthday.name'
return $ p >< b
generates left-joined SQL:
SELECT ALL T0.name AS f0, T0.age AS f1, T0.address AS f2,
T1.name AS f3, T1.day AS f4
FROM PUBLIC.person T0 LEFT JOIN
PUBLIC.birthday T1
ON (T0.name = T1.name)
groupBy :: MonadAggregate m
=> Projection Flat r
-- ^ Projection to add into group by
-> m (Projection Aggregated r)
-- ^ Result context and aggregated projection
count :: Projection Flat a -> Projection Aggregated Int64
max' :: Ord a
=> Projection Flat a -> Projection Aggregated (Maybe a)
'groupBy' can be used under only 'MonadAggregate' monad constraint, narrower than 'MonadQuery'.
'groupBy' returns a Projection value with an Aggregated context type:
SELECT .. GROUP BY ...
-- Accumulating keys
-- of SQL GROUP BY clause
agesOfFamilies :: Relation () (String, Maybe Int32)
agesOfFamilies = aggregateRelation $ do
my <- query myTable
gFam <- groupBy $ my ! family' -- Specify grouping key
return $ gFam >< sum' (my ! age') -- Aggregated results
sums ages per family.
Generated SQL:
SELECT ALL T0.family AS f0, SUM (T0.age) AS f1
FROM PUBLIC.my_table T0
GROUP BY T0.family
restrict :: MonadRestrict c m
=> Projection c (Maybe Bool)
-> m ()
wheres :: MonadRestrict Flat m
=> Projection Flat (Maybe Bool)
-> m ()
adds a WHERE clause restriction:
SELECT .. WHERE x AND y AND ...
-- Accumulating AND predicates
-- of SQL WHERE clause
restrict :: MonadRestrict c m
=> Projection c (Maybe Bool)
-> m ()
having :: MonadRestrict Aggregated m
=> Projection Aggregated (Maybe Bool)
-> m ()
adds a HAVING clause restriction, in which only Projection type values with aggregated context are allowed:
SELECT .. HAVING x AND y AND ...
-- Accumulating AND predicates
-- of SQL HAVING clause
sameBirthdayHeisei' :: Relation () (Day, Int64)
sameBirthdayHeisei' = aggregateRelation $ do
p <- query person
b <- query birthday
on $ p ! Person.name' .=. b ! Birthday.name'
wheres $ b ! Birthday.day' .>=. value (fromGregorian 1989 1 8)
gbd <- groupBy $ b ! Birthday.day'
having $ count (p ! Person.name') .>. value 1
return $ gbd >< count (p ! Person.name')
counts people with the same birthday, who were born in the Heisei period.
Generated SQL:
SELECT ALL T1.day AS f0, COUNT (T0.name) AS f1
FROM PUBLIC.person T0 INNER JOIN PUBLIC.birthday T1
ON (T0.name = T1.name)
WHERE (T1.day >= DATE '1989-01-08')
GROUP BY T1.day HAVING (COUNT (T0.name) > 1)
Generated SQL:
SELECT ALL T1.day AS f0, COUNT (T0.name) AS f1
FROM PUBLIC.person T0 INNER JOIN PUBLIC.birthday T1
ON (T0.name = T1.name)
WHERE (T1.day >= DATE '1989-01-08')
GROUP BY T1.day HAVING (COUNT (T0.name) > 1)
sameBirthdayHeisei :: Relation () (Day, Int64)
sameBirthdayHeisei = aggregateRelation $ do
p <- query person
b <- query birthday
on $ p ! Person.name' .=. b ! Birthday.name'
let birthDay = b ! Birthday.day'
wheres $ birthDay .>=. value (fromGregorian 1989 1 8)
gbd <- groupBy birthDay
let personCount = count $ p ! Person.name'
having $ personCount .>. value 1
return $ gbd >< personCount
binds using let.
orderBy :: Monad m
=> Projection c t
-- ^ Ordering terms to add
-> Order
-- ^ Order direction -- Asc | Desc
-> Orderings c m ()
-- ^ Result context with ordering
Only Projection type values with specified (Flat, Aggregated, ...) context are allowed.
SELECT .. ORDER BY ...
-- Accumulating terms of ORDER BY clause
personAndBirthdayO :: Relation () (Person, Birthday)
personAndBirthdayO = relation $ do
p <- query person
b <- query birthday
on $ p ! Person.name' .=. b ! Birthday.name'
orderBy (b ! Birthday.day') Asc -- Specify ordering key
orderBy (p ! Person.name') Asc
return $ p >< b
orders by birthday and then name:
SELECT ALL T0.name AS f0, T0.age AS f1, T0.address AS f2,
T1.name AS f3, T1.day AS f4
FROM PUBLIC.person T0 INNER JOIN PUBLIC.birthday T1
ON (T0.name = T1.name)
ORDER BY T1.day ASC, T0.name ASC
birthdayHeiseiDesc :: Relation () (Day, Int64)
birthdayHeiseiDesc = aggregateRelation $ do
p <- query person
b <- query birthday
on $ p ! Person.name' .=. b ! Birthday.name'
let birthDay = b ! Birthday.day'
wheres $ birthDay .>=. value (fromGregorian 1989 1 8)
gbd <- groupBy birthDay
let personCount = count $ p ! Person.name'
orderBy personCount Desc
return $ gbd >< personCount
orders by the number of people born on the same Heisei period dates:
SELECT ALL T1.day AS f0, COUNT (T0.name) AS f1
FROM PUBLIC.person T0 INNER JOIN PUBLIC.birthday T1
ON (T0.name = T1.name)
WHERE (T1.day >= DATE '1989-01-08')
GROUP BY T1.day ORDER BY COUNT (T0.name) DESC
SELECT ALL T1.day AS f0, COUNT (T0.name) AS f1
FROM PUBLIC.person T0 INNER JOIN PUBLIC.birthday T1
ON (T0.name = T1.name)
WHERE (T1.day >= DATE '1989-01-08')
GROUP BY T1.day ORDER BY COUNT (T0.name) DESC
specifyPerson :: Relation String (Person, Birthday)
specifyPerson = relation' $ do
pb <- query personAndBirthday -- Re-use predefined
(ph, ()) <- placeholder
(\ph' -> wheres $ pb ! fst' ! Person.name' .=. ph')
return (ph, pb)
specifies a person name using a placeholder:
SELECT ALL T2.f0 AS f0, T2.f1 AS f1, T2.f2 AS f2,
T2.f3 AS f3, T2.f4 AS f4
FROM (SELECT ALL
T0.name AS f0, T0.age AS f1, T0.address AS f2,
T1.name AS f3, T1.day AS f4
FROM PUBLIC.person T0 INNER JOIN
PUBLIC.birthday T1
ON (T0.name = T1.name)) T2
WHERE (T2.f0 = ?)
Mapping to records using Applicative style:
(|$|) :: (ProjectableFunctor p, ProductConstructor (a -> b))
=> (a -> b)
-> p a
-> p b
(|*|) :: ProjectableApplicative p
=> p (a -> b)
-> p a
-> p b
Assign record types to an SQL projection:
personAndBirthdayT :: Relation () PersonAndBirthday
personAndBirthdayT = relation $ do
p <- query person
b <- query birthday
wheres $ p ! Person.name' .=. b ! Birthday.name'
-- Build record phantom type
return $ PersonAndBirthday |$| p |*| b
(|$|) :: ProductConstructor (a -> b)
=> (a -> b) -> Projection c a -> Projection c b
(|*|) :: Projection c (a -> b) -> Projection c a -> Projection c b
SELECT ALL T0.name AS f0, T0.age AS f1, T0.address AS f2,
T1.name AS f3, T1.day AS f4
FROM PUBLIC.person T0 INNER JOIN PUBLIC.birthday T1
ON (T0.name = T1.name)
Column selectors can be mapped to a record:
Birthday.day' :: Pi Birthday Day
uncurryPB :: Pi (Person, Birthday) PersonAndBirthday
uncurryPB = PersonAndBirthday |$| fst' |*| snd'
(|$|) :: ProductConstructor (a -> b)
=> (a -> b) -> Pi r a -> Pi r b
(|*|) :: Pi r (a -> b) -> Pi r a -> Pi r b
Placeholders can be mapped to a record:
personAndBirthdayP2 :: Relation Person PersonAndBirthday
personAndBirthdayP2 = relation' $ do
p <- query person
b <- query birthday
(ph0, ()) <- placeholder (\ph0' -> on $ p ! Person.name' .=. ph0')
(ph1, ()) <- placeholder (\ph1' -> on $ p ! Person.age' .=. ph1')
(ph2, ()) <- placeholder (\ph2' -> on $ p ! Person.address' .=. ph2')
return (Person |$| ph0 |*| ph1 |*| ph2,
PersonAndBirthday |$| p |*| b)
(|$|) :: ProductConstructor (a -> b)
=> (a -> b) -> Placeholders a -> Placeholders b
(|*|) :: Placeholders (a -> b) -> Placeholders a -> Placeholders b
Generated SQL:
SELECT ALL T0.name AS f0, T0.age AS f1, T0.address AS f2,
T1.name AS f3, T1.day AS f4
FROM PUBLIC.person T0 INNER JOIN PUBLIC.birthday T1
ON (((T0.name = ?) AND (T0.age = ?)) AND (T0.address = ?))
Generated SQL:
SELECT ALL T0.name AS f0, T0.age AS f1, T0.address AS f2,
T1.name AS f3, T1.day AS f4
FROM PUBLIC.person T0 INNER JOIN PUBLIC.birthday T1
ON (((T0.name = ?) AND (T0.age = ?)) AND (T0.address = ?))
Record-typed placeholder:
placeholder :: (PersistableWidth t, Monad m)
=> (Projection c t -> m a) -> m (PlaceHolders t, a)
personAndBirthdayP :: Relation Person PersonAndBirthday
personAndBirthdayP = relation' $ do
p <- query person
b <- query birthday
(ph, ()) <- placeholder (\ph' -> wheres $ p .=. ph')
return $ (ph, PersonAndBirthday |$| p |*| b)
row value of Placeholders:
SELECT ALL T0.name AS f0, T0.age AS f1, T0.address AS f2,
T1.name AS f3, T1.day AS f4
FROM PUBLIC.person T0 INNER JOIN PUBLIC.birthday T1
ON ((T0.name, T0.age, T0.address) = (?, ?, ?))
Monadic-style window building:
ageRankOfFamilies :: Relation () ((Int64, String), Int32)
ageRankOfFamilies = relation $ do
my <- query myTable
return $
rank `over` do
partitionBy $ my ! family' -- Monad to build window
orderBy (my ! age') Desc
><
my ! family' >< my ! age'
SELECT ALL
RANK() OVER (PARTITION BY T0.family
ORDER BY T0.age DESC) AS f0,
T0.family AS f1, T0.age AS f2
FROM PUBLIC.my_table T0
exists :: (SqlProjectable p, ProjectableShowSql p)
=> ListProjection (Projection Exists) r -> p (Maybe Bool)
queryList :: MonadQualify ConfigureQuery m
=> Relation () r
-> m (ListProjection (Projection c) r)
in' :: (SqlProjectable p, ProjectableShowSql p)
=> p t -> ListProjection p t -> p (Maybe Bool)
queryList :: MonadQualify ConfigureQuery m
=> Relation () r
-> m (ListProjection (Projection c) r)
values :: (ShowConstantTermsSQL t, SqlProjectable p)
=> [t] -> ListProjection p t
queryScalar :: (MonadQualify ConfigureQuery m, ScalarDegree r)
=> UniqueRelation () c r
-> m (Projection c (Maybe r))