diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,3 +1,27 @@
+# 0.11.1.0
+
+## New features
+
+* Added a cross-backend abstraction for the `COPY` statement in
+  `Database.Beam.Backend.SQL.BeamExtensions`:
+  * file-mode `COPY ... TO 'file'` / `COPY ... FROM 'file'`. Defines
+    `MonadBeamCopyTo` / `MonadBeamCopyFrom`, and the user-facing
+    `copyTableTo` / `copySelectTo` / `copyTableFrom` builders.
+  * streaming `COPY ... TO STDOUT` / `COPY ... FROM STDIN`. Mirrors the file-mode
+    API: `MonadBeamCopyToStream` / `MonadBeamCopyFromStream`,
+     and the `copyTableToStream` / `copySelectToStream` / `copyTableFromStream` builders.
+
+## Bug fixes
+
+* Fixed an issue where a window function applied over the result of `nub_` (or
+  the Postgres-specific `pgNubBy_`) would emit `DISTINCT` and the window
+  expression in the same `SELECT`, causing the window to evaluate against the
+  pre-deduplicated rows. The inner select is now materialised as a subquery
+  whenever it carries `DISTINCT`, `GROUP BY`, or `HAVING` (#756).
+
+## Dependencies
+
+* Removed dependency on `ghc-prim`.
 
 # 0.11.0.0
 
diff --git a/Database/Beam/Backend/SQL/BeamExtensions.hs b/Database/Beam/Backend/SQL/BeamExtensions.hs
--- a/Database/Beam/Backend/SQL/BeamExtensions.hs
+++ b/Database/Beam/Backend/SQL/BeamExtensions.hs
@@ -16,6 +16,11 @@
   , runDeleteReturningList
   , BeamHasInsertOnConflict(..)
 
+  -- * Support for copying to external files
+  , module Database.Beam.Backend.SQL.BeamExtensions.Copy.File
+  -- * Support for streaming copy
+  , module Database.Beam.Backend.SQL.BeamExtensions.Copy.Stream
+
   , SqlSerial(..)
   , onConflictUpdateInstead
   , onConflictUpdateAll
@@ -41,6 +46,8 @@
 import           Data.Kind (Type)
 import           Data.Proxy
 import           Data.Semigroup
+import           Database.Beam.Backend.SQL.BeamExtensions.Copy.File hiding (projection) -- internal function
+import           Database.Beam.Backend.SQL.BeamExtensions.Copy.Stream
 
 --import GHC.Generics
 
diff --git a/Database/Beam/Backend/SQL/BeamExtensions/Copy/File.hs b/Database/Beam/Backend/SQL/BeamExtensions/Copy/File.hs
new file mode 100644
--- /dev/null
+++ b/Database/Beam/Backend/SQL/BeamExtensions/Copy/File.hs
@@ -0,0 +1,320 @@
+{-# LANGUAGE UndecidableInstances #-}
+
+-- | File-mode @COPY@: the data lives on the database server's filesystem.
+--
+-- For the variant that streams data through the client connection instead, see
+-- "Database.Beam.Backend.SQL.BeamExtensions.Copy.Stream".
+module Database.Beam.Backend.SQL.BeamExtensions.Copy.File
+  ( -- * Building a COPY statement
+    copyTableTo,
+    copySelectTo,
+    SqlCopyTo (..),
+    copyTableFrom,
+    SqlCopyFrom (..),
+
+    -- * Source-syntax classes (shared with streaming COPY)
+    IsSqlCopyToSourceSyntax (..),
+    IsSqlCopyFromSourceSyntax (..),
+
+    -- * File-mode statement-level syntax classes
+    IsSqlCopyToSyntax (..),
+    IsSqlCopyFromSyntax (..),
+    BeamSqlBackendCopyToSyntax,
+    BeamSqlBackendCopyFromSyntax,
+
+    -- * Runner classes
+    MonadBeamCopyTo (..),
+    MonadBeamCopyFrom (..),
+
+    -- * Internal — exposed for use by sibling modules
+    projection,
+  )
+where
+
+import Control.Monad.Cont (ContT)
+import Control.Monad.Except (ExceptT)
+import qualified Control.Monad.RWS.Lazy as Lazy
+import qualified Control.Monad.RWS.Strict as Strict
+import Control.Monad.Reader (ReaderT)
+import qualified Control.Monad.State.Lazy as Lazy
+import qualified Control.Monad.State.Strict as Strict
+import Control.Monad.Trans (lift)
+import qualified Control.Monad.Writer.Lazy as Lazy
+import qualified Control.Monad.Writer.Strict as Strict
+import Data.Data (Proxy (..))
+import Data.Kind (Type)
+import Data.List.NonEmpty (NonEmpty, nonEmpty)
+import Data.Text (Text)
+import Database.Beam.Backend.SQL (BeamSqlBackendSelectSyntax, MonadBeam (..))
+import Database.Beam.Query (QField, SqlSelect (..))
+import Database.Beam.Query.Internal (AnyType, ProjectibleWithPredicate, QField (..), project')
+import Database.Beam.Schema (TableEntity)
+import Database.Beam.Schema.Tables
+  ( Beamable,
+    Columnar' (..),
+    DatabaseEntity (..),
+    DatabaseEntityDescriptor (DatabaseTable, dbTableSettings),
+    IsDatabaseEntity (dbEntityName, dbEntitySchema),
+    changeBeamRep,
+    fieldName,
+  )
+import Lens.Micro ((^.))
+
+-- | This class allows to express the source of a `COPY ... TO` statement.
+-- The options are either from a table (with a possible projection),
+-- or from the result of a select query.
+--
+-- Reused by both file-mode and streaming COPY: the source shape (a table or
+-- a @SELECT@) is the same regardless of where the data ends up.
+class IsSqlCopyToSourceSyntax syntax where
+  -- | Expected to be equal to `BeamSqlBackendSelectSyntax be`
+  type SqlCopyToSourceSelectSyntax syntax :: Type
+
+  -- Copy an entire table, perhaps with some column projections
+  copyTableToSyntax ::
+    Maybe Text -> -- schema (Nothing = default search path)
+    Text -> -- table name
+    Maybe (NonEmpty Text) -> -- column list; `Nothing` means "all columns"
+    syntax
+
+  -- Copy the result of a select statement
+  copySelectToSyntax ::
+    SqlCopyToSourceSelectSyntax syntax ->
+    syntax
+
+-- | This class allows to express the source of a `COPY ... FROM` statement.
+-- Reused by both file-mode and streaming COPY.
+class IsSqlCopyFromSourceSyntax syntax where
+  -- Copy data into a table, perhaps with some column projections
+  copyTableFromSyntax ::
+    Maybe Text -> -- schema (Nothing = default search path)
+    Text -> -- table name
+    Maybe (NonEmpty Text) -> -- column list; `Nothing` means "all columns"
+    syntax
+
+-- | Statement-level syntax for backends that support @COPY ... TO@ file.
+class (IsSqlCopyToSourceSyntax (SqlCopyToSourceSyntax cmd)) => IsSqlCopyToSyntax cmd where
+  -- | The syntax for the source of the copy. For example, in Postgres,
+  --  this can be either a table (and optional projection), or a select statement.
+  type SqlCopyToSourceSyntax cmd :: Type
+
+  -- | All backend-specific options which determine HOW the copy is performed,
+  --  including the destination of the data (e.g. a filepath).
+  type SqlCopyToParams cmd :: Type
+
+  -- | Combine a source and a parameters value into a complete
+  -- @COPY ... TO ...@ statement.
+  copyToStmt ::
+    SqlCopyToSourceSyntax cmd ->
+    SqlCopyToParams cmd ->
+    cmd
+
+-- | Statement-level syntax for backends that support file-mode @COPY ... FROM@ file.
+--
+-- Symmetric to 'IsSqlCopyToSyntax', but the data flows in the opposite
+-- direction: the params value supplies the source path and any
+-- format-specific options.
+class (IsSqlCopyFromSourceSyntax (SqlCopyFromSourceSyntax cmd)) => IsSqlCopyFromSyntax cmd where
+  -- | The syntax for the destination table of the copy.
+  type SqlCopyFromSourceSyntax cmd :: Type
+
+  -- | All backend-specific options which determine HOW the copy is performed,
+  --  including the source of the data (e.g. a filepath).
+  type SqlCopyFromParams cmd :: Type
+
+  -- | Combine a destination and a parameters value into a complete
+  -- @COPY ... FROM ...@ statement.
+  copyFromStmt ::
+    SqlCopyFromSourceSyntax cmd ->
+    SqlCopyFromParams cmd ->
+    cmd
+
+-- | Type-family selector for the backend's file-mode @COPY ... TO@ syntax.
+-- A backend instance binds this to the concrete syntax type that implements
+-- 'IsSqlCopyToSyntax'.
+type family BeamSqlBackendCopyToSyntax be :: Type
+
+-- | Type-family selector for the backend's file-mode @COPY ... FROM@ syntax.
+-- See 'BeamSqlBackendCopyToSyntax'.
+type family BeamSqlBackendCopyFromSyntax be :: Type
+
+-- | A built file-mode @COPY ... TO@ statement, ready to be executed by
+-- 'runCopyTo'. Construct via 'copyTableTo' or 'copySelectTo'; the @table@
+-- and @proj@ phantom parameters track which table the statement applies to
+-- and which columns it projects.
+data SqlCopyTo be a
+  = SqlCopyTo !(BeamSqlBackendCopyToSyntax be)
+  | -- | A projection covering zero columns. 'runCopyTo' should treat this as a
+    --    no-op rather than emit an empty @COPY tbl () TO ...@ statement.
+    SqlCopyToNoColumns
+
+-- | A built file-mode @COPY ... FROM@ statement, ready to be executed by
+-- 'runCopyFrom'. Construct via 'copyTableFrom'.
+data SqlCopyFrom be a
+  = SqlCopyFrom !(BeamSqlBackendCopyFromSyntax be)
+  | -- | A projection covering zero columns. 'runCopyFrom' should treat this as
+    --    a no-op rather than emit an empty @COPY tbl () FROM ...@ statement.
+    SqlCopyFromNoColumns
+
+-- | Express the copy from a table, to a destination (typically a file).
+--
+-- To copy the result of a @SELECT@ query instead, see `copySelectTo`.
+--
+-- @since 0.11.1.0
+copyTableTo ::
+  ( IsSqlCopyToSyntax (BeamSqlBackendCopyToSyntax be),
+    ProjectibleWithPredicate AnyType () Text proj
+  ) =>
+  DatabaseEntity be db (TableEntity table) ->
+  -- | Projection from table to columns. If you want
+  --  to copy the entire table, use 'id'.
+  (table (QField s) -> proj) ->
+  -- | Backend-specific options. The output is also determined by this value
+  SqlCopyToParams (BeamSqlBackendCopyToSyntax be) ->
+  SqlCopyTo be proj
+copyTableTo (DatabaseEntity dt@(DatabaseTable {})) mkProj options =
+  case nonEmpty (projection dt mkProj) of
+    Nothing -> SqlCopyToNoColumns
+    Just cols ->
+      let source = copyTableToSyntax (dt ^. dbEntitySchema) (dt ^. dbEntityName) (Just cols)
+       in SqlCopyTo
+            (copyToStmt source options)
+
+-- | Express the copy from the result of a @SELECT@ statement to a destination
+-- (typically a file). Use this when the source rows are produced by a query
+-- rather than read directly from a table.
+--
+-- To copy a table, or a subset of columns, see `copyTableTo`.
+--
+-- @since 0.11.1.0
+copySelectTo ::
+  ( IsSqlCopyToSyntax (BeamSqlBackendCopyToSyntax be),
+    SqlCopyToSourceSelectSyntax (SqlCopyToSourceSyntax (BeamSqlBackendCopyToSyntax be))
+      ~ BeamSqlBackendSelectSyntax be
+  ) =>
+  SqlSelect be a ->
+  -- | Backend-specific options. The format is pinned by this value
+  SqlCopyToParams (BeamSqlBackendCopyToSyntax be) ->
+  SqlCopyTo be a
+copySelectTo (SqlSelect selectSyntax) options =
+  let source = copySelectToSyntax selectSyntax
+   in SqlCopyTo (copyToStmt source options)
+
+-- | Express the copy to a table, from an external source (typically a file).
+--
+-- @since 0.11.1.0
+copyTableFrom ::
+  ( IsSqlCopyFromSyntax (BeamSqlBackendCopyFromSyntax be),
+    ProjectibleWithPredicate AnyType () Text proj
+  ) =>
+  DatabaseEntity be db (TableEntity table) ->
+  -- | Projection from table to columns. If you want
+  --  to copy into the entire table, use 'id'.
+  (table (QField s) -> proj) ->
+  -- | Backend-specific options. The format is pinned by this value
+  SqlCopyFromParams (BeamSqlBackendCopyFromSyntax be) ->
+  SqlCopyFrom be proj
+copyTableFrom (DatabaseEntity dt@(DatabaseTable {})) mkProj options =
+  case nonEmpty (projection dt mkProj) of
+    Nothing -> SqlCopyFromNoColumns
+    Just cols ->
+      let source = copyTableFromSyntax (dt ^. dbEntitySchema) (dt ^. dbEntityName) (Just cols)
+       in SqlCopyFrom
+            (copyFromStmt source options)
+
+-- | Walk a projection and collect the names of the columns it touches.
+--
+-- This is the building block 'copyTableTo' / 'copyTableFrom' (and their
+-- streaming counterparts) use to derive the column list for the emitted
+-- @COPY@ statement. Exposed here so that the streaming submodule can reuse
+-- the same logic without duplication.
+projection ::
+  (ProjectibleWithPredicate AnyType () Text proj, Beamable table) =>
+  DatabaseEntityDescriptor be (TableEntity table) ->
+  (table (QField s) -> proj) ->
+  [Text]
+projection dt mkProj =
+  Strict.execWriter
+    ( project'
+        (Proxy @AnyType)
+        (Proxy @((), Text))
+        (\_ _ f -> Strict.tell [f] >> pure f)
+        (mkProj tblFields)
+    )
+  where
+    tblFields =
+      changeBeamRep
+        (\(Columnar' fd) -> Columnar' (QField False (dt ^. dbEntityName) (fd ^. fieldName)))
+        (dbTableSettings dt)
+
+-- | 'MonadBeam's that support copying data out of a database, to some other location.
+--
+-- See 'MonadBeamCopyFrom' for the inverse operation.
+--
+-- @since 0.11.1.0
+class (MonadBeam be m) => MonadBeamCopyTo be m | m -> be where
+  -- | Execute a built @COPY ... TO@ file statement.
+  runCopyTo :: SqlCopyTo be res -> m ()
+
+instance (MonadBeamCopyTo be m) => MonadBeamCopyTo be (ExceptT e m) where
+  runCopyTo = lift . runCopyTo
+
+instance (MonadBeamCopyTo be m) => MonadBeamCopyTo be (ContT r m) where
+  runCopyTo = lift . runCopyTo
+
+instance (MonadBeamCopyTo be m) => MonadBeamCopyTo be (ReaderT r m) where
+  runCopyTo = lift . runCopyTo
+
+instance (MonadBeamCopyTo be m) => MonadBeamCopyTo be (Lazy.StateT r m) where
+  runCopyTo = lift . runCopyTo
+
+instance (MonadBeamCopyTo be m) => MonadBeamCopyTo be (Strict.StateT r m) where
+  runCopyTo = lift . runCopyTo
+
+instance (MonadBeamCopyTo be m, Monoid r) => MonadBeamCopyTo be (Lazy.WriterT r m) where
+  runCopyTo = lift . runCopyTo
+
+instance (MonadBeamCopyTo be m, Monoid r) => MonadBeamCopyTo be (Strict.WriterT r m) where
+  runCopyTo = lift . runCopyTo
+
+instance (MonadBeamCopyTo be m, Monoid w) => MonadBeamCopyTo be (Lazy.RWST r w s m) where
+  runCopyTo = lift . runCopyTo
+
+instance (MonadBeamCopyTo be m, Monoid w) => MonadBeamCopyTo be (Strict.RWST r w s m) where
+  runCopyTo = lift . runCopyTo
+
+-- | 'MonadBeam's that support copying data into database, from other location.
+--
+-- See 'MonadBeamCopyTo' for the inverse operation.
+--
+-- @since 0.11.1.0
+class (MonadBeam be m) => MonadBeamCopyFrom be m | m -> be where
+  -- | Execute a built @COPY ... FROM@ file statement.
+  runCopyFrom :: SqlCopyFrom be proj -> m ()
+
+instance (MonadBeamCopyFrom be m) => MonadBeamCopyFrom be (ExceptT e m) where
+  runCopyFrom = lift . runCopyFrom
+
+instance (MonadBeamCopyFrom be m) => MonadBeamCopyFrom be (ContT r m) where
+  runCopyFrom = lift . runCopyFrom
+
+instance (MonadBeamCopyFrom be m) => MonadBeamCopyFrom be (ReaderT r m) where
+  runCopyFrom = lift . runCopyFrom
+
+instance (MonadBeamCopyFrom be m) => MonadBeamCopyFrom be (Lazy.StateT r m) where
+  runCopyFrom = lift . runCopyFrom
+
+instance (MonadBeamCopyFrom be m) => MonadBeamCopyFrom be (Strict.StateT r m) where
+  runCopyFrom = lift . runCopyFrom
+
+instance (MonadBeamCopyFrom be m, Monoid r) => MonadBeamCopyFrom be (Lazy.WriterT r m) where
+  runCopyFrom = lift . runCopyFrom
+
+instance (MonadBeamCopyFrom be m, Monoid r) => MonadBeamCopyFrom be (Strict.WriterT r m) where
+  runCopyFrom = lift . runCopyFrom
+
+instance (MonadBeamCopyFrom be m, Monoid w) => MonadBeamCopyFrom be (Lazy.RWST r w s m) where
+  runCopyFrom = lift . runCopyFrom
+
+instance (MonadBeamCopyFrom be m, Monoid w) => MonadBeamCopyFrom be (Strict.RWST r w s m) where
+  runCopyFrom = lift . runCopyFrom
diff --git a/Database/Beam/Backend/SQL/BeamExtensions/Copy/Stream.hs b/Database/Beam/Backend/SQL/BeamExtensions/Copy/Stream.hs
new file mode 100644
--- /dev/null
+++ b/Database/Beam/Backend/SQL/BeamExtensions/Copy/Stream.hs
@@ -0,0 +1,289 @@
+{-# LANGUAGE UndecidableInstances #-}
+
+-- | Streaming-mode @COPY@: the data flows through the client connection
+-- rather than to or from a file on the database server.
+--
+-- For the file-mode variant, see
+-- "Database.Beam.Backend.SQL.BeamExtensions.Copy.File".
+module Database.Beam.Backend.SQL.BeamExtensions.Copy.Stream
+  ( -- * Building a streaming COPY statement
+    copyTableToStream,
+    copySelectToStream,
+    SqlCopyToStream (..),
+    copyTableFromStream,
+    SqlCopyFromStream (..),
+
+    -- * Streaming statement-level syntax classes
+    IsSqlCopyToStreamSyntax (..),
+    IsSqlCopyFromStreamSyntax (..),
+    BeamSqlBackendCopyToStreamSyntax,
+    BeamSqlBackendCopyFromStreamSyntax,
+
+    -- * Runner classes
+    MonadBeamCopyToStream (..),
+    MonadBeamCopyFromStream (..),
+  )
+where
+
+import Control.Monad.Cont (ContT)
+import Control.Monad.Except (ExceptT)
+import qualified Control.Monad.RWS.Lazy as Lazy
+import qualified Control.Monad.RWS.Strict as Strict
+import Control.Monad.Reader (ReaderT)
+import qualified Control.Monad.State.Lazy as Lazy
+import qualified Control.Monad.State.Strict as Strict
+import Control.Monad.Trans (lift)
+import qualified Control.Monad.Writer.Lazy as Lazy
+import qualified Control.Monad.Writer.Strict as Strict
+import Data.ByteString (ByteString)
+import Data.Kind (Type)
+import Data.List.NonEmpty (nonEmpty)
+import Data.Text (Text)
+import Database.Beam.Backend.SQL (BeamSqlBackendSelectSyntax, MonadBeam)
+import Database.Beam.Backend.SQL.BeamExtensions.Copy.File
+  ( IsSqlCopyFromSourceSyntax (..),
+    IsSqlCopyToSourceSyntax (..),
+    projection,
+  )
+import Database.Beam.Query (QField, SqlSelect (..))
+import Database.Beam.Query.Internal (AnyType, ProjectibleWithPredicate)
+import Database.Beam.Schema (TableEntity)
+import Database.Beam.Schema.Tables
+  ( DatabaseEntity (..),
+    DatabaseEntityDescriptor (DatabaseTable),
+    IsDatabaseEntity (dbEntityName, dbEntitySchema),
+  )
+import Lens.Micro ((^.))
+
+-- | Statement-level syntax for backends that support streaming
+-- @COPY ... TO@ (data leaving the database through the client connection).
+--
+-- Mirrors 'Database.Beam.Backend.SQL.BeamExtensions.Copy.File.IsSqlCopyToSyntax',
+-- but the params value carries only format options — no destination path,
+-- since chunks travel through the wire.
+class (IsSqlCopyToSourceSyntax (SqlCopyToStreamSourceSyntax cmd)) => IsSqlCopyToStreamSyntax cmd where
+  -- | The syntax for the source of the streaming copy. As with file-mode
+  --  COPY, this can be a table (perhaps with a projection) or a @SELECT@
+  --  query.
+  type SqlCopyToStreamSourceSyntax cmd :: Type
+
+  -- | All backend-specific options which determine HOW the streaming copy
+  --  is performed (format, delimiter, etc.). Unlike the file-mode params,
+  --  there is no destination — chunks flow through the connection.
+  type SqlCopyToStreamParams cmd :: Type
+
+  -- | Combine a source and a parameters value into a complete
+  -- streaming @COPY ... TO@ stream statement.
+  copyToStreamStmt ::
+    SqlCopyToStreamSourceSyntax cmd ->
+    SqlCopyToStreamParams cmd ->
+    cmd
+
+-- | Statement-level syntax for backends that support streaming
+-- @COPY ... FROM@ (data entering the database through the client connection).
+--
+-- Mirrors 'Database.Beam.Backend.SQL.BeamExtensions.Copy.File.IsSqlCopyFromSyntax'.
+class (IsSqlCopyFromSourceSyntax (SqlCopyFromStreamSourceSyntax cmd)) => IsSqlCopyFromStreamSyntax cmd where
+  -- | The syntax for the destination table of the streaming copy.
+  type SqlCopyFromStreamSourceSyntax cmd :: Type
+
+  -- | All backend-specific options which determine HOW the streaming copy
+  --  is performed. Unlike the file-mode params, there is no source path —
+  --  chunks flow through the connection.
+  type SqlCopyFromStreamParams cmd :: Type
+
+  -- | Combine a destination and a parameters value into a complete
+  -- streaming @COPY ... FROM@ stream statement.
+  copyFromStreamStmt ::
+    SqlCopyFromStreamSourceSyntax cmd ->
+    SqlCopyFromStreamParams cmd ->
+    cmd
+
+-- | Type-family selector for the backend's streaming @COPY ... TO@ syntax.
+-- A backend instance binds this to the concrete syntax type that
+-- implements 'IsSqlCopyToStreamSyntax'.
+type family BeamSqlBackendCopyToStreamSyntax be :: Type
+
+-- | Type-family selector for the backend's streaming @COPY ... FROM@ syntax.
+-- See 'BeamSqlBackendCopyToStreamSyntax'.
+type family BeamSqlBackendCopyFromStreamSyntax be :: Type
+
+-- | A built streaming-mode @COPY ... TO@ statement, ready to be executed by
+-- 'runCopyToStream'.
+data SqlCopyToStream be a
+  = SqlCopyToStream !(BeamSqlBackendCopyToStreamSyntax be)
+  | -- | A projection covering zero columns. 'runCopyToStream' should treat
+    --    this as a no-op (it must still call the sink zero times) rather
+    --    than emit an empty @COPY tbl () TO STDOUT@ statement.
+    SqlCopyToStreamNoColumns
+
+-- | A built streaming-mode @COPY ... FROM@ statement, ready to be executed
+-- by 'runCopyFromStream'.
+data SqlCopyFromStream be a
+  = SqlCopyFromStream !(BeamSqlBackendCopyFromStreamSyntax be)
+  | -- | A projection covering zero columns. 'runCopyFromStream' should
+    --    treat this as a no-op (it should not pull from the source) rather
+    --    than emit an empty @COPY tbl () FROM STDIN@ statement.
+    SqlCopyFromStreamNoColumns
+
+-- | Express a streaming copy from a table, the data flowing through the
+-- client connection rather than to a server-side file.
+--
+-- To stream the result of a @SELECT@ query instead, see 'copySelectToStream'.
+--
+-- @since 0.11.1.0
+copyTableToStream ::
+  ( IsSqlCopyToStreamSyntax (BeamSqlBackendCopyToStreamSyntax be),
+    ProjectibleWithPredicate AnyType () Text proj
+  ) =>
+  DatabaseEntity be db (TableEntity table) ->
+  -- | Projection from table to columns. If you want
+  --  to copy the entire table, use 'id'.
+  (table (QField s) -> proj) ->
+  -- | Backend-specific options.
+  SqlCopyToStreamParams (BeamSqlBackendCopyToStreamSyntax be) ->
+  SqlCopyToStream be proj
+copyTableToStream (DatabaseEntity dt@(DatabaseTable {})) mkProj options =
+  case nonEmpty (projection dt mkProj) of
+    Nothing -> SqlCopyToStreamNoColumns
+    Just cols ->
+      let source = copyTableToSyntax (dt ^. dbEntitySchema) (dt ^. dbEntityName) (Just cols)
+       in SqlCopyToStream
+            (copyToStreamStmt source options)
+
+-- | Express a streaming copy from the result of a @SELECT@ statement.
+--
+-- To stream a table, or a subset of columns, see 'copyTableToStream'.
+--
+-- @since 0.11.1.0
+copySelectToStream ::
+  ( IsSqlCopyToStreamSyntax (BeamSqlBackendCopyToStreamSyntax be),
+    SqlCopyToSourceSelectSyntax (SqlCopyToStreamSourceSyntax (BeamSqlBackendCopyToStreamSyntax be))
+      ~ BeamSqlBackendSelectSyntax be
+  ) =>
+  SqlSelect be a ->
+  SqlCopyToStreamParams (BeamSqlBackendCopyToStreamSyntax be) ->
+  SqlCopyToStream be a
+copySelectToStream (SqlSelect selectSyntax) options =
+  let source = copySelectToSyntax selectSyntax
+   in SqlCopyToStream (copyToStreamStmt source options)
+
+-- | Express a streaming copy into a table, the data flowing through the
+-- client connection rather than from a server-side file.
+--
+-- @since 0.11.1.0
+copyTableFromStream ::
+  ( IsSqlCopyFromStreamSyntax (BeamSqlBackendCopyFromStreamSyntax be),
+    ProjectibleWithPredicate AnyType () Text proj
+  ) =>
+  DatabaseEntity be db (TableEntity table) ->
+  -- | Projection from which to copy columns. Other columns
+  -- will have their default value inserted.
+  --
+  -- To copy the stream into the entire table, use 'id'.
+  (table (QField s) -> proj) ->
+  SqlCopyFromStreamParams (BeamSqlBackendCopyFromStreamSyntax be) ->
+  SqlCopyFromStream be proj
+copyTableFromStream (DatabaseEntity dt@(DatabaseTable {})) mkProj options =
+  case nonEmpty (projection dt mkProj) of
+    Nothing -> SqlCopyFromStreamNoColumns
+    Just cols ->
+      let source = copyTableFromSyntax (dt ^. dbEntitySchema) (dt ^. dbEntityName) (Just cols)
+       in SqlCopyFromStream
+            (copyFromStreamStmt source options)
+
+-- | 'MonadBeam's that support streaming data out of a database through the
+-- client connection (e.g. PostgreSQL's @COPY ... TO STDOUT@).
+--
+-- The supplied @ByteString -> IO ()@ callback is invoked once per chunk
+-- received. The 'runCopyToStream' call blocks until the COPY completes; on
+-- failure it raises the underlying backend's exception.
+--
+-- See 'MonadBeamCopyFromStream' for the inverse operation.
+--
+-- @since 0.11.1.0
+class (MonadBeam be m) => MonadBeamCopyToStream be m | m -> be where
+  -- | Execute a built streaming @COPY ... TO@ stream statement. The supplied sink
+  -- is invoked from 'IO' once per chunk emitted by the server, in order;
+  -- 'runCopyToStream' returns once the server signals end of stream.
+  runCopyToStream ::
+    SqlCopyToStream be a ->
+    -- | Sink. Called once for each chunk of bytes the server emits.
+    (ByteString -> IO ()) ->
+    m ()
+
+instance (MonadBeamCopyToStream be m) => MonadBeamCopyToStream be (ExceptT e m) where
+  runCopyToStream s cb = lift (runCopyToStream s cb)
+
+instance (MonadBeamCopyToStream be m) => MonadBeamCopyToStream be (ContT r m) where
+  runCopyToStream s cb = lift (runCopyToStream s cb)
+
+instance (MonadBeamCopyToStream be m) => MonadBeamCopyToStream be (ReaderT r m) where
+  runCopyToStream s cb = lift (runCopyToStream s cb)
+
+instance (MonadBeamCopyToStream be m) => MonadBeamCopyToStream be (Lazy.StateT r m) where
+  runCopyToStream s cb = lift (runCopyToStream s cb)
+
+instance (MonadBeamCopyToStream be m) => MonadBeamCopyToStream be (Strict.StateT r m) where
+  runCopyToStream s cb = lift (runCopyToStream s cb)
+
+instance (MonadBeamCopyToStream be m, Monoid r) => MonadBeamCopyToStream be (Lazy.WriterT r m) where
+  runCopyToStream s cb = lift (runCopyToStream s cb)
+
+instance (MonadBeamCopyToStream be m, Monoid r) => MonadBeamCopyToStream be (Strict.WriterT r m) where
+  runCopyToStream s cb = lift (runCopyToStream s cb)
+
+instance (MonadBeamCopyToStream be m, Monoid w) => MonadBeamCopyToStream be (Lazy.RWST r w s m) where
+  runCopyToStream s cb = lift (runCopyToStream s cb)
+
+instance (MonadBeamCopyToStream be m, Monoid w) => MonadBeamCopyToStream be (Strict.RWST r w s m) where
+  runCopyToStream s cb = lift (runCopyToStream s cb)
+
+-- | 'MonadBeam's that support streaming data into a database through the
+-- client connection (e.g. PostgreSQL's @COPY ... FROM STDIN@).
+--
+-- The supplied @IO (Maybe ByteString)@ source is pulled repeatedly until it
+-- returns 'Nothing', signalling end of data. 'runCopyFromStream' blocks
+-- until the COPY commits; on failure it raises the underlying backend's
+-- exception.
+--
+-- See 'MonadBeamCopyToStream' for the inverse operation.
+--
+-- @since 0.11.1.0
+class (MonadBeam be m) => MonadBeamCopyFromStream be m | m -> be where
+  -- | Execute a built streaming @COPY ... FROM@ statement. The supplied
+  -- producer is pulled from 'IO' until it returns 'Nothing'; each 'Just'
+  -- chunk is forwarded to the server in order. 'runCopyFromStream' returns
+  -- once the server has acknowledged the end of stream.
+  runCopyFromStream ::
+    SqlCopyFromStream be a ->
+    -- | Source. Called repeatedly. 'Nothing' signals end of data.
+    IO (Maybe ByteString) ->
+    m ()
+
+instance (MonadBeamCopyFromStream be m) => MonadBeamCopyFromStream be (ExceptT e m) where
+  runCopyFromStream s producer = lift (runCopyFromStream s producer)
+
+instance (MonadBeamCopyFromStream be m) => MonadBeamCopyFromStream be (ContT r m) where
+  runCopyFromStream s producer = lift (runCopyFromStream s producer)
+
+instance (MonadBeamCopyFromStream be m) => MonadBeamCopyFromStream be (ReaderT r m) where
+  runCopyFromStream s producer = lift (runCopyFromStream s producer)
+
+instance (MonadBeamCopyFromStream be m) => MonadBeamCopyFromStream be (Lazy.StateT r m) where
+  runCopyFromStream s producer = lift (runCopyFromStream s producer)
+
+instance (MonadBeamCopyFromStream be m) => MonadBeamCopyFromStream be (Strict.StateT r m) where
+  runCopyFromStream s producer = lift (runCopyFromStream s producer)
+
+instance (MonadBeamCopyFromStream be m, Monoid r) => MonadBeamCopyFromStream be (Lazy.WriterT r m) where
+  runCopyFromStream s producer = lift (runCopyFromStream s producer)
+
+instance (MonadBeamCopyFromStream be m, Monoid r) => MonadBeamCopyFromStream be (Strict.WriterT r m) where
+  runCopyFromStream s producer = lift (runCopyFromStream s producer)
+
+instance (MonadBeamCopyFromStream be m, Monoid w) => MonadBeamCopyFromStream be (Lazy.RWST r w s m) where
+  runCopyFromStream s producer = lift (runCopyFromStream s producer)
+
+instance (MonadBeamCopyFromStream be m, Monoid w) => MonadBeamCopyFromStream be (Strict.RWST r w s m) where
+  runCopyFromStream s producer = lift (runCopyFromStream s producer)
diff --git a/Database/Beam/Backend/Types.hs b/Database/Beam/Backend/Types.hs
--- a/Database/Beam/Backend/Types.hs
+++ b/Database/Beam/Backend/Types.hs
@@ -6,8 +6,8 @@
   , Exposed, Nullable
 
   ) where
+import Data.Kind (Type, Constraint)
 
-import GHC.Types
 
 -- | Class for all Beam backends
 class BeamBackend be where
diff --git a/Database/Beam/Query/CTE.hs b/Database/Beam/Query/CTE.hs
--- a/Database/Beam/Query/CTE.hs
+++ b/Database/Beam/Query/CTE.hs
@@ -10,7 +10,7 @@
 
 import Control.Monad.Fix
 import Control.Monad.Free.Church
-import Control.Monad.Writer hiding ((<>))
+import Control.Monad.Writer (WriterT, tell)
 import Control.Monad.State.Strict
 
 import Data.Kind (Type)
diff --git a/Database/Beam/Query/Internal.hs b/Database/Beam/Query/Internal.hs
--- a/Database/Beam/Query/Internal.hs
+++ b/Database/Beam/Query/Internal.hs
@@ -9,6 +9,7 @@
 
 import qualified Data.DList as DList
 import           Data.Functor.Const
+import           Data.Kind (Type, Constraint)
 import           Data.String
 import qualified Data.Text as T
 import           Data.Typeable
@@ -20,7 +21,6 @@
 import           Control.Monad.Writer
 
 import           GHC.TypeLits
-import           GHC.Types
 
 import           Unsafe.Coerce
 
diff --git a/Database/Beam/Query/SQL92.hs b/Database/Beam/Query/SQL92.hs
--- a/Database/Beam/Query/SQL92.hs
+++ b/Database/Beam/Query/SQL92.hs
@@ -322,7 +322,12 @@
              _ -> doJoined
 
     buildQuery tblPfx (Free (QWindowOver mkWindows mkProjection q' next)) =
-        let sb = buildQuery tblPfx (fromF q')
+        -- If the inner select carries DISTINCT, GROUP BY, or HAVING, those must
+        -- apply to the row set *before* the window function. Emitting them in
+        -- the same SELECT as the window projection inverts that order in SQL,
+        -- yielding wrong results (issue #746). Materialise the inner select as
+        -- a subquery in that case so the window evaluates over its output.
+        let sb = forceSubqueryIfGrouping (buildQuery tblPfx (fromF q'))
 
             x = sbProj sb
             windows = mkWindows x
@@ -336,6 +341,17 @@
              _       ->
                let (x', qb) = selectBuilderToQueryBuilder tblPfx (setSelectBuilderProjection sb projection)
                in buildJoinedQuery tblPfx (next x') qb
+      where
+        forceSubqueryIfGrouping :: Projectible be x' => SelectBuilder be db x' -> SelectBuilder be db x'
+        forceSubqueryIfGrouping sb0
+          | hasGroupingClause sb0 = uncurry SelectBuilderQ $ selectBuilderToQueryBuilder tblPfx sb0
+          | otherwise = sb0
+          where
+            hasGroupingClause :: SelectBuilder be db x' -> Bool
+            hasGroupingClause (SelectBuilderGrouping _ _ grouping having distinct) =
+                isJust grouping || isJust having || isJust distinct
+            hasGroupingClause (SelectBuilderTopLevel _ _ _ inner _) = hasGroupingClause inner
+            hasGroupingClause _ = False
 
     buildQuery tblPfx (Free (QLimit limit q' next)) =
         let sb = limitSelectBuilder limit (buildQuery tblPfx (fromF q'))
diff --git a/Database/Beam/Schema/Tables.hs b/Database/Beam/Schema/Tables.hs
--- a/Database/Beam/Schema/Tables.hs
+++ b/Database/Beam/Schema/Tables.hs
@@ -79,10 +79,11 @@
 import           Control.Applicative (liftA2)
 import           Control.Arrow (first)
 import           Control.Monad.Identity
-import           Control.Monad.Writer hiding ((<>))
+import           Control.Monad.Writer (Writer, execWriter, tell)
 
 import           Data.Char (isUpper, toLower)
 import           Data.Foldable (fold)
+import           Data.Kind (Type, Constraint)
 import qualified Data.List.NonEmpty as NE
 import           Data.Monoid
 import           Data.Proxy
@@ -94,7 +95,6 @@
 import qualified GHC.Generics as Generic
 import           GHC.Generics hiding (R, C)
 import           GHC.TypeLits
-import           GHC.Types
 
 import           Lens.Micro hiding (to)
 
diff --git a/beam-core.cabal b/beam-core.cabal
--- a/beam-core.cabal
+++ b/beam-core.cabal
@@ -1,5 +1,5 @@
 name:                beam-core
-version:             0.11.0.0
+version:             0.11.1.0
 synopsis:            Type-safe, feature-complete SQL query and manipulation interface for Haskell
 description:         Beam is a Haskell library for type-safe querying and manipulation of SQL databases.
                      Beam is modular and supports various backends. In order to use beam, you will need to use
@@ -47,7 +47,10 @@
 
                        Database.Beam.Backend.Internal.Compat
 
-  other-modules:       Database.Beam.Query.Aggregate
+  other-modules:       Database.Beam.Backend.SQL.BeamExtensions.Copy.File
+                       Database.Beam.Backend.SQL.BeamExtensions.Copy.Stream
+
+                       Database.Beam.Query.Aggregate
                        Database.Beam.Query.Combinators
                        Database.Beam.Query.Extensions
                        Database.Beam.Query.Extract
@@ -63,10 +66,9 @@
                        bytestring   >=0.10    && <0.13,
                        mtl          >=2.2.1   && <2.4,
                        microlens    >=0.4     && <0.6,
-                       ghc-prim     >=0.5     && <0.14,
                        free         >=4.12    && <5.3,
                        dlist        >=0.7.1.2 && <1.1,
-                       time         >=1.6     && <1.15,
+                       time         >=1.6     && <1.17,
                        hashable     >=1.2.4.0 && <1.6,
                        network-uri  >=2.6     && <2.7,
                        containers   >=0.5     && <0.9,
diff --git a/test/Database/Beam/Test/SQL.hs b/test/Database/Beam/Test/SQL.hs
--- a/test/Database/Beam/Test/SQL.hs
+++ b/test/Database/Beam/Test/SQL.hs
@@ -31,6 +31,7 @@
                   , leftJoinSingle
                   , aggregates
                   , orderBy
+                  , innerNub
 
                   , joinHaving
 
@@ -127,7 +128,7 @@
 simpleWhereNoFrom =
   testCase "WHERE clause not dropped if there is no FROM" $ do
     SqlSelect Select { selectTable = SelectTable { .. }, .. } <- pure $ selectMock simple
-    
+
     selectGrouping @?= Nothing
     selectOrdering @?= []
     selectLimit @?= Nothing
@@ -137,7 +138,7 @@
     -- Important point: no FROM clause, yet WHERE clause should still be here
     selectFrom @?= Nothing
     selectWhere @?= (Just (ExpressionValue (Value False)))
-  
+
   where
     simple :: Q (MockSqlBackend Command) EmptyDb s (QExpr (MockSqlBackend Command) s Bool)
     simple = do
@@ -769,6 +770,61 @@
          selectFrom s @?= Just (InnerJoin (FromTable (TableNamed (TableName Nothing "roles")) (Just ("t0", Nothing)))
                                           (FromTable (TableFromSubSelect subselect) (Just ("t1", Nothing)))
                                           Nothing)
+
+
+-- | Ensure that a SQL DISTINCT in a subquery does not propagate up (see issue #746)
+innerNub :: TestTree
+innerNub =
+  testCase "DISTINCT clause on inner SELECT" $ do
+    stmt@(SqlSelect Select{selectTable = SelectTable{..}, ..}) <- pure $ selectMock query
+
+    selectGrouping @?= Nothing
+    selectOrdering @?= []
+    selectLimit @?= Nothing
+    selectOffset @?= Nothing
+    selectHaving @?= Nothing
+    selectQuantifier @?= Nothing -- quantifier should be in the subquery
+    selectFrom
+      @?= Just
+        ( FromTable
+            ( TableFromSubSelect
+                ( Select
+                    { selectTable =
+                        SelectTable
+                          { selectQuantifier = Just SetQuantifierDistinct
+                          , selectProjection = ProjExprs [(ExpressionFieldName (QualifiedField "t0" "first_name"), Just "res0")]
+                          , selectFrom = Just (FromTable (TableNamed (TableName Nothing "employees")) (Just ("t0", Nothing)))
+                          , selectWhere = Nothing
+                          , selectGrouping = Nothing
+                          , selectHaving = Nothing
+                          }
+                    , selectOrdering = []
+                    , selectLimit = Nothing
+                    , selectOffset = Nothing
+                    }
+                )
+            )
+            (Just ("t0", Nothing))
+        )
+    selectWhere
+      @?= Just
+        ( ExpressionCompOp
+            "=="
+            Nothing
+            (ExpressionFieldName (QualifiedField "t0" "res0"))
+            (ExpressionValue (Value @Text "Alice"))
+        )
+ where
+  query :: Q (MockSqlBackend Command) EmployeeDb s (QExpr (MockSqlBackend Command) s Text)
+  query = do
+    name <- subQuery
+    guard_ (name ==. val_ "Alice")
+    pure name
+
+  -- This sub query contains a DISTINCT clause via `nub_`
+  subQuery :: Q (MockSqlBackend Command) EmployeeDb s (QExpr (MockSqlBackend Command) s Text)
+  subQuery = nub_ $ _employeeFirstName <$> (all_ (_employees employeeDbSettings))
+
 
 -- | HAVING clause should not be floated out of a join
 
