Haskell Persistent如何使用<-.(in组合子)实现元组多字段过滤
Persistent 多列IN查询实现方案
问题背景
官方文档介绍了单字段场景下in组合子(<-.)的基础用法:
selectSPJ :: MonadIO m => ReaderT SqlBackend m [Entity User] selectSPJ = selectList [UserAge <-. [40]] []
直接尝试传入元组字段组合实现多列IN查询的写法无法通过类型检查:
-- 该写法无法编译 selectUsers :: MonadIO m => ReaderT SqlBackend m [Entity User] selectUsers = selectList [(UserName, UserAge) <-. [("SPJ", 40), ("John Doe", 30]] []
原因是<-.组合子的类型签名限制了单个字段入参:
(<-.) :: forall v typ. PersistField typ => EntityField v typ -> [typ] -> Filter v
EntityField的定义为data EntityField record :: Type -> Type,不支持直接传入元组作为字段类型。
目前通过OR拼接多组等值条件的写法虽然逻辑等效,但生成的SQL性能远低于原生多列IN语法:
-- 性能较差的OR拼接实现 mkFilters namesAndAges = foldl' mkFilter [] namesAndAges mkFilter filters (name, age) = [UserName ==. name, UserAge ==. age] ||. filters
其生成的SQL为:
select user.name, user.age from user where (name = 'SPJ' and age = 40) or (name = 'John Doe' and age = 30);
而期望的高性能原生多列IN查询SQL为:
select user.name, user.age from user where (name, age) in (values ('SPJ', 40), ('John Doe', 30));
可行实现方案
方案1:使用rawSql手写原生SQL(推荐,兼容性最好)
这是最稳定、性能完全可控的实现方式,支持动态参数长度,适配所有支持多列IN语法的数据库:
{-# LANGUAGE OverloadedStrings #-} import qualified Data.Text as T import Database.Persist.Sql (PersistValue(..), rawSql) -- 动态长度参数的多列IN查询 selectUsersByMultiColIn :: MonadIO m => [(Text, Int)] -> ReaderT SqlBackend m [Entity User] selectUsersByMultiColIn nameAgePairs = do let pairCount = length nameAgePairs -- 生成对应数量的占位符 placeholders = T.intercalate ", " $ replicate pairCount "(?, ?)" query = "SELECT ?? FROM user WHERE (name, age) IN (VALUES " <> placeholders <> ")" -- 扁平化参数列表 params = concatMap (\(name, age) -> [PersistText name, PersistInt64 $ fromIntegral age]) nameAgePairs rawSql query params
使用前请确认数据库版本支持多列IN语法:
- PostgreSQL:所有主流版本均支持
- MySQL:8.0.19及以上版本支持
- SQLite:3.15.0及以上版本支持
方案2:升级persistent版本使用官方原生API
persistent 2.14及以上版本已经内置了多列元组IN查询的支持,新增了对应元组长度的Filter组合子,升级依赖后可以直接使用官方Tuple IN相关API,无需手写原生SQL。
不推荐方案:自定义元组PersistField实例
理论上可以为常用长度的元组定义PersistField类型类实例,配合自定义的组合字段类型实现类<-.的调用,但该方案需要修改实体层定义,跨数据库兼容性差,维护成本极高,不建议在生产环境使用。
内容的提问来源于stack exchange,提问作者JH de Raigniac
相关产品推荐
相关产品推荐

