快速前言:我使用SQL实现持久化(Haskell)和esqueleto。
无论如何,我想有一个SQL表的类型[String]
列,即字符串列表。现在我想做一个查询,它给出所有记录,其中给定列表是记录中列表的子列表。
例如
表ID Category
0 ["math", "algebra"]
1 ["personal", "life"]
2 ["algebra", "university", "personal"]
查询["personal", "algebra"]
将只返回ID=2的记录,因为["personal", "algebra"]
是["algebra", "university", "personal"]
的子列表。
像这样的查询可能与我追捧的子列表和"基本"SQL操作符的可变长度吗?
如果有人知道如何使用persistent/esqueleto,那当然很棒。
谢谢。
对Gordon Linoff的评论和上一个答案进行扩展:
SQL数据库的能力有时是有限的。由于[String]
中字符串的顺序似乎无关紧要,因此您正试图将set
之类的东西放入关系数据库中,并且对于您的查询,您建议使用is a subset of
之类的操作符。
如果有一个数据库引擎提供这些结构,使用它不会有什么错(我不知道)。但是,近似您的集合逻辑(或数据库本身不支持的任何逻辑)有缺点:
- 你必须明确地处理边缘情况(参考xnyhps的答案) 比起隐藏存储数据的复杂性,您需要在代码中显式地处理它
- 你需要学习数据库引擎,而不是写你的Haskell代码
- 数据库和Haskell代码之间的接口变得模糊
一种更强大的方法是将您的存储任务重新表述为易于适应关系数据库概念的东西。也就是说,试着把它放在关系的角度。实体和关系很简单,因此可以避免边缘情况。您不需要担心如何准确地存储数据库后端数据。您根本不需要为数据库操心。并且您的接口被简化为相当简单的查询(使用连接)。所有不能(相对地)轻易地用查询实现的东西,都(可能)属于Haskell代码。
当然,具体情况要根据具体情况而定。
在你的特殊情况下,你可以这样使用:
Table: Category
ID Description
0 math
1 algebra
2 personal
3 life
4 university
Table: CategoryGroup
ID CategoryID
0 0
0 1
1 2
1 3
2 1
2 4
2 2
…其中外键关系允许拥有类别组。在这里,您使用的是关系数据库的优点。为了查询CategoryGroup
,您将连接这两个表,从而产生类型为
[(Entity CategoryGroup, Entity Category)]
在Haskell中转换成
[(Entity CategoryGroup, [Entity Category])]
为每个CategoryGroup
收集Category
实体(这需要CategoryGroup
-Model中的deriving (Eq, Ord)
)。
cs :: [Entity Category]
,如上所述的set-logic将像
import qualified Data.Set as Set
import Data.Set (isSubsetOf)
let s = Set.fromList ["personal", "algebra"]
let s0 = Set.fromList $ map (categoryDescription . entityVal) cs
if s `isSubsetOf` s0 -- ... ?
一开始习惯关系数据库的限制可能会很烦人。我想,对于一些至关重要的事情(持久化数据),一个健壮的概念通常比一个强大的概念要好,并且总是知道你的数据库在做什么
通过使用[String]
, persistent将整个列表转换为带引号的字符串,这使得从SQL处理它非常困难。
你可以这样做:
mapM (cat ->
where_ (x ^. Category `like` (%) ++. val (show cat) ++. (%)))
["personal", "algebra"]
但是这是非常脆弱的(当类别包含"
等时可能会破裂)。
更好的方法是:
如果数据库足够小,可以在Haskell中进行过滤
将数据建模为:
对象:
ID ... 0 ... 1 ... 2 ...
ObjectCategories:
ObjectID Category 0 math 0 algebra 1 personal 1 life 2 algebra 2 university 2 personal