diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,6 +1,11 @@
 # Revision history for Selda
 
 
+## 0.3.4.0 -- 2018-09-29
+
+* Added convenience functions for working with nullable columns.
+
+
 ## 0.3.3.1 -- 2018-09-04
 
 * DISTINCT should now always return distinct results.
diff --git a/selda.cabal b/selda.cabal
--- a/selda.cabal
+++ b/selda.cabal
@@ -1,5 +1,5 @@
 name:                selda
-version:             0.3.3.1
+version:             0.3.4.0
 synopsis:            Multi-backend, high-level EDSL for interacting with SQL databases.
 description:         This package provides an EDSL for writing portable, type-safe, high-level
                      database code. Its feature set includes querying and modifying databases,
@@ -47,6 +47,7 @@
     Database.Selda
     Database.Selda.Backend
     Database.Selda.Migrations
+    Database.Selda.Nullable
     Database.Selda.SqlType
     Database.Selda.Unsafe
     Database.Selda.Validation
diff --git a/src/Database/Selda/Nullable.hs b/src/Database/Selda/Nullable.hs
new file mode 100644
--- /dev/null
+++ b/src/Database/Selda/Nullable.hs
@@ -0,0 +1,80 @@
+{-# LANGUAGE TypeFamilies, TypeOperators, ConstraintKinds, FlexibleContexts #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+-- | Convenience facilities for working with nullable columns.
+module Database.Selda.Nullable
+  ( NonNull, (:?~)
+  , nonNull, restrict', (?!)
+  , (?==), (?/=), (?>), (?<), (?>=), (?<=), (?+), (?-), (?*), (?/)
+  ) where
+import Database.Selda
+import Database.Selda.Unsafe (cast)
+import Database.Selda.Selectors
+import Database.Selda.Column
+import Unsafe.Coerce
+
+-- | Two SQL types which are identical modulo nullability.
+type a :?~ b =
+  ( NonNull a ~ NonNull b
+  , SqlType (NonNull a)
+  , SqlType (NonNull b)
+  )
+
+type family NonNull a where
+  NonNull (Maybe a) = a
+  NonNull a         = a
+
+-- | Unconditionally convert a nullable value into a non-nullable one,
+--   using the standard SQL null-coalescing behavior.
+fromNullable :: SqlType (NonNull a) => Col s a -> Col s (NonNull a)
+fromNullable = cast
+
+(?==), (?/=) :: (a :?~ b, SqlType a) => Col s a -> Col s b -> Col s (Maybe Bool)
+
+(?>), (?<), (?>=), (?<=) :: (a :?~ b, SqlOrd (NonNull a))
+                         => Col s a -> Col s b -> Col s (Maybe Bool)
+
+(?+), (?-), (?*) :: (a :?~ b, Num (NonNull a))
+                 => Col s a -> Col s b -> Col s (Maybe (NonNull a))
+
+(?/) :: (a :?~ b, Fractional (Col s (NonNull a)))
+     => Col s a -> Col s b -> Col s (Maybe (NonNull a))
+
+a ?== b = cast $ fromNullable a .== fromNullable b
+a ?/= b = cast $ fromNullable a ./= fromNullable b
+a ?>  b = cast $ fromNullable a .>  fromNullable b
+a ?<  b = cast $ fromNullable a .<  fromNullable b
+a ?>= b = cast $ fromNullable a .>= fromNullable b
+a ?<= b = cast $ fromNullable a .<= fromNullable b
+a ?+  b = cast $ fromNullable a +   fromNullable b
+a ?-  b = cast $ fromNullable a -   fromNullable b
+a ?*  b = cast $ fromNullable a *   fromNullable b
+a ?/  b = cast $ fromNullable a /   fromNullable b
+infixl 4 ?==
+infixl 4 ?/=
+infixl 4 ?>
+infixl 4 ?<
+infixl 4 ?>=
+infixl 4 ?<=
+infixl 6 ?+
+infixl 6 ?-
+infixl 7 ?*
+infixl 7 ?/
+
+-- | Selector indexing, overloaded to work on nullable as well as non-nullable
+--   rows.
+(?!) :: forall s t a. SqlType a
+     => Row s t -> Selector (NonNull t) a -> Col s (Coalesce (Maybe a))
+(Many xs) ?! i = case xs !! (selectorIndex i) of Untyped x -> One (unsafeCoerce x)
+infixl 9 ?!
+
+-- | Converts a nullable column into a non-nullable one, yielding the empty
+--   result set if the column is null.
+nonNull :: SqlType a => Col s (Maybe a) -> Query s (Col s a)
+nonNull x = do
+  restrict (not_ $ isNull x)
+  return (cast x)
+
+-- | Restrict a query using a nullable expression.
+--   Equivalent to @restrict . ifNull false@.
+restrict' :: Col s (Maybe Bool) -> Query s ()
+restrict' = restrict . cast
diff --git a/src/Database/Selda/Selectors.hs b/src/Database/Selda/Selectors.hs
--- a/src/Database/Selda/Selectors.hs
+++ b/src/Database/Selda/Selectors.hs
@@ -32,7 +32,7 @@
 --   If a nullable column is extracted from a nullable row, the resulting
 --   nested @Maybe@s will be squashed into a single level of nesting.
 (?) :: SqlType a => Row s (Maybe t) -> Selector t a -> Col s (Coalesce (Maybe a))
-Many xs ? Selector i = case xs !! i of Untyped x -> One (unsafeCoerce x)
+(Many xs) ? (Selector i) = case xs !! i of Untyped x -> One (unsafeCoerce x)
 infixl 9 ?
 
 upd :: Row s a -> Assignment s a -> Row s a
