我在C ++中使用SQL解析器从头开始编写一个非常简单的数据库。我做了一切。所以如果我有这样的SQL输入:
SELECT * FROM table WHERE `First Name`="John" AND `Last Name`="Doe"
解析器解析它。现在的问题是用这些信息做些什么。我需要查看我的表,并找到所有记录的名字是John和姓氏是Doe的记录。
我在想我可以实现一个将AND作为主节点的树,其中==
为其子节点。左边的孩子是姓,右边的孩子是姓。无论什么时候都是如此,将该记录推送到矢量中,然后在最后打印出矢量。
这在理论上听起来很棒,但我不知道如何实际实现这一点。如果我有一个if语句,如
if(record.firstname == "John")
如何动态更改==
,可能是!=
。
答案 0 :(得分:6)
您需要的是将SQL转换为可以直接执行的语言。对此的技术术语是“查询计划”。查询计划是数据库引擎将执行的低级操作(例如索引搜索,连接,排序),以及操作如何组合在一起。
任何体面的数据库引擎都会为您提供查看查询计划的方法。对于SQL系统,它通常称为EXPLAIN
。我建议你得到你最喜欢的DBMS(如果你没有最喜欢的,所有体面的开源DBMS都会这样做,包括MySQL和PostgreSQL),并查看各种查询的计划只是为了看看什么样的操作“真实“系统实施。
我也建议研究关系代数。如果您可以访问库存充足的库,那么任何体面的数据库教科书都会有一个部分或章节,但要求Google返回一些很好的参考资料。关系代数的优点在于它在理论上都很好,并且具有使用低级数据库操作实现它的“明显”方法。您最终可能会将其修改为无法识别,但这应该会给您一个良好的开端。
让我们看一下基本概述。首先阅读有关关系代数的内容,然后继续阅读。
您需要实现的主要数据结构是元组流。流中的每个元组都是不同的,但它们都具有相同的形状:元组的每个字段都有一个名称和一个类型。查询操作采用一个或多个元组流(顺便说一下,表可以被认为是一个元组流)并返回单个元组流。
考虑以下形式的基本SQL SELECT
语句:
SELECT fields
FROM table1,table2
WHERE select_conditions, join_conditions
此处,select_conditions
是gender='F'
或age > 18
等条件,其中字段与常量进行比较。 join_conditions
是将一个表中的字段与另一个表中的字段匹配的任何条件。 (我们暂时忽略了比较同一个表中两个字段的情况。)
然后一个简单的查询计划可能是:
s1 := Select(table1, select_conditions_1)
s2 := Select(table2, select_conditions_2)
j := Join(join_conditions, s1, s2)
res := Project(fields, j)
Select
操作采用元组流,并返回具有相同形状的元组流,其中任何元组与删除的条件不匹配。 Project
操作采用元组流并返回不同类型的元组流;它所做的就是删除,重新排列或复制字段。最后,Join
操作将两个元组流连接在一起,连接任何两个与连接条件匹配的元组。 (如果你不知道数据库连接操作是什么,你真的需要知道这一点。问Google,并查看Unix“join”命令。)
因此,在这种情况下,s1
是一个元组流,表示表1中与元组1的选择条件相匹配的元组。s2
的类似故事。流j
是根据连接条件加入的流s1
和s2
。最后,只展开查询中提到的字段。
将SQL转换为类似关系代数的中间形式实际上非常简单。但是,简单的翻译往往效率极低。在这里,我们通过检查表中的每个记录并返回匹配的记录来实现select操作。因此,需要根据查询的结构以及表中可用的信息来优化查询。
例如,假设table1
包含字段age
和gender
,我们的选择条件为age > 18
和gender = 'F'
。进一步假设table1
在table1_age_idx
字段上有一个索引(称为age
),但在gender
字段上没有索引。显然我们应该使用索引。我们可以通过将操作分成两个更基本的操作来实现:
s1a := IndexSelect(table1_age_idx, age > 18)
s2b := FilterSelect(s1a, gender = 'F')
在这里,我们将select操作拆分为两个。第一个选择是使用索引查询实现的(请注意,select现在位于索引上,而不是表!),第二个选择可以通过过滤流来实现,删除任何gender
不是的元组。 'F'
。
实现连接可以通过多种方式完成(排序合并连接和散列连接很受欢迎)。哪一个最好再次取决于查询和数据库。一些索引(例如B树和朋友)返回按键排序的记录,因此如果您已经在随后加入的字段上完成了IndexSelect
,那么排序合并连接可能会更好,因为排序是不必要的。如果连接字段上有ORDER BY
子句,则同样适用。
正如您所看到的,这是真正聪明的事情发生的地方。真正的查询优化器使用有关表大小的统计信息以及中间元组流的可能大小作为其计算的一部分。在这里了解一些关于编译器的事情是值得的。