【问题标题】:Join gives unexpected (but pleasant) results加入会产生意想不到(但令人愉快)的结果
【发布时间】:2012-03-09 04:17:40
【问题描述】:

我目前正在提取三张桌子。 reportsberriesmelons。我这样设置我的查询,它得到了我想要的。

SELECT 
   rpt.*, 
   ber.shipper, ber.po, ber.commodity, ber.label

FROM reports rpt

LEFT JOIN berries ber ON rpt.inspection_number = ber.report_key
LEFT JOIN melons mel ON rpt.inspection_number = mel.report_key

WHERE rpt.status='1'  OR rpt.status='0'
ORDER BY rpt.inspection_number DESC

我的预期回报是

key | role | region   | inspection_type | inspection_number | shipper   | po    | commodity     | label
3   | NULL | Seattle  | melons          | 5555              | Shipper1  | PO2   | Commodity2    | Label2
2   | NULL | Seattle  | berries         | 1023              | Shipper1  | PO1   | Commodity1    | Label1

如果我从我的声明中删除了LEFT JOIN melons mel ON rpt.inspection_number = mel.report_key,我得到了完全相同的东西....我从来没有提到melons??

如果我修改并使用JOIN 代替浆果

SELECT 
   rpt.*, 
   ber.shipper, ber.po, ber.commodity, ber.label

FROM reports rpt

JOIN berries ber ON rpt.inspection_number = ber.report_key

WHERE rpt.status='1'  OR rpt.status='0'
ORDER BY rpt.inspection_number DESC

它产生了我期望的结果!

key | role | region   | inspection_type | inspection_number | shipper   | po    | commodity     | label
2   | NULL | Seattle  | berries         | 1023              | Shipper1  | PO1   | Commodity1    | Label1

但是试图像这样修改我的 SQL 语句....

SELECT 
   rpt.*, 
   ber.shipper, ber.po, ber.commodity, ber.label
   mel.shipper, mel.po, mel.commodity, mel.label

FROM reports rpt

JOIN berries ber ON rpt.inspection_number = ber.report_key
JOIN melons mel ON rpt.inspection_number = mel.report_key

WHERE rpt.status='1'  OR rpt.status='0'
ORDER BY rpt.inspection_number DESC

网我....

MySQL returned an empty result set (i.e. zero rows). ( Query took 0.0011 sec ) 给我大中指。我勒个去?谁能解释一下我显然做错了什么,以及如何解决?

【问题讨论】:

  • 把浆果和甜瓜放在同一张桌子上有意义吗?
  • 我希望可以,但我们实际上有大约 12 种水果类型,它们都有自己独立的字段。试图合并它们,然后再利用它们将是一场噩梦……不过,这是个好问题!
  • 我相信无论有没有LEFT JOINmel,你都会得到相同的结果,但我不相信你的结果。对于每个 melon 行,shipperpo 和其他 ber 列将为 NULL。

标签: mysql join left-join


【解决方案1】:

Daniel Lyons 提供的以下查询效果很好,但我想稍微讨论一下,并且严格出于学术目的,提供另一种解决方案,可能会更加优化。

这是丹尼尔的问题:

SELECT 
   rpt.*, 
   COALESCE(ber.shipper, mel.shipper) AS shipper,
   COALESCE(ber.po, mel.po) AS po, 
   COALESCE(ber.commodity, mel.commodity) AS commodity,
   COALESCE(ber.label, mel.label) AS label
FROM reports rpt
LEFT JOIN berries ber ON rpt.inspection_number = ber.report_key
LEFT JOIN melons mel ON rpt.inspection_number = mel.report_key
WHERE rpt.status='1'  OR rpt.status='0'
ORDER BY rpt.inspection_number DESC

这个查询效果很好,而且只对两个水果进行了相当优化。尽管如此,由于报告是互斥的,因此查询正在尝试进行不必要的额外连接。例如,如果一个报表记录已经连接到一个浆果记录,我们知道它不会连接到一个甜瓜记录,但 MySQL 不知道这一点。相反,MySQL 将进行另一次查找以尝试连接到 melon 表,即使找不到相应的记录。

只有两个连接,一半的连接尝试都被浪费了。但是,如果有 3 个结果,则浪费了三分之二的连接尝试,有四个结果,则浪费了四分之三的连接尝试,依此类推。

为了避免额外的连接尝试,我们可以颠倒连接的顺序,如下所示:

(SELECT rpt.*, ber.shipper, ber.po, ber.commondity, ber.label
FROM berries ber
JOIN reports rpt
  ON rpt.inspection_number = ber.report_key
WHERE rpt.status = '1' OR rpt.status = '0')
UNION ALL
(SELECT rpt.*, mel.shipper, mel.po, mel.commondity, mel.label
FROM melons mel
JOIN reports rpt
  ON rpt.inspection_number = mel.report_key
WHERE rpt.status = '1' OR rpt.status = '0')
ORDER BY inspection_number DESC

在这里,我们从另一个方向(水果)开始,并重新加入报告。这使我们可以在每个报告中只加入一个。

请注意,我们现在对每个水果使用 INNER JOIN 而不是 LEFT JOIN,并且我们使用 UNION ALL 将每个水果的结果合并到一个更大的结果集中。

为了进一步优化,有时 MySQL 无法将 10 这样的两个常量识别为一个范围,尤其是在它不是整数字段的情况下。范围查找比两个单独的查找要快,所以要提示 MySQL 10 的 rpt.status 是一个范围,请使用 BETWEEN 而不是 OR,假设您的覆盖索引为 @987654333 @。

【讨论】:

【解决方案2】:

这并不复杂。你的第一个查询,你加入了对 mel 但从来没有用它做任何事情,所以你只得到 ber 的数据。您的最后一个查询更接近,但因为您是针对浆果和甜瓜进行内部连接,并且您没有任何两者的报告,所以您没有得到任何结果。但答案更接近您在第二个查询中所做的事情,我认为您想要的是:

SELECT 
   rpt.*, 
   COALESCE(ber.shipper, mel.shipper) AS shipper,
   COALESCE(ber.po, mel.po) AS po, 
   COALESCE(ber.commodity, mel.commodity) AS commodity,
   COALESCE(ber.label, mel.label) AS label
FROM reports rpt
LEFT JOIN berries ber ON rpt.inspection_number = ber.report_key
LEFT JOIN melons mel ON rpt.inspection_number = mel.report_key
WHERE rpt.status='1'  OR rpt.status='0'
ORDER BY rpt.inspection_number DESC

这个查询说,给我浆果或甜瓜中存在连接的行,但对于它们共有的列,给我任何存在的列。没有特别的原因,我们首先采用 ber。

假设这两个表是互斥的,我认为这就是你想要的。

编辑:基于@MarcusAdams 在下面指出的内容,如果水果桌数量过多,则可以重写为使用UNION

SELECT report_key, shipper, po, commodity, label FROM berries
UNION
SELECT report_key, shipper, po, commodity, label FROM melons
UNION
SELECT report_key, shipper, po, commodity, label FROM ...
...

此查询将为您提供一些方便的东西,您可以稍后将其用作子查询(或视图)。您还可以像这样硬编码来源名称:

SELECT report_key, shipper, po, commodity, label, 'berries' AS type FROM berries
UNION
SELECT report_key, shipper, po, commodity, label, 'melons' FROM melons
UNION
SELECT report_key, shipper, po, commodity, label, '...' FROM ...
...

然后要在原始查询中使用它,您可以像这样嵌入它:

SELECT *
FROM reports rpt,
JOIN (SELECT report_key, shipper, po, commodity, label, 'berries' AS type FROM berries
      UNION
      SELECT report_key, shipper, po, commodity, label, 'melons' FROM melons
      UNION
      SELECT report_key, shipper, po, commodity, label, '...' FROM ...
      ...) fruits ON rpt.inspection_number = fruits.report_key
WHERE rpt.status='1'  OR rpt.status='0'
ORDER BY rpt.inspection_number DESC

【讨论】:

  • 这似乎可行,但我注意到我的rpt.status 正在覆盖我的mel.statusber.status?我想我想要更像WHERE mel.status > 2 AND ber.status > 2 的东西?这听起来是对的,还是有道理的?
  • 您没有选择mel.statusber.status——您必须像上面的其他四列一样将其添加到查询中。
  • 你想多了。 :) COALESCE(ber.status, mel.status) AS fruitstatus 就足够了。 rpt.status 已经通过选择 rpt.* 被拉入,但即使它不是,COALESCE 所做的是给你第一个不为空的项目——如果你在相同的值上使用它两次,它是与根本不使用没有什么不同。
  • 不错的简单查询,但连接的数量随着每个添加的水果成比例地增长。对于许多水果,可以通过将连接颠倒并使用 UNION 来优化它。
  • @MarcusAdams 他在上面透露他有 12 张水果桌,所以你应该写一个答案来证明他的技术
【解决方案3】:

首先,您会注意到这个 SELECT 查询没有从 melons 表中选择任何数据。因此,您不需要加入 melons 表,因为您没有从中选择任何数据,而且它的内容或引用方式都无关紧要,因为您没有从中选择任何内容。

SELECT 
   rpt.*, 
   ber.shipper, ber.po, ber.commodity, ber.label

接下来,我不太熟悉LEFT JOININNER JOINJOIN 之间的区别,所以我无法回答这个问题。但是,根据我对 MySQL 的经验,尝试将最后一个查询更改为 INNER JOIN 而不是 JOIN 并查看是否有效。 MySQL 网站上有documentation 说明了不同类型的连接之间的差异。

如果您仍然遇到问题,请告诉我,我会尽力为您提供进一步帮助。希望我至少回答了一些个问题。

【讨论】:

  • JOIN 没有任何前缀 INNER JOIN.
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-04-05
  • 2019-04-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-18
相关资源
最近更新 更多