两个相关表之间的SQL SELECT数据不在特定日期范围内

时间:2014-02-27 08:23:38

标签: mysql sql doctrine-orm

我有2个多对多的表,还有一个表来加入它们。

  • ID
  • 名称

报告

  • ID
  • performanceDate
  • 标题

report_officer

  • officer_id
  • REPORT_ID

我想选择所有未与报告相关联的人员或在特定时间范围内未与报告关联的人员。

到目前为止,我已尝试过以下内容(下面对我不起作用!):

SELECT * 
  FROM Officer 
       LEFT JOIN report_officer 
            ON Officer.id = report_officer.officer_id 
       LEFT JOIN Report 
            ON Report.id = report_officer.report_id
 WHERE (performanceDate IS NULL 
        OR performanceDate < "2014-03-23 00:00:00" 
        OR performanceDate > "2014-04-01 00:00:00"
        )

我的左连接查询仅在官员在特定时间范围内与报告关联时才有效,但在有多个报告后失败。

结果:

+------------+-----------------+
| officer_id | performanceDate |
+------------+-----------------+
|        130 | NULL            | # good

|        134 | 2014-03-02      | # bad - officer_id 134 has a performanceDate  
|        134 | 2014-03-09      | # on 2014-3-30, I do not want this in the results.
|        134 | 2014-03-16      | # 

|        135 | 2014-03-02      | # good
+------------+-----------------+

SQL小提琴: http://sqlfiddle.com/#!2/1bf72/3&lt; - 在sql小提琴中,请参阅我想要返回的列的'name'字段。

关于如何使这项工作的任何想法?

理想情况下,我希望尽可能简单地使用我的ORM。我正在使用doctrine并且不想开始使用完全自定义的代码(所以如果只使用连接可以完成,那就太好了)。我感觉不好,我需要一个子查询。

6 个答案:

答案 0 :(得分:4)

SELECT Officer.*, Report.performanceDate FROM Officer
LEFT JOIN report_officer ON Officer.id = report_officer.officer_id 
LEFT JOIN Report ON Report.id = report_officer.report_id
 AND
   (performanceDate > "2014-03-23 00:00:00" AND
    performanceDate < "2014-04-01 00:00:00")
WHERE Report.id IS NULL

您只想连接特定日期范围内的行,因此必须将约束移动到连接的on子句中并反转约束。

如果您想删除重复项,可以尝试group by

SELECT Officer.id, MAX(Report.performanceDate) FROM Officer
LEFT JOIN report_officer ON Officer.id = report_officer.officer_id 
LEFT JOIN Report ON Report.id = report_officer.report_id
 AND
   (performanceDate > "2014-03-23 00:00:00" AND
    performanceDate < "2014-04-01 00:00:00")
WHERE Report.id IS NULL
GROUP BY Officer.id

但是如果您所请求的日期范围内有多个效果日期(或者您可以使用GROUP_CONCAT收集所有日期),则必须决定要获得的日期。

更新

实际上我相对确定,LEFT JOIN根本无法实现你想要实现的目标......

总是有效的是子查询解决方案:

SELECT Officer.id as OfficerID, Officer.name,
Report.id as ReportID,
Report.performanceDate

FROM Officer
LEFT JOIN report_officer
  ON Officer.id = report_officer.officer_id 
LEFT JOIN Report
  ON Report.id = report_officer.report_id

WHERE Report.id IS NULL 
OR NOT EXISTS (
    SELECT * FROM report_officer
    INNER JOIN Report ON report_id = Report.id
    WHERE officer_id = Officer.id AND
      performanceDate > "2014-03-23 00:00:00" 
      AND performanceDate < "2014-04-01 00:00:00"
)

但这些并不是那么高效......这个看起来是否有报告禁止输出该行。

答案 1 :(得分:2)

  

我想选择所有与之无关的军官   报告或未与某个报告相关联的报告   时间表。

你的两个条件是多余的:如果一个官员没有被关联,那么他也不能在任何时间范围内被关联,并且将被第二个条件选中。如果他在时间范围内有报告,那么由于第二个条件他没有被选中,但他也至少有一个报告而且不能满足第一个报告。

所以你想要的是,“一名在时间框架内没有报告的军官”。

要这样做,只需反转条件:首先在选定的时间范围内获取的那些报告(即想要的那些人员);然后LEFT JOINOfficer,要求连接产生null。这将为您提供其他人员,即在所选时间范围内没有报告的人员(或者根本没有报告)。

但是,在这种情况下,不能有报告日期,因为您没有报告(对于那些根本没有报告的人员来说,这更为明显):

SELECT
   Officer.id as OfficerID,
   Officer.name,
   MAX(Report.id) as ReportID,
   MAX(performanceDate) AS performanceDate
FROM Officer
LEFT JOIN report_officer ON (Officer.id = report_officer.officer_id)
LEFT JOIN Report ON (Report.id = report_officer.report_id 
   AND performanceDate BETWEEN 20140323 AND 20140401)
GROUP BY Officer.id, Officer.name
HAVING ReportID IS NULL;

我不知道Doctrine和HAVING。如果你不能使用HAVING子句,你可以尝试通过运行它来模拟它,这应该是非常标准的:

SELECT
   Officer.id as OfficerID,
   Officer.name,
   COUNT(Report.id) as reports
FROM Officer
LEFT JOIN report_officer ON (Officer.id = report_officer.officer_id)
LEFT JOIN Report ON (Report.id = report_officer.report_id 
   AND performanceDate BETWEEN 20140323000000 AND 20140401235959)
GROUP BY Officer.id, Officer.name;

然后应用reports等于0的过滤器,即在给定的时间范围内没有报告。您可以添加MAX(performanceDate) AS performanceDate, MAX(Report.id) AS ReportID以获取在时间范围之外至少有一个的官员的(例如最新的)报告的日期。这可能不是您想要的报告。

指定日期范围时必须小心,因为YYYYMMDD通常等于YYYYMMDD000000,这可能导致相当于半包含范围。否则,请将BETWEEN替换为performanceDate >= '2014-03-23 00:00:00' AND performanceDate <= '2014-04-01 23:59:59'

答案 2 :(得分:1)

感谢大家对此问题的帮助。我的最终解决方案是使用GROUP BYHAVING子句。

@Iserni,我不需要SELECT在时间范围内有0个报告的官员,我能够SELECT所有在时间范围之外报告的人员或使用{{ 1}}。

这是我的最终代码:

HAVING

这样做的好处是,我可以利用performanceDate报告上次官员提交报告或报告他从未创建报告的时间。建议的所有其他解决方案都删除了在上次官员创建报告时检索有价值信息的功能。

答案 3 :(得分:0)

添加其他AND条件可能会解决您的问题。

AND performanceDate NOT BETWEEN "2014-03-23 00:00:00" AND "2014-04-01 00:00:00"

答案 4 :(得分:0)

或者您可以排除满足您条件的记录......

SELECT *
FROM OFFICER
WHERE ID NOT IN (SELECT OFFICER_ID
                 FROM REPORT_OFFICER)
   OR ID NOT IN (SELECT OFFICER_ID
                 FROM REPORT_OFFICER
                 WHERE performanceDate BETWEEN "2014-03-23 00:00:00" AND "2014-04-01 00:00:00")

答案 5 :(得分:0)

您可以使用如下所示的WHERE NOT EXISTS语句吗?

SELECT *
FROM Officer
WHERE NOT EXISTS
  (
    SELECT Report.ID
FROM 
  Report_Officer
   INNER JOIN 
  Report ON 
    Report_Officer.Report_ID = Report.ID
WHERE 
  Report_Officer.Officer_ID = Officer.ID AND
  Report.PerformanceDate BETWEEN "2014-03-23 00:00:00" AND "2014-04-01 00:00:00"
   )