Mysql 特德加入了
如果我们在这里使用Mysql 特德加入了,mysql,cakephp,associations,cakephp-1.3,Mysql,Cakephp,Associations,Cakephp 1.3,如果我们在这里使用'recursive'=>false,我们仍然可以得到正确的事件,并且没有数据重复,但是日期和[Sub[Sub]]类型将丢失。使用2级递归,Cake将自动循环返回的事件,并对每个事件运行必要的查询以获取相关的模型数据 这几乎就是您正在做的,但是没有可控制性,并且有一些额外的调整。我知道这仍然是一段冗长、难看和枯燥的代码,但毕竟涉及13个数据库表 这都是未经测试的代码,但我相信它应该可以工作 在这种情况下,我倾向于不使用Cake's Association或Containeabl
'recursive'=>false
,我们仍然可以得到正确的事件,并且没有数据重复,但是日期和[Sub[Sub]]类型将丢失。使用2级递归,Cake将自动循环返回的事件,并对每个事件运行必要的查询以获取相关的模型数据
这几乎就是您正在做的,但是没有可控制性,并且有一些额外的调整。我知道这仍然是一段冗长、难看和枯燥的代码,但毕竟涉及13个数据库表
这都是未经测试的代码,但我相信它应该可以工作 在这种情况下,我倾向于不使用Cake's Association或Containeable,而是自己制作连接:
$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
));
请注意,我删除了大多数ORs。这是因为您可以在条件
中将数组作为值传递,而Cake将使其成为SQL查询中(…)语句中的。例如:'Model.field'=>数组(1,2,3)
在(1,2,3)中生成'Model.field'
。这就像ORs一样工作,但需要的代码更少。因此,上面的代码块与您的代码完全相同,但更短
现在是复杂的部分,find
本身
通常我建议单独使用强制连接,不使用Containable,并且使用'recursive'=>false
。我相信这通常是处理复杂发现的最好方法。使用关联和Containable,Cake对数据库运行多个SQL查询(每个模型/表一个查询),这往往效率低下。此外,Containable并不总是返回预期的结果(正如您在尝试时所注意到的)
但是因为在您的案例中涉及到四个复杂的关联,也许混合方法将是理想的解决方案-否则,清理重复数据将过于复杂。(4个复杂关联为:Event hasMany Dates[通过Event hasMany Schedule,Schedule hasMany Date]、Event HABTM EventType、Event HABTM EventSubType、Event HABTM EventSubType)。因此,我们可以让Cake处理EventType、EventSubType和EventSubType的数据检索,避免过多重复
因此,我建议:对所有必需的筛选使用联接,但不要在字段中包含日期和[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
));
我们消除了包含的,一些额外的查询也因此而运行。大多数联接的类型为内部
。这意味着联接中涉及的两个表上必须至少存在一条记录,否则将得到比预期更少的结果。我假设每个活动都在一家餐厅或一个地点举行,但不是同时在这两个地点举行,这就是为什么我对这些桌子(和城市)使用LEFT
。如果联接中使用的某些字段是可选的,则应在相关联接上使用LEFT
而不是internal
如果我们在这里使用'recursive'=>false
,我们仍然可以得到正确的事件,并且没有数据重复,但是日期和[Sub[Sub]]类型将丢失。使用2级递归,Cake将自动循环返回的事件,并对每个事件运行必要的查询以获取相关的模型数据
这几乎就是您正在做的,但是没有可控制性,并且有一些额外的调整。我知道这仍然是一段冗长、难看和枯燥的代码,但毕竟涉及13个数据库表
这都是未经测试的代码,但我相信它应该可以工作 Dave,你有一个相当复杂的问题要解决,这需要的不仅仅是基本的蛋糕。你必须了解发生了什么才能解决它。我假设您对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
下面是蛋糕在幕后的作用:
从表“main”检索所有行
SELECT * FROM main
根据结果,Cake将构建一个ID数组,然后使用该数组获取相关模型的数据。此数据将使用如下查询从MySQL获取:
SELECT * FROM related WHERE main_id IN ([comma_separated_list_of_ids_here])
最后,Cake将从Main循环遍历结果数组,并将相关数据添加到每一行(如适用)。完成后,返回“装饰”数组
有时,根据关联的类型,Cake会对主模型检索到的每一行执行额外的SQL查询。那真的很慢。解决方案是使用单个查询从两个表中获取数据,这就是联接的用途。问题是数据重复。例如:
SELECT Main.*, Related.*
FROM main as Main
INNER JOIN related AS Related
ON Related.main_id = main.id
SELECT DISTINCT Main.*
FROM main as Main
INNER JOIN related AS Related
ON Related.main_id = main.id
WHERE Related.val LIKE '%Foo%'
结果:
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
给出:
(如果需要有关内部联接与左侧联接的详细信息,请参见)。(编辑:链接已更新)
回到重复项:用PHP中的一个简单的foreach
循环很容易清理它们。当只涉及两个表时,这很简单,但对于添加到查询中的每一个额外表(如果新表与main或related有一对多关系),它就会变得越来越复杂
但是你确实有很多表格和关联。因此,我上面建议的解决方案在某种程度上是性能和代码简单性之间的折衷。让我试着解释一下我写这篇文章时的思路
- 您需要处理13个表才能获得所需的所有数据。您需要显示来自大多数表的数据,并且还需要根据相当多的表过滤事件
- 蛋糕本身无法理解您想要什么,并且会返回太多的数据,包括您希望它过滤掉的数据
- 其中涉及到一些1-n和n-n关系。如果您使用J将所有13项添加到单个查询中
$data = $this->find('all', array(
'recursive' => 1
));
SELECT * FROM main
SELECT * FROM related WHERE main_id IN ([comma_separated_list_of_ids_here])
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
SELECT DISTINCT Main.*
FROM main as Main
INNER JOIN related AS Related
ON Related.main_id = main.id
WHERE Related.val LIKE '%Foo%'
Main.id | Main.val
1 | A
3 | C
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
$qOpts['fields'] = array(
...
'GROUP_CONCAT(Date.start, "|", Date.end ORDER BY Date.start ASC SEPARATOR "||") AS EventDates'
);
//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;
}
}