【问题标题】:How to return NULL values in MySQL query if no results in LEFT JOIN and WHERE clause are found如果在 LEFT JOIN 和 WHERE 子句中没有找到结果,如何在 MySQL 查询中返回 NULL 值
【发布时间】:2018-10-23 08:39:54
【问题描述】:

我的 MySQL 查询有问题。

这是我的三个表:

员工:

    id  initials  deleted  
------  --------  ---------
     1  TV                0
     2  AH                0
     3  JE                0
     4  MA                0
     5  MJ                0
     6  CE                0
     7  KB                1
     8  KL                1

时间表:

    id  place                         start                  end  deleted  
------  --------------  -------------------  -------------------  ---------
     1  Somewhere       2018-05-11 16:48:17  2018-05-15 16:48:26          0
     2  Somewhere else  2018-05-12 16:48:50  2018-05-14 16:48:55          1
     3  Here            2018-05-13 00:00:00  2018-05-13 00:00:00          0
     4  Not here        2018-05-18 16:49:42  2018-05-16 16:49:48          0

还有 schedule_link :

    id  id_employee  id_schedule  
------  -----------  -------------
     1            1              1
     2            1              2
     3            4              3
     4            5              4

我想要一个返回每个员工在当天日期相关的所有内容的请求。即使员工没有找到任何记录,我也希望查询在其他列中返回其首字母并带有 NULL。

这是我当前的查询:

SELECT
  `employees`.`id`,
  `employees`.`initials`,
  `schedule`.`place`,
  `schedule`.`start`,
  `schedule`.`end`,
  `schedule`.`deleted`
FROM
  `employees`
  LEFT JOIN `schedule_link`
    ON (
      `employees`.`id` = `schedule_link`.`id_employee`
    )
  LEFT JOIN `schedule`
    ON (
      `schedule_link`.`id_schedule` = `schedule`.`id`
    )
WHERE (
    `employees`.`deleted` = '0'
    AND `schedule`.`deleted` = '0'
  )
  AND (
    DATE(CURDATE()) BETWEEN CAST(schedule.start AS DATE)
    AND CAST(schedule.end AS DATE)
  )

这会返回以下数据:

    id  initials  place                    start                  end  deleted  
------  --------  ---------  -------------------  -------------------  ---------
     1  TV        Somewhere  2018-05-11 16:48:17  2018-05-15 16:48:26       0
     4  MA        Here       2018-05-13 00:00:00  2018-05-13 00:00:00       0

没错,但我想要的是以下结果:

    id  initials  place                    START                  END  deleted  
------  --------  ---------  -------------------  -------------------  ---------
     1  TV        Somewhere  2018-05-11 16:48:17  2018-05-15 16:48:26          0
     4  MA        Here       2018-05-13 00:00:00  2018-05-13 00:00:00          0
     2  AH        NULL       NULL                 NULL                         0
     3  JE        NULL       NULL                 NULL                         0   
     5  MJ        NULL       NULL                 NULL                         0        
     6  CE        NULL       NULL                 NULL                         0

是否可以通过单个请求获得此结果?

提前感谢您的帮助。

【问题讨论】:

    标签: mysql sql database left-join inner-join


    【解决方案1】:

    您需要将条件放在on 子句中除第一个表之外的所有表上。您的 where 子句正在将外连接变为内连接。

    我还有一些其他建议:

    SELECT e.id, e.initials, s.place, s.start, s.end, s.deleted
    FROM employees e LEFT JOIN
         schedule_link sl
         ON e.id = sl.id_employee LEFT JOIN
         schedule s
         ON sl.id_schedule = s.id AND s.deleted = 0 AND
            CURDATE() BETWEEN CAST(s.start AS DATE) AND CAST(s.end AS DATE)
    WHERE e.deleted = 0;
    

    注意事项:

    • 表别名使查询更易于编写和阅读。
    • 反引号只会使查询更难读写。
    • 不要使用startend 作为列名(即,如果可以的话,重命名它们)。它们是关键字(虽然不是保留的),因此它们在 SQL 语句中还有其他用途。
    • 我猜deleted 是数字。不要使用单引号进行比较(除非该列确实是一个字符串)。
    • CURDATE() 已经是一个日期。无需转换。
    • 我不建议将BETWEEN 与日期一起使用,因为可能会出现延迟时间组件。但是,您使用的是显式转换,因此代码会明确执行您想要的操作(可能存在不使用可用索引的风险)。

    编辑:

    我明白了。因为日期条件在第三个表中,而不是第二个表中,所以您会得到重复的行。我认为这将解决您的问题:

    SELECT e.id, e.initials, ss.place, ss.start, ss.end, ss.deleted
    FROM employees e LEFT JOIN
         (SELECT sl.id_employee, s.*
          FROM schedule_link sl JOIN
               schedule s
               ON sl.id_schedule = s.id AND s.deleted = 0
          WHERE CURDATE() BETWEEN CAST(s.start AS DATE) AND CAST(s.end AS DATE)
          ) ss
          ON e.id = ss.id_employee
    WHERE e.deleted = 0;
    

    这将包括在时间范围内没有匹配的每个员工恰好一次。如果有多个匹配项,您仍然会从schedule 获得每条记录。

    您实际上可以在没有子查询的情况下表达这一点:

    SELECT e.id, e.initials, s.place, s.start, s.end, s.deleted
    FROM employees e LEFT JOIN
         (schedule_link sl JOIN
          schedule s
          ON sl.id_schedule = s.id AND s.deleted = 0 AND
            CURDATE() BETWEEN CAST(s.start AS DATE) AND CAST(s.end AS DATE)
         )
         ON e.id = sl.id_employee
    WHERE e.deleted = 0;
    

    我发现子查询版本更容易理解。

    【讨论】:

    • 有效!感谢您的建议,我会改进我的代码!
    • 它可以工作,但我只会有一点额外的需求,是否有可能每个员工只返回一个“NULL”行而没有结果?因为如果员工在日期之外还有其他记录,我每次都会多出一行。它可以工作,但我不需要所有的行。每位员工只有一个,没有记录。
    • @乔尔。 . .只需切换到JOIN。如果是这种情况,您不需要外连接。
    • 我正在尝试...但我还没有结果。
    • JOIN 似乎不起作用。我希望每个员工有一行 NULL 没有记录,如果找到员工的记录,则无需返回带有“NULL”的行。我累了:))
    【解决方案2】:

    我的一般看法(因为我不在手机 atm 上发帖,所以没有测试查询):

    1. 您应该在第二次加入查询时使用RIGHT JOIN

    2. 您还需要额外的 WHERE 子句,即where employees.id is not null

    3. 不要忘记将ifull() 用于调度表中的所有字段,即ifnull(schedule_link.id, schedule_link.id, schedule.myfield) 请注意,对于您要显示的来自此查询中schedule 表的所有字段,都应该这样做

    希望这些指南对您有所帮助。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-03-25
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多