You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Esqueleto中用VALUES常量表实现与Haskell列表的全反连接?

在Yesod+Esqueleto中实现Haskell列表与数据库表的全反连接

需求背景

需要在Yesod应用中,基于Esqueleto EDSL实现全反连接:取仅出现在数据库表MyTable或仅出现在Haskell计算列表ys中的元素,排除两边都存在的元素。

对应的纯PostgreSQL查询逻辑如下:

CREATE TEMPORARY TABLE antijoin_demo (x INT); 
INSERT INTO antijoin_demo (x)
  VALUES (2),(4),(6),(8),(10),(12),(14),(16),(18) 
; 

SELECT *
FROM 
 antijoin_demo
 FULL OUTER JOIN 
 (VALUES (3),(6),(9),(12),(15),(18)) AS t(y) 
 ON x = y
WHERE x IS NULL OR y IS NULL 
ORDER BY x,y
; 

预期结果为:2,3,4,8,9,10,14,15,16,即排除同时存在的6,12,18。

现有方案的局限

当前仅能通过E.valList实现全反连接的一侧:获取MyTable中不在ys的元素,代码示例:

-- Model定义
MyTable
  x Int

-- 初始化数据
do 
  let xs = [2,4,6,8,10,12,14,16,18]
      ys = [3,6,9,12,15,18] -- 动态计算的Haskell列表,未存储在数据库
  runDB $ mapM_ (insert_ . MyTable) xs 

-- 查询MyTable中不在ys的元素
zs <- runDB $ select $ do
  xs <- from $ table @MyTable
  where_ $ (xs ^. MyTableX `E.notIn` E.valList ys)
  return xs

该代码返回[2,4,8,10,14,16],但无法直接获取ys中不在MyTable的元素[3,9,15]。同时由于MyTable和ys数据量达数十万级,无法将整张表读入内存用Data.Set计算,也不适合写入临时表。

解决方案

利用Esqueleto的values函数将Haskell列表转换为数据库可查询的数据源,结合exists子查询实现双向反连接,最后合并结果:

1. 获取ys中不在MyTable的元素

ysNotInDB <- runDB $ select $ do
  -- 将Haskell列表转换为数据库可查询的行集合
  yVal <- from $ values (map E.val ys)
  -- 过滤出MyTable中不存在匹配x的y值
  where_ $ not_ $ exists $ do
    xRow <- from $ table @MyTable
    where_ $ xRow ^. MyTableX ==. yVal
  return yVal

这段代码会生成对应的SQL,在数据库层面完成判断,避免加载全表数据到内存。

2. 合并双向结果得到全反连接

将之前获取的zs(MyTable不在ys的元素)和ysNotInDB(ys不在MyTable的元素)合并,即可得到全反连接的完整结果:

-- 提取数值(zs是Entity MyTable类型,ysNotInDB是Value Int类型)
let dbSide = map (\(Entity _ mt) -> mt ^. MyTableX) zs
    listSide = map unValue ysNotInDB
    fullAntijoin = dbSide ++ listSide
-- 可选:排序结果
sortedResult = sort fullAntijoin

完整代码示例

import qualified Database.Esqueleto as E
import Database.Persist.Sql (runDB)
import Data.List (sort)

-- 假设Model定义已存在:MyTable x Int

getFullAntijoin :: IO [Int]
getFullAntijoin = do
  let xs = [2,4,6,8,10,12,14,16,18]
      ys = [3,6,9,12,15,18]
  -- 初始化测试数据
  runDB $ mapM_ (insert_ . MyTable) xs

  -- 获取MyTable中不在ys的元素
  dbNotInList <- runDB $ select $ do
    xRow <- from $ table @MyTable
    where_ $ xRow ^. MyTableX `E.notIn` E.valList ys
    return xRow

  -- 获取ys中不在MyTable的元素
  listNotInDB <- runDB $ select $ do
    yVal <- from $ values (map E.val ys)
    where_ $ not_ $ exists $ do
      xRow <- from $ table @MyTable
      where_ $ xRow ^. MyTableX ==. yVal
    return yVal

  -- 提取数值并合并排序
  let dbVals = map (\(Entity _ mt) -> mt ^. MyTableX) dbNotInList
      listVals = map unValue listNotInDB
  return $ sort (dbVals ++ listVals)

这个方案全程在数据库层面完成过滤逻辑,仅将最终的少量结果(十几个)加载到Haskell内存,避免了大数据量的内存开销。

内容的提问来源于stack exchange,提问作者STurtle

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 17:46:08