【问题标题】:Accessing associated models of HABTM relationships via bindModel() w/o recursion通过无递归的 bindModel() 访问 HABTM 关系的关联模型
【发布时间】:2013-12-03 01:27:17
【问题描述】:

问题简述

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

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

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

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

具体例子/实际情况

数据库有这些表,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 Unknown table 'Course'

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

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

我意识到我可以从 CoursesModules 获取 course_ids,然后再进行一次查找以获取课程模型数据,但那是 a) 不是很像蛋糕,b) 很痛苦,因为我真的很感激拥有访问渲染视图文件中的$modules['Module']['Course']

任何人都可以在这里指出正确的方向吗?或者,哈哈,上帝保佑,帮我构建这个 MySQL 查询(我都是 MySQL 连接的大拇指)?非常感谢,谢谢!

更新

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

至于我为什么选择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' => '',
        )
    );

【问题讨论】:

  • CoursesModules 中列出的所有 Modules 不在 UsersModules 中” - 你能解释一下你的意思吗请问简单的英文好吗?我是否将其理解为“查找属于课程但没有用户的所有模块”?
  • 我的意思是,“查找 Module.id 在 CoursesModules.module_id 中存在但在 UsersModules.module_id 中不存在的所有模块”。
  • “我需要能够检索[学生尚未]完成的模块,在他们所在课程的背景下。” - 这就是我要的!
  • 哦,喜欢,普通英文,哈哈。道歉。 :)
  • 您是否可以为生成的查询和执行的查询提供日志。

标签: mysql cakephp model has-and-belongs-to-many cakephp-2.4


【解决方案1】:

据我了解,这就是问题所在;用户通过他们的课程拥有可用模块的列表。我们希望找到用户尚未完成的可用模块。换句话说,我们必须找到用户的课程模块,而这些模块不在该用户的模块中。

这是我能想到的最好方法:

$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,
));

应该构建这样的查询:

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

前两个连接连同 user_id 条件将获得所有可用的模块。我们不是让用户,他们的课程,然后是该课程的模块,而是向后工作(有点);我们得到模块并加入我们加入用户的课程。一旦我们添加了 user_id 条件,关联就会被过滤,并且只会找到可以加入用户的模块。由于我们从模块开始,因此不会有任何重复,我们不知道(或关心)用于建立链接的课程。

使用这些可用模块,然后我们加入用户已完成的模块,并将记录限制为无法加入的模块。

CakePHP 文档中有更多关于 joining tables 的信息。

编辑:

如果您想手动设置课程 ID,您可以这样做:

$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,
));

【讨论】:

  • 非常感谢,这确实是我一直在寻找的答案(即代码和理论,可以这么说)。先生打得好。
【解决方案2】:

您建立关系的方式有问题。您能否发布以显示您为建立关系所做的工作,以及导致错误的 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

【讨论】:

  • 所以我似乎没有忽略您的意见和建议,我周末不在,现在才收到这个。我会在上午(对我而言;GMT-4)回复您的问题和一些有用的 cmets,因为我已经想到了其中的一些。非常感谢您的回复。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多