使用连接和偏移时,Doctrine会跳过数据集

时间:2011-05-27 18:52:03

标签: php symfony1 doctrine

我在PHP5.3上的Symfony 1.4中使用Doctrine 1.2。

我有一个数据库,其中事件有多个开始日期时间(1:n)和类型(n:m)。

对于一个程序,我想要一个开始时间和事件标题列表,它们属于特殊类型且不会过时:

$dql = Doctrine_Query::create()
  ->select('ed.start_datetime,e.title')/*edd.*,*/
  ->from('EventDate ed')
  ->leftJoin('ed.Event e')
  ->leftJoin('e.EventTypes et')
  ->leftJoin('e.Domain d')
  ->orderBy('ed.start_datetime, ed.event_id');
  ->where('e.start_datetime >= ? AND e3.id = ?',array('2011-05-27 17:19:41',2))
  ->fetchArray();

可以在pastebin.com/SdjvsaxW处查看架构。

现在,学说发表了两个陈述

SELECT DISTINCT e5.event_id FROM event_date e5 LEFT JOIN event e6 ON e5.event_id = e6.id LEFT JOIN event_event_type e8 ON (e6.id = e8.event_id) LEFT JOIN event_type e7 ON e7.id = e8.event_type_id WHERE (e5.start_datetime >= '2011-05-27 17:19:41' AND e7.id = '2') ORDER BY e5.start_datetime, e5.event_id

SELECT e.event_id AS e__event_id, e.start_datetime AS e__start_datetime, e2.id AS e2__id, e2.title AS e2__title FROM event_date e LEFT JOIN event e2 ON e.event_id = e2.id LEFT JOIN event_event_type e4 ON (e2.id = e4.event_id) LEFT JOIN event_type e3 ON e3.id = e4.event_type_id WHERE e.event_id IN (*all the IDs from the first query*) AND (e.start_datetime >= '2011-05-27 17:19:41' AND e3.id = '2') ORDER BY e.start_datetime, e.event_id

当我引入分页时问题开始:当我添加偏移时,它将被添加到第一个查询中。现在,由于事件的启动时间可能不止一个,因此第一个SELECT DISTINCT查询中的ID数可能小于日期数。因此,可以跳过日期。在我的情况下,甚至有不到50个不同的事件,所以显示事件日期51-100的第二个页面确实是空的,尽管有几百个事件日期(由于重复发生的事件)。

我认为这个问题可能会解决,但是我无法控制它。

有人有任何想法吗?

1 个答案:

答案 0 :(得分:3)

当你有一个基表并加入一对多关系时,Doctrine会以一种名为“limit subquery”的模式运行两个查询,第一个是不同的查询。这是因为您要选择连接的产品,并且最终会有比基表中的记录更多的行。例如,如果事件#5有两个EventType,则数据库将在两行中返回Event#5。这两个查询行为仅在MySQL中,其他数据库可以在子查询中运行限制,而Doctrine生成单个查询。

由于您的事件有多个日期,并且EventDate有一个复合主键,Doctrine应该在第一个查询中选择一组不同的(event_id,start_datetime),但它似乎忽略了复合主键的第二部分,可能是因为它无法将它们填充到第二个查询中的ON子句中。

你可以用两种或三种方式解决这个问题: 1)向EventDate(自动增量列)添加唯一的主键。您可能不必在其他地方引用它,但学说将开始使用它 2)手动执行2个查询。从EventDate中选择所有不同的event_id,start_datetime,添加限制和偏移量,然后以编程方式创建where子句:

 ... WHERE ((event_id = ? AND start_datetime = ?) OR (event_id = ? AND start_datetime = ?))... 

3)重新排序查询以从其他表开始或在select中添加DISTINCT。尝试选择distinct(ed.event_id,ed.start_datetime)。有时这些东西可以避免限制子查询算法被触发。 4)关掉这个系统,看下面的链接,从未尝试过这个,我不确定水合作用和学说对象缓存有什么副作用。

就个人而言,我会选择#1。我发现,使用Doctrine获得额外的主键列总是有帮助的,即使您可以跨多个列形成主键。

如果您想查看代码学说用于此目的,请参阅lib / Doctrine / Record.php,函数getLimitSubquery()。你会注意到评论“//复合键怎么样?”在代码中两次。您还可以阅读http://www.doctrine-project.org/documentation/manual/1_2/en/dql-doctrine-query-language:limit-and-offset-clauses:the-limit-subquery-algorithm