【问题标题】:Left/Right joins not returning nulls as expected左/右连接未按预期返回空值
【发布时间】:2017-09-05 07:49:00
【问题描述】:

我正在使用 MariaDB-10.2.6。

目前架构如下:

CREATE TABLE `daily_report` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `date` date DEFAULT NULL,
  `user_id` varchar(255) NOT NULL,
  CONSTRAINT `daily_report_user_id_fk` FOREIGN KEY (`user_id`) REFERENCES `user` 
  (`id`) ON DELETE SET NULL ON UPDATE CASCADE
)

CREATE TABLE `user` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `shall_create_every_day` tinyint(1) NOT NULL
)

CREATE TABLE `calendar` (
  `datefield` date
)

规则:

  1. shall_create_every_day 设置为 1 的用户应每天创建一个 daily_report
  2. 我有一个日历表 calendar,它包含从 2000-01-012020-12-31 的每个日期(因为 generate_series 在 MySQL/MariaDB 中不可用)

我尝试了以下查询:

select daily_report.id, daily_report.date, user.id, calendar.datefield
from calendar
left join daily_report on daily_report.date=calendar.datefield
right join user on daily_report.user_id=user.id and user.shall_create_every_day=1
where calendar.datefield='2017-08-02'

我尝试了一个日期范围,但出于测试目的,我将where 语句限制为仅一天。

它应该返回所有用户,shall_create_every_day,在'2017-08-12'没有创建任何daily_report。但我只得到那天创建的报告的组合。

我对左/右连接返回不匹配行的空值有什么误解吗?

【问题讨论】:

    标签: sql mariadb


    【解决方案1】:

    MariaDB 中提供了生成系列。见here。有点像

    SELECT '2000-01-01' + INTERVAL seq
        FROM seq_0_241
    

    您可以JOIN 这样做,而无需实际实现表格。

    请不要将LEFT JOINRIGHT JOIN 混用。这要么是问题的原因,要么是我忙于旋转而无法理解它。请记住,它可能会被解释为

    FROM a LEFT JOIN (b RIGHT JOIN c)
    

    这可能不是您想要的。在任何情况下,括号都是允许的(并且在混合LEFTRIGHT 时鼓励使用)。

    【讨论】:

      【解决方案2】:

      永远不要合并左右连接;这只是令人困惑。 (好吧,我们大多数人根本不使用右连接,因为它们比左连接更难阅读。)

      您的查询是这样的:

      1. 获取所有日历记录。
      2. 加入 daily_report 记录(如果有),否则创建一个空的虚拟记录来加入。
      3. 通过 shall_create_every_day = 1 获取所有用户记录。
      4. 如果可用,将第一个数据集加入用户,否则创建一个空的虚拟记录以加入用户。
      5. 通过应用 where 子句,忽略 (4) 中创建的所有外连接记录。

      所以首先你应该问自己:我需要外连接吗?关于用户和日历,您不知道。实际上,您想要从日历中检查是否存在“2017-08-02”条目。至于用户表:这也只是标准;您想要特定用户和特定日期的所有报告。

      因此,请从报告表中选择并确定它所属的条件:在您的 WHERE 子句中。

      select id, date, user_id
      from daily_report 
      where user_id in (select id from user where shall_create_every_day = 1)
      and date = (select datefield from calendar where datefield = '20170802');
      

      要显示日期和用户及其报告(包括丢失的报告),您需要交叉加入日历和用户并离开加入报告:

      select r.id, c.datefield, u.id as user_id
      from (select id from user where shall_create_every_day = 1) u
      cross join (select datefield from calendar where datefield = '20170802') c
      left join daily_report r on r.user_id = u.id and r.date = c.datefield;
      

      【讨论】:

      • 我刚刚发现同时使用左右连接是一个非常糟糕的主意。刚刚找到了一个可行的交叉连接和左连接的解决方案,稍后会发布答案。
      • 但是为什么要加入呢?如您所见,您不想要合并的结果;您只需要报告表中的数据。不要加入。
      • 或者您是否也想显示丢失的日期和用户报告?然后是的,交叉加入日历和用户并离开加入报告。
      • 是的。我想显示日期/用户的缺失报告。
      • 我已经添加了查询,你可以和你的比较一下。
      【解决方案3】:

      您的查询措辞有些复杂。我将通过将user 表左连接到包含每个用户每天的报告条目的第二个表来表达这一点。没有写入的用户的标志是给定的用户记录与第二个表中的任何内容都不匹配。

      select
          u.id
      from user u
      left join
      (
          select
              c.datefield,
              dr.me_id
          from calendar c
          left join daily_report dr
              on dr.date = c.datefield
          where c.datefield = '2017-08-02'
       ) cal
          on cal.me_id = u.id and
             u.shall_create_every_day = 1
      where
          cal.me_id is null;
      

      【讨论】:

      • 是的。这就是我要查询的内容,但您提供的查询仍然没有返回任何行。即使删除了daily_report.me_id is null,它所显示的只是任何字段中没有 null 的行。
      【解决方案4】:

      可以这样做。

      SELECT * 
      FROM daily_report 
      RIGHT JOIN (calendar c,user u) ON user_id=u.id AND date=c.datefield 
      WHERE c.datefield='2017-08-02' AND u.shall_create_every_day=1
      

      附:可能是表结构有错误。 daily_report.user_id 应该是 int 而不是 varchar

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-11-01
        • 1970-01-01
        • 1970-01-01
        • 2015-10-05
        • 2012-03-20
        • 1970-01-01
        • 2015-06-17
        相关资源
        最近更新 更多