准备绑定查询:DatabasePrepare

在许多情况下,需要在 SQL 查询中嵌入参数。由于 SQL 查询“原本”是一个符合特殊语法的字符串,因此可以通过简单的 StringFormat 调用或通过连接,并在正确的位置添加参数值来构建。我们已经在创建表的查询 ("CREATE TABLE %s '%s' (%s);") 中使用过这种技术,但这里只有部分参数包含数据(值列表替换了括号内的 %s),其余部分则表示一个选项和表名。在本节中,我们将专门关注向查询中替换数据。以原生的 SQL 方式执行此操作非常重要,原因有几个。

首先,SQL 查询仅作为字符串传递给 SQLite 引擎,查询在引擎中被解析为组件,检查正确性,并以某种方式“编译”(当然,不是由 MQL5 编译器进行编译)。然后,编译后的查询由数据库执行。这就是为什么我们把“原本”这个词放在引号里。

当需要使用不同参数执行相同的查询时(例如,向表中插入许多记录;我们正在逐步接近这个任务),为每条记录单独编译和检查查询效率相当低下。更正确的做法是编译查询一次,然后批量执行它,只需替换不同的值即可。

这种编译操作称为查询准备,由 DatabasePrepare 函数执行。

预置查询还有另一个目的:借助它们,SQLite 引擎将查询执行结果返回给 MQL5 代码(你将在 执行预置查询单独读取查询结果记录字段等章节中找到更多相关内容)。

与参数化查询相关的最后但同样重要的一点是,它们能保护你的程序免受潜在的黑客攻击,即所谓的 SQL 注入。首先,这对于公共站点的数据库至关重要,在这些站点中,用户输入的信息会嵌入到 SQL 查询中而记录到数据库:如果在这种情况下使用简单的格式替换(如 '%s'),用户将能够输入一个包含额外 SQL 命令的长字符串来代替预期的数据,这将成为原始 SQL 查询的一部分,从而扭曲其含义。但是,如果 SQL 查询已经编译完成,它就不能被输入数据更改:输入数据始终被视作数据处理。

尽管 MQL 程序不是服务器程序,但仍然可以将从用户那里接收到的信息存储在数据库中。

int DatabasePrepare(int database, const string sql, ...)

DatabasePrepare 函数在指定的数据库中为 sql 字符串中的查询创建一个句柄。database 必须事先通过 DatabaseOpen 函数打开。

查询参数的位置在 sql 字符串中使用片段 '?1'、'?2'、'?3' 等来指定。该编号表示将来在 DatabaseBind函数中为其分配输入值时使用的参数索引。sql 字符串中的数字不需要按顺序排列,如果同一参数需要插入到查询的不同位置,也可以重复使用。

注意!替换片段 '?n' 中的索引从 1 开始,而在 DatabaseBind 函数中从 0 开始。例如,查询主体中的 '?1' 参数将在调用索引为 0 的 DatabaseBind 时获取值,'?2' 参数在索引为 1 时获取值,依此类推。即使 '?n' 参数的编号存在间隙(无论是意外还是有意为之),这个固定的偏移量 1 也会保持不变。

如果你计划严格按顺序绑定所有参数,可以使用缩写表示法:在每个参数的位置,只需指明符号 '?' 而不带数字即可:在这种情况下,参数会自动编号。任何不带数字的参数 '?' 都会获得一个比其左侧已读取参数(带有显式数字或根据相同原则计算得出)的最大编号大 1 的数字,而第一个参数将获得编号 1,即 '?1'。

因此,请求

SELECT * FROM table WHERE risk > ?1 AND signal = ?2

等同于:

SELECT * FROM table WHERE risk > ? AND signal = ?

如果某些参数是常量,或者查询是为了获取结果而一次性准备执行的,则参数值可以作为逗号分隔的列表传递给 DatabasePrepare 函数,而不是省略号(与 PrintComment 函数相同)。

查询参数只能用于设置表列中的值(在写入、更改或筛选条件时)。表名、列名、选项和 SQL 关键字不能通过 '?'/'?n' 参数传递。

DatabasePrepare 函数本身并不执行查询。从它返回的句柄必须随后传递给 DatabaseReadDatabaseReadBind 函数调用。这些函数会执行查询并使结果可供读取(它可以是一条记录或多条记录)。当然,如果查询中存在参数占位符('?' 或 '?n'),并且在 DatabasePrepare 中没有为它们指定值,那么在执行查询之前,你需要使用适当的 DatabaseBind函数来绑定参数和数据。

如果没有为参数分配值,则在查询执行期间将为其替换为 NULL。

如果发生错误,DatabasePrepare 函数将返回 INVALID_HANDLE。

DatabasePrepare 的使用示例将在后续章节中探讨了与预置查询相关的其他特性之后介绍。