11行代码实现更好的SQL
Prela是一种在UCLA RePL开发的新查询语言。该语言与SQL有很大不同,但其关键思想非常简单。在这个简短的教程中,我们将在Python中构建一个Prela的玩具版本,以理解其核心原理。在本教程结束时,您将知道以下查询是如何工作的:movie.where(company.s(country).eq("[us]") & keyword.eq("character-name-in-title")).select(title & cast.s(person).s(alias).s(text))。您可能已经能够猜测它的作用:该查询查找每部由美国公司制作且标题中包含角色名字的电影,并输出每个演员的标题和别名。请注意,SQL中等效查询的代码超过20行。Prela的第一个特殊之处在于,它只有二元关系,即只有两列的表。一开始这可能听起来非常有限,但将一个多列宽表“二元化”是非常简单的。假设我们有一个电影表:ID title year 646 《教父》 1972 478 《七武士》 1954 583 《卡萨布兰卡》 1942。我们可以将这个3列的表分解为3个二元关系,每个关系将行号映射到列值:movie = Rel([( 646 , 0 ), ( 478 , 1 ), ( 583 , 2 )]) title = Rel([( 0 , "《教父}" ), ( 1 , "《七武士}" ), ( 2 , "《卡萨布兰卡}" )]) year = Rel([( 0 , 1972 ), ( 1 , 1954 ), ( 2 , 1942 )])小贴士:本教程使用snip将代码单元连接成一个类似于笔记本的环境,两个单元中的更改会反映在后续单元中。上面的movie、title和year关系分别表示原始表的ID、标题和年份列。请注意,标题和年份的行号首先出现,但movie(也不是称为ID)的行号第二出现。原因稍后会变得清晰。关注二元关系的动机在于它们概括了函数。函数之所以强大,是因为它们可以组合,因此成为程序的构建模块。一个函数将每个输入映射到唯一的输出,而关系可以将一个输入映射到多个不同的输出。从某种意义上说,关系可以被视为非确定性函数。这一切都非常抽象,所以让我们回到我们的例子。为了保持简单,我们将重点放在映射每个输入到确切一个输出的关系上,即它们恰好都是函数。“调用”关系实际上归结为将该关系转换为字典并查找值:print(dict(movie)[646], dict(title)[0], dict(year)[0])。我们现在可以引入Prela中的第一个也是最重要的操作符,即关系组合。函数组合通过先应用一个函数,再将另一个函数应用于输出。两个关系r和s的组合本身也是一个关系,首先将x与r映射以获取某些y,然后将y与s映射以获得最终的“输出”。这可以通过将s转换为字典d,迭代r中的(x,y)对,并在y在d中找到时输出(x,d[y])来实现:def select(r, s): d = dict(s) return [(x, d[y]) for x, y in r if y in d]。使用我们的例子,下面的查询将movie与title组合,以获取将每部电影ID映射到其标题的关系:print(movie.select(title))。尝试将title更改为year,看看您能得到什么。当我们将多个.select调用链接在一起时,组合的威力得到了真正的体现。假设我们添加了一个外键列,将每部电影映射到其制作公司,以及一个关于电影公司的另一张表:ID title year company ... ... ... 0 ... ... ... 1 ... ... ... 2 ID name country 0 派拉蒙 [us] 1 东宝 [jp] 2 华纳兄弟 [us]。以相同的方式分解将为我们提供四个更多的关系:company = Rel([(0, 0), (1, 1), (2, 2)]) id2row = Rel([(0, 0), (1, 1), (2, 2)]) name = Rel([(0, "派拉蒙"), (1, "东宝"), (2, "华纳兄弟")]) country = Rel([(0, "[us]"), (1, "[jp]"), (2, "[us]" )])。然后,我们可以通过一系列的.select调用,借助.s来找到一部电影制作公司的国家:print(movie.s(company).s(id2row).s(country))。因为通过外键连接几乎总是需要“解析”ID为行,Prela会自动插入这一步,因此可以写出以下内容,这和“电影的制作公司的国家”读起来很像!print(movie.s(company).s(country))。这也是在教程开始时片段的最后一行中发生的cast.s(person).s(alias).s(text)。到目前为止,每个查询都返回了一列值。要选择多个属性,我们引入了&操作符。&可以将r和s的第一个列匹配,而.select在r的第二列中与s的第一列匹配,然后将它们的第二列配对:def and_(r, s): d = dict(s) return [(x, (y, d[x])) for x, y in r if x in d]。因此,title & year将每部电影行映射到它的两个属性:print(title & year)。请注意,结果仍然是一个二元关系,&只是将值嵌套到一个元组中。这意味着我们可以继续组合。
本站免费、广告极少。如果觉得有帮助,可以请我们喝杯咖啡 —— 任何金额都对持续运营有实际帮助。
☕请我喝杯咖啡