3

简而言之问题

我想通过模型 B 的控制器中的 find() 操作从模型 A中检索到 HABTM模型 B的数据,而不依赖于广泛的递归。

$this->ModelB->bindModel('hasMany' => array('ModelAsModelBs'));
$this->ModelB->find('all', array('fields' => array('ModelA.*'))); //cond'ts below

我知道执行此操作需要 bindModel() ,但如果没有多次递归,我似乎无法访问关联的模型字段(即不仅是 HABTM 表的字段,还有实际的关联模型)。

我突然想到,我也可能从根本上误解了关于模型关系应该如何交互、设计或检索等方面的一些东西——简而言之,我认识到我可能不成功的原因是这可能不是我应该做的事情,哼。如果是这样,我同样很乐意学习如何做得更好,因为我经常处理非常复杂的模型关系(我主要为学术界和在线课程材料/远程研究进行网络开发)。

具体例子/实际情况

数据库有这些表,id 和你期望的一样:

  • 用户
  • 培训班
  • 模块
  • 用户课程
  • 用户模块
  • 课程模块

我尝试执行的模型查询发生在Users控制器内,如下所示:

class Users extends AppController {
    public function doSomething() {
        $hasOne = array( 'hasOne' => array('CoursesModules','UsersModules'));
        $this->User->Course->Module->bindModel($hasOne);
        $conditions = array('`CoursesModules`.`course_id`' => $courseIds, //this is a known constraint within the app
                    'NOT' => array('`UsersModules`.`module_id` = `CoursesModules`.`module_id`' ));
        $options = array( 'fields' => array('Module', 'Course.*'), // Note the attempt to get Course model information
                  'conditions' => $conditions,
                  'recursive' => 0);
        $modules = $this->User->Course->Module->find('all', $options);
        $this->set(compact('modules'));
    }
}

此查询导致:

错误: SQLSTATE [42S02]:未找到基表或视图:1051 未知表“课程”

但我也不能bindModel()用来连接CoursesModules. 这让我觉得很奇怪,因为关联路径是Users->Courses->Modules. 我可以将递归提高一个档次,但它会导致各种需要大量数据的地狱,unbind()并且还会提取完全荒谬的数据量。

第二个奇怪的地方是,如果我Course.*从字段列表中删除,上面的查询会执行,但不会像我预期的那样工作;我认为这是正确地要求 CoursesModules 中列出的所有模块这些模块也不在UsersModules中。此类数据确实存在于我的记录中,但并未由此检索。

我意识到我可以从中获取course_idsCoursesModules然后再进行一次查找以获取 Course 模型数据,但这 a) 不是很像 Cake,b) 很痛苦,因为我非常感谢能够访问$modules['Module']['Course']渲染的视图文件。

谁能在这里指出我正确的方向?或者,哈哈,上帝保佑,帮我构建这个 MySQL 查询(我都是 MySQL 连接的大拇指)?真心感谢,谢谢!

更新

@Kai:为了建立关系,我设置了我的桌子并烤了它们。也许值得注意的是,我对 MySQL 有相当基本的了解,并且通常通过 PhpMyAdmin 完成所有工作。至于生成初始的 Cake 文件,我使用cake bake all然后修改了一些东西。$hasAndBelongsToMany最后发布了来自各个模型的表和数组的 SQL 。

至于我为什么选择hasOne……我也假设hasMany;使用这种关系总是会从我绑定的表中生成“未找到列”错误(与哪一列无关)。同时,在一定程度上,明显错误的选择hasOne奏效了。

最后,我有一个潜伏的怀疑,这个containable behavior行业可能是我所追求的,但我并不真正理解它。尽我所能,这是这些模型的上下文以及我尝试执行的各种查询:

我正在为大学教员构建一个程序,该程序基本上可以让教授进行一些在线课程。但是课程作业(即模块)可能在不同的班级(即课程)之间共享,学生可能在任何或所有班级。另一个限制是学生可以选择她将在给定课程中学习哪些模块——教授可能会提供其中五个,他们必须完成其中的三个。因此,当学生登录时,我需要能够在他们所在课程的上下文中检索他们尚未完成的模块。

我必须提出大量类似的查询,这些查询或多或少属于这种性质。就目前而言,我可以通过各种使用来实现所有这一切(因为这是在最后期限内完成的)loadModel(),执行更简单$this->Model->find()的,通过一些逻辑对结果进行排序foreach,冲洗重复。除了烦人之外,我还担心由于我的处理不当,它无法扩展,最后......我讨厌做错事,哈哈。我知道 CakePHP 可以处理我想问我的数据的问题,我只是不知道如何问他们(和/或设置数据以便可以问这样的问题)。

CREATE TABLE IF NOT EXISTS `modules` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `domain_id` int(11) NOT NULL,
  `subject_id` int(11) NOT NULL,
  `name` varchar(255) NOT NULL,
  `description` text NOT NULL,
  `passing_score` int(11) NOT NULL,
  `max_attempts` int(11) NOT NULL,
  `allow_retry` int(11) NOT NULL,
  `running_score` tinyint(1) NOT NULL,
  `score_privacy` int(11) NOT NULL,
  `created` datetime NOT NULL,
  `modified` datetime NOT NULL,
  PRIMARY KEY (`id`),
  KEY `domain_id` (`domain_id`),
  KEY `subject_id` (`subject_id`)
)  ENGINE=InnoDB  DEFAULT CHARSET=latin1;

CREATE TABLE IF NOT EXISTS `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `group_id` int(11) NOT NULL,
  `institution_id` int(11) NOT NULL,
  `password` varchar(255) NOT NULL,
  `firstname` varchar(63) NOT NULL,
  `lastname` varchar(63) NOT NULL,
  `email` varchar(255) NOT NULL,
  `studentno` varchar(31) NOT NULL,
  `claimed` tinyint(1) NOT NULL,
  `verified` tinyint(1) NOT NULL,
  `created` datetime NOT NULL,
  `modified` datetime NOT NULL,
  PRIMARY KEY (`id`),
  KEY `group_id` (`group_id`),
  KEY `institution_id` (`institution_id`)
)  ENGINE=InnoDB  DEFAULT CHARSET=latin1;

CREATE TABLE IF NOT EXISTS `courses` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `institution_id` int(11) NOT NULL,
  `coursecode` varchar(255) NOT NULL,
  `name` varchar(255) NOT NULL,
  `semester` varchar(7) NOT NULL,
  `educator_id` int(11) NOT NULL,
  `year` year(4) NOT NULL,
  `description` text NOT NULL,
  `created` datetime NOT NULL,
  `modified` datetime NOT NULL,
  PRIMARY KEY (`id`),
  KEY `user_id` (`educator_id`), /* educator is an alias of user in some cases */
  KEY `institution_id` (`institution_id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1;

CREATE TABLE IF NOT EXISTS `users_courses` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NOT NULL,
  `course_id` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `user_id` (`user_id`,`course_id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1; 

CREATE TABLE IF NOT EXISTS `users_modules` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NOT NULL,
  `module_id` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `user_id` (`user_id`,`module_id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 

CREATE TABLE IF NOT EXISTS `courses_modules` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `course_id` int(11) NOT NULL,
  `module_id` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `course_id` (`course_id`,`module_id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1;

模型协会

注意:这并不全面——hasOne、hasMany、belongsTo 等。已省略以节省空间;如果需要,我可以发布整个模型。

// from User Model
public $hasAndBelongsToMany = array(
        'Course' => array(
            'className' => 'Course',
            'joinTable' => 'users_courses',
            'foreignKey' => 'user_id',
            'associationForeignKey' => 'course_id',
            'unique' => 'keepExisting',
        ),
        'Module' => array(
            'className' => 'Module',
            'joinTable' => 'users_modules',
            'foreignKey' => 'user_id',
            'associationForeignKey' => 'module_id',
            'unique' => 'keepExisting',
        )
    );

// from Course Model
    public $hasAndBelongsToMany = array(
        'Module' => array(
            'className' => 'Module',
            'joinTable' => 'courses_modules',
            'foreignKey' => 'course_id',
            'associationForeignKey' => 'module_id',
            'unique' => 'keepExisting',
            'conditions' => '',
            'fields' => '',
            'order' => '',
            'limit' => '',
            'offset' => '',
            'finderQuery' => '',
        ),
        'User' => array(
            'className' => 'User',
            'joinTable' => 'users_courses',
            'foreignKey' => 'course_id',
            'associationForeignKey' => 'user_id',
            'unique' => 'keepExisting',
            'conditions' => '',
            'fields' => '',
            'order' => '',
            'limit' => '',
            'offset' => '',
            'finderQuery' => '',
        )
    );

// from Module Model
    public $hasAndBelongsToMany = array(
        'Excerpt' => array(
            'className' => 'Excerpt',
            'joinTable' => 'excerpts_modules',
            'foreignKey' => 'module_id',
            'associationForeignKey' => 'excerpt_id',
            'unique' => 'keepExisting',
            'conditions' => '',
            'fields' => '',
            'order' => '',
            'limit' => '',
            'offset' => '',
            'finderQuery' => '',
        ),
        'Course' => array(
            'className' => 'Course',
            'joinTable' => 'courses_modules',
            'foreignKey' => 'module_id',
            'associationForeignKey' => 'course_id',
            'unique' => 'keepExisting',
            'conditions' => '',
            'fields' => '',
            'order' => '',
            'limit' => '',
            'offset' => '',
            'finderQuery' => '',
        )
    );
4

2 回答 2

1

As I understand it, this is the problem; A user has a list of available modules through their courses. We want to find available modules that the user has not completed. In other words, we have to find a user's course's modules which are not in that user's modules.

Here's the best way I can think of doing that:

$modules = $this->User->Module->find('all', array(
    'joins' => array(
        array(
            'table' => 'courses_modules',
            'alias' => 'CoursesModule',
            'type' => 'LEFT',
            'conditions' => array('CoursesModule.module_id = Module.id'),
        ),
        array(
            'table' => 'courses_users',
            'alias' => 'CoursesUser',
            'type' => 'LEFT',
            'conditions' => array('CoursesUser.course_id = CoursesModule.course_id'),
        ),
        array(
            'table' => 'modules_users',
            'alias' => 'ModulesUser',
            'type' => 'LEFT',
            'conditions' => array(
                'ModulesUser.module_id = Module.id',
                'ModulesUser.user_id' => $userId,
            ),
        ),
    ),
    'conditions' => array(
        'CoursesUser.user_id' => $userId,
        'ModulesUser.id' => null,
    ),
    'recursive' => -1,
));

Which should build a query like this:

SELECT Module.* FROM modules AS Module
LEFT JOIN courses_modules AS CoursesModule
    ON (CoursesModule.module_id = Module.id)
LEFT JOIN courses_users AS CoursesUser
    ON (CoursesUser.course_id = CoursesModule.course_id)
LEFT JOIN modules_users AS ModulesUser
    ON (ModulesUser.module_id = Module.id
        AND ModulesUser.user_id = $userId)
WHERE CoursesUser.user_id = $userId
AND ModulesUser.id IS NULL

The first two joins along with the user_id condition will get all the available modules. Rather than getting the user, their courses and then that course's modules we work backwards (kind of); we get the modules and join the courses to which we join the users. Once we add the user_id condition the association will be filtered and only modules that can be joined to the user will be found. Since we are starting with the modules there won't be any duplicates, we don't know (or care) which course was used to make the link.

With those available modules we then join the modules a user has completed and limit the records to those that couldn't be joined.

The CakePHP documentation has more info on joining tables.

Edit :

If you want to set the course IDs manually, you can do this:

$modules = $this->User->Module->find('all', array(
    'joins' => array(
        array(
            'table' => 'courses_modules',
            'alias' => 'CoursesModule',
            'type' => 'LEFT',
            'conditions' => array('CoursesModule.module_id = Module.id'),
        ),
        array(
            'table' => 'modules_users',
            'alias' => 'ModulesUser',
            'type' => 'LEFT',
            'conditions' => array(
                'ModulesUser.module_id = Module.id',
                'ModulesUser.user_id' => $userId,
            ),
        ),
    ),
    'conditions' => array(
        'CoursesModule.course_id' => $courseIds,
        'ModulesUser.id' => null,
    ),
    'recursive' => -1,
));
于 2013-12-11T12:53:13.417 回答
0

你建立关系的方式有问题。您能否发布以显示您为建立关系所做的工作,以及导致错误的 SQL 查询是什么?我也对您的查询到底应该做什么感到困惑。

不过,我已经可以做一些观察了:

  • 您的查询中似乎没有定义课程,这就是您收到 sql 错误的原因。您与 Course 的关系可能有问题。

  • 如果您尝试在用户和模块以及课程和模块之间建立 HABTM 关系,为什么要在模块和课程模块以及模块和用户模块之间创建 hasOne 关系?

我想知道的简短版本是,那不应该是 hasMany 而不是 hasOne 吗?

较长的版本是,有时,当您有一个这样的连接表时,出于某种原因,您需要与连接表本身创建关系,而不仅仅是使用 HABTM(当您获取数据时,连接中的数据表不包括在内)。通常这是因为除了两个外键之外,还有一些数据也必须存储在连接表中。但在这种情况下,您应该使用所谓的“有很多”关系。如果不需要使用 a has many through,您应该只将 HABTM 与用户和课程一起使用,而不是直接与连接表创建关系。 http://book.cakephp.org/2.0/en/models/associations-linking-models-together.html#hasmany-through-the-join-model

于 2013-12-06T17:44:19.497 回答