MariaDB - 左连接不返回所有行

Posted

技术标签:

【中文标题】MariaDB - 左连接不返回所有行【英文标题】:MariaDB - LEFT JOIN does not return all rows 【发布时间】:2020-12-17 16:30:24 【问题描述】:

(在将其标记为重复之前,请阅读问题)。 我有两个不同的表,BotSwitch

Bot
ID | Info | Active
------------------
0  | abc  |     1
1  | def  |     1

Switch
Date       | Activated | BotID | User
-------------------------------------
2020-01-01 |         1 |     0 | John
2020-01-02 |         0 |     0 | John

对于每个机器人,我想检索其最新状态,这意味着:对于每个机器人,我想知道最新行的Activated 字段是1 还是0。为了为那些在Switch 表中没有条目的机器人返回结果,我尝试使用LEFT JOIN 语句。这是查询:

SELECT IFNULL(x.Activated, 0) AS Following, b.ID, b.Info
FROM bot b
LEFT JOIN (
    SELECT *
    FROM switch s1
    WHERE s1.Date IN (
        SELECT MAX(s.Date)
        FROM switch s
        WHERE s.User = 'SomeUsername'
        GROUP BY s.Bot
    )
) AS x ON x.BotID = b.ID
WHERE b.Active = 1

我的预期结果是:

Following | ID | Info
---------------------
        0 | 0  | abc
        0 | 1  | def

重点是我没有得到所有行,而是这个查询只返回一行:

Following | ID | Info
---------------------
        0 | 0  | abc

这很奇怪,因为我使用的是LEFT JOIN。为了弄明白怎么回事,我分别跑了

SELECT * 
FROM bot

SELECT *
FROM switch s1
WHERE s1.Date IN (
    SELECT MAX(s.Date)
    FROM switch s
    WHERE s.User = 'SomeUsername'
    GROUP BY s.Bot
)

当然,第一个查询返回:

ID | Info
---------
0  | abc
1  | def

当第二个返回时:

Date       | Activated | BotID | User
-------------------------------------
2020-01-02 |         0 |     0 | John

我真的不明白为什么LEFT JOIN 没有同时保留Bot 行。我检查了this 问题,但我没有使用任何外部WHERE 所以这不是我的情况。 提前致谢!

【问题讨论】:

b.Active 不在您显示的架构中 @GarrGodfrey 抱歉,我试图尽可能简化架构。刚刚编辑了问题。谢谢你告诉我。 你的查询应该做你想做的事。你能设置一个db<>fiddle 来演示这个问题吗? 这里有问题。您声称此连接有效 AS x ON x.Bot = b.ID 但 x 不包含列 Bot 根据您的“而第二个返回”输出 旁注;如果您使用的是 MariaDB,请先声明您使用的是它,而不是 mysql。两个产品不一样 【参考方案1】:

您的查询应该做您想做的事,如您所见in this db fiddle(我修复了列名的问题并稍微更改了数据,因此很容易理解它正在工作)。所以问题要么出在你的数据上,要么出在你的查询过于简单。

不过,让我提出一个优化建议。由于您只需要表 switch 中的一列,因此您可以使用行限制相关子查询而不是连接:

select 
    coalesce(
        (
            select s.activated
            from switch s
            where s.user = 'SomeUsername' and s.botid = b.id
            order by s.date desc limit 1
        ),
        0
    ) as following,
    b.*
from bot

我希望这比带有连接和过滤的原始版本更有效。

如果你运行的是 MySQL 8.0,你可以使用row_number():

select coalesce(s.activated, 0) as following, b.*
from bot b
left join (
    select s.*, row_number() over(partition by botid order by date desc) rn
    from switch s
    where user = 'SomeUsername'
) s on s.botid = b.id and rn = 1

Demo on DB Fiddle.


如果您的 MySQL / MariaDB 版本不支持窗口函数,并且您需要来自switch 的多个列,那么您可以尝试对查询进行一些不同的表述,以查看问题是否仍然存在。考虑在left joinon 子句中进行过滤,如下所示:

SELECT ...
FROM bot b
LEFT JOIN switch s
    ON s.botid = b.id
    AND s.date = (
        SELECT MAX(s1.date)
        FROM switch s1
        WHERE s1.User = 'SomeUsername' AND s1.botid = s.botid
    )
WHERE b.Active = 1

【讨论】:

正如 Garr 所建议的,我可能遇到了 MariaDB 错误,因为我的查询在小提琴中工作。非常感谢您的回答,当然我正在寻找更好的性能,您的查询实际上并没有引起任何问题。 还有一个问题,如果我还需要从switch 检索另一列怎么办?以前从未使用过COALESCE,我不知道该怎么做,在互联网上找不到太多。 @DamiToma:如果您想要多个列,那么相关子查询是不够的(您需要为每一列重复该列)。我在答案中添加了另一个查询,它与您的原始查询执行相同的操作,但使用不同的技术 - 也许不会遇到错误。注意:COALESCE() 就像 IFNULL() 一样(但它是标准 SQL,而后者不是)。 非常感谢,这个答案非常有帮助。期待提高我的 SQL 技能!【参考方案2】:

在 Maria 10.5 上,您可以使用分析让您的生活更轻松:

WITH x AS (
  SELECT 
    COALESCE(s.Activated, 0) AS Following,
    b.ID, 
    b.Info, 
    ROW_NUMBER() OVER(PARTITION BY b.ID ORDER BY s.Date DESC) rn
  FROM 
    bots b
    LEFT JOIN switch s ON s.Bot = b.ID
  WHERE b.Active = 1
)

SELECT * FROM x WHERE rn = 1

这对您来说应该很容易编辑 - 从本质上讲,它是一个标准的左连接 bots:switch。有时你得到一个开关,有时没有。当你得到一个开关时,你会从 row_number 中得到最近的日期。如果您删除WHERE rn = 1,您会看到所有日期,但是有一个 rn 列,它是一个递增计数器,当机器人 ID 更改时从 1 重新开始。 rn = 1 行是您想要的,因为随着日期从最近到过去,计数器会递增

向这里添加更多列只是将它们添加到 WITH 块内的选择中的一种情况

COALESCE 没什么神奇的;它就像 IFNULL 但可以有很多参数。它从左到右返回第一个非空值。当与 2 个参数一起使用时,它就像 IFNULL,但它的好处是它适用于所有主要规范兼容的数据库,而 IFNULL/NVL/ISNULL 一直在更改名称


如果分析不可用,典型的“获取最新行”如下所示:

WITH switchlatest AS (
    SELECT s.*
    FROM
      switch s
      INNER JOIN (SELECT bot, MAX(date) maxd FROM switch GROUP BY bot) maxd
      ON s.bot = maxd.bot AND s.date = maxd.date

)

你可以像switch一样离开加入这个switchlatest

如果有多个日期与 true max 相同(它们都返回,rownum 路由只返回一个),它的行为与 rownum 有点不同

不支持 WITH 的数据库版本的一般模式

WITH alias AS 
(
   SELECT columns FROM ...
)
SELECT ... 
FROM 
  table 
  JOIN 
  alias 
  ON ...

您将“别名”一词复制到右括号之后:

(
   SELECT columns FROM ...
) alias

然后复制整个内容并将其粘贴到查询中的别名上:

SELECT ... 
FROM 
  table 
  JOIN 
  (
     SELECT columns FROM ...
  ) alias
  ON 
...

Or you can take the WITH and turn it into a view (Replace the WITH with CREATE VIEW) - might as well have it on hand without having to code it into every query,  because you clearly have a business need for "the latest switch" 

Starting to think it might be easier to just upgrade the db!

【讨论】:

非常感谢您的有用回答!请注意,我使用的是 MariaDB 10.1.25,而不是 10.5,请检查问题下方的 cmets。 哎呀。真可惜.. 我认为 10.2 引入了分析 我又补充了一点信息 @CaiusJard:with clause 也是在 Maria DB 10.2.1 中引入的......不过,您可以将子查询直接放在 from 子句中。

以上是关于MariaDB - 左连接不返回所有行的主要内容,如果未能解决你的问题,请参考以下文章

MySQL连接5种方式

左连接后缺少导航属性

MS Access:左连接不返回左表中的所有行

左连接不返回所有结果

左外连接不返回左表中的所有行?

MariaDB / MySQL:存在左连接时忽略索引提示