【问题标题】:SQL query and join: why?SQL 查询和连接:为什么?
【发布时间】:2011-03-13 20:04:23
【问题描述】:

如果这是一个新手问题,我很抱歉,但我似乎不明白为什么这不像我想要的那样工作:

mysql> select t.id,t.date_fin_val,tc.date_fin_val
from tiers t
join tiers_critere_int tc on tc.id_tiers=t.id
where (t.date_fin_val is null) and (tc.date_fin_val is null);
+----+---------------------+---------------------+
| id | date_fin_val        | date_fin_val        |
+----+---------------------+---------------------+
|  1 | 0000-00-00 00:00:00 | 0000-00-00 00:00:00 | 
|  1 | 0000-00-00 00:00:00 | 0000-00-00 00:00:00 | 
|  1 | 0000-00-00 00:00:00 | 0000-00-00 00:00:00 | 
+----+---------------------+---------------------+
3 rows in set (0.00 sec)

mysql> select t.id,t.date_fin_val,tc.date_fin_val
from tiers t
left outer join tiers_critere_int tc on tc.id_tiers=t.id
where (t.date_fin_val is null) and (tc.date_fin_val is null);
Empty set (0.00 sec)

mysql> 

我认为“左外连接”的意思是:“如果右侧没有结果,但左侧有一个,则继续无论如何将左侧的那个放在“@ 987654322@" 右侧的值。 如果我是对的,使用“left outer join”而不是“join”的第二个查询应该返回值。但事实并非如此。为什么?

这是我的数据:

mysql> select * from tiers t where date_fin_val is null;
+----+---------------------+--------------------+
| id | date_fin_val        | est_tiers_physique |
+----+---------------------+--------------------+
|  1 | 0000-00-00 00:00:00 |                  1 | 
+----+---------------------+--------------------+
1 row in set (0.00 sec)

mysql> select * from tiers_critere_int  where date_fin_val is null;
+----+---------------------+----------+------------+---------+
| id | date_fin_val        | id_tiers | id_critere | critere |
+----+---------------------+----------+------------+---------+
|  1 | 0000-00-00 00:00:00 |        1 |          2 |      86 |
|  2 | 0000-00-00 00:00:00 |        1 |          6 |     170 |
|  3 | 0000-00-00 00:00:00 |        1 |          7 |      65 |
+----+---------------------+----------+------------+---------+
3 rows in set (0.00 sec)

mysql>

非常感谢!

【问题讨论】:

  • 你的意思是说 second 查询不返回值吗?
  • 我对第一个查询如何返回结果感到困惑。在 where 子句中,您指示两个日期值都必须为空,但在结果集中,两个日期值都不是空的。无论是联接还是左联接,查询都应返回相同的结果。左联接不应得到更少的结果,只有当右表中的相应字段为空时才会得到更多结果。
  • 向我们展示在 tiers 和 tiers_critre_int(id 为 1)表中存在哪些类型的数据。
  • @Tandu 如果datetimenull,那么MySQL 返回"0000-00-00 00:00:00"。因此,您看到的结果。无论如何,我已经在我的问题中添加了表格中的数据。
  • @OlivierDofus 据我所知这是不正确的,它将返回 null。我自己运行了查询,但没有得到任何结果。

标签: sql join


【解决方案1】:

我的结果对你的数据和查询是正确的。

CREATE TABLE `tiers` (
  `id` int(11) DEFAULT NULL,
  `date_fin_val` datetime DEFAULT NULL,
  `est_tiers_physique` int(11) DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1

insert into tiers (id, est_tiers_physique) values (1, 1);

select * from tiers;
+------+--------------+--------------------+
| id   | date_fin_val | est_tiers_physique |
+------+--------------+--------------------+
|    1 | NULL         |                  1 |
+------+--------------+--------------------+
1 row in set (0.00 sec)

CREATE TABLE `tiers_critere_int` (
  `id` int(11) DEFAULT NULL,
  `date_fin_val` datetime DEFAULT NULL,
  `id_tiers` int(11) DEFAULT NULL,
  `id_critere` int(11) DEFAULT NULL,
  `critere` int(11) DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1

insert into tiers_critere_int (id, id_tiers, id_critere, critere) 
values 
(1, 1, 2, 86),
(2, 1, 6, 170),
(3, 1, 7, 65)
;

select * from tiers_critere_int;
+------+--------------+----------+------------+---------+
| id   | date_fin_val | id_tiers | id_critere | critere |
+------+--------------+----------+------------+---------+
|    1 | NULL         |        1 |          2 |      86 |
|    2 | NULL         |        1 |          6 |     170 |
|    3 | NULL         |        1 |          7 |      65 |
+------+--------------+----------+------------+---------+
3 rows in set (0.00 sec)

select t.id,t.date_fin_val,tc.date_fin_val
from tiers t
join tiers_critere_int tc on tc.id_tiers=t.id
where (t.date_fin_val is null) and (tc.date_fin_val is null);
+------+--------------+--------------+
| id   | date_fin_val | date_fin_val |
+------+--------------+--------------+
|    1 | NULL         | NULL         |
|    1 | NULL         | NULL         |
|    1 | NULL         | NULL         |
+------+--------------+--------------+
3 rows in set (0.00 sec)

select t.id,t.date_fin_val,tc.date_fin_val
from tiers t
left outer join tiers_critere_int tc on tc.id_tiers=t.id
where (t.date_fin_val is null) and (tc.date_fin_val is null);
+------+--------------+--------------+
| id   | date_fin_val | date_fin_val |
+------+--------------+--------------+
|    1 | NULL         | NULL         |
|    1 | NULL         | NULL         |
|    1 | NULL         | NULL         |
+------+--------------+--------------+
3 rows in set (0.02 sec)

现在如果你插入一个空字符串,你会得到:

insert into tiers (id, date_fin_val, est_tiers_physique) values (2, '', 1);
Query OK, 1 row affected, 1 warning (0.00 sec)

select * from tiers;
+------+---------------------+--------------------+
| id   | date_fin_val        | est_tiers_physique |
+------+---------------------+--------------------+
|    1 | NULL                |                  1 |
|    2 | 0000-00-00 00:00:00 |                  1 |
+------+---------------------+--------------------+
2 rows in set (0.00 sec)

【讨论】:

  • 这是一个关键问题,感谢您指出这一点,您的示例表明我是对的,并且我正确地了解了“左外连接”的用途:)。再次感谢。我已经发布了详细的答案,以及困惑的来源。
  • @Olivier 真正的问题是您的数据中没有空值。相反,有空日期
  • 你是对的。但这对 MySQL 来说是一个真正的问题,因为它允许插入空键(这应该不起作用),它允许在键上选择空值(应该总是返回一个空结果),有时它有效(请参阅我的问题中的我的#1 查询),有时它不起作用(请参阅我的问题中的我的#2 查询)。这太令人困惑了。我已经失去了 4 个小时试图解决这个问题。我什至重新检查了我对“左外连接”的理解是否正确(这里的所有答案都证实我是对的)
  • 抱歉,我花了很长时间来验证您的答案 =)
【解决方案2】:

MySQL 中似乎存在错误。 以下是我创建表格的方式:

CREATE TABLE tiers_critere_int (
  id bigint AUTO_INCREMENT NOT NULL,
  date_debut_val datetime not null,
  date_fin_val datetime **default NULL**,
  id_tiers bigint(20) NOT NULL,
  id_critere bigint(20) NOT NULL,
  critere bigint(20) NOT NULL,
  PRIMARY KEY  (id,date_debut_val,date_fin_val),
  KEY id (id),
  KEY date_debut_val (date_debut_val),
  KEY date_fin_val (date_fin_val),
  KEY date_debut_val_2 (date_debut_val,date_fin_val),
  KEY critere (critere),
  KEY id_tiers (id_tiers),
  KEY id_critere (id_critere),
  FOREIGN KEY (id_tiers) REFERENCES tiers (id)
  ON UPDATE CASCADE ON DELETE CASCADE,
  FOREIGN KEY (id_critere) REFERENCES critere (id)
  ON UPDATE CASCADE ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

tiers_critere_int 中,date_fin_val 默认为空。 你是那样想的。 但如果你这样做:

mysql> update tiers_critere_int set date_fin_val= null;
Query OK, 0 rows affected, 3 warnings (0.00 sec)
Rows matched: 3  Changed: 0  Warnings: 3

mysql> show warnings;
+---------+------+--------------------------------------+
| Level   | Code | Message                              |
+---------+------+--------------------------------------+
| Warning | 1048 | Column 'date_fin_val' cannot be null | 
| Warning | 1048 | Column 'date_fin_val' cannot be null | 
| Warning | 1048 | Column 'date_fin_val' cannot be null | 
+---------+------+--------------------------------------+
3 rows in set (0.00 sec)

mysql> 

问题在于 MySQL 确实接受表的创建,但 接受键的空值。 但是不正常的是,这个查询有效并且显然令人惊讶(或者不正常,由您来判断):

mysql> select * from tiers_critere_int
where date_fin_val is null;
+----+---------------------+---------------------+----------+------------+---------+
| id | date_debut_val      | date_fin_val        | id_tiers | id_critere | critere |
+----+---------------------+---------------------+----------+------------+---------+
|  1 | 2011-03-13 06:07:05 | 0000-00-00 00:00:00 |        1 |          2 |      86 | 
|  2 | 2011-03-13 06:07:05 | 0000-00-00 00:00:00 |        1 |          6 |     170 | 
|  3 | 2011-03-13 06:07:05 | 0000-00-00 00:00:00 |        1 |          7 |      65 | 
+----+---------------------+---------------------+----------+------------+---------+
3 rows in set (0.00 sec)

mysql> 

所以这真的很令人困惑,更令人困惑的是子句“is null”在子句where (t.date_fin_val is null)中得到了正确处理,但它在子句and (tci.date_fin_val is null)中没有得到正确处理,而它在中相同请求,在以相同方式创建的表上,在相同列上。

于是,故事的结尾: 正确的要求是

mysql> select t.id,t.date_fin_val,tci.date_fin_val
from tiers t
left outer join tiers_critere_int tci
on t.id=tci.id_tiers
where (t.date_fin_val ='0000-00-00 00:00:00')
and (tci.date_fin_val ='0000-00-00 00:00:00');
+----+---------------------+---------------------+
| id | date_fin_val        | date_fin_val        |
+----+---------------------+---------------------+
|  1 | 0000-00-00 00:00:00 | 0000-00-00 00:00:00 |
|  1 | 0000-00-00 00:00:00 | 0000-00-00 00:00:00 |
|  1 | 0000-00-00 00:00:00 | 0000-00-00 00:00:00 |
+----+---------------------+---------------------+
3 rows in set (0.00 sec)

mysql> 

【讨论】:

  • 啊!钥匙把事情搞砸了。在 MySQL 上搞砸了恕我直言。
【解决方案3】:

我认为这与 how MySQL optimizes LEFT (and RIGHT) JOINs 结合日期列为 NULL 有关。

【讨论】:

【解决方案4】:

LEFT OUTER JOIN 在相同的条件和相同的数据下应该返回更多或相同数量的行作为INNER JOIN

请检查:

  1. 您的数据,看看它们对于两个查询是否相同
  2. 这两个查询是否如您在问题中指定的那样(例如您是否打错字)

【讨论】:

  • MySQL 中似乎有一个错误(是是是>_select * from tiers t left outer join tiers_critere_int tci on t.id=tci.id_tiers where (tci.date_fin_val is null); 我会得到 Empty set (0.00 sec) 如果我尝试 exactly 相同的请求但使用 where (tci.date_fin_val ='0000-00-00 00:00:00'); 我会得到我需要的一切。所以这要么是一个错误,要么是一个奇怪的行为。
  • Hhhmmmm.... 似乎 MySQL 可能会为空日期值返回 0000-00-00 00:00:00 而不是 NULL。您可能需要查看手册以查看是否记录了此行为。如果你问我,这肯定不是正确的 SQL 行为。
  • 你说得对,这是一个关键问题,但 MySQL 处理这个问题的方式太令人困惑了。 (它没有一致的行为:一次有效,另一次无效)。
【解决方案5】:

哇 --- 我认为你们都缺少一些东西:

这是当前的左连接:

mysql> select t.id,t.date_fin_val,tc.date_fin_val
from tiers t
left outer join tiers_critere_int tc on tc.id_tiers=t.id
where (t.date_fin_val is null) and (tc.date_fin_val is null);

左连接条件大概应该是这样的:

mysql> select t.id,t.date_fin_val,tc.date_fin_val
from tiers t
left outer join tiers_critere_int tc on tc.id_tiers=t.id AND (tc.date_fin_val is null)
where (t.date_fin_val is null);

您仍在 WHERE 子句中过滤行的事实隐藏了您尝试对 LEFT OUTER JOIN 执行的操作。

【讨论】:

  • 不,如果日期实际上是 NULL,就像 OP 认为的那样,原始查询会起作用。事实证明它们是不同的,这是由于列被定义为 KEY 造成的。但是,通常情况下,您是对的,从左外连接的左侧添加用于过滤 values 的 WHERE 条件有效地使连接成为内部连接。
  • 非常感谢您的回答,这可能是我在做这样的“左外连接”时必须记住的事情(我即将做很多 lot 像这样)!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-03-29
  • 1970-01-01
  • 1970-01-01
  • 2020-09-27
  • 1970-01-01
  • 2021-09-09
  • 1970-01-01
相关资源
最近更新 更多