0

(Before you mark this as duplicate, please read the question).

I have two different tables, Bot and Switch.

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

For each bot, I would like to retrieve its latest status, which means: for each bot, I would like to know whether latest row's Activated field was 1 or 0. In order to return a result for those bots who do not have an entry in the Switch table, I tried to use a LEFT JOIN statement. Here is the query:

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

My expected result is:

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

Point is that I'm not getting all rows, instead this query just return a single row:

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

This is strange since I'm using the LEFT JOIN. In order to understand what's going on, I separately ran

SELECT * 
FROM bot

and

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

Of course, the first query returns:

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

While the second one returns:

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

I really cannot understand why the LEFT JOIN is not keeping both Bot rows. I checked this question, but I'm not using any outer WHERE so it's not my case.
Thanks in advance!

4

2 回答 2

1

当您使用 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

这对您来说应该很容易编辑 - 从本质上讲,它是一个标准的左连接机器人:开关。有时你得到一个开关,有时没有。当你得到一个开关时,你会从 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

)

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

如果有多个日期与 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!
于 2020-08-28T19:14:42.840 回答
1

您的查询应该做您想做的事情,正如您在这个 db fiddle中看到的那样(我修复了列名的问题并稍微更改了数据,因此很容易理解它正在工作)。所以问题出在您的数据上,或者您的查询过于简单。

但是,让我提出一个优化建议。由于您只需要 table 中的一列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

DB Fiddle 上的演示


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

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
于 2020-08-28T18:27:14.297 回答