【问题标题】:Why did my WHERE clause affect my LEFT JOIN?为什么我的 WHERE 子句会影响我的 LEFT JOIN?
【发布时间】:2019-11-13 10:55:13
【问题描述】:

我正在编写一些基于给定代码返回产品描述的 SQL。我准备了我的查询,假设不同大小写的代码可以共存。但是,在过滤我的主表的结果时,我希望我的结果区分大小写。也就是说,搜索一些小写代码只会返回小写代码,而不是大写等效项。

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

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

然后,我创建了每个表:

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

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

但是,完全相同的查询,更改 WHERE 子句,返回不同的结果。所以查询:

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 | ABC           | My product

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

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

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

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

【问题讨论】:

  • 你描述的行为听起来像一个错误。
  • “%abc%”更改为“%ABC%”。外壳不同,因此预期连接会有所不同。如果 products.code = '%ABC%',这并不一定意味着它将 = '%abc%',因为区分大小写。你检查过这个吗?
  • 但是请注意,仅仅比较具有不同排序规则的两个字符串是未定义的行为,或者至少是不容易正确定义的行为。也许,谁知道,将来会被禁止,除非您将表达式的两边显式转换为相同的字符集和排序规则
  • @Hugo 请注意,我正在重现一个问题,我从您对真实数据库和查询的相当不完整的复述中猜到了这个问题。虽然这很可能与您的问题相同,但不确定

标签: sql left-join firebird collation


【解决方案1】:

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

编写该代码并不清楚您认为自己在要求什么。

Firebird 文档没有指定它允许这样做。不幸的是,您说您没有收到任何错误或警告。

来自(草稿)SQL 标准:

第 2 部分:基金会 2011-12-21

9.11 等式运算
语法规则
4) 设 VS 为相等运算操作数的声明类型集合。如果 VS 包含字符串类型,则将 VS 作为 TYPESET 应用第 9.15 节“排序规则”的语法规则;让在相等操作中使用的排序规则是从应用这些语法规则返回的 COLL。

9.15 排序规则确定
语法规则
4) 案例:
e) 否则,其排序规则派生为隐式的每个操作数都应具有相同的声明类型排序规则 IDTC,并且要使用的排序规则是 IDTC。

您可以通过appropriate use of UPPER or COLLATE 获得不区分大小写的比较。

不区分大小写的搜索

对于不区分大小写的搜索,UPPER 函数可用于在尝试匹配之前将搜索参数和搜索字符串都转换为大写

对于具有不区分大小写排序规则的字符集中的字符串,您可以简单地应用排序规则,以直接比较搜索参数和搜索的字符串。

另请参阅: CONTAINING

同样LIKE 文档说:

注意
如果您需要对包含在字符串 ( LIKE '%Abc%' ) 中的内容进行不区分大小写的搜索,建议使用 CONTAINING 谓词,而不是 LIKE 谓词。

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

【讨论】:

  • 似乎标准在这一点上非常模棱两可,规定了排序规则匹配时要做什么,但省略了它们不匹配的情况。 FB 核心开发人员之一 Yemanov Dmitry 也呼吁抛出一个显式错误。然而,这样的强制执行可能会破坏大量应用程序代码。
  • @Arioch'标准并不含糊。它通过“语法规则”部分说排序规则必须一致。强制执行不“破坏代码”,违反代码被写入破坏。
【解决方案2】:

我被告知,在 SQL 的查询处理过程中,JOIN 子句将在 WHERE 子句之前运行,以确保后者将查看连接的结果。

这是对 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%') 被推送到“读取”阶段,以便仅读取相关行。然后优化器可以决定它将用于读取输入表的物理操作(例如全扫描与基于索引的读取)。

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

【讨论】:

  • 转载于 Firebird 2.1,可能是一个 bug...sql.ru/forum/…
  • Plan: PLAN JOIN (T_CI NATURAL, T_CS INDEX (RDB$PRIMARY3)) /// Adapted plan: PLAN JOIN (T_CI NATURAL, T_CS INDEX (INTEG_21))
  • Mid-string search不能使用索引,所以Firebird使用了二级表索引,失败了。使用starting 而不是like 使用主表索引,然后它按预期工作:select ... where T_CI.id starting with 'abc' 然后PLAN JOIN (T_CI INDEX (RDB$PRIMARY2), T_CS INDEX (RDB$PRIMARY3))
  • 另一种解决方法是明确指定排序规则,例如......where T_CI.id collate Unicode_ci like '%bc%'
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-06-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多