为什么我的 WHERE 子句会影响我的 LEFT JOIN?

Why did my WHERE clause affect my LEFT JOIN?

我正在写一些 SQL,return 根据给定的代码编辑产品描述。我在假设具有不同大小写的代码可以共存的情况下准备了我的查询。但是,在过滤我的主要 table 的结果时,我希望我的结果区分大小写。也就是说,搜索一些小写代码只会 return 小写代码,而不是大写等效代码。

然而,我发现,根据 WHERE 子句条件的大小写,结果会发生变化。 我查看了每个 table,每个都有不同的排序规则。我已经用 RIGHT JOIN 进行了测试,并且它在两种字符的情况下都正确地加入了 tables。此外,从来不需要检查不同的大小写:根据我们系统的标准和验证,所有代码 应该 都是大写的。因此,虽然解决这个问题就像确保我的 WHERE 子句是大写一样简单,但我仍然想知道 为什么 查询 return 得到不同的结果。我被告知,在 SQL 的查询处理期间,JOIN 子句将 运行 WHERE 子句之前,确保后者将查看 加入结果。

为了重现这个错误,首先,我用DEFAULT CHARACTER SET UTF8 COLLATION UNICODE_CI_AI创建了一个数据库。

然后,我这样创建每个 table:

CREATE TABLE MAIN_TABLE (
  val VARCHAR(40) NOT NULL PRIMARY KEY,
  code VARCHAR(40) NOT NULL COLLATE UNICODE_CI
);

CREATE TABLE PRODUCTS  (
  name VARCHAR(40) NOT NULL PRIMARY KEY,
  code VARCHAR(40) NOT NULL COLLATE UNICODE
);

然后我插入了以下测试条目:

INSERT INTO MAIN_TABLE (val, code) VALUES ('This value is returned', 'ABC');
INSERT INTO PRODUCTS (name, code) VALUES ('My product', 'ABC');

最后,我执行了以下查询:

SELECT * FROM MAIN_TABLE
LEFT JOIN PRODUCTS 
ON MAIN_TABLE.code = PRODUCTS.code
WHERE MAIN_TABLE.code LIKE '%abc%'

结果是:

MAIN_TABLE.code | MAIN_TABLE.val         | PRODUCTS.code | PRODUCTS.name
----------------+------------------------+---------------+---------------
 ABC            | This value is returned | null          | null

请注意,虽然我的查询 did 在 MAIN_TABLE 中找到结果,但 LEFT JOIN 结果为空。

但是,完全相同的查询,改变WHERE子句,return得到不同的结果。所以查询:

SELECT * FROM MAIN_TABLE
LEFT JOIN PRODUCTS 
ON MAIN_TABLE.code = PRODUCTS.code
WHERE MAIN_TABLE.code LIKE '%ABC%'

最后 returning:

MAIN_TABLE.code | MAIN_TABLE.val         | PRODUCTS.code | PRODUCTS.name
----------------+------------------------+---------------+---------------
 ABC            | This value is returned | ABC           | My product

我在想--是不是我对运算顺序的理解有误?数据库服务器是否通读查询,确定 WHERE 子句的列 (MAIN_TABLE.code) 与 JOIN 中的列相同,并且 then 更改内部处理 JOIN 的方式(优化或其他)?或者这仅仅是 Firebird 如何解释查询的一个错误?考虑到不同的排序规则,我确实预料到会出现一些奇怪的行为,但我不确定这是否是某种功能。

为什么我的 WHERE 子句会影响我的 LEFT JOIN?

我不是在寻找修复它的方法,因为我发现了很多 - 更改排序规则、大写我的查询、预先验证代码等。

我的数据库 运行 在 Firebird 3.0 上。我检查了显示所有消息的选项,检查了日志,并检查了有效的查询变体。我在那里没有看到任何东西可以让我知道为什么会这样。

I was taught that, during the SQL's query processing, the JOIN clause would run before the WHERE clause, ensuring that the latter would look through the joined result.

这是 SQL 语义 的正确描述,因此您看到的很可能是错误。

RDBMS 的实际实现更为复杂。在高层次上,SQL 查询被解析为 logical query plan,这是一棵紧跟输入 SQL 结构的树。然后,优化器负责将逻辑计划转换为将 运行 产生结果的实际步骤(物理运算符)。

您的查询的逻辑计划类似于:

read MAIN_TABLE        read PRODUCTS
       \                  /
      join them on MAIN_TABLE.code = PRODUCTS.code
              |
       apply filter MAIN_TABLE.code LIKE '%ABC%'

优化器的工作是找出执行此操作的有效方法。它可以进行谓词下推之类的转换,其中过滤器 (MAIN_TABLE.code LIKE '%ABC%') 被推送到 "read" 阶段,以便只读取相关行。然后优化器可以决定它将用于读取输入 table 的物理操作(例如 full-scan 与 index-based 读取)。

(这是我的猜测。)优化器还可能注意到,由于您加入 code,因此只能匹配满足 PRODUCTS.code LIKE '%ABC%' 的产品,因此它可以推送也将谓词向下传递给 PRODUCTS 扫描运算符。根据输入 tables 的排序规则,如果优化器不是很小心,LIKE '%ABC%' 谓词的语义可能会发生变化,从而导致您看到的行为。

SQL 标准规定,当比较两个字符类型表达式时,它们必须具有相同的排序规则。这是因为两个值是否相等只在给定的排序规则中才有意义。你对 = & LIKE 的调用违反了这一点。

不清楚您认为您编写该代码的目的是什么。

Firebird 文档未指定它允许这样做。很遗憾,您说您没有收到任何错误或警告。

来自 SQL 标准(草案):

Part 2: Foundation 2011-12-21

9.11 Equality operations
Syntax Rules
4) Let VS be the set of declared types of the operands of an equality operation. If VS comprises character string types, then the Syntax Rules of Subclause 9.15, "Collation determination", are applied with VS as TYPESET; let the collation to be used in the equality operation be the COLL returned from the application of those Syntax Rules.

9.15 Collation determination
Syntax Rules
4) Case:
e) Otherwise, every operand whose collation derivation is implicit shall have the same declared type collation IDTC and the collation to be used is IDTC.

您可以通过 appropriate use of UPPER or COLLATE 进行 case-insensitive 比较。

Case-Insensitive Searching

For a case-insensitive search, the UPPER function could be used to convert both the search argument and the searched strings to upper-case before attempting a match

For strings in a character set that has a case-insensitive collation available, you can simply apply the collation, to compare the search argument and the searched strings directly.

See also: CONTAINING

同样 LIKE 文档说:

Note
If you need to do a case-insensitive search for something enclosed inside a string ( LIKE '%Abc%' ), use of the CONTAINING predicate is recommended, in preference to the LIKE predicate.

@Arioch'The commented with a link to a bug report(他们添加了这个例子)。但是bug就是没有报错