技术注释
关系数据库快速入门
本教程的目的 本教程解释了如何使用实体框架构造和执行关系数据库的查询。 它旨在作为一个基于示例的快速入门指南,而不是一个完整的参考。 它涵盖了最常见的用例,并用具体示例说明了这些用例。 在阅读完本教程之后,人们可以预期已经对如何构造实体框架查询并执行它们有了核心的实际理解。 一些更高级的材料、微妙之处和角落案例被有意省略,以保持教程的规模和入门级。 然而,大多数核心查询构建机制都有介绍,并用示例进行了说明,这应该足以开始使用。 关系数据库连接的作用 关系数据库是一种重要且广泛使用的信息存储、处理和查找手段。 在过去的几十年中,相关技术已经非常成熟,现代关系数据库后端具有存储和处理结构化数据(从小数据量到超大数据量)的卓越功能。 因此,能够将关系数据库无缝集成到工作流程中,对于在从数据科学到web应用程序的多个领域中进行工作,这一点很重要。 这方面的一个重要方面是能够在数据库端处理甚至非常大的数据集,“超出核心”。 大多数常见的关系数据库后端都经过了高度优化,并应用了前沿技术,能够非常高效地处理如此大的数据集。 在Wolfram语言的上下文中,将大型数据集上的计算卸载到数据库引擎的能力尤为重要,因为许多此类计算不需要Wolfram Language的全部计算能力,而生成的数据集的大小可以大大减小, 以便可以在Wolfram语言中轻松地进行进一步处理。 Wolfram语言中的关系数据库连通性 一般注意事项 将关系数据库直接集成到个人工作中可能会变得复杂,原因有很多。 首先,许多领域专家不具备直接使用关系数据库所需的SQL知识。 其次,SQL方言和常用的不同数据库后端支持的功能集之间存在(通常是细微的)差异。 第三,必须手动构建数据管道,将数据库连接到编程环境。 由于这些原因,将关系数据库直接紧密集成到语言中是非常有益的。 它允许用户专注于解决他们的问题,而不是提供技术基础设施。 Wolfram语言的高级符号特性为将关系数据库集成到该语言中的框架设定了严格的要求。 它必须是高级的、符号化的和惯用的,并且要公开足够的结构,以避免对原始SQL的查询构建和其他功能进行过多的限制。 特别是,惯用的Wolfram语言编程倾向于函数式编程风格、函数组合和不变性 — 因此,连接框架需要包含和支持的功能。 实体框架的作用 事实证明,实体框架在很大程度上满足了概述的要求。 虽然Wolfram语言中没有OOP意义上的对象,但在关系数据库的上下文中,Wolfram Language的实体框架在许多方面与其他语言的对象关系映射器(ORM)类似。 有些过于简化了,实体映射到数据库表的单行,实体类映射到数据库表格(现有或虚拟),实体属性映射到数据库列。 因此,在大量需要查询数据库的情况下,可以使用Wolfram语言,并通过使用实体框架功能查询语言构建的高级实体框架查询与数据库通信。 然后,该框架负责将查询转换为适当的SQL方言,在数据库端执行查询,并以适当的格式将结果返回给用户。 实体框架和DatabaseLink 长期以来,用Wolfram语言与关系数据库交互的主要工具 数据库链接 它建立在J/Link和JDBC之上,提供了一个功能齐全的工具箱来与关系数据库交互。 重要的是要了解 数据库链接 工具箱和本教程中讨论的技术。 最重要的区别在于这些工具提供的抽象级别,以及支持与数据库交互的功能。 工具箱由提供 数据库链接 是相当低级的:虽然它有一些特性,允许在简单的情况下以符号方式构造数据库查询,但在与一起使用时,大多数实际查询都必须编写为SQL字符串 数据库链接 这对用户有很多影响,从需要熟悉SQL(以及与使用的特定数据库后端相对应的特定SQL方言)到无法使用Wolfram语言在符号功能和高级抽象方面的优点。 所有这些都导致数据库不完整、低级地集成到Wolfram语言中。 基于实体框架的技术从头开始设计,以提供关系数据库到Wolfram语言的高级无缝集成,并包含功能(如符号查询语言、关系、自动SQL生成和后端专门化、内置类型检查等) 这使得在Wolfram语言中涉及关系数据库的更强大和更高级的工作流成为可能。 另一方面, 数据库链接 支持涉及数据库的常见工作流所需的许多核心功能,例如写操作(SQL 插入 / 更新 / 删除 )、事务支持、超时控制、连接池等。为了提高效率,它还提供对数据库结果集的低级访问。 所有这些基于实体框架的技术目前都缺乏,因此可以说 数据库链接 目前功能更加完善。 除了两种技术都集成到Wolfram语言中之外,这两种技术之间目前没有深入的互操作性。 在未来版本的Wolfram语言中,互操作性可能会变得更好。 教程的组织 这个 第一节 解释了准备使用关系数据库的标准工作流:建立连接、创建和注册数据库备份 实体存储 对象。 这个 第二部分 回顾并说明了关系数据库上下文中Entity框架的关键特性。 这个 第三部分 通过Entity框架提供的核心查询构建块来构建更复杂的查询。 这个 第四部分 包含一些额外有用的构造和工具的简要介绍,这些构造和工具可能被认为更高级,但在实践中非常有用。 这个 第五部分 说明了实体框架支持的各种查询构建原语通常可以生成何种SQL。 这个 第六段 描述了如何以编程方式生成查询,并举例说明了为什么这可能是有益的。 这个 第八节 包含错误处理的简要介绍,以及构建和运行查询时可能遇到的典型错误。 这个 最后一段 包含多个更复杂查询的示例,说明如何将不同的查询构建块结合在一起来构造更复杂的查询。 示例数据库 本教程中的示例基于示例公共域数据库 经典车型 (尤其是其SQLite版本),这是一家经典汽车数据库缩放模型零售商。 它具有以下实体关系图(请注意,图上的数据类型对应于数据库的MySQL版本):
办公室 表与具有1到N的关系 员工 表:通常有很多员工在同一个办公室工作。 通过外键实现关系 办公室代码 的 员工 与主键对应的表 办公室代码 的 办公室 表。
员工 表本身有一个1到N的关系:一般来说,每个员工可能有0个、1个或多个其他员工向他们报告。 通过外键实现关系 报告收件人 ,链接到主键 员工编号 相同的 员工 表。
员工 表与具有1到N的关系 客户 表:每个员工可以处理0个、1个或多个客户,而每个客户最多只能由一个员工处理。 通过外键实现关系 销售代表员工编号 的 客户 表,对应于主键 员工编号 的 员工 表。
客户 表与具有1到N的关系 付款 表:每个客户可能已经支付了0、1或更多款项。 通过外键实现关系 客户编号 对应于主键的付款表 客户编号 客户表的。
客户 表与具有1到N的关系 订单 表:每个客户可能已经下了0、1或更多订单。 通过外键实现关系 客户编号 的 订单 表,对应于主键 客户编号 的 客户 表。
订单 表与具有1到N的关系 订单详细信息 表:每个订单由一个或多个项目组成,不同产品类型的每个项目在 订单详细信息 表。 通过外键实现关系 订单编号 属于 订单详细信息 表,对应于主键 订单编号 的 订单 表。
订单详细信息 表与具有N对1关系 产品 表:每个产品可能已订购0次、1次或更多次。 通过外键实现关系 产品代码 属于 订单详细信息 表,对应于主键 产品代码 属于 产品 表。
产品 表与具有N对1关系 生产线 表:每个产品线可能有一个或多个属于它的产品。关系通过外键实现 产品线 属于 产品 表,对应于主键 产品线 属于 生产线 表。
连接到数据库
创建一个 关系数据库 对象,使用该连接(此操作检查数据库并检索有关数据库模式的数据库元数据等)。
以下是如何对本教程中使用的示例数据库执行此操作的:
构建数据库连接的特定语法可能因不同的数据库后端而异。 使用RelationalDatabase对象 如前一节所述,创建数据库备份所需的步骤之一 实体存储 包括创建 关系数据库 对象。 然而,这个对象本身也很有用。 它包含有关数据库模式(表、列、约束等)的信息,可以用于可视化检查和以编程方式提取感兴趣的部分信息。
的格式 关系数据库 对象可以使用分层组打开器轻松直观地检查数据库结构,可以展开这些打开器来检查给定表或列的更多详细信息:
有关各种属性和方法的更多详细信息,请参阅 关系数据库 对象。 介绍 本节介绍了实体框架支持的重要核心操作,以及它们在关系数据库上下文中的使用方式。 它并不是主要关注查询构造,它有一个 单独部分 相反,它奠定了基础并讨论了其他重要方面,其中许多方面是通过Entity框架有效处理关系数据库的先决条件。 为了在处理关系数据库的工作中有效地使用实体框架,必须至少基本了解实体框架的概念和结构如何与关系数据库的核心概念和结构相对应。 首先讨论这个主题。 接下来将讨论查询执行和所谓的“解析器”(通过启动查询编译和执行过程并返回结果,将符号表示的查询实际转换为特定结果的函数)。 接下来,计算属性和 实体函数 已覆盖。 这些是非常重要的构建块,允许用户动态定义和计算新属性,而这些新属性可能需要在数据库端执行复杂的计算。 在许多情况下,能够使用单个实体非常重要。 这对于各种用途都很有用,从更好地可视化和理解结果到查询构造(对于调试和原型设计,能够在单个实体上执行部分查询非常有价值)。 下面简要介绍这个主题。 对于关系数据库,数据库表的主键是一个中心概念。 实体框架对应于 规范名称 单个实体的属性。 与规范名称一起定义单个实体的另一部分是该实体的类型。 这些主题被认为足够重要,值得单独一节。 虽然涉及关系数据库的大多数现代工作流都处理具有主键的数据库表,但当表没有主键,或者表没有主钥但在数据库模式级别上没有强制执行时,这种情况并非闻所未闻。 这些在实体框架的上下文中更为重要,因为对于此类表/实体类型,只有实体框架的一部分功能有效。接下来将讨论这个问题。 虽然本教程并不关注与关系数据库交互中隐含的类型和类型系统,但有一个类型区别对于有效工作来说非常重要。即,实体类型可能包含实体值和实体类值的属性。 在本教程中,这些属性称为关系,并由框架添加到每个实体类型的核心属性集(直接对应于数据库表列的属性)。 本节讨论的最后一个主题涉及在关系数据库上下文中缺少的值以及在结果中可能遇到这些值的方式。 实体框架和SQL之间的近似映射 由于实体框架在Wolfram语言中使用 — 尤其是表示关系数据库 — 显然,实体框架和关系数据库/SQL之间必须有核心概念和构造的映射。 对于Entity框架的许多功能,这样的映射相当直接。 然而,在某些方面,实体框架代表了更丰富的数据模型。 例如,对于实体框架的内存使用,实体属性可以将任意Wolfram语言表达式作为其值 — 如果直接使用,甚至不符合关系数据库的第一种范式(例如,当实体属性为 列表 值)。 另一个例子是,实体框架能够用无限多的实体来表示类型,这是关系数据库不容易做到的。此外,对于使用Wolfram语言的关系数据库,容易实现的计算集更加有限, 后者自然具有“开箱即用”的更丰富的计算功能。 关系模型的限制也有其优点,例如能够为符合ACID的数据库的数据一致性、事务和其他相关有用功能提供强有力的保证。 这些限制还意味着对数据库支持实体存储的实体框架施加类似的限制。 也就是说,核心关系和实体框架结构之间的映射相对简单。 下表对其进行了总结。
数据库(模式)
实体类型(数据库表)的集合
数据库表
实体类型
具有类似属性集(表行)的实体集合(句柄)
数据库表行
一组属性/值的(句柄),表示具有唯一标识的单个“事物”(表行)
数据库表字段(列名)
特定属性,通常具有特定类型
主键
对于给定的实体类型(db表),一个属性或一组属性保证是唯一的(当将一组属性组合在一起时)
外键
实体价值财产
值为单个实体的实体属性(可以是相同或不同的实体类型)。 对于数据库,指向表中唯一行的字段(可以是同一个表,也可以是不同的表)。
(派生)表
同一类型实体的集合(句柄)(由查询注册或定义)。 在数据库端,派生表是一个虚拟表/子查询,在FROM子句中使用。
如前所述,这种映射具有一定的不对称性。 它使将现有关系数据库表示为实体存储变得简单。 然而,在不创建额外抽象层的情况下,并非所有现有内存中实体存储都可以轻松映射到关系数据库。 特别是,以下是存在的一些限制:
实体属性的值只能是给定数据库后端支持的特定有限类型集,以便将给定类型的实体轻松映射到关系数据库表行。
某些实体属性具有限定符。 为了在数据库端支持此类属性,需要额外的结构(例如,限定符值可能需要单独的数据库表等)。
当需要通过关系数据库(实体框架当前不支持的功能)支持现有内存中实体存储时,这些考虑因素更为重要,但记住这些因素对于更好地理解整体情况仍然很有用。 也有一些限制在另一个方向上起作用。 例如,从技术上讲,可以让数据库表没有主键,并与数据库端的表一起工作。 然而,在实体框架方面,每个实体都必须有一个 规范名称 在给定的实体类中是唯一的。 这意味着无法为此类数据库表定义单个实体,因此entity框架的所有与单个实体相关的功能都无法用于此类数据库表/类型。 特别是,功能如下 实体列表 和 实体值 带有某些修饰语(例如。 “实体属性关联” )在这种情况下不起作用。 该问题将在中进一步讨论 这个部分 . 使用EntityValue和EntityList执行查询 实体框架中只有两个“解析器”(可用于执行查询的命令): 实体值 和 实体列表 . 主要和最常用的是 实体值 它通常用于执行给定的查询并以各种形式获取结果。 在关系数据库的上下文中,查询通常表示(虚拟)数据库表,这些表在实体框架中对应于(虚拟)实体类型。 的作用 实体值 在这个上下文中,是将给定的查询编译成合适的SQL方言,在数据库上执行,并以各种形式(值的列表或关联)提取某组实体属性(数据库术语中的结果表列)的值。
结果可以以不同的形式获得,例如作为关联列表,保留属性名称:
对于提取的属性,可以使用完整的 实体属性 代替短字符串名称,在这种情况下,数据关联中的结果键也将是 实体属性 [ ] 表达:
的第三个参数的全部可能修饰符 实体值 可以在文档中找到 实体值 . 在某些情况下,可能需要获取给定实体类型/类中包含的实体列表。 这可以通过 实体列表 .
请注意,除了将在后续章节中讨论的某些特殊情况外,对于重复调用 实体列表 或 实体值 . 计算属性和EntityFunction 除了提取现有属性外,还可以提取计算的属性 — 也就是说,动态创建属性,这可能需要在数据库端进行复杂的计算。 这些属性必须使用 实体函数 .的语义 实体函数 在这种情况下类似于 功能 ,在接下来的章节中将介绍一些重要的差异。 人们可以想到 实体函数 作为类似于的构造 实体属性 (在本节的上下文中)获取单个实体并返回使用该实体执行的某些计算的结果。 目前重要的是 实体函数 只能返回标量,但不能返回值列表、实体或实体类。 以下是使用 实体函数 检索需要在数据库端进行计算的属性。 以下查询提取属性 “办公代码” 和计算的属性,表示中每个办公室的完整字符串办公室地址 “办公室” 类型:
实体函数 有 全部保留 属性,就像 功能 ,以防止过早评估其变量和主体。 的主体 实体函数 是一个表达式,其中可以使用一组有限的基元 实体函数 能够理解并编译为SQL。 这些原语的完整列表可以在中找到 实体函数 文档,而实际使用的更多示例 实体函数 可以在后续章节中找到。 使用单个实体 单个实体是实体框架中的一个重要概念和构建块。使用单个实体的能力为工作流增加了交互性,通常允许人们更好地理解数据,即使最终结果应该是对多个实体(实体类)起作用的查询。 重要的是要理解单个实体本质上是对实际数据的处理(或引用)。 除了类型和规范名称外,它们不包含数据。 因此,它们是懒惰的:每次需要从实体中提取数据时,都必须对其运行新的查询。实体的这种懒惰特性是entity框架的一个非常有用的特性,因为它允许以更抽象的方式处理实体。 然而,它附带了一些需要注意的事项。 例如,如果某个特定实体的某些属性在同一查询的两次连续执行之间发生更改,则返回的结果通常也会有所不同,从而反映出这些更改。 也可能会发生这样的情况:给定实体类中的某个实体在某个时候被删除,不再存在。 当然,这些复杂情况只会发生在时间变化的数据上。
重要的是要认识到,可以使用 实体函数 这对于快速查找和构建更复杂的查询原型都非常有用。 例如,这将从 “城市” 和 “状态” 给定办公室的属性,在数据库端:
实体类型、单个实体的唯一性和规范名称 在实体框架中,每个实体在其实体类型/实体类中必须是唯一的。 另一种说法是,每个实体都必须具有唯一的标识,并且不允许任何实体类/实体类型包含重复的实体。 实体类型和规范名称 从语法上讲,实体由包含两部分的表达式表示:实体类型和实体唯一标识符,即规范名称。
类型的实体 “订单详细信息” 将一对整数订单号和字符串产品代码作为其规范名称:
实体的实体类型始终是的第一个参数 实体 [ ] ,但不总是字符串。特别是,当类型不是注册的类型,而是由实体框架查询隐式定义的“运行时”类型时,它将是该查询本身。
对于与数据库中的数据库表相对应的实体类型,实体的规范名称正好是相应数据库表的主键。 规范名称何时可以是列表? 在关系数据库的上下文中,实体的规范名称可以是以下情况之一(或其组合)的列表:
实体属于注册类型,该类型对应于具有复合主键的数据库表。
实体属于“运行时”类型,由 聚合实体类 [ ... ] ,其中用于聚合的实体组是基于多个属性形成的。
前面的示例属于最后一类案例,其中分组属性为 “城市” 和 “国家” ,而前面的示例中包含的实体 “订单详细信息” 类型属于第一个类别,主键为 “订单详细信息” 表是一个复合表,由属性组成 “订单编号” 和 “产品代码” . 下面定义了一个组合类型的实体类 “员工” 和 “办公室” 。此类的实体具有规范名称,即员工编号和办公室代码对的列表:
没有主键的单个实体和数据库表 某些数据库表在数据库模式中没有主键约束的情况可能并不典型,但在实践中很重要。 请注意,并非在所有这些情况下,表中都不存在具有唯一值的列。 然而,目前还没有办法指示Entity框架使用这样一列作为此类表的主键:关于主键的所有信息目前都是在数据库检查时获得的(当 关系数据库 对象)并完全基于现有数据库约束。 这对于基于Entity-framework的工作流意味着,对于此类表,无法定义单个实体,因为没有明确的方法可以将规范名称附加到此类表中的数据库行。 这并不意味着不能对这些表做任何有用的事情,但实体框架的某些功能部分将无法与这些表一起工作。 为了说明这些表在实体框架功能方面存在的局限性 泰坦尼克号 将使用数据库,这是一个托管在Wolfram Cloud中的SQLite数据库,基于以下内容 泰坦尼克号 数据集:
从这个数据集的结构可以看出,没有字段或字段组合可以自然地作为主键。 甚至无法保证数据集中没有重复记录,因为数据中甚至没有乘客姓名,只有乘客的旅行级别、年龄、性别以及他们是否幸存下来。 尽管如此,这个数据集还是一个有用的信息来源。 示例工作流说明了一些实体框架操作,这些操作对于此类数据集是可行的,也不可行。
这一步应该没有问题; 正确标识数据库及其包含的单个表。
如图所示,警告由 实体存储 单个实体无法用于此类型/数据库表。 特别地, 实体列表 和一些形式的 实体值 将不起作用。
只要结果数据的形式不涉及单个实体,仍然可以提取特定属性的值:
以下查询为乘客引入了一个粗粒度的年龄等级,其中0至20岁的乘客属于第一组,20至40岁的乘客位于第二组,依此类推。然后计算每个乘客等级、年龄组和性别的幸存乘客比例,并按该比例将结果按降序排序:
数据库架构中没有主键约束的数据库表对应于没有主键的实体类型 规范名称 定义。 这意味着不能为此类类型定义单个实体。
可以在 实体存储 对象; 然而,并非所有实体框架功能都适用于它们。
虽然单个实体不能用于此类类型(也不能用于以下功能 实体列表 和 实体值 对于某些修饰符) 实体值 仍然可以工作,以及所有不涉及该类型单个实体的查询。
实体值和实体类值属性 属性可以是实体值或实体类值。 这些属性不对应于相应数据库表的任何数据库表列,而是由框架添加并表示相关表。 下面获取的值为 “员工” 属性,它是一个(隐式)实体类:
可以使用以下命令将此实体类扩展为实体列表 实体列表 :
这将为给定办公室选择第一个员工,并提取该员工的实体属性和值:
除了 “办公代码” 属性,在数据库术语中,它是 “办公室” 表中,有一个生成的属性 “办公室” 其价值是代表该员工工作的办公室的实体:
请注意,此类实体和实体类值属性没有直接的数据库列来源,而是由框架基于数据库模式(关系/外键信息)生成的。 此时需要注意的一点是,实体类值属性不会自动解析为实体列表,因此 实体列表 如果想要获得实体列表,则必须显式应用于其值。 结果中缺少值和属性名无效 对于某些实体,某些属性在数据库表中可能没有值(值可能是 无效的 ). 在Wolfram语言方面,这对应于 缺少 [ ] 值。
该办公室位于法国巴黎,因此对 “状态” 字段。 缺少这样的值对应于 无效的 值,并由表示 缺少 [ “不可用” , ... ] 结果是:
另一种类型的输入导致 缺少 [ ] 值是无效属性名的请求值。 然而,在这种情况下,缺少该值的原因不同: 缺少 [ “未知属性” , ... ] . 下面是一个示例,其中不存在属性的值 “foo” 请求:
介绍 本节描述实体框架为构建更复杂的查询而提供的核心原语。 首先,介绍了类属性查询。 通过大量示例对其进行解释和说明,这些示例涉及如何使用计算属性显著扩展可用实体属性集,以及如何 实体函数 用于表示这些计算的属性。 本节的其余部分专门讨论接受实体类(和其他参数)并返回新的(转换的)实体类的各种原语。 实体框架为以下典型的数据处理操作提供了此类原语:
根据特定条件筛选实体(SQL对应项:WHERE子句)
根据某些条件对实体进行排序(SQL对应项:ORDER BY子句)
获取实体子集(SQL对应项:LIMIT/OFFSET子句)
使用动态计算的新属性扩展实体类中的实体(SQL对应项:SELECT列表)
在实体类或其中的实体组上聚合(SQL对应项:聚合/GROUP BY子句)
将两个实体类组合在一起形成一个新的实体类,实体具有两个原始实体类的属性(SQL对应项:JOIN子句)
这些实体类转换原语可以相互组合和嵌套,从而可以构造更复杂的查询。 实体函数和类属性查询 类属性查询是用于构建部分实体框架查询的Wolfram语言表达式,在查询编译和执行过程中,这些查询被编译为SQL表达式。 用于构造类属性查询的主要构造是 实体函数 . 类属性查询在许多地方都很有用,例如定义计算属性、过滤数据的谓词、排序标准、聚合等等。 在本教程中,将考虑其中的一些典型示例。 可以使用标准算术运算: 次数 , Plus(加) , 减去 , 划分 , 国防部 等。 以下计算每个订购项目的付款总额,同时考虑订购项目的数量:
下面提取了 “状态” 中每个实体的属性 “办公室” 类型并计算布尔表达式,该表达式检查此属性值是否以字母开头 “C” :
以下示例显示了如何使用 MissingQ公司 检查缺失值的谓词 — 在这种情况下,对于属性 “状态” 对于类型 “办公室” :
还支持标准比较运算符。 请注意 相等 和 SameQ公司 可以互换使用,以及 不平等 和 UnsameQ(取消命名) .
可以构造相当复杂的数值表达式和其他类型的表达式,这些表达式将被转换为SQL并在数据库端执行。 以下查询计算建议零售价和购买价之间的差额与建议零售价之间的比率,即商店以建议零售价销售商品时从销售商品中获得的收入百分比:
下面是一个更复杂的度量,也涉及 电源 和 平方米 ,仅供说明:
布尔表达式尤其重要,因为布尔值 实体函数 表达式经常用作筛选谓词、条件 组合实体类 等。 以下查询计算一个布尔属性,该属性将生成 真的 对于订购数量>30且每项价格>=200美元的项目 “订单详细信息” 类型,其中Wolfram语言端后处理仅选择它为其生成的那些产品 真的 :
以下查询计算的属性 真的 对于可被100整除的订单号:
以下查询计算一个布尔属性,该属性将生成 真的 对于其所在城市中包含信件的所有办公室 “a” ,或其状态存在(不存在 缺少 [ ] )他们的城市里有字母 “o” :
以下查询计算一个布尔属性,该属性将生成 真的 对于要求在下订单之日起八天内发货的所有订单:
在下面的示例中,布尔值查询表达式用作内部的筛选条件 筛选的实体类 ,查找订购数量大于30的项目的所有订单:
关于实体类变换器的一个注记 以下各节中描述的操作,即 筛选的实体类 , 排序实体类 , 示例实体类 , 扩展实体类 , 聚合实体类 和 组合实体类 ,都可以称为实体类转换器。 它们都接受一个实体类(如果是 组合实体类 )并返回一个新的实体类。 重要的是要认识到,这些结构完全是象征性的和惰性的。 当其中一个应用于实体类参数时,不会执行任何实际工作。 结果查询保持符号化,需要调用其中一个解析器函数( 实体值 或 实体列表 )以实际执行查询。 下面是一个查询示例,其中包含多个相互嵌套的变压器。 这将根据他们过去的付款选择五位付款最高的客户,并按付款总额递减的顺序列出:
求值时,符号Entity框架查询对自身求值,保持为惰性符号表达式。 但是,仍然可以使用它做一些有用的事情,例如,查找此查询表示的实体类的所有实体属性(这不需要调用数据库)。
实体框架查询的符号性和惰性为 程序化查询构造 ,因为查询本身是惰性的Wolfram语言表达式,可以手动或通过编程从较小的构建块构造。 使用FilteredEntityClass进行过滤 最常用的操作之一是根据特定标准筛选数据库数据。在实体框架中, 筛选的实体类 用于此目的,使用 实体函数 并传递给 筛选的实体类 作为第二个论点。 以下列出了除以下职务以外的所有员工的名字、姓氏和职务 “销售代表” :
以下查询选择电子邮件名称短(长度<=5个字符)的所有员工:
以下查询查找名称以以下之一开头的所有员工 “M” , “P” , “D” :
使用SortedEntityClass排序 根据某些标准对实体(数据库表中的行)进行排序是另一种常用的操作。 在数据库方面,这是通过使用SQL的ORDERBY子句来实现的。 在实体框架方面, 排序实体类 可以用来实现这一点。 在最简单的情况下,需要根据单个现有实体属性(数据库列)的值按升序排序。 在这种情况下,字符串字段名传递给 排序实体类 作为第二个论点。
如果需要结果的降序,可以使用 fieldName->“降序” 语法,如下所示。
以下查询列出了所有员工的员工编号、名字和姓氏,并按名字的长度以升序排序:
可以按多个属性排序。 在这种情况下,第二属性的值被用作具有第一属性的相同值的项目组的平局决胜器,并且相同的逻辑扩展到第三等排序属性。 可以将 “升序” 或 “降序” 分别为每个排序属性指定限定符,以控制实体(子)组中的排序顺序。 以下查询按产品线排序产品,然后按库存数量降序排序:
以下是前面的查询之一,列出了按名字长度排序的员工信息,其中结果现在仅限于前七条记录:
使用SampledEntityClass进行子集设置 在某些情况下,只从一个实体类中选取一定数量的实体(或者,从数据库术语中,只从表或虚拟表中选取行的特定子集)很有用。 在实体框架中执行此操作的方法是使用 示例实体类 .
注意,只有在以下情况下才能保证结果的顺序 示例实体类 应用于 排序实体类 然而,即使在其他情况下,它仍然非常有用,例如,当人们想查看大型数据集或使用小样本创建查询原型时。
在这种情况下,顺序是由数据集在子集之前已经排序这一事实来保证的。 可以更经济地获得相同的结果 排序实体类 有三个参数:
使用ExtendedEntityClass引入新属性 可以将新的计算属性添加到给定类型/实体类的可用实体属性集。 结果是一个新的实体类。 新添加的属性可以在原始属性可以使用的任何位置使用 — 在里面 实体值 ,进一步在查询的外层等。创建这样一个新的扩展实体类的构造是 扩展实体类 . 下面的示例演示了一个简单的用例,其中向实体类添加了一个新属性。
以下使用关系(更详细地描述 在这里 ,属性 “员工报告到” )添加经理的名字和姓氏,对于有经理的员工:
新属性可以使用各种构造,包括条件逻辑等。此功能允许进行非平凡的计算。 以下查询通过将当前信用限额>=100000美元的客户的限额增加15000美元,为客户添加了一个新的调整后的信用限额:
使用AggregatedEntityClass进行聚合 聚合是一种非常常见的操作。 它用于计算在多个实体(数据库中的表行)上聚合的值。 聚合的典型示例包括计算一系列实体上某些属性或更复杂表达式的总计、平均值或最小值和最大值。 在Entity框架中,可用于执行聚合的构造是 聚合实体类 . 正如其名称所示,其应用程序的结果是一个新的实体类。 如果在整个原始实体类上执行聚合,则生成的实体类将仅包含一个实体,其属性为计算的聚合属性。 还可以首先根据实体的特定属性集的值对实体进行分组,然后对每个这样的组执行聚合 — 在这种情况下,新实体类将包含与此类组一样多的此类新聚合实体。 目前,实体框架为数据库支持的实体存储支持的核心聚合函数集有限且很小: 总计 , 长度 , 分钟 , 马克斯 , 平均值 , 方差 和 标准偏差 。但是,可以使用标准算术函数和其他类似属性的查询构建块来组合这些核心操作并计算更复杂的表达式。 值得注意的是 实体函数 对于聚合的情况,它绑定到要聚合的整个实体类,而不是该类的单个实体。
理解的语义 实体函数 在这种情况下,使用Wolfram语言在顶层计算相同的内容很有用。 此代码使用Wolfram语言计算相同的数量进行聚合,其中 功能 用于使类比最为明显:
可以使用更简单的语法,即使用与所聚合属性名称相同的名称作为所生成聚合属性的名称。 这适用于一种简单的情况,即在每种情况下都有一个属性被聚合,但一个属性可以有多个聚合:
还有一种更简洁的形式适用于存在单个聚合的情况(但是,返回标量而不是列表):
最后一个示例的一个重要备注是,对于当前版本的Entity框架 实体值 在顶级查询中用于聚合时,将在Wolfram语言端而不是数据库端执行聚合(在子查询中使用此语法时不会发生这种情况)。 相反,所有聚合都显式使用 聚合实体类 将始终在数据库端执行。 下面的查询定义了一个具有三个聚合属性的聚合实体类:所有订单的最大、最小和平均订购数量(即 “订单详细信息” 表提供):
可以计算特定实体组的聚合,而不是整个实体类。 在这种情况下 聚合实体类 必须使用,以指示其值将定义用于聚合的唯一实体组的属性或属性列表。
可以通过使用一组属性而不是单个属性对实体进行分组。 以下查询计算来自同一城市和国家的客户总数,按客户编号降序排序:
在这种情况下,对具有相同城市和国家组合的实体组进行聚合。 以下查询计算所有已下订单的总支付金额(在本例中,语义上是 o个 [ “每个价格” ] 和 o个 [ “订购数量” ] 表示数据库中的列。 它们的乘法是允许的,因为它们属于同一个表(因此具有相同的长度),并且必须理解为矢量化的元素操作。 这个乘法运算的结果在语义上是另一列,它被传递给 总计 ):
以下查询以两种不同的方式计算每个订购项的平均数量:通过显式计算和使用内置函数( 平均值 ):
将实体类与CombinedEntityClass组合 在关系数据库中,数据通常存储在多个表中,通过外键约束相互连接。 只有在极少数情况下,对于人们通常想要编写的实际查询,人们只使用一个表,并且考虑到数据经过规范化后通常被拆分并存储在(可能很大)数量的相关表中。 在SQL查询中使用多个表中的数据的最常见方法是使用SQLJOIN操作。 SQL JOIN的实体框架对应物是 组合实体类 构造。 它需要两个实体类/类型,说明如何组合它们的规范,以及可选的JOIN类型。 结果是一个新的实体类,新的实体包含两个原始实体类的属性。 以下查询组合了实体类 “员工” 和 “办公室” ,通过 “办公代码” 属性,然后提取属性 “员工编号” , “名字” , “姓氏” , “城市” 和 “国家” ,其中前三个属于类型 “员工” 后两种类型 “办公室” :
如果查看新类的完整属性列表,这可能会变得更加明显(实际查看它们的 输入表单 ):
应用生成的新实体类 组合实体类 可以在更复杂的查询中进一步使用,就像任何其他实体类一样。 以下查询查找在法国或英国工作的所有员工,并列出他们的员工编号、名字和姓氏以及国家:
对于信用限额大于120000美元的客户,以下返回付款信息,包括客户编号、姓名、支付金额和付款日期(其中需要Wolfram Language后处理步骤,因为日期存储在数据库端的Unix时间中):
注意,在上一个示例中,有必要显式地指出 实体属性 [ " 客户 " , " 客户编号 " ] 在中的属性列表中 实体值 ,因为这两种类型 “客户” 和 “付款” 拥有具有此名称的属性,并且必须准确区分请求的属性。 对于来自法国的客户,以下显示客户名称及其销售代表的姓名:
将一个类型与其本身结合起来是可能的,而且通常也是可取的。 在关系数据库操作的上下文中,这种情况对应于自联接。 在这种情况下,需要更加小心地消除属性名称的歧义 — 必须为组合的至少一个相同类型引入(字符串)别名。 下面的示例说明了这种情况。 以下查询列出了员工的名字和姓氏,以及他们的经理姓名。 这个 “经理” 第二个的别名 “员工” 类型,然后用于消除实体属性的歧义:
在某些情况下,测试用于确定是否应将来自两个被合并实体类的两个实体的组合包含在结果实体类中的条件比两个实体某些(组合)属性之间的简单等式更复杂。 对于这种情况,可以使用 实体函数 使用两个参数,每个参数绑定到要组合的相应实体类的实体。 这样一个 实体函数 必须返回布尔值,否则可以是任意复杂(可编译)的表达式。 下面是一个非常有趣的示例,因为它说明了 组合实体类 用法。 此查询代表了查找拥有多个销售办事处的所有国家/地区的一种方法。 想法是将 “办公室” 类型及其本身,并作为的条件 组合实体类 使用谓词检查合并的两个实体的国家是否相同,而办公代码是否不同。 二者的任何此类组合 “办公室” 类型实体确实对应于一个拥有多个办事处的国家。 因此,只需提取合并实体的国家名称并删除可能的重复项:
在最后一个示例中,还需要在组合的 “办公室” 类型,以消除属性的歧义,就像前面的自连接示例中一样。 最后, 删除重复项 用作的第三个参数 实体值 要从结果中删除重复项(如前所述,重要的是要记住,在这种情况下 实体值 当前在Wolfram语言端执行,而不是在数据库上执行)。 子查询 子查询是在更复杂的查询中用作构建块的查询。 当子查询的结果是标量时,它们采用最简单的形式。 在本节中,将使用几个子查询示例来说明它们的一般用途。 作为一个起点,考虑从某个实体类获取单个聚合属性的常见任务。 这还不是子查询。 以下示例说明了实现此目的的各种方法。
然而,值得注意的是,虽然结果相同,但第一个(较短的)版本实际上在Wolfram语言中执行聚合,而最后两个版本在数据库端聚合。 然而,当此类查询用作较大查询中的子查询时,在这种情况下,它们总是在数据库端执行,包括 实体值 . 以下查询使用上一个查询作为子查询。 从价格范围来看,它选择了所有MSRP价格在最昂贵产品中排名前10%的产品:
与上一示例中的子查询类似的子查询称为不相关查询,因为它们不引用查询外层的实体(表行)。 还可以构建所谓的关联子查询,其中确实包含这样的引用(因此,如果不进行修改,就无法单独执行)。 以下示例说明了构造涉及更复杂和/或相关子查询的查询通常要经历的步骤。 以下查询计算办公室代码为的办公室的员工总数 "4" :
也可以对办公室实体进行相同的计算,其中硬编码 "4" 已从条件中删除,现在有嵌套 实体函数 表达式,内部表达式引用外部表达式的变量:
现在很容易理解以下代码,它计算每个办公室的员工人数,并根据员工人数按降序对办公室进行排序:
最后一个示例是相关子查询的示例。 相关性通过内部 实体函数 的主体引用外部 实体函数 的变量。 请注意,在许多情况下,用联接替换相关子查询很简单。
子查询是一个强大的工具,但它们很容易被滥用。 根据具体情况,它们可能是也可能不是解决给定问题的最佳方法。 深度相关的子查询可能会导致性能低下,因此应该谨慎使用此强大的工具。 成员Q 这个 成员Q 实体框架查询中使用数据库支持实体存储上下文中的谓词来公开SQL 英寸 操作员。 在最简单的形式中,它用于测试显式值列表中值的成员身份。
也可以使用 成员Q 与子查询结合使用,在这种情况下 成员Q 不是文字列表,而是子查询的结果。 在这种情况下,子查询应该返回列而不是标量,通常可以写为 班 [ “属性” ] 或者,同等地, 实体值 [ 班 , “属性” ] ,其中 班 是某个实体类(由内部查询注册或定义)。
删除重复项和SQL DISTINCT 在某些情况下,只需保留那些不同的选定值或值组。 在SQL中 与众不同 关键字用于此目的。 在实体框架中,可以使用 删除重复项 作为第三个论点 实体值 以达到类似的效果。
如前所述,在前面的示例中, 删除重复项 当前在Wolfram语言端执行。 但是,当将上述查询用作子查询时,将在数据库端删除重复项。 一个稍微不同的用例是当一个人需要在不同的值上进行聚合时。 在这种情况下,还可以使用 删除重复项 里面 实体函数 实现这一目标。 下面计算不同精确价格的数量以及四舍五入价格,以及平均不同价格和平均四舍五进价格(忽略价格乘数):
关系 在本教程中,由实体框架为相关实体类型(数据库表)生成的实体值和实体类值实体属性称为关系。 除了与现有表列对应的属性外,Entity框架还为与其他表相关的表创建了新属性。 它们构成了一种机制,提供了一种更高级(w.r.t.显式联接或子查询)的方法,以在查询中使用来自多个相关实体类型(数据库表)的数据。 重要的是要认识到,没有什么是可以用关系完成的,而核心原语则无法完成。 然而,在许多情况下使用关系可以导致更简洁的查询,这需要更少的构造和理解工作。
在此示例中,有四个属性与现有数据库列不对应: “办公室” , “客户” , “员工-报告To” 和 “员工反向” .
相关财产分别对应于该员工服务的客户、该员工工作的办公室、该员工的经理以及该员工担任经理的所有员工:
以下是一个更有趣的查询,它返回在同一办公室工作的所有员工,并与给定员工向同一经理报告:
可以使用关系来经济地表示查询。 以下示例对此进行了说明。
如果在查询中进一步需要该属性,可以使用 扩展实体类 使用该属性扩展给定实体类:
关系的一个重要特征是人们可以多次遵循它们,特别是对于实体价值关系。 以下查询通过向每个订单添加两个新属性来演示关系的使用:下此订单的客户的姓名和为该客户提供服务的员工的全名。 请注意如何遵循关系来提取属于相关类型的数据(数据库表):
在需要实体类的任何地方,都可以在查询中使用实体类值关系。 以下示例计算每个员工的所有客户总数以及具有高信贷限额(>50000美元)的客户总数,并将这些值添加为新属性。 在这种情况下, e(电子) [ “客户” ] 是实体类值关系,可用于 筛选的实体类 .
关于如何使用关系来构建更复杂的查询的更多示例,请参阅 最后一段 本教程的。 介绍 本节的目的是对框架为各种受支持的构造通常生成的SQL代码的类型提供一个基本概念。 此处提供的生成的SQL主要用于演示。 虽然本节中提供的许多SQL查询实际上与当前版本的Entity framework查询编译器(用于SQLite后端)生成的SQL代码相对应,但不应假定生成的SQL总是采用这种形式。它可能会因版本而异。 从实体框架的观点来看,只要最终结果正确且具有合理的效率,生成的SQL的确切形式就是内部实现细节。 因此,不应以任何方式依赖本节中显示的生成的SQL代码的详细信息。 EntityValue和EntityList调用
在使用基于 实体函数 在里面 实体值 ,它们在SQL端以自动生成的属性名称作为别名。
选择REGEXP(state,'^C',0)作为synthetic_prop_9 来自办公室
生成的属性如 “合成_提升_9” 对用户不可见,因为 实体值 当属性基于 实体函数 请求。 对于以下情况 实体列表 ,数据库调用实际提取计算 规范名称 对于给定类型的实体(对应数据库表的主键)。
计算属性和EntityFunction表达式 计算属性使用定义 实体函数 。这些通常会编译成SQL表达式。 这里有几个例子。
orderdetails.price每个*订单详细信息.quantity已订购
REGEXP(offices.city,'a',0)或REGEXP(officescity,'o',0
在简单的情况下,算术运算会编译成类似的SQL级别的算术运算。 例如,对于 “产品” 类型:
(products.MSRP-products.buyPrice)/products。 建议零售价
然而,在某些情况下,生成的SQL代码可能涉及类型转换/强制。
可以编译成类似这样的内容(其中执行到reals的转换以保留此处涉及的Wolfram Language操作在数据库端的语义):
功率(CAST((功率(CAST(products.MSRP AS REAL),2)-功率
orderdetails.quantityOrdered>30和orderdeails.priceEach>=200
orderdetails.orderNumber%100=0
总和(orderdetails.quantityOrdered)
sum(orderdetails.quantityOrdered*orderdeails.priceEach)/CAST(count(orderdetails.orderLineNumber)AS REAL)总计(订单详情.quantity有序*订单详情.priceEach)
案例 WHEN(1>长度(employees.firstName))THEN NULL WHEN 1 THEN substr(employees.firstName,1,1) 结束IN('M','P','D')
核心查询生成基元 筛选的实体类 此构造通常对应于SQL 哪里 子句,带有第二个参数( 实体函数 表达式)对应于 哪里 子句SQL表达式。
选择employees.firstName、employeres.lastName、empleoyees.jobTitle 来自员工 WHERE employees.jobTitle!=' 销售代表
如果可以,查询编译器会尝试优化查询。 特别是,连续应用几个条件通常不会导致 选择 在生成的SQL代码中。 例如,以下查询使用三个嵌套的 筛选的实体类 旨在寻找来自美国、加利福尼亚州、位于旧金山、洛杉矶或圣何塞等城市之一的所有客户:
选择 customers.customer编号, customers.customerName, customers.creditLimit, 客户.城市 来自客户 WHERE customers.country='USA'AND customers.state='CA'AND customers.city IN('San Francisco','Los Angeles','San Jose')
排序实体类
选择employees.employeeNumber、employers.lastName、emplyees.officeCode 来自员工 按员工订购.officeCode DESC
SQL和Entity框架排序工具之间的一个显著区别是,并非所有SQL后端都直接支持 订购依据 子句,while for 排序实体类 排序所涉及的属性可以是简单的实体属性,也可以是 实体函数 表达。
选择“T308”。 “员工编号”,“T308”。 “firstName”,“T308”。 “姓氏” 发件人( 选择 “员工_T306”。 “employeeNumber”AS“员工编号”, “员工_T306”。 “firstName”作为“firstName”, “员工_T306”。 “lastName”AS“lastName”, 长度(“employees_T306”.“firstName”)AS synthetic_prop_17 从员工到“员工_T306” )AS“T308” 按“T308”订购。synthetic_prop_17
示例实体类 此构造最直接的SQL对应项是 极限 和 抵消 SQL关键字。 然而,细分的实际策略和内部实施可能不同,因为在某些情况下,其他策略可能更有效地获得相同的结果。
选择 “payments_T316”。 “customerNumber”AS“customerNumber”, “payments_T316”.金额AS金额 从付款中“payments_T316” 限制10偏移10
扩展实体类 在SQL端,此构造对应于使用SQL表达式定义 选择 列表中,只要需要计算比数据库表或查询中的原始字段更复杂的属性。
选择 “员工_T328”。 “employeeNumber”AS“员工编号” (“employees_T328”.“firstName”||'')||“emplyees_T32.8”。 “lastName”AS“全名” 来自员工AS“employees_T328”
或者,它们可能很复杂,并且包含相关子查询,例如,对于以下查询,使用 关系 :
其中,在生成的SQL中 选择 列表由(相关的)标量子查询表示:
选择 “员工_T352”。 “employeeNumber”AS“员工编号”, “员工_T352”。 “firstName”作为“firstName”, “员工_T352”。 “lastName”AS“lastName”, ( 选择“employees_T355”。 “firstName”AS“firstName_1” 来自员工AS“employees_T355” 其中“employees_T355”。 “employeeNumber”=“employees_T352”。 “报告收件人” )AS“经理名字”, ( 选择“employees_T358”。 “lastName”AS“lastName_1” 来自员工AS“employees_T358” 其中“employees_T358”。 “employeeNumber”=“emplyees_T352”。 “报告收件人” )AS“经理姓氏” 来自员工AS“employees_T352”
聚合实体类 在没有第三个参数的情况下使用时, 聚合实体类 表示对整个第一个参数(实体类)执行的聚合,并对应于SQL聚合查询。 在这种情况下,通常没有与 聚合实体类 [ ... ] ,但在 选择 列表必须全部使用SQL聚合函数。
选择 max(“orderdetails_T403”.“quantityOrdered”)AS“maxOrdered“, min(“orderdetails_T403”.“quantityOrdered”)AS“minOrdered“, avg(“orderdetails_T403”.“quantityOrdered”)AS“avgOrdered 来自orderdetails AS“orderdeails_T403”
当第三个参数 聚合实体类 使用,这对应于SQL 分组依据 条款。
选择 “customers_T434”.城市AS城市, “customers_T434”.国家AS国家, count(“customers_T434”.“customerNumber”)AS“customer count” 来自客户AS“customers_T434” 按“customers_T434”.city,“customers _T434“.country分组
需要注意的是,对于可能需要在 聚合实体类 ,通常生成的SQL会有一个额外的层 选择 .
选择 “T497”。城市, “T497”国家, “T497”。 “客户计数” 发件人( 选择 “customers_T495”.城市AS城市, “customers_T495”.国家AS国家, count(“customers_T495”.“customerNumber”)AS“customer count” 来自客户AS“customers_T495” 按“customers_T495”.city,“customers _T495“.country分组 )AS“T497” 按“T497”排序。 “customerCount”DESC(客户计数)
组合实体类 下面是这种类型的简单查询的一个示例,它组合了类型 “员工” 和 “办公室” :
选择 “员工_T529”。 “employeeNumber”AS“员工编号”, “员工_T529”。 “firstName”作为“firstName”, “员工_T529”。 “lastName”AS“lastName”, “T534”。城市, “T534”。国家 从员工到“员工_T529” 加入( 选择 “offices_T532”。城市AS城市, “offices_T532”.国家AS国家, “办公室_T532”。 “officeCode”AS“officeCode” 来自办公室AS“offices_T532” )AS“T534” 打开“employees_T529”。 “officeCode”=“T534”。 “办公代码”
下面是一个考虑过的早期查询示例,它使用带有更复杂连接条件的self-join来查找所有国家/地区至少有两个不同的办事处:
它将转换为类似于以下内容的SQL(请注意,当前 删除重复项 在顶层 实体值 在Wolfram语言端执行,因此没有 与众不同 SQL查询中的关键字):
选择“offices_T571”。country AS country 来自办公室AS“offices_T571” 加入( 选择 “offices_T574”。country AS country_1, “办公室_T574”。 “officeCode”AS“officeCode” FROM办公室AS“办公室_T574” )AS“T576” 打开“offices_T571”。country=“T576”.country_1和“offices.T571”。 “officeCode”!= “T576”。 “办公代码”
子查询 在本教程中,子查询通常表示较大查询的一部分,可以使用 实体值 。在SQL方面,大多数情况下这对应于内部 选择 带有单个字段的语句,通常返回标量(因为只有一行或因为正在执行聚合),但有时也返回列(在 英寸 条款,对应于 成员Q 实体框架方面)。 以下示例(在前面关于子查询和返回昂贵产品的部分中已经考虑过)表示一个简单的、不相关的子查询:
选择 “products_T583”。 “productName”AS“product_Name”, “products_T583”。 “MSRP”作为“MSRP“ 来自产品AS“products_T583” 其中“products_T583”。 “建议零售价”>=0.9*( 选择最大值(“products_T586”.“MSRP”) 来自产品AS“products_T586” )
以下更复杂的前一个查询版本扩展了每个产品的窗口中当前产品15美元内的产品数量,以建议零售价计算:
其中子查询位于 实体函数 定义 “closelyPricedProductsCount” 扩展属性是一个相关子查询,因为产品的筛选现在取决于当前产品的价格,必须从该筛选/聚合子查询中引用当前产品。 这将导致一个包含相关子查询的查询,其中相关子查询位于 选择 内部查询的列表,别名为 “closelyPricedProductsCount” ,成为新的扩展属性:
选择 “T637”。 “产品名称”, “T637”。 “MSRP”, “T637”。 “closelyPricedProductsCount” 发件人( 选择 ( 选择“T640”。计数 发件人( 选择计数(“products_T638”.“productCode”)作为计数 来自产品AS“products_T638” WHERE abs(“products_T638”.“MSRP”-“producss_T635”.“MSRP”)<=15 )AS“T640” )AS“closelyPricedProductsCount”, “products_T635”。 “MSRP”作为“MSRP“, “products_T635”。 “productName”AS“productName” 来自产品AS“products_T635” )AS“T637” 按“T637”订购。 “closelyPricedProductsCount”DESC,“T637”。 “MSRP”DESC
目前没有自动优化生成的SQL代码,这些代码涉及实体框架查询编译器执行的子查询(例如尝试将其转换为JOIN等)。 人们应该知道在Entity框架中使用相关子查询的性能影响,这与SQL相似。 关系 关系提供了一种高级方法,可以在与给定实体类/类型(相关数据库表)相关的实体类/类中查找属性,而无需执行显式联接。 它们的内部实现可以利用不同的工具来实现这一目标,例如子查询和/或联接,但这些都是对用户隐藏的。 有关可以从使用关系的查询生成的SQL类型的示例,请考虑以下查询,该查询计算每个办公室中单个员工处理的最大客户数(本教程最后一节也将考虑此问题):
对于当前版本的查询编译器,为此查询生成的SQL可能如下所示(诚然,不太容易阅读):
选择 “offices_T652”。 “officeCode”AS“officeCode”, ( 选择“T662”。synthetic_prop_20 发件人( 选择最大值(“T657”.“customerCount”)AS synthetic_prop_20 发件人( 选择 ( 选择“T660”。synthetic_prop_21 发件人( 选择计数(“customer_T658”.“customerNumber”)AS synthetic_prop_21 来自客户AS“customers_T658” 其中“employees_T655”。 “employeeNumber”=“customers_T658”。 “销售代表员工编号” )AS“T660” )AS“customerCount” 来自员工AS“employees_T655” 其中“offices_T652”。 “officeCode”=“employees_T655”。 “办公代码” )AS“T657” )AS“T662” )AS“maxEmployeeCustomerCount” 来自办公室AS“offices_T652”
由于优化或内部实现的变化,使用关系为查询实际生成的SQL在未来可能会发生变化。 介绍 实体框架查询的符号特性允许以编程方式生成此类查询。 此功能有许多用例。 其中一个用例是在多个位置重用查询的某些部分。 它可以位于多个不同的查询中,也可以位于同一个查询中。 需要记住的一点是 实体函数 是 全部保留 ,因此,如果需要使用存储在变量中的查询的某些部分,请在 实体函数 ,必须使用 使用 (或类似的构造)将该查询部分注入 实体函数 . 在不同查询中重用查询部件 在实践中,经常需要在几个较大的查询中重用惰性查询块。 由于查询是惰性的,所以可以很容易地做到这一点。特别是,可以将查询的较小部分存储在变量中,并在较大的查询中使用这些变量。
这些可以单独使用,例如,用于查找五个支付最高的客户:
但是,它还可以用于获取特定员工服务的所有客户的总付款:
在同一查询中重用查询部件 在某些情况下,可能需要在同一个查询和同一个较大的查询中多次重用同一查询。 在SQL中,WITH关键字用于实现这一点。 虽然实体框架将来可能会获得此构造的本机支持,但可以很容易地进行模拟。 考虑以下查询,该查询通过汇总所有订单来计算每个特定产品的订购总次数:
但它也可以用于查找订购最多的所有项目,在这种情况下,它将在查询中出现两次。 注意,在这种情况下, 使用 使用,因为 总计订单详细信息 应该注射到 实体函数 :
编程查询生成 可以使用标准的Wolfram语言技术以编程方式生成查询表达式。 下面的示例通过构建计算各种聚合数量的聚合查询来说明这一点。
这个示例可能有些人为,但它说明了主要思想,即可以使用标准的Wolfram语言函数和习惯用法来自动化查询构造过程。 符号查询转换 对查询及其符号性质的编程访问对于某些更高级的场景也很有用,例如非平凡的查询转换或生成。 例如,考虑常用查询构建块的运算符窗体。 目前,由于各种原因,对于核心实体框架查询构建块,还没有直接的支持。 然而,用户通常更喜欢这种查询编写方式,在某些情况下,它可以通过减少查询中的嵌套量使查询更具可读性。 首先可以定义一组新的符号,这些符号将用作实体框架查询原语的代理,但将支持运算符形式:
以下示例查询是以操作员风格编写的,并执行以下操作:选择五个付款最多的客户,并返回他们的客户编号、客户名称和支付的总金额,按总金额递减的顺序排序:
当然,查询的符号特性和用于符号表达式操作的出色Wolfram语言功能为我们提供了更多的可能性; 这只是一个这样的例子。 介绍 本节简要介绍了查询构造的一些实用技术,这些技术在工具箱中可能很有用。 构建复杂查询的一个障碍是,该过程可能看起来和感觉有点类似于用编译语言编写代码,在这种情况下,很难测试中间结果或代码片段,必须想出一些完整的函数或程序来编译和测试它。 本节旨在说明实体框架查询并非如此,通过适当的技术,可以使查询构建过程与Wolfram语言中的大多数其他活动一样具有交互性。 增量生成查询 用于说明本节所述技术的查询应执行以下操作:选择五个支付最高的客户,并按总金额递减的顺序返回他们的客户编号、客户名称和支付的总金额。 这里显示了几个步骤,它们从一个非常基本的查询开始,并在中间结果的指导下逐步构建感兴趣的查询。 首先可以查看 “付款” 表/实体类型。 但出于原型设计的目的,对 “付款” 类型:
可以看到,对于给定的客户,通常会有多笔付款。 要计算每个客户支付的总金额,需要将这些值加总,按客户编号分组:
由于还需要客户名称,因此获得客户名称的一种方法是使用 组合实体类 ,如下所示:
请注意,在最后一个查询中,在属性列表中使用完整的 实体属性 [ “客户” , “客户编号” ] ,而不仅仅是 “客户编号” ,自 组合实体类 [ ... ] 现在包含两个 “客户编号” 属性,并且有必要消除真正请求哪个属性的歧义。
最后一步是更换目前使用的样品( 示例付款 )带着满满的 “付款” 类型:
此示例说明了如何逐步构建查询,一次一步,每次都以在前一步上构建的查询为起点,并检查中间结果。 使用单个实体创建复杂嵌套查询的原型 在实际为整个实体类型构造查询之前,使用给定实体类型的单个实体对查询进行原型化通常很有用。 本节使用一个具体示例说明了此过程。 感兴趣的查询是选择至少有两名员工有客户的所有办公室。
在构建实际的查询之前,请使用顶级查询获取所能获得的信息。 出发点是看看哪些员工在这个办公室工作:
注意,这是一种效率很低的方法,只有在原型阶段才有意义,因为它会导致执行大量查询。 下一步是构造一个查询,计算每个员工的客户数量。 作为第一步,可以对特定的员工编号进行硬编码。 例如,从前面可以看出,编号为1165的员工应该有六个客户。
下一步是删除硬编码的员工编号,可以按以下方式执行:
其中,在最后一个查询中 实体函数 引入了,这允许将特定的员工实体传递到查询中,而不是在查询中对其编号进行硬编码。 现在可以测试结果 实体函数 对某个特定办公室的所有员工进行评估,根据上述顶层分析得出的结果 实体列表 :
下一步可以按照相同的逻辑进行,但现在是针对办公实体。
以前构造的查询已被用作内部构建块 o个 [ “员工” ] 已使用,就像以前在 实体列表 . 现在一切都准备好了,可以构建最初的兴趣查询了。 它可能如下所示:
请注意,如何使用单个实体,可以增量地构建嵌套的复杂查询,确保在每个级别都能正常工作。 从技术上讲,这个特定查询包含一个双重嵌套的子查询,其中一个级别是相关的。 这种逻辑也反过来工作:给定一个功能不正常且包含复杂内部结构(涉及嵌套 实体函数 表达式等),可以将其分解为多个部分,并在单个实体上测试查询的内部部分,以快速定位并修复有问题的位置。 查询执行期间可能发生的各种错误分为两大类:软错误和硬错误。 软错误 软错误是指导致正常查询求值的错误,但不会返回使用完全正确的查询生成的结果。 软错误的一个例子是,试图从类型中提取不存在的属性值:
在这种情况下, 缺少 [ “未知属性” , … ] 返回不存在属性的值。
硬错误 在本教程中,硬错误是指返回 失败 物体。 因此,用户级错误处理可能相当于检查 实体值 的返回值 失败 物体。 EntityFunction中的属性无效
EntityFunction中的不可编译表达式 由于存在全局变量,以下查询失败 年 在没有值且无法编译为SQL的查询中:
这里也发生了同样的情况,但这次是因为数据库端 贝塞尔J 当前不支持计算:
EntityFunction中表达式的类型不兼容 硬错误的另一个常见来源是在中使用了错误类型的表达式 实体函数 .
在以下情况下,试图将整数和字符串相加会导致类型错误:
通常,在这种情况下,错误消息在识别错误原因方面信息量很大。 查询中存在不支持的类型的值 关系数据库上下文中的实体框架目前不支持某些类型的Wolfram语言表达式。 以下查询失败,因为的值 10^100 太大,无法在数据库查询中使用:
然而,以下内容并未失败,因为在这里,数字已明确转换为机器精度双精度:
数据库后端不直接支持复数,因此以下内容也会导致错误:
EntityFunction中的返回类型不兼容 一些操作需要 实体函数 作为参数,需要特定的返回类型。 例如,在中使用时 筛选的实体类 或 组合实体类 , 实体函数 应具有布尔返回类型。 在以下示例中,筛选谓词的返回类型 实体函数 是整数,而布尔类型是必需的:
尝试从EntityFunction返回非标量
聚合中关系查找的使用不当 通过在同一聚合操作中组合不同表中的列,可以使用关系构造无效的聚合查询。 这样的查询无法编译。 以下查询失败,因为减法实际上是用不同表的不同列进行的,这不是有效的查询:
未在AggregatedEntityClass的EntityFunction中使用聚合函数 用于计算中聚合属性的表达式 实体函数 对于聚合,必须使用其中一个聚合函数。 从技术上讲,可以构造不需要编译的查询。 以下查询失败,因为 实体函数 这里本质上是一种“列”类型(值列表),而不是标量 — 这看起来类似于已经讨论过的另一个错误,但这里是在聚合的上下文中:
以下操作不会失败,因为它包含一个聚合函数( 总计 在这种情况下),从而使身体 实体函数 是标量:
操作错误 操作错误是发生在数据库端的错误。 虽然实体框架努力尽早拦截查询中的大多数语法错误、类型相关错误和其他错误,但在某些情况下,这要么尚未完成,要么可能不容易完成。 下面的示例尝试扩展类型 “办公室” 具有一个实际上是实体类的新属性。 数据库支持的实体目前不支持此类操作,这种情况下的错误发生在数据库端:
最有趣的现实世界问题需要以非平凡的方式将几个查询构建原语组合在一起的查询。 本节通过一些更有趣的示例说明了如何实现这一点。 示例:按客户支付的总金额排序的客户 以下查询扩展了 “客户” 具有属性的类型 “支付总额” 它给出了该客户支付的总金额,然后进行排序 “支付总额” 按降序排列的值。
同样可以使用 组合实体类 和带分组的聚合(注意这里必须添加 “客户” 在聚合到用于分组的属性列表后,希望可用的类型。 即使只有一个值 “客户名称” 对应于的特定值 “客户编号” ,除非我们明确地告诉它,否则查询不会知道这一点)。
示例:每个办事处处理的客户总数 处理这类问题的一种方法是使用 组合实体类 将这些类型组合在一起,然后对组合的类型进行聚合,对某些属性进行分组。 以下查询计算每个办公室中所有员工服务的客户总数。 它通过结合三种类型来实现这一点: “办公室” , “员工” 和 “客户” ,然后对办公代码值执行聚合:
请注意,在最后一个查询中,使用较长的表单很重要 实体属性 [ “办公室” , “办公代码” ] 而不是 “办公代码” 消除财产的歧义,因为 组合实体类 [ “办公室” , “员工” , “办公代码” ] 包含两个短名称属性 “officeCode”(办公室代码) ": 实体属性 [ “办公室” , “办公代码” ] 和 实体属性 [ “员工” , “办公代码” ] (在这种情况下,可以使用任何一种)。 下面是前面查询的更复杂版本,它计算每个办公室服务的客户总数 和 居住在该办事处所在国。 在这种情况下,必须使用 实体属性 对于 “国家” 属性以消除属性的歧义,因为这两种类型 “办公室” 和 “客户” 拥有财产 “国家” :
示例:每个办公室每个员工的最大客户数 以下查询为每个办公室计算该办公室中单个员工处理的最大客户数量。 它严重依赖关系:
为了了解这里的工作关系为用户做了多少工作,下面是当前版本的查询编译器为此查询生成的SQL:
选择 “offices_T652”。 “officeCode”AS“officeCode”, ( 选择“T662”。synthetic_prop_20 发件人( 选择最大值(“T657”.“customerCount”)AS synthetic_prop_20 发件人( 选择 ( 选择“T660”。synthetic_prop_21 发件人( 选择计数(“customers_T658”.“customerNumber”)AS synthetic_prop_21 来自客户作为“客户_T658” 其中“employees_T655”。 “employeeNumber”=“customers_T658”。 “销售代表员工编号” )AS“T660” )AS“customerCount” 来自员工AS“employees_T655” 其中“offices_T652”。 “officeCode”=“employees_T655”。 “办公代码” )AS“T657” )AS“T662” )AS“maxEmployeeCustomerCount” 来自办公室AS“offices_T652”
示例:欠钱的客户 这是另一个展示关系力量的窗口。 任务是选择仍然欠款的客户 — 换言之,那些订单总额超过迄今为止总付款金额的人。 要计算要支付的总金额,可以从给定客户的所有订单开始,这些订单由关系给出 c(c) [ “订单” ] 并且是类型为的实体类 “订单” 。由于每个订单可能包含多个项目,并且每个项目可能订购多个数量,下一步是使用属性扩展此类 “totalToPay” ,它通过联系所有人来计算每个订单的支付金额 “订单详细信息” 与此订单相关的实体,并计算商品价格和订购数量的乘积的总和。 注意这里的关系 o个 [ “订单详细信息” ] 用于每个订单。 然后可以进行第二次聚合,这一次是对给定客户的所有订单进行聚合,以获得他们应该支付的总金额。 计算客户已经支付的总金额要简单得多,因为它需要一次关系查找 c(c) [ “付款” ] 引入小截点0.00001以避免舍入误差。
要在不使用关系的情况下获得相同的结果,需要做大量的工作。 以下查询是一种可能的方法,它使用相关的子查询和 组合实体类 .
这个 使用 在最后一个例子中用于可读性,而不是必需的; 可以使用单个大型查询。 但请注意 使用 与一起使用 := 初始化作用域变量,以便将它们注入main的主体 实体函数 没有评估。 示例:五大支付客户为每位员工支付的总金额 本例的目标是计算每位员工所服务的五位薪酬最高的客户应支付的总金额,并按总金额的降序对员工进行排序(可用于衡量给定员工的成功程度)。 以下查询正在使用关系。 它说明了如何方便地遵循关系和计算聚合(如 总计 [ c(c) [ “付款” ] [ “金额” ] ] (见下文)。 注意,在这种情况下,使用关系可以使用三个不同数据库表中的数据( “员工” , “客户” 和 “付款” )以简洁、经济的方式:
同样,在不使用关系的情况下获得相同的结果需要付出更多的努力:
在前面的示例中,正如前面的示例一样, 使用 主要用于可读性。