F# 递归类型到 SQL 表

标签 f# data-modeling modeling recursive-query recursive-type

我在 F# 中对应用程序进行建模,但在尝试为以下递归类型构建数据库表时遇到了困难:

type Base = 
  | Concrete1 of Concrete1
  | Concrete2 of Concrete2
and Concrete1 = {
  Id : string
  Name : string }
and Concrete2 = {
  Id : string
  Name : string
  BaseReference : Base }

我目前得到的解决方案(我在这里找到了灵感 http://www.sqlteam.com/article/implementing-table-inheritance-in-sql-server)是:

enter image description here

我对此解决方案有两个担忧:
  • 即使在我的模型中没有意义,基表上也会有行。但我可以忍受。
  • 查找有关 Concrete2 的 BaseReference 的所有信息的查询似乎很复杂,因为我必须考虑类型的递归性和不同的具体表。此外,向模型添加新的具体类型必须修改这些查询。当然,除非有一个等价于 match SQL 中的 F# 关键字。

  • 我是不是太担心这些问题了?或者,是否有更好的方法在 SQL 表中对这种递归 F# 类型进行建模?

    最佳答案

    第 1 部分:在关系表中编码代数数据类型

    我已经为这件事挣扎了很多次。我终于发现了在关系表中建模代数数据类型的关键:Check constraints .

    使用检查约束,您可以为多态类型的所有成员使用一个公用表,但仍然强制每个成员的不变量。

    考虑以下 SQL 模式:

    CREATE TABLE ConcreteType (
        Id TINYINT NOT NULL PRIMARY KEY,
        Type VARCHAR(10) NOT NULL
    )
    INSERT ConcreteType
    VALUES 
    (1,'Concrete1'),
    (2,'Concrete2')
    
    CREATE TABLE Base (
        Id INT NOT NULL PRIMARY KEY,
        Name VARCHAR(100) NOT NULL,
        ConcreteTypeId TINYINT NOT NULL,
        BaseReferenceId INT NULL)
    
    GO
    
    ALTER TABLE Base
    ADD CONSTRAINT FK_Base_ConcreteType
    FOREIGN KEY(ConcreteTypeId)
    REFERENCES ConcreteType(Id)
    
    ALTER TABLE Base
    ADD CONSTRAINT FK_Base_BaseReference
    FOREIGN KEY(BaseReferenceId)
    REFERENCES Base(Id)
    

    很简单,对吧?

    我们已经通过消除该表解决了表示抽象基类的表中存在无意义数据的问题#1。我们还组合了用于独立建模每种具体类型的表,而是选择存储所有 Base实例——不管它们的具体类型——在同一个表中。

    照原样,此模式不限制您的 Base 的多态性。类型。按原样,可以插入 ConcreteType1 的行带有非空 BaseReferenceIdConcereteType2 的行为空 BaseReferenceId .
    没有什么可以阻止您插入无效数据,因此您需要非常勤奋地进行插入和编辑。

    这就是检查约束真正发挥作用的地方。
    ALTER TABLE Base
    ADD CONSTRAINT Base_Enforce_SumType_Properties
    CHECK
    (
        (ConcreteTypeId = 1 AND BaseReferenceId IS NULL)
        OR
        (ConcreteTypeId = 2 AND BaseReferenceId IS NOT NULL)
    )
    

    检查约束 Base_Enforce_SumType_Properties为每种具体类型定义不变量,保护插入和更新数据。继续运行所有 DDL 以创建 ConcreteTypeBase您自己的数据库中的表。然后尝试将行插入 Base这违反了检查约束中描​​述的规则。你不能!最后,您的数据模型结合在一起。

    解决问题 #2:现在您的类型的所有成员都在一个表中(强制执行不变量),您的查询将更简单。您甚至不需要“等同于 SQL 中的 match F# 关键字”。添加新的具体类型就像在 ConcreteType 中插入新行一样简单。表,将任何新属性添加为 Base 中的列表,并修改约束以反射(reflect)任何新的不变量。

    第 2 部分:在 SQL Server 中编码分层(阅读:递归)关系

    部分关注#2 我考虑了跨 ConcreteType2 之间存在的“父子”关系进行查询的复杂性。和 Base .有很多方法可以处理这种查询并选择一种,我们需要记住一个特定的用例。

    示例用例:我们希望查询每一个 Base实例化并组装包含每一行的对象图。这很简单;我们甚至不需要加入。我们只需要一个可变的 Dictionary<int,Base>Id用作 key 。

    这里有很多内容,但需要考虑:有一个名为 HierarchyID 的 MSSQL 数据类型。 ( docs ) 实现了“物化路径”模式,从而可以更轻松地对像您这样的层次结构进行建模。您可以尝试使用 HierarchyID而不是 INT在您的 Base.ID 上/Base.BaseReferenceID列。

    我希望这有帮助。

    关于F# 递归类型到 SQL 表,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46694251/

    相关文章:

    sql - 哪个数据库设计更好?

    google-app-engine - 关系与非关系数据建模

    应用程序日志的 Cassandra 数据模型(数十亿次操作!)

    f# - 嵌套循环和函数式编程

    haskell - 有没有办法强制 Haskell 代码排序像 F# 一样工作?

    modeling - BPMN 和 CMMN 有什么区别?

    javascript - 如何在 Three.js 场景中平滑多边形?

    diagram - BPMN 中的 "error"和 "compensation"事件有什么区别?

    .net - F# 绑定(bind)重定向不适用于 F# 4.3.0-4.3.1

    recursion - 复制递归 F# 记录类型