在CakePHP中选择包含事件的所有事件 - >计划 - >开始日期和结束日期之间的日期

时间:2011-06-16 14:04:47

标签: mysql cakephp associations cakephp-1.3

试图找出如何在CakePHP中构建查询,我可以在其中选择X和Y日期之间的所有事件(用户输入的日期)。

问题在于Event在表格中没有日期。

Event hasMany Schedule
Schedule belongsTo Event

Schedule hasMany Date
Date belongsTo Schedule
  • Events table:活动详情 - 姓名,地点,说明等
  • Schedules table:重复选项的开始和结束日期
  • Dates table:根据Schedules
  • 中的数据创建的事件的实际日期

所以 - 我实际上需要选择在X和Y日期之间至少有一个Date条目的任何事件。

我还需要能够使用事件数据显示日期。


修改(修订):

我试过这个,但它似乎是在检查事件而不考虑日期,但只检索日期信息,如果日期在范围内:

$this->Event->Behaviors->attach('Containable');
$events = $this->Event->find('all', array(
    'limit'=>5,
    'order'=>'Event.created DESC',
    'contain' => array(
    'Schedule' => array(
        'fields'=>array(),
        'Date' => array(
            'conditions'=>array(
                'start >=' => $start_date,
                'start <=' => $end_date,
                )
            )
        )
    ),
));

*只是为了澄清 - Date.start和Date.end总是相同的日期 - 它们还包括一个时间(两个日期时间字段) - 因此我为什么要检查两者的“开始”。


我尝试使用可包含的,我尝试过unbind / bindModel..etc - 我必须做错事或偏离轨道。

要记住的事情 - 一旦我弄清楚如何根据日期获取事件,我还需要添加其他条件,如事件类型等等 - 不确定这是否会影响答案


更新

这是我正在使用的似乎工作 - 似乎也很丑 - 任何想法?:

function getEvents($opts = null) {
    //$opts = limit, start(date), end(date), types, subtypes, subsubtypes, cities

    $qOpts['conditions'] = array();

    //dates
    $qOpts['start'] = date('Y-m-d') . ' 00:00:00';
    if(isset($opts['start'])) $qOpts['start'] = $opts['start'];

    $qOpts['end'] = date('Y-m-d') . ' 23:59:59';
    if(isset($opts['end'])) $qOpts['end'] = $opts['end'];

    //limit
    $qOpts['limit'] = 10;
    if(isset($opts['limit'])) $qOpts['limit'] = $opts['limit'];

    //fields
    //$qOpts['fields'] = array('Event.id', 'Event.name', 'Event.slug', 'City.name', 'Date.start');  
    // if(isset($opts['fields'])) $qOpts['fields'] = $opts['fields'];


    //date conditions
    array_push($qOpts['conditions'], array(
        "Date.start >=" => $qOpts['start'],
        "Date.start <=" => $qOpts['end'],
    ));

    //cities conditions
    if(isset($opts['cities'])) {
        if(is_array($opts['cities'])) {
            $cityConditions['OR'] = array();
            foreach($opts['cities'] as $city_id) {
                array_push($cityConditions['OR'], array('OR'=>array('Venue.city_id'=>$city_id, 'Restaurant.city_id'=>$city_id)));
            }
            array_push($qOpts['conditions'], $cityConditions);
        }
    }

    //event types conditions
    //$opts['event_types'] = array('1');
    if(isset($opts['event_types'])) {
        if(is_array($opts['event_types'])) {
            $eventTypeConditions['OR'] = array();
            foreach($opts['event_types'] as $event_type_id) {
                array_push($eventTypeConditions['OR'], array('EventTypesEvents.event_type_id' => $event_type_id));
            }
            array_push($qOpts['conditions'], $eventTypeConditions);
        }
    }

    //event sub types conditions
    if(isset($opts['event_sub_types'])) {
        if(is_array($opts['event_sub_types'])) {
            $eventSubTypeConditions['OR'] = array();
            foreach($opts['event_sub_types'] as $event_sub_type_id) {
                array_push($eventSubTypeConditions['OR'], array('EventSubTypesEvents.event_sub_type_id' => $event_sub_type_id));
            }
            array_push($qOpts['conditions'], $eventSubTypeConditions);
        }
    }

    //event sub sub types conditions
    if(isset($opts['event_sub_sub_types'])) {
        if(is_array($opts['event_sub_sub_types'])) {
            $eventSubSubTypeConditions['OR'] = array();
            foreach($opts['event_sub_sub_types'] as $event_sub_sub_type_id) {
                array_push($eventSubSubTypeConditions['OR'], array('EventSubSubTypesEvents.event_sub_sub_type_id' => $event_sub_sub_type_id));
            }
            array_push($qOpts['conditions'], $eventSubSubTypeConditions);
        }
    }


    $this->recursive = 2;

    $data = $this->find('all', array(
        'contain' => array(
            'Restaurant' => array(
                'fields' => array('id', 'name', 'slug', 'address', 'GPS_Lon', 'GPS_Lat', 'city_id'),
                'City' => array(
                    'fields' => array('id', 'name', 'url_name'),
                ),
            ),
            'Venue' => array(
                'fields' => array('id', 'name', 'slug', 'address', 'GPS_Lon', 'GPS_Lat', 'city_id'),
                'City' => array(
                    'fields' => array('id', 'name', 'url_name')
                )
            ),
            'Schedule' => array(
                'fields' => array('id', 'name'),
                'Date' => array(
                    'fields' => array('start', 'end'),
                    'conditions' => array(
                        'Date.start >=' => $qOpts['start'],
                        'Date.start <=' => $qOpts['end'],
                    ),
                ),
            ),
            'EventType' => array(
                'fields' => array('id', 'name', 'slug'),
            ),
            'EventSubType' => array(
                'fields' => array('id', 'name', 'slug'),
            ),
            'EventSubSubType' => array(
                'fields' => array('id', 'name', 'slug'),
            ),
        ),
        'joins' => array(
            array(
                'table' => $this->Schedule->table,
                'alias' => 'Schedule',
                'type' => 'INNER',
                'foreignKey' => false,
                'conditions' => array(
                    'Schedule.event_id = Event.id',
                ),
            ),
            array(
                'table' => $this->Schedule->Date->table,
                'alias' => 'Date',
                'type' => 'INNER',
                'foreignKey' => false,
                'conditions' => array(
                    'Date.schedule_id = Schedule.id',
                ),
            ),
            array(
                'table' => $this->EventTypesEvent->table,
                'alias' => 'EventTypesEvents',
                'type' => 'INNER',
                'foreignKey' => false,
                'conditions' => array(
                    'EventTypesEvents.event_id = Event.id',
                ),
            ),
            array(
                'table' => $this->EventSubTypesEvent->table,
                //'table' => 'event_sub_types_events',
                'alias' => 'EventSubTypesEvents',
                'type' => 'INNER',
                'foreignKey' => false,
                'conditions' => array(
                    'EventSubTypesEvents.event_id = Event.id',
                ),
            ),
            array(
                'table' => $this->EventSubSubTypesEvent->table,
                'alias' => 'EventSubSubTypesEvents',
                'type' => 'INNER',
                'foreignKey' => false,
                'conditions' => array(
                    'EventSubSubTypesEvents.event_id = Event.id',
                ),
            ),
        ),
        'conditions' => $qOpts['conditions'],
        'limit' => $qOpts['limit'],
        'group' => 'Event.id'
    ));
    return $data;
}

4 个答案:

答案 0 :(得分:3)

在这种情况下,我倾向于不使用Cake的关联或Containable,并自己制作连接:

$events = $this->Event->find('all', array(
    'joins'=>array(
        array(
            'table' => $this->Schedule->table, 
            'alias' => 'Schedule', 
            'type' => 'INNER', 
            'foreignKey' => false,
            'conditions'=> array(
                'Schedule.event_id = Event.id',
            ),
        ),
        array(
            'table' => $this->Date->table, 
            'alias' => 'Date', 
            'type' => 'INNER', 
            'foreignKey' => false,
            'conditions'=> array(
                'Date.schedule_id = Schedule.id',
            ),
        ),
    ),
    'conditions'=>array(
        'Date.start >=' => $start_date,
        'Date.start <=' => $end_date,
    ),
    'order'=>'Event.created DESC',
    'limit'=>5
));

它有点粗糙,但会产生我想要的确切查询。

<强>更新

让我们分解您的代码,看看我们可以改进它的位置。第一部分是find的准备工作。我已经重写了你的代码,试图缩短它,这就是我提出的:

// Default options go here
$defaultOpts = array(
    'start' => date('Y-m-d') . ' 00:00:00',
    'end' => date('Y-m-d') . ' 23:59:59',
    'limit' => 10
)

// Use default options if nothing is passed, otherwise merge passed options with defaults
$opts = is_array($opts) ? array_merge($defaultOpts, $opts) : $defaultOpts;

// Initialize array to hold query conditions
$conditions = array();

//date conditions
$conditions[] = array(
    "Date.start >=" => $qOpts['start'],
    "Date.start <=" => $qOpts['end'],
));

//cities conditions
if(isset($opts['cities']) && is_array($opts['cities'])) {
    $conditions['OR'] = array();
    $conditions['OR'][] = array('Venue.city_id'=>$opts['cities']);
    $conditions['OR'][] = array('Restaurant.city_id'=>$opts['cities']);
}

//event types conditions
//$opts['event_types'] = array('1');
if(isset($opts['event_types']) && is_array($opts['event_types'])) {
    $conditions[] = 'EventTypesEvents.event_type_id' => $opts['event_types']
}

//event sub types conditions
if(isset($opts['event_sub_types']) && is_array($opts['event_sub_types'])) {
    $conditions[] = 'EventSubTypesEvents.event_sub_type_id' => $opts['event_sub_types']
}

//event sub sub types conditions
if(isset($opts['event_sub_types']) && is_array($opts['event_sub_sub_types'])) {
    $conditions[] = 'EventSubSubTypesEvents.event_sub_sub_type_id' => $opts['event_sub_sub_types']
}

请注意,我删除了大部分的OR。那是因为您可以将数组作为conditions中的值传递,而Cake将使其成为SQL查询中的IN(...)语句。例如:'Model.field' => array(1,2,3)生成'Model.field IN (1,2,3)'。这就像OR一样,但需要的代码更少。因此,上面的代码块与您的代码完全相同,但它更短。

现在出现了复杂的部分,find本身。

通常我会建议单独使用强制连接,不使用Containable,并使用'recursive'=>false。我相信这个通常是处理复杂发现的最佳方式。使用Associations和Containable,Cake会针对数据库运行多个SQL查询(每个Model /表一个查询),这往往效率低下。此外,Containable并不总是返回预期的结果(正如您在尝试时所注意到的那样)。

但是因为在你的情况下涉及四个复杂关联,也许混合方法将是理想的解决方案 - 否则,清理起来会太复杂重复的数据。 (4个复杂的关联是:事件有很多日期[通过事件有多个时间表,时间表有多个日期],事件HABTM事件类型,事件HABTM事件类型,事件HABTM事件子类型子事件)。因此,我们可以让Cake处理EventType,EventSubType和EventSubSubType的数据检索,避免重复过多。

所以这就是我的建议:使用连接进行所有必需的过滤,但不要在字段中包含Date和[Sub [Sub]]类型。由于您具有模型关联,Cake将自动对DB运行额外查询以获取这些数据位。不需要包含。

代码:

// We already fetch the data from these 2 models through
// joins + fields, so we can unbind them for the next find,
// avoiding extra unnecessary queries. 
$this->unbindModel(array('belongsTo'=>array('Restaurant', 'Venue'));

$data = $this->find('all', array(
    // The other fields required will be added by Cake later
    'fields' => "
        Event.*, 
        Restaurant.id, Restaurant.name, Restaurant.slug, Restaurant.address, Restaurant.GPS_Lon, Restaurant.GPS_Lat, Restaurant.city_id,
        Venue.id, Venue.name, Venue.slug, Venue.address, Venue.GPS_Lon, Venue.GPS_Lat, Venue.city_id,
        City.id, City.name, City.url_name
    ",  
    'joins' => array(
        array(
            'table' => $this->Schedule->table,
            'alias' => 'Schedule',
            'type' => 'INNER',
            'foreignKey' => false,
            'conditions' => 'Schedule.event_id = Event.id',
        ),
        array(
            'table' => $this->Schedule->Date->table,
            'alias' => 'Date',
            'type' => 'INNER',
            'foreignKey' => false,
            'conditions' => 'Date.schedule_id = Schedule.id',
        ),
        array(
            'table' => $this->EventTypesEvent->table,
            'alias' => 'EventTypesEvents',
            'type' => 'INNER',
            'foreignKey' => false,
            'conditions' => 'EventTypesEvents.event_id = Event.id',
        ),
        array(
            'table' => $this->EventSubSubTypesEvent->table,
            'alias' => 'EventSubSubTypesEvents',
            'type' => 'INNER',
            'foreignKey' => false,
            'conditions' => 'EventSubSubTypesEvents.event_id = Event.id',
        ),
        array(
            'table' => $this->Restaurant->table,
            'alias' => 'Restaurant',
            'type' => 'LEFT',
            'foreignKey' => false,
            'conditions' => 'Event.restaurant_id = Restaurant.id',
        ),
        array(
            'table' => $this->City->table,
            'alias' => 'RestaurantCity',
            'type' => 'LEFT',
            'foreignKey' => false,
            'conditions' => 'Restaurant.city_id = city.id',
        ),
        array(
            'table' => $this->Venue->table,
            'alias' => 'Venue',
            'type' => 'LEFT',
            'foreignKey' => false,
            'conditions' => 'Event.venue_id = Venue.id',
        ),
        array(
            'table' => $this->City->table,
            'alias' => 'VenueCity',
            'type' => 'LEFT',
            'foreignKey' => false,
            'conditions' => 'Venue.city_id = city.id',
        ),
    ),
    'conditions' => $conditions,
    'limit' => $opts['limit'],
    'recursive' => 2
));

我们删除了contains,并且因为它而运行了一些额外的查询。大多数联接都是INNER类型。这意味着在连接中涉及的两个表上必须至少存在一条记录,否则您将得到的结果会比您预期的要少。我假设每个活动都在一个餐厅一个地点进行,但不是两个,这就是为什么我使用LEFT表示这些表(和城市)的原因。如果联接中使用的某些字段是可选的,则应在相关联接上使用LEFT而不是INNER

如果我们在这里使用'recursive'=>false,我们仍然会得到正确的事件,并且没有数据重复,但是日期和[Sub [Sub]]类型将会丢失。通过2级递归,Cake将自动遍历返回的事件,并且对于每个事件,它将运行必要的查询以获取关联的模型数据。

这几乎就是你在做什么,但是没有包含,并且有一些额外的调整。我知道它仍然是一段冗长,丑陋和无聊的代码,但毕竟有13个数据库表...

这是所有未经测试的代码,但我相信它应该可行。

答案 1 :(得分:1)

您可以尝试以下内容,假设Cake 1.3并使用可包含的行为。我假设您的表格中的日期字段名为start_dateend_date,因此可能需要调整这些条件。

如果您拥有大量数据,这可能会或可能不会起作用,并且可能会产生大量查询,因此可能需要进一步细化 - 当然选择了字段。

您可以发布生成的查询,如果它运行,我们可以帮助进一步调整它。

/* in your EventController (the method) for on the fly */
$this->Event->Behaviors->attach('Containable');

/* Your dates */
$x_date = '2011-06-01';
$y_date = '2011-07-01';

$this->paginate = array(
            'limit'=>10,
            'order'=>'Event.created DESC',
            'contain'=>array(
                'Schedule',
                'Event'=>array(
                    'conditions'=>array('Event.start_date'=>$x_date,
                                        'Event.end_date'<=$y_date)
                )
            ),
        );

$this->set('events',$this->Paginate('Event'));

// print_r($events);

答案 2 :(得分:1)

GROUP_CONCAT救援!!!长话短说 - 我需要用很多日期返回事件(能够查询不同的HABTM表) - 但是当我尝试时,我要么得到太多事件(每个日期一个等等)或者我会使用GROUP BY,而不是所有日期。答案......仍然使用GROUP BY,但使用GROUP_CONCAT将日期合并到一个字段中:

$qOpts['fields'] = array(
        ...
        'GROUP_CONCAT(Date.start, "|", Date.end ORDER BY Date.start ASC SEPARATOR "||") AS EventDates'
    );

我发布了很多代码 - 如果你像我一样陷入困境,请随意浏览。

我学到的东西:

  • 不建议使用包含和加入 - 选择一个并坚持使用它 - 这是我存在的一点点的祸根 - 我会得到一些工作,但后来没有w / pagination..etc等< / LI>
  • 如果您需要根据HABTM数据进行查询,请选择加入,不包含
  • 我的加入工作得很好,但我会得到同样的事件10次(每个日期存在1次)
  • 但是当我尝试GROUP BY时,它将它们组合起来,所以我只有1个约会,当我真的需要所有日期时
  • GROUP_CONCAT很棒(之前从未听说过)

希望这有助于某人。随意指出我的代码有任何问题 - 我总是希望改进。但是现在,我在圈子里跳舞,因为它起作用!现在它起作用了,我将回去并尝试清理像@bfavaretto提到的那些OR。

    //returns events based on category, subcategory, and start/end datetimes
function getEvents($opts = null) {
    //$opts = limit, start(date), end(date), types, subtypes, subsubtypes, cities, paginate(0,1), venues, excludes(event ids)

    $qOpts['conditions'] = array();

    //order
    $qOpts['order'] = 'Date.start ASC';
    if(isset($opts['order'])) $qOpts['order'] = $opts['order'];

    //dates
    $qOpts['start'] = date('Y-m-d') . ' 00:00:00';
    if(isset($opts['start'])) $qOpts['start'] = $opts['start'];

    //limit
    $qOpts['limit'] = 10;
    if(isset($opts['limit'])) $qOpts['limit'] = $opts['limit'];

    //event excludes (example: when you want "other events at this venue", you need to exclude current event)
    if(isset($opts['excludes'])) {
        if(is_array($opts['excludes'])) {
            foreach($opts['excludes'] as $exclude_id) {
                array_push($qOpts['conditions'], array('Event.id <>' => $exclude_id));
            }
        }
    }

    //approval status conditions
    if(!isset($opts['approval_statuses'])) $opts['approval_statuses'] = array('1'); //default 1 = approved
    if(isset($opts['approval_statuses'])) {
        if(is_array($opts['approval_statuses'])) {
            $approvalStatusesConditions['OR'] = array();
            foreach($opts['approval_statuses'] as $status) {
                array_push($approvalStatusesConditions['OR'], array('Event.approval_status_id' => $status));
            }
            array_push($qOpts['conditions'], $approvalStatusesConditions);
        }
    }

    //date conditions
    $date_conditions = array();
    array_push($qOpts['conditions'], array('Date.start >=' => $qOpts['start']));
    array_push($date_conditions, array('Date.start >=' => $qOpts['start']));

    if(isset($opts['end'])) {
        array_push($qOpts['conditions'], array('Date.start <=' => $opts['end']));
        array_push($date_conditions, array('Date.start <=' => $opts['end']));
    }


    //venues conditions
    if(isset($opts['venues'])) {
        if(is_array($opts['venues'])) {
            $venueConditions['OR'] = array();
            foreach($opts['venues'] as $venue_id) {
                array_push($venueConditions['OR'], array('OR'=>array('Venue.id'=>$venue_id)));
            }
            array_push($qOpts['conditions'], $venueConditions);
        }
    }

    //cities conditions
    if(isset($opts['cities'])) {
        if(is_array($opts['cities'])) {
            $cityConditions['OR'] = array();
            foreach($opts['cities'] as $city_id) {
                array_push($cityConditions['OR'], array('OR'=>array('Venue.city_id'=>$city_id, 'Restaurant.city_id'=>$city_id)));
            }
            array_push($qOpts['conditions'], $cityConditions);
        }
    }

    //event types conditions
    if(isset($opts['event_types'])) {
        if(is_array($opts['event_types'])) {
            $eventTypeConditions['OR'] = array();
            foreach($opts['event_types'] as $event_type_id) {
                array_push($eventTypeConditions['OR'], array('EventTypesEvents.event_type_id' => $event_type_id));
            }
            array_push($qOpts['conditions'], $eventTypeConditions);
        }
    }

    //event sub types conditions
    if(isset($opts['event_sub_types'])) {
        if(is_array($opts['event_sub_types'])) {
            $eventSubTypeConditions['OR'] = array();
            foreach($opts['event_sub_types'] as $event_sub_type_id) {
                array_push($eventSubTypeConditions['OR'], array('EventSubTypesEvents.event_sub_type_id' => $event_sub_type_id));
            }
            array_push($qOpts['conditions'], $eventSubTypeConditions);
        }
    }

    //event sub sub types conditions
    if(isset($opts['event_sub_sub_types'])) {
        if(is_array($opts['event_sub_sub_types'])) {
            $eventSubSubTypeConditions['OR'] = array();
            foreach($opts['event_sub_sub_types'] as $event_sub_sub_type_id) {
                array_push($eventSubSubTypeConditions['OR'], array('EventSubSubTypesEvents.event_sub_sub_type_id' => $event_sub_sub_type_id));
            }
            array_push($qOpts['conditions'], $eventSubSubTypeConditions);
        }
    }


    //joins
    $qOpts['joins'] = array();

    //Restaurants join
    array_push($qOpts['joins'], array(
            'table' => $this->Restaurant->table,
            'alias' => 'Restaurant',
            'type' => 'LEFT',
            'foreignKey' => false,
            'conditions' => array(
                'Restaurant.id = Event.restaurant_id',
            ),
        )
    );

    //Venues join
    array_push($qOpts['joins'], array(
            'table' => $this->Venue->table,
            'alias' => 'Venue',
            'type' => 'LEFT',
            'foreignKey' => false,
            'conditions' => array(
                'Venue.id = Event.venue_id',
            ),
        )
    );

    //Schedules join
    array_push($qOpts['joins'], array(
            'table' => $this->Schedule->table,
            'alias' => 'Schedule',
            'type' => 'INNER',
            'foreignKey' => false,
            'conditions' => array(
                'Schedule.event_id = Event.id',
            ),
        )
    );

    //Dates join
    array_push($qOpts['joins'], array(
        'table' => $this->Schedule->Date->table,
        'alias' => 'Date',
        'type' => 'INNER',
        'foreignKey' => false,
        'conditions' => array(
            'Date.schedule_id = Schedule.id',
            //$date_conditions
        ),
    ));

    //Uploads join
    array_push($qOpts['joins'], array(
            'table' => $this->Upload->table,
            'alias' => 'Upload',
            'type' => 'LEFT',
            'foreignKey' => false,
            'conditions' => array(
                'Upload.event_id = Event.id',
            ),
        )
    );

    //Event types join
    if(isset($opts['event_types'])) {
        if(is_array($opts['event_types'])) {
            array_push($qOpts['joins'], array(
                'table' => $this->EventTypesEvent->table,
                'alias' => 'EventTypesEvents',
                'type' => 'INNER',
                'foreignKey' => false,
                'conditions' => array(
                    'EventTypesEvents.event_id = Event.id',
                ),
            ));
        }
    }
    if(isset($opts['event_sub_types'])) {
        if(is_array($opts['event_sub_types'])) {
            array_push($qOpts['joins'], array(
                'table' => $this->EventSubTypesEvent->table,
                'alias' => 'EventSubTypesEvents',
                'type' => 'INNER',
                'foreignKey' => false,
                'conditions' => array(
                    'EventSubTypesEvents.event_id = Event.id',
                ),
            ));
        }
    }
    if(isset($opts['event_sub_sub_types'])) {
        if(is_array($opts['event_sub_sub_types'])) {
            array_push($qOpts['joins'], array(
                'table' => $this->EventSubSubTypesEvent->table,
                'alias' => 'EventSubSubTypesEvents',
                'type' => 'INNER',
                'foreignKey' => false,
                'conditions' => array(
                    'EventSubSubTypesEvents.event_id = Event.id',
                ),
            ));
        }
    }

    $qOpts['fields'] = array(
        'Event.*',
        'Venue.id', 'Venue.slug', 'Venue.name', 'Venue.GPS_Lon', 'Venue.GPS_Lat',
        'Restaurant.id', 'Restaurant.slug', 'Restaurant.name', 'Restaurant.GPS_Lat', 'Restaurant.GPS_Lon',
        'GROUP_CONCAT(Date.start, "|", Date.end ORDER BY Date.start ASC SEPARATOR "||") AS EventDates'
    );

    //group by
    $qOpts['group'] = 'Event.id';

    //you need to set the recursion to -1 for this type of join-search
    $this->recursive = -1;


    $paginate = false;
    if(isset($opts['paginate'])) {
        if($opts['paginate']) {
            $paginate = true;
        }
    }

    //either return the options just created (paginate)
    if($paginate) {
        return $qOpts;

    //or return the events data
    } else {
        $data = $this->find('all', $qOpts);
        return $data;
    }

}

答案 3 :(得分:0)

戴夫,你有一个相当复杂的问题需要解决,这需要的不仅仅是基本的蛋糕。你必须了解正在发生的事情才能解决问题。我假设你没有太多的SQL经验,并且不太了解'底层'蛋糕。所以我将尝试解释这里的基础知识。

考虑你有两个表,叫做'main'和'related':

main             related

id | val         id | main_id | val
1  | A           1  | 1       | Foo
2  | B           2  | 1       | FooBar
3  | C           3  | 2       | Bar
4  | D           4  | 3       | BarFoo

在Cake中,您将拥有Main和Related模型来处理它们。 Main hasMany RelatedRelated belongsTo Main。现在,您执行以下操作(从Main中的方法):

$data = $this->find('all', array(
    'recursive' => 1
));

这是Cake将在幕后做的事情:

  1. 检索表'main'

    中的所有行
    SELECT * FROM main
    
  2. 根据结果,Cake将构建一个ID数组,然后将用于获取相关模型的数据。这些数据将使用如下查询从MySQL获取:

    SELECT * FROM related WHERE main_id IN ([comma_separated_list_of_ids_here])
    
  3. 最后,Cake将从Main循环遍历结果数组,并根据需要将相关数据添加到每一行。完成后,它将返回“装饰”数组。

  4. 有时,根据关联类型,Cake会为为主模型检索的每一行执行额外的SQL查询。那可能真的很慢。解决方案是使用单个查询从两个表中获取数据,这就是JOIN的用途。问题在于数据重复。例如:

    SELECT Main.*, Related.*
    FROM main as Main
    INNER JOIN related AS Related
    ON Related.main_id = main.id
    

    结果:

        Main.id | Main.val | Related.id | Related.main_id | Related.val
        1       | A        | 1          | 1               | Foo
        1       | A        | 2          | 1               | FooBar
        2       | B        | 3          | 2               | Bar
        3       | C        | 4          | 3               | BarFoo
    

    注意事项:

    1. 我们有2行Main.id = 1.它们之间的区别在于Related.id和Related.val。如果从SELECT子句中删除这些列,则重复将消失。如果您需要在相关表上添加条件,这非常有用。例如:

      SELECT DISTINCT Main.*
      FROM main as Main
      INNER JOIN related AS Related
      ON Related.main_id = main.id
      WHERE Related.val LIKE '%Foo%'
      

      给出:

    2.    
              Main.id | Main.val
              1       | A       
              3       | C     
      

      在我们的条件(Foo和FooBar)上实际上有两行匹配,但A在结果中只显示一次,因为我们没有要求SQL显示Related.val,并且还告诉它忽略确切重复(使用DISTINCT)。

      1. 在原始结果中,缺少来自Main的D项!那是因为我们使用了INNER JOIN,它将结果限制为Main的行,这些行在Related上也有一个或多个相应的行。如果我们使用LEFT JOIN,则结果会有一个额外的行,如下所示:
      2.     Main.id | Main.val | Related.id | Related.main_id | Related.val
            1       | A        | 1          | 1               | Foo
            1       | A        | 2          | 1               | FooBar
            2       | B        | 3          | 2               | Bar
            3       | C        | 4          | 3               | BarFoo
            4       | D        | NULL       | NULL            | NULL
        

        (如果您需要有关INNER与LEFT JOIN的更多详细信息,请参阅here)。 (编辑:链接已更新)

        回到重复项:使用PHP中的简单foreach循环很容易清理它们。只涉及2个表时很简单,但是对于添加到查询中的每个额外表变得越来越复杂(如果新表与主表或相关表有一对多的关系)。

        但你确实有很多表和协会参与其中。因此,我上面提出的解决方案在性能和代码简单性之间有所妥协。当我写这篇文章时,让我试着解释一下我的思路。

        • 您需要处理13个表以获取所需的所有数据。您需要显示来自大多数表的数据,并且还需要基于相当多的表来过滤事件。

        • 单独的蛋糕无法理解您的需求,并且会返回太多数据,包括您希望过滤掉的内容。

        • 涉及一些1-n和n-n关系。如果您使用JOIN将所有13添加到单个查询中,结果将会有太多的欺骗,并且将无法管理。

        • 所以我决定尝试一种混合的方法:首先获得一个过滤的事件列表,没有任何欺骗,然后让Cake用来自一些相关模型的数据“装饰”它。要做到这一点,你必须:

          1. JOIN所有需要条件的表格。这将允许我们通过单个查询检索我们的最终事件列表,考虑所有条件。
          2. 如果您JOIN编辑的任何表格可能导致重复,请不要在SELECT子句(或Cake的fields列表)中包含其字段。如果正确设置了关联,Cake将在稍后运行额外的查询以获取关联数据(因为我们使用了recursive=2)。
          3. 防止Cake运行额外查询以获取我们已在主查询中检索到的数据。这是通过在运行查找之前取消绑定正确的模型来完成的。
          4. 如果这仍然是您不想要的返回字段,并且此类字段来自关联模型,则必须使用Containable告诉Cake您希望从每个模型中获取哪些字段。

        我知道这可能听起来很复杂,但除非你了解Cake的功能以及SQL的工作原理,否则你无法自己解决这个问题。我希望这会有所帮助。