关于mysql:为什么这个简单的Left Join从不匹配的行返回数据?

Why does this simple Left Join return data from unmatched rows?

有关操作中的此问题,请参见简单的http://sqlfiddle.com/#!9/e853f/1。

我指的是MySQL ver 5.6.12-log

据我所知,左连接对最右边数据集中的列返回NULL,而在右边数据集中不存在左数据集中的键。

但是,即使右边没有左键,我也会从右侧获得数据。

任何人都可以解释这里发生了什么吗?

SQLfiddle创建:

  • 具有6行的表,每行仅包含一个整数ID
  • 第二张表包含3行,其中包含一些整数ID加2
    更多INT字段
  • 基于第二个表的视图,该表返回3行,其中包含整数ID以及从其他两个INT字段派生的文本字段

(显然,视图中的3个ID对应于6行表中的某些ID。)

SQL
SELECT * FROM LEFT JOIN ON table_ID = view_ID;
返回预期的6行,但所有行均在文本字段中具有数据,而不是3个不匹配的行为NULL

但是

如果视图中用于派生文本列的方法稍有更改,则"左联接SQL"将给出正确的结果。
(您可以通过在sql小提琴中有选择地注释掉两种方法中的一种或另一种来显示此内容)

但是,优化器当然不会首先评估视图,因此,如何创建数据以及包含的内容都无关紧要?

(这是我先前提出的一个问题的简化版本,我承认这个问题对于非法的明智答案而言过于复杂)。

有人建议(Jeroen Mostert)显示数据和预期结果。它是:

餐桌人物

1
2
3
4
5
6
7
8
personID
--------
   1
   2
   3
   4
   5
   6

查看付款状态

1
2
3
4
5
payment_personID  |   state
----------------------------
       1          |   'equal'
       2          |   'under'
       3          |   'over'

查询

1
2
3
SELECT * FROM  person
LEFT JOIN   payment_state
ON personID = payment_personID;

预期结果

1
2
3
4
5
6
7
8
personID | payment_personID  |state
-------------------------------------
    1    |      1            | 'equal'
    2    |      2            | 'under'
    3    |      3            | 'over'
    4    |     NULL          |  NULL
    5    |     NULL          |  NULL
    6    |     NULL          |  NULL

实际结果

1
2
3
4
5
6
7
8
personID | payment_personID  |state
-------------------------------------
    1    |      1            | 'equal'
    2    |      2            | 'under'
    3    |      3            | 'over'
    4    |     NULL          | 'equal'
    5    |     NULL          | 'equal'
    6    |     NULL          | 'equal'


我不同意其他答案。这是MySQL的缺陷。实际上,这是MySQL 5.6中的错误#83707。看起来它已在MySQL 5.7

中修复

此错误已在MariaDB 5.5中修复。

内部联接策略(例如嵌套循环联接,合并联接或哈希联接)无关紧要。在任何情况下,结果都应该正确。

我在PostgreSQL和Oracle中尝试了相同的查询,它按预期工作,在最后三行返回空值。

Oracle示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
CREATE TABLE person (personID INT);

INSERT INTO person (personID) VALUES (1);
INSERT INTO person (personID) VALUES(2);
INSERT INTO person (personID) VALUES(3);
INSERT INTO person (personID) VALUES(4);
INSERT INTO person (personID) VALUES(5);
INSERT INTO person (personID) VALUES(6);

CREATE TABLE payments (
   payment_personID INT,
   Due INT,
   Paid INT) ;

INSERT INTO payments  (payment_personID, due, paid) VALUES (1, 5, 5);
INSERT INTO payments  (payment_personID, due, paid) VALUES (2, 5, 3);
INSERT INTO payments  (payment_personID, due, paid) VALUES (3, 5, 8);

CREATE VIEW payment_state AS (
SELECT
   payment_personID,
  CASE
   WHEN COALESCE(paid,0) < COALESCE(due,0) AND due <> 0 THEN 'under'
   WHEN COALESCE(paid,0) > COALESCE(due,0) THEN 'over'
   WHEN COALESCE(paid,0) = COALESCE(due,0) THEN 'equal'
   END AS state
FROM payments);

SELECT *
FROM
    person
LEFT JOIN
    payment_state  
ON personID = payment_personID;

结果:

1
2
3
4
5
6
7
8
PERSONID  PAYMENT_PERSONID  STATE
========  ================  =====
       1                 1  equal
       2                 2  under
       3                 3  over
       6            <null>  <null>
       5            <null>  <null>
       4            <null>  <null>

完美运行!

PostgreSQL示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
CREATE TABLE person (personID INT);
INSERT INTO person (personID) VALUES
(1),(2),(3),(4),(5),(6);

CREATE TABLE payments (
   payment_personID INT,
   Due INT,
   Paid INT) ;

INSERT INTO payments  (payment_personID, due, paid) VALUES
(1, 5, 5), (2, 5, 3), (3, 5, 8);

CREATE VIEW payment_state AS (
SELECT
   payment_personID,
  CASE
   WHEN COALESCE(paid,0) < COALESCE(due,0) AND due <> 0 THEN 'under'
   WHEN COALESCE(paid,0) > COALESCE(due,0) THEN 'over'
   WHEN COALESCE(paid,0) = COALESCE(due,0) THEN 'equal'
   END AS state
FROM payments);

SELECT *
FROM
    person
LEFT JOIN
    payment_state  
ON personID = payment_personID;

结果:

1
2
3
4
5
6
7
8
personid  payment_personid  state
========  ================  =====
       1                 1  equal
       2                 2  under
       3                 3  over
       4            <null>  <null>
       5            <null>  <null>
       6            <null>  <null>

也很完美!


您的视图的处理算法会导致此结果。默认情况下,MySQL通常选择MERGE,因为它效率更高。如果使用" TEMPTABLE"算法创建视图,则对于不匹配的行,您将看到NULL。

http://www.mysqltutorial.org/create-sql-views-mysql.aspx

1
2
3
4
5
6
7
8
9
CREATE ALGORITHM =  TEMPTABLE VIEW  payment_state AS (
SELECT
   payment_personID,
 CASE
   WHEN IFNULL(paid,0) < IFNULL(due,0) AND due <> 0 THEN 'under'
   WHEN IFNULL(paid,0) > IFNULL(due,0) THEN 'over'
   WHEN IFNULL(paid,0) = IFNULL(due,0) THEN 'equal'
   END AS state
FROM payments);


这是LEFT JOIN正常的工作方式。它将新列追加到结果中,然后用以下内容填充:

  • 如果JOIN成功,则从要JOIN的表中提取的值,
  • 如果JOIN不匹配,则为NULL s(包括您加入的ON字段)!

通常,从真实表(匹配JOIN的表)中提取的NULL和由于JOIN不匹配而填充的NULL之间没有区别。 CASE IFNULL只是寻找NULL s并将它们交换到0 s(无论它们的来源)。这就是为什么即使在不匹配的行中,状态列中也有结果的原因。

事实上,如果您想知道您正在查看的给定NULL是否是与JOIN不匹配的结果,则需要显式检查-如果所有关键字段都属于JOIN如果此列中的NULL是填写的结果,则表示NULL。如果该行中存在来自key的字段,但其他列中仍然有NULL,则该字段在那里,因为它是从您JOIN所创建的表中提取的。